atagen

matrix-rust-sdk

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