matrix_sdk_ffi/
ruma.rs

1// Copyright 2023 The Matrix.org Foundation C.I.C.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use 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-based authentication (`m.login.password`).
100    Password { password_details: AuthDataPasswordDetails },
101}
102
103#[derive(uniffi::Record)]
104pub struct AuthDataPasswordDetails {
105    /// One of the user's identifiers.
106    identifier: String,
107
108    /// The plaintext password.
109    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/// Parse a matrix entity from a given URI, be it either
128/// a `matrix.to` link or a `matrix:` URI
129#[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/// A Matrix entity that can be a room, room alias, user, or event, and a list
149/// of via servers.
150#[derive(uniffi::Record)]
151pub struct MatrixEntity {
152    id: MatrixId,
153    via: Vec<String>,
154}
155
156/// A Matrix ID that can be a room, room alias, user, or event.
157#[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    // Used on Element X Android
254    #[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    // Used on Element X Android
263    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
382/// From MSC2530: https://github.com/matrix-org/matrix-spec-proposals/blob/main/proposals/2530-body-as-caption.md
383/// If the filename field is present in a media message, clients should treat
384/// body as a caption instead of a file name. Otherwise, the body is the
385/// file name.
386///
387/// So:
388/// - if a media has a filename and a caption, the body is the caption, filename
389///   is its own field.
390/// - if a media only has a filename, then body is the filename.
391fn 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    /// The computed filename, for use in a client.
522    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    /// The computed filename, for use in a client.
557    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    /// The computed filename, for use in a client.
598    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    /// The computed filename, for use in a client.
633    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/// Creates a [`RoomMessageEventContentWithoutRelation`] given a
1079/// [`MessageContent`] value.
1080#[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/// Types of global account data events.
1089#[derive(Clone, uniffi::Enum)]
1090pub enum AccountDataEventType {
1091    /// m.direct
1092    Direct,
1093    /// m.identity_server
1094    IdentityServer,
1095    /// m.ignored_user_list
1096    IgnoredUserList,
1097    /// m.push_rules
1098    PushRules,
1099    /// m.secret_storage.default_key
1100    SecretStorageDefaultKey,
1101    /// m.secret_storage.key.*
1102    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/// Global account data events.
1126#[derive(Clone, uniffi::Enum)]
1127pub enum AccountDataEvent {
1128    /// m.direct
1129    Direct {
1130        /// The mapping of user ID to a list of room IDs of the ‘direct’ rooms
1131        /// for that user ID.
1132        map: HashMap<String, Vec<String>>,
1133    },
1134    /// m.identity_server
1135    IdentityServer {
1136        /// The base URL for the identity server for client-server connections.
1137        base_url: Option<String>,
1138    },
1139    /// m.ignored_user_list
1140    IgnoredUserList {
1141        /// The map of users to ignore. This is a mapping of user ID to empty
1142        /// object.
1143        ignored_users: HashMap<String, IgnoredUser>,
1144    },
1145    /// m.push_rules
1146    PushRules {
1147        /// The global ruleset.
1148        global: Ruleset,
1149    },
1150    /// m.secret_storage.default_key
1151    SecretStorageDefaultKey {
1152        /// The ID of the default key.
1153        key_id: String,
1154    },
1155    /// m.secret_storage.key.*
1156    SecretStorageKey {
1157        /// The ID of the key.
1158        key_id: String,
1159
1160        /// The name of the key.
1161        name: Option<String>,
1162
1163        /// The encryption algorithm used for this key.
1164        ///
1165        /// Currently, only `m.secret_storage.v1.aes-hmac-sha2` is supported.
1166        algorithm: SecretStorageEncryptionAlgorithm,
1167
1168        /// The passphrase from which to generate the key.
1169        passphrase: Option<PassPhrase>,
1170    },
1171}
1172
1173/// The policy that decides if media previews should be shown in the timeline.
1174#[derive(Clone, uniffi::Enum, Default)]
1175pub enum MediaPreviews {
1176    /// Always show media previews in the timeline.
1177    #[default]
1178    On,
1179    /// Show media previews in the timeline only if the room is private.
1180    Private,
1181    /// Never show media previews in the timeline.
1182    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/// The policy that decides if avatars should be shown in invite requests.
1207#[derive(Clone, uniffi::Enum, Default)]
1208pub enum InviteAvatars {
1209    /// Always show avatars in invite requests.
1210    #[default]
1211    On,
1212    /// Never show avatars in invite requests.
1213    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/// Details about an ignored user.
1236///
1237/// This is currently empty.
1238#[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/// A push ruleset scopes a set of rules according to some criteria.
1248#[derive(Clone, uniffi::Record)]
1249pub struct Ruleset {
1250    /// These rules configure behavior for (unencrypted) messages that match
1251    /// certain patterns.
1252    pub content: Vec<PatternedPushRule>,
1253
1254    /// These user-configured rules are given the highest priority.
1255    ///
1256    /// This field is named `override_` instead of `override` because the latter
1257    /// is a reserved keyword in Rust.
1258    pub override_: Vec<ConditionalPushRule>,
1259
1260    /// These rules change the behavior of all messages for a given room.
1261    pub room: Vec<SimplePushRule>,
1262
1263    /// These rules configure notification behavior for messages from a specific
1264    /// Matrix user ID.
1265    pub sender: Vec<SimplePushRule>,
1266
1267    /// These rules are identical to override rules, but have a lower priority
1268    /// than `content`, `room` and `sender` rules.
1269    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/// Like [`SimplePushRule`], but with an additional `pattern`` field.
1303#[derive(Clone, uniffi::Record)]
1304pub struct PatternedPushRule {
1305    /// Actions to determine if and how a notification is delivered for events
1306    /// matching this rule.
1307    pub actions: Vec<Action>,
1308
1309    /// Whether this is a default rule, or has been set explicitly.
1310    pub default: bool,
1311
1312    /// Whether the push rule is enabled or not.
1313    pub enabled: bool,
1314
1315    /// The ID of this rule.
1316    pub rule_id: String,
1317
1318    /// The glob-style pattern to match against.
1319    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/// Like [`SimplePushRule`], but with an additional `conditions` field.
1341#[derive(Clone, uniffi::Record)]
1342pub struct ConditionalPushRule {
1343    /// Actions to determine if and how a notification is delivered for events
1344    /// matching this rule.
1345    pub actions: Vec<Action>,
1346
1347    /// Whether this is a default rule, or has been set explicitly.
1348    pub default: bool,
1349
1350    /// Whether the push rule is enabled or not.
1351    pub enabled: bool,
1352
1353    /// The ID of this rule.
1354    pub rule_id: String,
1355
1356    /// The conditions that must hold true for an event in order for a rule to
1357    /// be applied to an event.
1358    ///
1359    /// A rule with no conditions always matches.
1360    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/// A push rule is a single rule that states under what conditions an event
1386/// should be passed onto a push gateway and how the notification should be
1387/// presented.
1388#[derive(Clone, uniffi::Record)]
1389pub struct SimplePushRule {
1390    /// Actions to determine if and how a notification is delivered for events
1391    /// matching this rule.
1392    pub actions: Vec<Action>,
1393
1394    /// Whether this is a default rule, or has been set explicitly.
1395    pub default: bool,
1396
1397    /// Whether the push rule is enabled or not.
1398    pub enabled: bool,
1399
1400    /// The ID of this rule.
1401    ///
1402    /// This is generally the Matrix ID of the entity that it applies to.
1403    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/// An algorithm and its properties, used to encrypt a secret.
1441#[derive(Clone, uniffi::Enum)]
1442pub enum SecretStorageEncryptionAlgorithm {
1443    /// Encrypted using the `m.secret_storage.v1.aes-hmac-sha2` algorithm.
1444    ///
1445    /// Secrets using this method are encrypted using AES-CTR-256 and
1446    /// authenticated using HMAC-SHA-256.
1447    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/// The key properties for the `m.secret_storage.v1.aes-hmac-sha2`` algorithm.
1464#[derive(Clone, uniffi::Record)]
1465pub struct SecretStorageV1AesHmacSha2Properties {
1466    /// The 16-byte initialization vector, encoded as base64.
1467    pub iv: Option<String>,
1468
1469    /// The MAC, encoded as base64.
1470    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/// The content of an `m.media_preview_config` event.
1483///
1484/// Is also the content of the unstable
1485/// `io.element.msc4278.media_preview_config`.
1486#[derive(Clone, uniffi::Record, Default)]
1487pub struct MediaPreviewConfig {
1488    /// The media previews setting for the user.
1489    pub media_previews: Option<MediaPreviews>,
1490
1491    /// The invite avatars setting for the user.
1492    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/// A passphrase from which a key is to be derived.
1505#[derive(Clone, uniffi::Record)]
1506pub struct PassPhrase {
1507    /// The algorithm to use to generate the key from the passphrase.
1508    ///
1509    /// Must be `m.pbkdf2`.
1510    pub algorithm: KeyDerivationAlgorithm,
1511
1512    /// The salt used in PBKDF2.
1513    pub salt: String,
1514
1515    /// The number of iterations to use in PBKDF2.
1516    pub iterations: u64,
1517
1518    /// The number of bits to generate for the key.
1519    ///
1520    /// Defaults to 256
1521    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/// A key algorithm to be used to generate a key from a passphrase.
1538#[derive(Clone, uniffi::Enum)]
1539pub enum KeyDerivationAlgorithm {
1540    /// PBKDF2
1541    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/// Types of room account data events.
1623#[derive(Clone, uniffi::Enum)]
1624pub enum RoomAccountDataEventType {
1625    /// m.fully_read
1626    FullyRead,
1627    /// m.marked_unread
1628    MarkedUnread,
1629    /// m.tag
1630    Tag,
1631    /// com.famedly.marked_unread
1632    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/// Room account data events.
1650#[derive(Clone, uniffi::Enum)]
1651pub enum RoomAccountDataEvent {
1652    /// m.fully_read
1653    FullyReadEvent {
1654        /// The event the user's read marker is located at in the room.
1655        event_id: String,
1656    },
1657    /// m.marked_unread
1658    MarkedUnread {
1659        /// The current unread state.
1660        unread: bool,
1661    },
1662    /// m.tag
1663    Tag { tags: HashMap<TagName, TagInfo> },
1664    /// com.famedly.marked_unread
1665    UnstableMarkedUnread {
1666        /// The current unread state.
1667        unread: bool,
1668    },
1669}
1670
1671/// The name of a tag.
1672#[derive(Clone, PartialEq, Eq, Hash, uniffi::Enum)]
1673pub enum TagName {
1674    /// `m.favourite`: The user's favorite rooms.
1675    Favorite,
1676
1677    /// `m.lowpriority`: These should be shown with lower precedence than
1678    /// others.
1679    LowPriority,
1680
1681    /// `m.server_notice`: Used to identify
1682    ServerNotice,
1683
1684    /// `u.*`: User-defined tag
1685    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/// A user-defined tag name.
1703#[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/// Information about a tag.
1715#[derive(Clone, uniffi::Record)]
1716pub struct TagInfo {
1717    /// Value to use for lexicographically ordering rooms with this tag.
1718    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}