atagen

macltok

public
Star0
Watch0
fa51490b6ae98fdf5eeddb0799f4e3a160bf53cb
3
ssh://brewery@pls.lobotomise.me:33/atagen/macltok.git
src/model.rsRust2151 lines60.8 KB
1
use std::{fmt, sync::Arc};
2
3
use imbl::Vector;
4
use ruma::{
5
    MxcUri, OwnedDeviceId, OwnedEventId, OwnedMxcUri, OwnedRoomAliasId, OwnedRoomId,
6
    OwnedRoomOrAliasId, OwnedServerName, OwnedTransactionId, OwnedUserId, RoomId,
7
    events::{
8
        AnyGlobalAccountDataEventContent, AnyRoomAccountDataEventContent, AnySyncTimelineEvent,
9
        GlobalAccountDataEventType, RoomAccountDataEventType,
10
        room::member::StrippedRoomMemberEvent,
11
    },
12
    serde::Raw,
13
};
14
#[cfg(feature = "serde")]
15
use serde::{Deserialize, Serialize};
16
17
/// every command answers exactly once through `ClientHandle::send` or `execute`.
18
/// the event stream only ever carries state
19
#[derive(Debug)]
20
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
21
#[non_exhaustive]
22
pub enum Command {
23
    Account(AccountCommand),
24
    Room(RoomCommand),
25
    Timeline(TimelineCommand),
26
    Media(MediaCommand),
27
    Encryption(EncryptionCommand),
28
    Notification(NotificationCommand),
29
    Lifecycle(LifecycleCommand),
30
    Pack(PackCommand),
31
}
32
33
#[derive(Debug)]
34
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
35
#[non_exhaustive]
36
pub enum AccountCommand {
37
    Logout,
38
    SetAccountData {
39
        event_type: GlobalAccountDataEventType,
40
        content: Raw<AnyGlobalAccountDataEventContent>,
41
    },
42
    SetDisplayName(Option<String>),
43
    SetAvatar(Option<OwnedMxcUri>),
44
    /// uploads the image and points the profile at it, same byte cap as any upload
45
    UploadAvatar {
46
        mimetype: String,
47
        bytes: Vec<u8>,
48
    },
49
    /// unencrypted, answers with `CommandOutput::Mxc`. same byte cap as any upload
50
    UploadMedia {
51
        mimetype: String,
52
        bytes: Vec<u8>,
53
    },
54
    IgnoreUser(OwnedUserId),
55
    UnignoreUser(OwnedUserId),
56
    /// writes msc3230 orders so these spaces sort in exactly this sequence, touching as few as it can
57
    ReorderSpaces(Vec<OwnedRoomId>),
58
    RenameDevice {
59
        device_id: OwnedDeviceId,
60
        display_name: String,
61
    },
62
    DeleteDevices {
63
        device_ids: Vec<OwnedDeviceId>,
64
        auth: Option<crate::InteractiveAuth>,
65
    },
66
}
67
68
#[derive(Clone, Debug)]
69
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
70
#[non_exhaustive]
71
pub enum RoomCommand {
72
    /// the snapshot turns up as `Event::TimelineReset`. opening an open room sends a fresh one
73
    Open(OwnedRoomId),
74
    Close(OwnedRoomId),
75
    Join {
76
        room: OwnedRoomOrAliasId,
77
        via: Vec<OwnedServerName>,
78
    },
79
    Leave(OwnedRoomId),
80
    Forget(OwnedRoomId),
81
    AcceptInvite(OwnedRoomId),
82
    RejectInvite(OwnedRoomId),
83
    InviteUser {
84
        room_id: OwnedRoomId,
85
        user_id: OwnedUserId,
86
    },
87
    KickUser {
88
        room_id: OwnedRoomId,
89
        user_id: OwnedUserId,
90
        reason: Option<String>,
91
    },
92
    BanUser {
93
        room_id: OwnedRoomId,
94
        user_id: OwnedUserId,
95
        reason: Option<String>,
96
    },
97
    UnbanUser {
98
        room_id: OwnedRoomId,
99
        user_id: OwnedUserId,
100
        reason: Option<String>,
101
    },
102
    SetPowerLevels {
103
        room_id: OwnedRoomId,
104
        changes: PowerLevelChanges,
105
    },
106
    SetUserPowerLevel {
107
        room_id: OwnedRoomId,
108
        user_id: OwnedUserId,
109
        level: i64,
110
    },
111
    /// muting drops them below what sending a message takes, unmuting puts a muted user back
112
    /// at users_default and leaves anyone else alone
113
    MuteUser {
114
        room_id: OwnedRoomId,
115
        user_id: OwnedUserId,
116
        muted: bool,
117
    },
118
    /// adding m.favourite drops m.lowpriority, the way Element does
119
    SetFavourite {
120
        room_id: OwnedRoomId,
121
        favourite: bool,
122
    },
123
    /// your name in one room. None goes back to your global name
124
    SetOwnDisplayName {
125
        room_id: OwnedRoomId,
126
        name: Option<String>,
127
    },
128
    /// your avatar in one room. None goes back to your global avatar
129
    SetOwnAvatar {
130
        room_id: OwnedRoomId,
131
        avatar: Option<OwnedMxcUri>,
132
    },
133
    Create(CreateRoomOptions),
134
    /// hands back the existing dm if there is one
135
    CreateDirect(OwnedUserId),
136
    CancelSend {
137
        room_id: OwnedRoomId,
138
        transaction_id: OwnedTransactionId,
139
    },
140
    RetrySend {
141
        room_id: OwnedRoomId,
142
        transaction_id: OwnedTransactionId,
143
    },
144
    SetTyping {
145
        room_id: OwnedRoomId,
146
        typing: bool,
147
    },
148
    /// the key work a send into an encrypted room would otherwise start with, done now.
149
    /// nothing in a plain room, and a second ask joins the first
150
    PrepareToSend(OwnedRoomId),
151
    SetAccountData {
152
        room_id: OwnedRoomId,
153
        event_type: RoomAccountDataEventType,
154
        content: Raw<AnyRoomAccountDataEventContent>,
155
    },
156
}
157
158
#[derive(Clone, Debug, Default, Eq, PartialEq)]
159
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
160
#[non_exhaustive]
161
pub struct CreateRoomOptions {
162
    pub name: Option<String>,
163
    pub topic: Option<String>,
164
    pub alias_localpart: Option<String>,
165
    pub invite: Vec<OwnedUserId>,
166
    pub direct: bool,
167
    pub visibility: RoomVisibility,
168
    pub preset: Option<RoomPreset>,
169
}
170
171
impl CreateRoomOptions {
172
    #[must_use]
173
    pub fn with_name(mut self, name: impl Into<String>) -> Self {
174
        self.name = Some(name.into());
175
        self
176
    }
177
178
    #[must_use]
179
    pub fn with_topic(mut self, topic: impl Into<String>) -> Self {
180
        self.topic = Some(topic.into());
181
        self
182
    }
183
184
    #[must_use]
185
    pub fn with_alias_localpart(mut self, localpart: impl Into<String>) -> Self {
186
        self.alias_localpart = Some(localpart.into());
187
        self
188
    }
189
190
    #[must_use]
191
    pub fn with_invite(mut self, invite: impl IntoIterator<Item = OwnedUserId>) -> Self {
192
        self.invite = invite.into_iter().collect();
193
        self
194
    }
195
196
    #[must_use]
197
    pub const fn with_direct(mut self, direct: bool) -> Self {
198
        self.direct = direct;
199
        self
200
    }
201
202
    #[must_use]
203
    pub const fn with_visibility(mut self, visibility: RoomVisibility) -> Self {
204
        self.visibility = visibility;
205
        self
206
    }
207
208
    #[must_use]
209
    pub const fn with_preset(mut self, preset: RoomPreset) -> Self {
210
        self.preset = Some(preset);
211
        self
212
    }
213
}
214
215
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
216
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
217
pub enum RoomVisibility {
218
    #[default]
219
    Private,
220
    Public,
221
}
222
223
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
224
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
225
pub enum RoomPreset {
226
    PrivateChat,
227
    TrustedPrivateChat,
228
    PublicChat,
229
}
230
231
#[derive(Clone, Debug, Eq, PartialEq)]
232
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
233
#[non_exhaustive]
234
pub enum TimelineCommand {
235
    OpenThread {
236
        room_id: OwnedRoomId,
237
        root_event_id: OwnedEventId,
238
    },
239
    CloseThread {
240
        room_id: OwnedRoomId,
241
        root_event_id: OwnedEventId,
242
    },
243
    /// Re-emit an authoritative reset for an open room or thread without
244
    /// closing it. The reset arrives before this command's terminal result.
245
    Resync {
246
        timeline_id: TimelineId,
247
    },
248
    Paginate {
249
        timeline_id: TimelineId,
250
        limit: u16,
251
    },
252
    Send {
253
        timeline_id: TimelineId,
254
        content: MessageContent,
255
    },
256
    Edit {
257
        timeline_id: TimelineId,
258
        target: EventRef,
259
        content: MessageEditContent,
260
    },
261
    /// redacting your own unsent message just cancels it
262
    Redact {
263
        timeline_id: TimelineId,
264
        target: EventRef,
265
        reason: Option<String>,
266
    },
267
    ToggleReaction {
268
        timeline_id: TimelineId,
269
        target: EventRef,
270
        key: String,
271
        /// a custom emote's shortcode, sent along so clients without the pack can label it
272
        shortcode: Option<String>,
273
    },
274
    /// works on a closed room too, it just can't refuse to move the marker backwards
275
    MarkRead {
276
        timeline_id: TimelineId,
277
        event_id: OwnedEventId,
278
    },
279
}
280
281
impl TimelineCommand {
282
    #[must_use]
283
    pub const fn room_id(&self) -> &OwnedRoomId {
284
        match self {
285
            Self::OpenThread { room_id, .. } | Self::CloseThread { room_id, .. } => room_id,
286
            Self::Resync { timeline_id }
287
            | Self::Paginate { timeline_id, .. }
288
            | Self::Send { timeline_id, .. }
289
            | Self::Edit { timeline_id, .. }
290
            | Self::Redact { timeline_id, .. }
291
            | Self::ToggleReaction { timeline_id, .. }
292
            | Self::MarkRead { timeline_id, .. } => timeline_id.room_id(),
293
        }
294
    }
295
296
    #[must_use]
297
    pub const fn timeline_id(&self) -> Option<&TimelineId> {
298
        match self {
299
            Self::OpenThread { .. } | Self::CloseThread { .. } => None,
300
            Self::Resync { timeline_id }
301
            | Self::Paginate { timeline_id, .. }
302
            | Self::Send { timeline_id, .. }
303
            | Self::Edit { timeline_id, .. }
304
            | Self::Redact { timeline_id, .. }
305
            | Self::ToggleReaction { timeline_id, .. }
306
            | Self::MarkRead { timeline_id, .. } => Some(timeline_id),
307
        }
308
    }
309
}
310
311
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
312
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
313
#[non_exhaustive]
314
pub enum TimelineId {
315
    Room(OwnedRoomId),
316
    Thread {
317
        room_id: OwnedRoomId,
318
        root_event_id: OwnedEventId,
319
    },
320
}
321
322
impl TimelineId {
323
    #[must_use]
324
    pub const fn room_id(&self) -> &OwnedRoomId {
325
        match self {
326
            Self::Room(room_id) | Self::Thread { room_id, .. } => room_id,
327
        }
328
    }
329
330
    #[must_use]
331
    pub fn thread(room_id: OwnedRoomId, root_event_id: OwnedEventId) -> Self {
332
        Self::Thread {
333
            room_id,
334
            root_event_id,
335
        }
336
    }
337
338
    #[must_use]
339
    pub fn thread_root(&self) -> Option<&OwnedEventId> {
340
        match self {
341
            Self::Room(_) => None,
342
            Self::Thread { root_event_id, .. } => Some(root_event_id),
343
        }
344
    }
345
}
346
347
impl From<OwnedRoomId> for TimelineId {
348
    fn from(room_id: OwnedRoomId) -> Self {
349
        Self::Room(room_id)
350
    }
351
}
352
353
#[derive(Clone, Debug, Eq, PartialEq)]
354
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
355
#[non_exhaustive]
356
pub enum MediaCommand {
357
    /// succeeds once the send queue has it. progress and failure arrive as events
358
    Upload(AttachmentUpload),
359
}
360
361
/// msc2545 pack editing. edits read the server's copy and keep keys macltok doesn't model
362
#[derive(Clone, Debug, Eq, PartialEq)]
363
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
364
#[non_exhaustive]
365
pub enum PackCommand {
366
    /// body defaults to the shortcode
367
    SendSticker {
368
        room_id: OwnedRoomId,
369
        pack_id: String,
370
        shortcode: String,
371
    },
372
    /// fails if the state key already holds a pack
373
    CreateRoomPack {
374
        room_id: OwnedRoomId,
375
        state_key: String,
376
        display_name: String,
377
    },
378
    /// empties the content, which is all deleting a pack can mean
379
    Delete(ImagePackSource),
380
    SetMeta {
381
        pack: ImagePackSource,
382
        display_name: Patch<String>,
383
        avatar: Patch<OwnedMxcUri>,
384
        usage: Patch<Vec<PackUsage>>,
385
    },
386
    /// adds or replaces by shortcode. a replaced image keeps its info unless the url changed
387
    PutImage {
388
        pack: ImagePackSource,
389
        shortcode: String,
390
        mxc: OwnedMxcUri,
391
        body: Option<String>,
392
        usage: Option<Vec<PackUsage>>,
393
        info: Option<PackImageInfo>,
394
    },
395
    /// fails if `to` is taken
396
    RenameImage {
397
        pack: ImagePackSource,
398
        from: String,
399
        to: String,
400
    },
401
    RemoveImage {
402
        pack: ImagePackSource,
403
        shortcode: String,
404
    },
405
    SetEnabledEverywhere {
406
        room_id: OwnedRoomId,
407
        state_key: String,
408
        enabled: bool,
409
    },
410
    /// answers with `CommandOutput::Mxc`, the same uri when it already lives on your homeserver
411
    Rehost(OwnedMxcUri),
412
    /// the rest answer with `CommandOutput::Cloned`. clashes get `-2`, `-3` and so on
413
    CloneImage {
414
        from_pack: String,
415
        shortcode: String,
416
        to: ImagePackSource,
417
    },
418
    CloneSticker {
419
        room_id: OwnedRoomId,
420
        event_id: OwnedEventId,
421
        to: ImagePackSource,
422
        shortcode: String,
423
    },
424
    /// makes the room pack if its state key is free, else merges. images that can't be
425
    /// fetched are skipped and listed, the rest are written
426
    ClonePack {
427
        from_pack: String,
428
        to: ImagePackSource,
429
        display_name: Option<String>,
430
    },
431
}
432
433
#[derive(Clone, Debug, Default, Eq, PartialEq)]
434
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
435
pub enum Patch<T> {
436
    #[default]
437
    Keep,
438
    Clear,
439
    Set(T),
440
}
441
442
#[derive(Clone, Debug, Default, Eq, PartialEq)]
443
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
444
#[non_exhaustive]
445
pub struct PackImageInfo {
446
    pub mimetype: Option<String>,
447
    pub size_bytes: Option<u64>,
448
    pub width: Option<u64>,
449
    pub height: Option<u64>,
450
}
451
452
impl PackImageInfo {
453
    #[must_use]
454
    pub fn new(
455
        mimetype: Option<String>,
456
        size_bytes: Option<u64>,
457
        width: Option<u64>,
458
        height: Option<u64>,
459
    ) -> Self {
460
        Self {
461
            mimetype,
462
            size_bytes,
463
            width,
464
            height,
465
        }
466
    }
467
}
468
469
#[derive(Clone, Debug, Eq, PartialEq)]
470
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
471
#[non_exhaustive]
472
pub struct AttachmentUpload {
473
    pub timeline_id: TimelineId,
474
    pub filename: String,
475
    pub mimetype: String,
476
    pub bytes: Vec<u8>,
477
    pub caption: Option<RichText>,
478
    pub mentions: MessageMentions,
479
    pub reply_to: Option<OwnedEventId>,
480
    pub notify_replied_to: bool,
481
    pub details: Option<UploadDetails>,
482
    pub thumbnail: Option<UploadThumbnail>,
483
}
484
485
impl AttachmentUpload {
486
    #[must_use]
487
    pub fn new(
488
        timeline_id: impl Into<TimelineId>,
489
        filename: impl Into<String>,
490
        mimetype: impl Into<String>,
491
        bytes: Vec<u8>,
492
    ) -> Self {
493
        Self {
494
            timeline_id: timeline_id.into(),
495
            filename: filename.into(),
496
            mimetype: mimetype.into(),
497
            bytes,
498
            caption: None,
499
            mentions: MessageMentions::default(),
500
            reply_to: None,
501
            notify_replied_to: true,
502
            details: None,
503
            thumbnail: None,
504
        }
505
    }
506
}
507
508
#[derive(Clone, Debug, Eq, PartialEq)]
509
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
510
#[non_exhaustive]
511
pub enum UploadDetails {
512
    Image {
513
        width: Option<u32>,
514
        height: Option<u32>,
515
        blurhash: Option<String>,
516
    },
517
    Video {
518
        width: Option<u32>,
519
        height: Option<u32>,
520
        duration_ms: Option<u64>,
521
        blurhash: Option<String>,
522
    },
523
    Audio {
524
        duration_ms: Option<u64>,
525
    },
526
    Voice {
527
        duration_ms: u64,
528
        waveform: Vec<u16>,
529
    },
530
    File,
531
}
532
533
#[derive(Clone, Debug, Eq, PartialEq)]
534
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
535
#[non_exhaustive]
536
pub struct UploadThumbnail {
537
    pub bytes: Vec<u8>,
538
    pub mimetype: String,
539
    pub width: u32,
540
    pub height: u32,
541
}
542
543
impl UploadThumbnail {
544
    #[must_use]
545
    pub fn new(bytes: Vec<u8>, mimetype: impl Into<String>, width: u32, height: u32) -> Self {
546
        Self {
547
            bytes,
548
            mimetype: mimetype.into(),
549
            width,
550
            height,
551
        }
552
    }
553
}
554
555
#[derive(Eq, PartialEq)]
556
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
557
#[non_exhaustive]
558
pub enum EncryptionCommand {
559
    Recover(String),
560
    CreateKeyBackup,
561
    EnableKeyBackup,
562
    DisableKeyBackup { delete_from_server: bool },
563
    RecoverKeyBackup(String),
564
    StartVerification(VerificationTarget),
565
    Verify(VerificationAction),
566
}
567
568
impl fmt::Debug for EncryptionCommand {
569
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
570
        match self {
571
            Self::Recover(_) => formatter.debug_tuple("Recover").field(&"...").finish(),
572
            Self::CreateKeyBackup => formatter.write_str("CreateKeyBackup"),
573
            Self::EnableKeyBackup => formatter.write_str("EnableKeyBackup"),
574
            Self::DisableKeyBackup { delete_from_server } => formatter
575
                .debug_struct("DisableKeyBackup")
576
                .field("delete_from_server", delete_from_server)
577
                .finish(),
578
            Self::RecoverKeyBackup(_) => formatter
579
                .debug_tuple("RecoverKeyBackup")
580
                .field(&"...")
581
                .finish(),
582
            Self::StartVerification(target) => formatter
583
                .debug_tuple("StartVerification")
584
                .field(target)
585
                .finish(),
586
            Self::Verify(action) => formatter.debug_tuple("Verify").field(action).finish(),
587
        }
588
    }
589
}
590
591
#[derive(Clone, Debug, Eq, PartialEq)]
592
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
593
#[non_exhaustive]
594
pub enum NotificationCommand {
595
    SetRoomMode {
596
        room_id: OwnedRoomId,
597
        mode: RoomNotificationMode,
598
    },
599
    ResetRoomMode(OwnedRoomId),
600
    SetDefaultMode {
601
        encrypted: bool,
602
        one_to_one: bool,
603
        mode: RoomNotificationMode,
604
    },
605
    AddKeyword(String),
606
    RemoveKeyword(String),
607
}
608
609
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
610
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
611
#[non_exhaustive]
612
pub enum LifecycleCommand {
613
    Shutdown,
614
}
615
616
#[derive(Clone, Debug, Eq, PartialEq)]
617
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
618
#[non_exhaustive]
619
pub enum CommandOutput {
620
    Done,
621
    Room(OwnedRoomId),
622
    Mxc(OwnedMxcUri),
623
    /// the shortcodes the copies ended up under, and the ones that couldn't be fetched
624
    Cloned {
625
        shortcodes: Vec<String>,
626
        skipped: Vec<String>,
627
    },
628
}
629
630
#[derive(Clone, Debug, Eq, PartialEq)]
631
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
632
#[non_exhaustive]
633
pub enum MessageContent {
634
    Text {
635
        content: RichText,
636
        mentions: MessageMentions,
637
    },
638
    Emote {
639
        content: RichText,
640
        mentions: MessageMentions,
641
    },
642
    /// m.notice, for bots. clients render it muted and never answer it automatically
643
    Notice {
644
        content: RichText,
645
        mentions: MessageMentions,
646
    },
647
    Reply {
648
        event_id: OwnedEventId,
649
        content: RichText,
650
        mentions: MessageMentions,
651
        notify_replied_to: bool,
652
    },
653
}
654
655
impl MessageContent {
656
    #[must_use]
657
    pub fn text(body: impl Into<String>) -> Self {
658
        Self::Text {
659
            content: RichText::plain(body),
660
            mentions: MessageMentions::default(),
661
        }
662
    }
663
664
    #[must_use]
665
    pub fn html(body: impl Into<String>, formatted_body: impl Into<String>) -> Self {
666
        Self::Text {
667
            content: RichText::html(body, formatted_body),
668
            mentions: MessageMentions::default(),
669
        }
670
    }
671
672
    #[must_use]
673
    pub fn emote(content: RichText, mentions: MessageMentions) -> Self {
674
        Self::Emote { content, mentions }
675
    }
676
677
    #[must_use]
678
    pub fn notice(content: RichText, mentions: MessageMentions) -> Self {
679
        Self::Notice { content, mentions }
680
    }
681
682
    #[must_use]
683
    pub fn reply(event_id: OwnedEventId, content: RichText, mentions: MessageMentions) -> Self {
684
        Self::Reply {
685
            event_id,
686
            content,
687
            mentions,
688
            notify_replied_to: true,
689
        }
690
    }
691
692
    #[must_use]
693
    pub fn reply_without_notification(
694
        event_id: OwnedEventId,
695
        content: RichText,
696
        mentions: MessageMentions,
697
    ) -> Self {
698
        Self::Reply {
699
            event_id,
700
            content,
701
            mentions,
702
            notify_replied_to: false,
703
        }
704
    }
705
}
706
707
#[derive(Clone, Debug, Eq, PartialEq)]
708
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
709
#[non_exhaustive]
710
pub enum MessageEditContent {
711
    Text {
712
        content: RichText,
713
        mentions: MessageMentions,
714
    },
715
    Emote {
716
        content: RichText,
717
        mentions: MessageMentions,
718
    },
719
}
720
721
impl MessageEditContent {
722
    #[must_use]
723
    pub fn text(body: impl Into<String>) -> Self {
724
        Self::Text {
725
            content: RichText::plain(body),
726
            mentions: MessageMentions::default(),
727
        }
728
    }
729
730
    #[must_use]
731
    pub fn html(body: impl Into<String>, formatted_body: impl Into<String>) -> Self {
732
        Self::Text {
733
            content: RichText::html(body, formatted_body),
734
            mentions: MessageMentions::default(),
735
        }
736
    }
737
738
    #[must_use]
739
    pub fn emote(content: RichText, mentions: MessageMentions) -> Self {
740
        Self::Emote { content, mentions }
741
    }
742
}
743
744
#[derive(Clone, Debug, Default, Eq, PartialEq)]
745
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
746
#[non_exhaustive]
747
pub struct RichText {
748
    pub body: String,
749
    pub formatted_body: Option<String>,
750
}
751
752
impl RichText {
753
    #[must_use]
754
    pub fn plain(body: impl Into<String>) -> Self {
755
        Self {
756
            body: body.into(),
757
            formatted_body: None,
758
        }
759
    }
760
761
    #[must_use]
762
    pub fn html(body: impl Into<String>, formatted_body: impl Into<String>) -> Self {
763
        Self {
764
            body: body.into(),
765
            formatted_body: Some(formatted_body.into()),
766
        }
767
    }
768
}
769
770
#[derive(Clone, Debug, Default, Eq, PartialEq)]
771
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
772
#[non_exhaustive]
773
pub struct MessageMentions {
774
    pub user_ids: Vec<OwnedUserId>,
775
    pub room: bool,
776
}
777
778
impl MessageMentions {
779
    #[must_use]
780
    pub fn with_user_ids(user_ids: impl IntoIterator<Item = OwnedUserId>) -> Self {
781
        Self {
782
            user_ids: user_ids.into_iter().collect(),
783
            room: false,
784
        }
785
    }
786
787
    #[must_use]
788
    pub fn room() -> Self {
789
        Self {
790
            user_ids: Vec::new(),
791
            room: true,
792
        }
793
    }
794
}
795
796
#[derive(Clone, Debug, Eq, PartialEq)]
797
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
798
#[non_exhaustive]
799
pub enum Event {
800
    ConnectionChanged(ConnectionState),
801
    Authenticated {
802
        user_id: OwnedUserId,
803
    },
804
    /// the tokens rotated. with a `SessionStore` the record is already saved by now,
805
    /// without one persist a fresh `ClientHandle::session_record()` atomically
806
    SessionTokensChanged,
807
    /// the rotated tokens didn't reach the `SessionStore`, so the saved record may not
808
    /// restore. the engine tries again on the next rotation
809
    SessionSaveFailed {
810
        message: String,
811
    },
812
    /// the server stopped honouring our token and the engine stops. a soft logout
813
    /// names the device to log in again as, keeping the store. otherwise the record
814
    /// and the store are both dead
815
    SessionAccessRejected {
816
        soft_logout: Option<OwnedDeviceId>,
817
    },
818
    NotificationSettingsChanged,
819
    VerificationTargetChanged(VerificationTarget),
820
    RoomsChanged,
821
    /// an image pack came, went or changed somewhere. ask `image_packs` again
822
    ImagePacksChanged,
823
    /// replaces whatever you had for this timeline. every diff after it applies to it
824
    TimelineReset {
825
        timeline_id: TimelineId,
826
        items: Vector<TimelineItem>,
827
    },
828
    TimelineChanged {
829
        timeline_id: TimelineId,
830
        diffs: Vec<TimelineDiff>,
831
    },
832
    HistoryChanged {
833
        timeline_id: TimelineId,
834
        state: HistoryState,
835
    },
836
    /// the timeline was closed, or its room was left, and nothing more comes for it
837
    /// until another `TimelineReset`
838
    TimelineClosed {
839
        timeline_id: TimelineId,
840
    },
841
    /// a room that stops being watched says so with an empty `users`
842
    TypingChanged(TypingState),
843
    MediaUploadProgress {
844
        room_id: OwnedRoomId,
845
        transaction_id: OwnedTransactionId,
846
        uploaded_bytes: u64,
847
        total_bytes: u64,
848
        complete: bool,
849
    },
850
    /// not the end of it. `will_retry` means the send queue has another go by itself,
851
    /// otherwise it waits for `retry_send` or `cancel_send`
852
    MediaUploadFailed {
853
        room_id: OwnedRoomId,
854
        transaction_id: OwnedTransactionId,
855
        kind: crate::ErrorKind,
856
        will_retry: bool,
857
    },
858
    /// an upload that turned up in a progress or failure event gets exactly one of
859
    /// these while the engine runs, and nothing about it after. stopping ends
860
    /// nothing, `Stopped` covers whatever is still going and the sdk resumes it next run
861
    MediaUploadFinished {
862
        room_id: OwnedRoomId,
863
        transaction_id: OwnedTransactionId,
864
        outcome: UploadOutcome,
865
    },
866
    EncryptionChanged(EncryptionState),
867
    VerificationChanged(VerificationState),
868
    /// logout went through and the engine stops. delete the record and the store
869
    LoggedOut,
870
    Failed(EngineFailure),
871
    /// always the last event. keep the saved record unless `LoggedOut` or a
872
    /// hard `SessionAccessRejected` came first. commands still in flight answer `Interrupted`
873
    Stopped,
874
}
875
876
#[derive(Clone, Debug, Eq, PartialEq)]
877
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
878
#[non_exhaustive]
879
pub enum UploadOutcome {
880
    Sent {
881
        event_id: OwnedEventId,
882
    },
883
    Cancelled,
884
    /// its room was forgotten, and the queue with it
885
    RoomForgotten,
886
    /// the sdk's update channel overflowed while it ended, so whether it was sent is unknown
887
    Missed,
888
}
889
890
/// why `Engine::run` came back, when nothing went wrong
891
#[derive(Clone, Debug, Eq, PartialEq)]
892
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
893
#[non_exhaustive]
894
pub enum Termination {
895
    ShutDown,
896
    /// every `ClientHandle` was dropped, so nobody could have asked for anything else
897
    HandlesDropped,
898
    LoggedOut,
899
    /// the server rejected our token for good
900
    AccessRevoked,
901
    /// the server wants this device to log in again, and its e2ee keys live in the store
902
    SoftLoggedOut {
903
        device_id: OwnedDeviceId,
904
    },
905
}
906
907
impl Termination {
908
    #[must_use]
909
    pub const fn retain(&self) -> Retain {
910
        match self {
911
            Self::ShutDown | Self::HandlesDropped => Retain::RecordAndStore,
912
            Self::SoftLoggedOut { .. } => Retain::Store,
913
            Self::LoggedOut | Self::AccessRevoked => Retain::Nothing,
914
        }
915
    }
916
}
917
918
/// what's still worth keeping of the saved record and the store
919
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
920
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
921
pub enum Retain {
922
    RecordAndStore,
923
    /// keep the store and a record naming the device to log in again as
924
    Store,
925
    Nothing,
926
}
927
928
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
929
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
930
#[non_exhaustive]
931
pub enum ConnectionState {
932
    Idle,
933
    Connecting,
934
    Online,
935
    Offline,
936
    Stopped,
937
}
938
939
#[derive(Clone, Debug, Eq, PartialEq)]
940
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
941
#[non_exhaustive]
942
pub struct RoomSummary {
943
    pub room_id: OwnedRoomId,
944
    pub name: String,
945
    pub membership: RoomMembership,
946
    pub unread_messages: u64,
947
    pub unread_mentions: u64,
948
    /// unread events the push rules said to notify about, which is neither of the above
949
    pub unread_notifications: u64,
950
    pub encrypted: bool,
951
    pub space: bool,
952
    pub direct: bool,
953
    pub favourite: bool,
954
    pub low_priority: bool,
955
    pub avatar: Option<MediaId>,
956
    pub latest_event_timestamp_ms: Option<u64>,
957
    pub latest_event: Option<LatestEvent>,
958
    pub notification_mode: Option<RoomNotificationMode>,
959
    pub notification_mode_custom: bool,
960
    pub topic: Option<String>,
961
    pub canonical_alias: Option<OwnedRoomAliasId>,
962
    pub joined_members: u64,
963
    pub invited_members: u64,
964
    /// who a nameless room gets named after
965
    pub heroes: Vec<RoomHero>,
966
    /// set once the room has been upgraded, this is where everyone went
967
    pub successor: Option<OwnedRoomId>,
968
    pub inviter: Option<OwnedUserId>,
969
    /// joined spaces that list this room as a child
970
    pub parent_spaces: Vec<OwnedRoomId>,
971
    /// for a space, the children it lists, joined or not
972
    pub children: Vec<OwnedRoomId>,
973
    /// a space's msc3230 order. ordered spaces sort first by (order, room id), the rest by room id
974
    pub space_order: Option<String>,
975
    /// you can send im.ponies.room_emotes here, so you can make and edit its packs
976
    pub can_edit_packs: bool,
977
}
978
979
#[derive(Clone, Debug, Eq, PartialEq)]
980
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
981
#[non_exhaustive]
982
pub struct RoomHero {
983
    pub user_id: OwnedUserId,
984
    pub display_name: Option<String>,
985
    pub avatar: Option<MediaId>,
986
}
987
988
/// enough for a room list preview line. `kind` and `body` are only set for messages,
989
/// and an edit shows its new text
990
#[derive(Clone, Debug, Eq, PartialEq)]
991
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
992
#[non_exhaustive]
993
pub struct LatestEvent {
994
    pub sender: Option<OwnedUserId>,
995
    pub kind: Option<MessageKind>,
996
    pub body: Option<String>,
997
    pub timestamp_ms: Option<u64>,
998
    pub send_state: SendState,
999
}
1000
1001
#[derive(Clone, Debug, Eq, PartialEq)]
1002
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1003
#[non_exhaustive]
1004
pub struct Profile {
1005
    pub user_id: OwnedUserId,
1006
    pub display_name: Option<String>,
1007
    pub avatar: Option<MediaId>,
1008
}
1009
1010
/// one room in a space's hierarchy, as the server describes it, which may be a room you're not in
1011
#[derive(Clone, Debug, Eq, PartialEq)]
1012
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1013
#[non_exhaustive]
1014
pub struct SpaceChild {
1015
    pub room_id: OwnedRoomId,
1016
    pub name: String,
1017
    pub topic: Option<String>,
1018
    pub avatar: Option<MediaId>,
1019
    pub canonical_alias: Option<OwnedRoomAliasId>,
1020
    pub joined_members: u64,
1021
    pub space: bool,
1022
    pub children_count: u64,
1023
    pub membership: Option<RoomMembership>,
1024
    pub via: Vec<OwnedServerName>,
1025
    pub suggested: bool,
1026
    pub world_readable: Option<bool>,
1027
    pub guest_can_join: bool,
1028
}
1029
1030
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1031
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1032
#[non_exhaustive]
1033
pub enum RoomNotificationMode {
1034
    AllMessages,
1035
    MentionsAndKeywordsOnly,
1036
    Mute,
1037
}
1038
1039
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1040
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1041
#[non_exhaustive]
1042
pub struct RoomNotificationSettings {
1043
    pub mode: RoomNotificationMode,
1044
    pub custom: bool,
1045
}
1046
1047
#[derive(Clone, Debug)]
1048
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1049
#[non_exhaustive]
1050
pub enum PushNotificationStatus {
1051
    Event(Box<PushNotification>),
1052
    NotFound,
1053
    FilteredOut,
1054
    Redacted,
1055
}
1056
1057
#[derive(Clone, Debug)]
1058
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1059
#[non_exhaustive]
1060
pub struct PushNotification {
1061
    pub event_id: OwnedEventId,
1062
    pub event: PushNotificationEvent,
1063
    pub sender: PushNotificationSender,
1064
    pub room: PushNotificationRoom,
1065
    pub noisy: Option<bool>,
1066
    pub has_mention: Option<bool>,
1067
    pub thread_id: Option<OwnedEventId>,
1068
}
1069
1070
#[derive(Clone, Debug, Eq, PartialEq)]
1071
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1072
#[non_exhaustive]
1073
pub struct PushNotificationSender {
1074
    pub user_id: OwnedUserId,
1075
    pub display_name: Option<String>,
1076
    pub avatar_url: Option<MediaId>,
1077
    pub name_ambiguous: bool,
1078
}
1079
1080
#[derive(Clone, Debug, Eq, PartialEq)]
1081
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1082
#[non_exhaustive]
1083
pub struct PushNotificationRoom {
1084
    pub room_id: OwnedRoomId,
1085
    pub name: String,
1086
    pub avatar_url: Option<MediaId>,
1087
    pub canonical_alias: Option<OwnedRoomAliasId>,
1088
    pub topic: Option<String>,
1089
    pub encrypted: Option<bool>,
1090
    pub direct: bool,
1091
    pub joined_members_count: u64,
1092
    pub service_members: Vec<OwnedUserId>,
1093
    pub active_service_members_count: u64,
1094
    pub space: bool,
1095
}
1096
1097
#[derive(Clone, Debug)]
1098
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1099
#[non_exhaustive]
1100
pub enum PushNotificationEvent {
1101
    Timeline(Raw<AnySyncTimelineEvent>),
1102
    Invite(Raw<StrippedRoomMemberEvent>),
1103
}
1104
1105
#[derive(Clone, Debug, Eq, PartialEq)]
1106
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1107
#[non_exhaustive]
1108
pub struct RoomPowerLevels {
1109
    pub ban: i64,
1110
    pub invite: i64,
1111
    pub kick: i64,
1112
    pub redact: i64,
1113
    pub events_default: i64,
1114
    pub state_default: i64,
1115
    pub users_default: i64,
1116
    pub room_notification: i64,
1117
    pub users: Vec<(OwnedUserId, i64)>,
1118
    pub events: Vec<(String, i64)>,
1119
    /// in rooms from version 12 the creators outrank everyone and aren't listed in `users`
1120
    pub creators: Vec<OwnedUserId>,
1121
}
1122
1123
#[derive(Clone, Debug, Default, Eq, PartialEq)]
1124
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1125
#[non_exhaustive]
1126
pub struct PowerLevelChanges {
1127
    pub ban: Option<i64>,
1128
    pub invite: Option<i64>,
1129
    pub kick: Option<i64>,
1130
    pub redact: Option<i64>,
1131
    pub events_default: Option<i64>,
1132
    pub state_default: Option<i64>,
1133
    pub users_default: Option<i64>,
1134
    pub room_notification: Option<i64>,
1135
    pub room_name: Option<i64>,
1136
    pub room_avatar: Option<i64>,
1137
    pub room_topic: Option<i64>,
1138
    pub space_child: Option<i64>,
1139
    pub beacon: Option<i64>,
1140
    pub beacon_info: Option<i64>,
1141
}
1142
1143
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1144
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1145
#[non_exhaustive]
1146
pub enum RoomMembership {
1147
    Joined,
1148
    Invited,
1149
    Left,
1150
    Knocked,
1151
    Banned,
1152
}
1153
1154
#[derive(Clone, Debug, Default, Eq, PartialEq)]
1155
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1156
#[non_exhaustive]
1157
pub struct RoomListFilter {
1158
    pub membership: Option<RoomMembership>,
1159
    pub category: Option<RoomCategory>,
1160
    pub favourite: Option<bool>,
1161
    pub unread_only: bool,
1162
    pub search: Option<String>,
1163
}
1164
1165
impl RoomListFilter {
1166
    #[must_use]
1167
    pub fn with_membership(mut self, membership: RoomMembership) -> Self {
1168
        self.membership = Some(membership);
1169
        self
1170
    }
1171
1172
    #[must_use]
1173
    pub fn with_category(mut self, category: RoomCategory) -> Self {
1174
        self.category = Some(category);
1175
        self
1176
    }
1177
1178
    #[must_use]
1179
    pub fn with_favourite(mut self, favourite: bool) -> Self {
1180
        self.favourite = Some(favourite);
1181
        self
1182
    }
1183
1184
    #[must_use]
1185
    pub const fn with_unread_only(mut self, unread_only: bool) -> Self {
1186
        self.unread_only = unread_only;
1187
        self
1188
    }
1189
1190
    #[must_use]
1191
    pub fn with_search(mut self, search: impl Into<String>) -> Self {
1192
        self.search = Some(search.into());
1193
        self
1194
    }
1195
1196
    #[must_use]
1197
    pub fn matches(&self, room: &RoomSummary) -> bool {
1198
        self.matcher().matches(room)
1199
    }
1200
1201
    /// lowercases the search once, for filtering a whole list
1202
    #[must_use]
1203
    pub fn matcher(&self) -> RoomMatcher<'_> {
1204
        RoomMatcher {
1205
            filter: self,
1206
            search: self.search.as_deref().map(str::to_lowercase),
1207
        }
1208
    }
1209
}
1210
1211
#[derive(Debug)]
1212
pub struct RoomMatcher<'a> {
1213
    filter: &'a RoomListFilter,
1214
    search: Option<String>,
1215
}
1216
1217
impl RoomMatcher<'_> {
1218
    #[must_use]
1219
    pub fn matches(&self, room: &RoomSummary) -> bool {
1220
        let filter = self.filter;
1221
        filter
1222
            .membership
1223
            .is_none_or(|membership| room.membership == membership)
1224
            && filter.category.is_none_or(|category| match category {
1225
                RoomCategory::People => room.direct && !room.space,
1226
                RoomCategory::Rooms => !room.direct && !room.space,
1227
                RoomCategory::Spaces => room.space,
1228
            })
1229
            && filter
1230
                .favourite
1231
                .is_none_or(|favourite| room.favourite == favourite)
1232
            && (!filter.unread_only || room.unread_messages > 0 || room.unread_mentions > 0)
1233
            && self.search.as_ref().is_none_or(|search| {
1234
                room.name.to_lowercase().contains(search)
1235
                    || room.room_id.as_str().to_lowercase().contains(search)
1236
            })
1237
    }
1238
}
1239
1240
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1241
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1242
#[non_exhaustive]
1243
pub enum RoomCategory {
1244
    People,
1245
    Rooms,
1246
    Spaces,
1247
}
1248
1249
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1250
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1251
#[non_exhaustive]
1252
pub enum MemberFilter {
1253
    Active,
1254
    Joined,
1255
    Invited,
1256
    Banned,
1257
    Left,
1258
    All,
1259
}
1260
1261
#[derive(Clone, Debug, Eq, PartialEq)]
1262
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1263
#[non_exhaustive]
1264
pub enum MemberMembership {
1265
    Joined,
1266
    Invited,
1267
    Left,
1268
    Knocked,
1269
    Banned,
1270
    Custom(String),
1271
}
1272
1273
#[derive(Clone, Debug, Eq, PartialEq)]
1274
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1275
#[non_exhaustive]
1276
pub struct MemberSummary {
1277
    pub user_id: OwnedUserId,
1278
    pub name: String,
1279
    pub display_name: Option<String>,
1280
    pub avatar: Option<MediaId>,
1281
    pub membership: MemberMembership,
1282
    pub own: bool,
1283
    pub name_ambiguous: bool,
1284
    pub ignored: bool,
1285
    pub service: bool,
1286
}
1287
1288
#[derive(Clone, Debug, Eq, PartialEq)]
1289
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1290
#[non_exhaustive]
1291
pub struct OwnDevice {
1292
    pub device_id: OwnedDeviceId,
1293
    pub display_name: Option<String>,
1294
    pub last_seen_ip: Option<String>,
1295
    pub last_seen_timestamp_ms: Option<u64>,
1296
    pub current: bool,
1297
}
1298
1299
#[derive(Clone, Debug, Eq, PartialEq)]
1300
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1301
#[non_exhaustive]
1302
pub enum TimelineItem {
1303
    Event(Box<TimelineEvent>),
1304
    DayDivider { timestamp_ms: u64 },
1305
    ReadMarker,
1306
    TimelineStart,
1307
}
1308
1309
#[derive(Clone, Debug, Eq, PartialEq)]
1310
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1311
#[non_exhaustive]
1312
pub enum TimelineDiff {
1313
    Append { values: Vec<TimelineItem> },
1314
    Clear,
1315
    PushFront { value: TimelineItem },
1316
    PushBack { value: TimelineItem },
1317
    PopFront,
1318
    PopBack,
1319
    Insert { index: usize, value: TimelineItem },
1320
    Set { index: usize, value: TimelineItem },
1321
    Remove { index: usize },
1322
    Truncate { length: usize },
1323
    Reset { values: Vec<TimelineItem> },
1324
}
1325
1326
#[derive(Clone, Copy, Debug, Eq, PartialEq, thiserror::Error)]
1327
#[error("timeline diff is incompatible with the current {length}-item snapshot")]
1328
pub struct TimelineDiffError {
1329
    pub length: usize,
1330
}
1331
1332
impl TimelineDiff {
1333
    /// Apply a diff only when its indices match the current snapshot.
1334
    /// On error the snapshot is unchanged; request `TimelineCommand::Resync`
1335
    /// and ignore further diffs until the corresponding `TimelineReset`.
1336
    pub fn try_apply(self, items: &mut Vector<TimelineItem>) -> Result<(), TimelineDiffError> {
1337
        let length = items.len();
1338
        if self.length_after(length).is_none() {
1339
            return Err(TimelineDiffError { length });
1340
        }
1341
        self.apply(items);
1342
        Ok(())
1343
    }
1344
1345
    /// a whole `TimelineChanged` or none of it. whether a diff fits depends only on the
1346
    /// length before it, so the batch is checked against lengths alone before anything
1347
    /// moves. the error carries the length the snapshot would have had at the misfit
1348
    pub fn try_apply_all(
1349
        diffs: Vec<Self>,
1350
        items: &mut Vector<TimelineItem>,
1351
    ) -> Result<(), TimelineDiffError> {
1352
        diffs.iter().try_fold(items.len(), |length, diff| {
1353
            diff.length_after(length)
1354
                .ok_or(TimelineDiffError { length })
1355
        })?;
1356
        for diff in diffs {
1357
            diff.apply(items);
1358
        }
1359
        Ok(())
1360
    }
1361
1362
    fn length_after(&self, length: usize) -> Option<usize> {
1363
        match self {
1364
            Self::Append { values } => Some(length + values.len()),
1365
            Self::Clear => Some(0),
1366
            Self::PushFront { .. } | Self::PushBack { .. } => Some(length + 1),
1367
            Self::PopFront | Self::PopBack => length.checked_sub(1),
1368
            Self::Insert { index, .. } => (*index <= length).then_some(length + 1),
1369
            Self::Set { index, .. } => (*index < length).then_some(length),
1370
            Self::Remove { index } => (*index < length).then(|| length - 1),
1371
            Self::Truncate { length: target } => (*target <= length).then_some(*target),
1372
            Self::Reset { values } => Some(values.len()),
1373
        }
1374
    }
1375
1376
    fn apply(self, items: &mut Vector<TimelineItem>) {
1377
        match self {
1378
            Self::Append { values } => items.extend(values),
1379
            Self::Clear => items.clear(),
1380
            Self::PushFront { value } => items.push_front(value),
1381
            Self::PushBack { value } => items.push_back(value),
1382
            Self::PopFront => {
1383
                items.pop_front();
1384
            }
1385
            Self::PopBack => {
1386
                items.pop_back();
1387
            }
1388
            Self::Insert { index, value } => items.insert(index, value),
1389
            Self::Set { index, value } => {
1390
                items.set(index, value);
1391
            }
1392
            Self::Remove { index } => {
1393
                items.remove(index);
1394
            }
1395
            Self::Truncate { length } => items.truncate(length),
1396
            Self::Reset { values } => *items = values.into_iter().collect(),
1397
        }
1398
    }
1399
}
1400
1401
#[cfg(test)]
1402
mod tests {
1403
    use imbl::{Vector, vector};
1404
1405
    use super::{TimelineDiff, TimelineItem};
1406
1407
    fn item(timestamp_ms: u64) -> TimelineItem {
1408
        TimelineItem::DayDivider { timestamp_ms }
1409
    }
1410
1411
    #[test]
1412
    fn timeline_diff_try_apply_handles_every_variant() {
1413
        let mut items = vector![item(1), item(2)];
1414
1415
        TimelineDiff::Append {
1416
            values: vec![item(3)],
1417
        }
1418
        .try_apply(&mut items)
1419
        .unwrap();
1420
        assert_eq!(items, vector![item(1), item(2), item(3)]);
1421
1422
        TimelineDiff::Clear.try_apply(&mut items).unwrap();
1423
        assert!(items.is_empty());
1424
1425
        TimelineDiff::PushFront { value: item(2) }
1426
            .try_apply(&mut items)
1427
            .unwrap();
1428
        TimelineDiff::PushBack { value: item(3) }
1429
            .try_apply(&mut items)
1430
            .unwrap();
1431
        assert_eq!(items, vector![item(2), item(3)]);
1432
1433
        TimelineDiff::PopFront.try_apply(&mut items).unwrap();
1434
        TimelineDiff::PopBack.try_apply(&mut items).unwrap();
1435
        assert!(items.is_empty());
1436
1437
        TimelineDiff::Reset {
1438
            values: vec![item(1), item(3)],
1439
        }
1440
        .try_apply(&mut items)
1441
        .unwrap();
1442
        TimelineDiff::Insert {
1443
            index: 1,
1444
            value: item(2),
1445
        }
1446
        .try_apply(&mut items)
1447
        .unwrap();
1448
        assert_eq!(items, vector![item(1), item(2), item(3)]);
1449
1450
        TimelineDiff::Set {
1451
            index: 1,
1452
            value: item(4),
1453
        }
1454
        .try_apply(&mut items)
1455
        .unwrap();
1456
        assert_eq!(items, vector![item(1), item(4), item(3)]);
1457
1458
        TimelineDiff::Remove { index: 1 }
1459
            .try_apply(&mut items)
1460
            .unwrap();
1461
        assert_eq!(items, vector![item(1), item(3)]);
1462
1463
        TimelineDiff::Truncate { length: 1 }
1464
            .try_apply(&mut items)
1465
            .unwrap();
1466
        assert_eq!(items, vector![item(1)]);
1467
    }
1468
1469
    #[test]
1470
    fn invalid_timeline_diff_leaves_snapshot_unchanged() {
1471
        let invalid = [
1472
            TimelineDiff::Insert {
1473
                index: 3,
1474
                value: item(3),
1475
            },
1476
            TimelineDiff::Set {
1477
                index: 2,
1478
                value: item(3),
1479
            },
1480
            TimelineDiff::Remove { index: 2 },
1481
            TimelineDiff::Truncate { length: 3 },
1482
        ];
1483
        for diff in invalid {
1484
            let mut items = vector![item(1), item(2)];
1485
            assert_eq!(diff.try_apply(&mut items).unwrap_err().length, 2);
1486
            assert_eq!(items, vector![item(1), item(2)]);
1487
        }
1488
        for diff in [TimelineDiff::PopFront, TimelineDiff::PopBack] {
1489
            let mut items = Vector::new();
1490
            assert_eq!(diff.try_apply(&mut items).unwrap_err().length, 0);
1491
            assert!(items.is_empty());
1492
        }
1493
    }
1494
1495
    #[test]
1496
    fn a_batch_that_goes_wrong_halfway_changes_nothing() {
1497
        let mut items = vector![item(1), item(2)];
1498
        let batch = vec![
1499
            TimelineDiff::PushBack { value: item(3) },
1500
            TimelineDiff::PushBack { value: item(4) },
1501
            TimelineDiff::Remove { index: 0 },
1502
            TimelineDiff::Set {
1503
                index: 3,
1504
                value: item(5),
1505
            },
1506
        ];
1507
        assert_eq!(
1508
            TimelineDiff::try_apply_all(batch, &mut items)
1509
                .unwrap_err()
1510
                .length,
1511
            3
1512
        );
1513
        assert_eq!(items, vector![item(1), item(2)]);
1514
1515
        let batch = vec![
1516
            TimelineDiff::PushBack { value: item(3) },
1517
            TimelineDiff::Remove { index: 0 },
1518
            TimelineDiff::Set {
1519
                index: 1,
1520
                value: item(4),
1521
            },
1522
        ];
1523
        TimelineDiff::try_apply_all(batch, &mut items).unwrap();
1524
        assert_eq!(items, vector![item(2), item(4)]);
1525
    }
1526
}
1527
1528
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
1529
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1530
pub struct TimelineItemId(String);
1531
1532
impl TimelineItemId {
1533
    pub(crate) fn new(value: String) -> Self {
1534
        Self(value)
1535
    }
1536
1537
    #[must_use]
1538
    pub fn as_str(&self) -> &str {
1539
        &self.0
1540
    }
1541
}
1542
1543
#[derive(Clone, Debug, Eq, PartialEq)]
1544
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1545
#[non_exhaustive]
1546
pub struct TimelineEvent {
1547
    pub item_id: TimelineItemId,
1548
    pub event_id: Option<OwnedEventId>,
1549
    pub transaction_id: Option<OwnedTransactionId>,
1550
    pub sender: Sender,
1551
    pub timestamp_ms: u64,
1552
    pub own: bool,
1553
    pub send_state: SendState,
1554
    pub content: TimelineContent,
1555
    pub reply_to: Option<ReplyReference>,
1556
    pub thread_root: Option<OwnedEventId>,
1557
    pub thread_summary: Option<ThreadSummary>,
1558
    pub reactions: Vec<Reaction>,
1559
    pub read_by: Vec<OwnedUserId>,
1560
    pub editable: bool,
1561
}
1562
1563
impl TimelineEvent {
1564
    /// what to hand edit, redact and react. works before the server has echoed it back
1565
    #[must_use]
1566
    pub fn target(&self) -> Option<EventRef> {
1567
        self.event_id
1568
            .clone()
1569
            .map(EventRef::Remote)
1570
            .or_else(|| self.transaction_id.clone().map(EventRef::Local))
1571
    }
1572
}
1573
1574
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
1575
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1576
#[non_exhaustive]
1577
pub enum EventRef {
1578
    Remote(OwnedEventId),
1579
    /// a local echo, by the transaction id it was queued under
1580
    Local(OwnedTransactionId),
1581
}
1582
1583
impl From<OwnedEventId> for EventRef {
1584
    fn from(event_id: OwnedEventId) -> Self {
1585
        Self::Remote(event_id)
1586
    }
1587
}
1588
1589
#[derive(Clone, Debug, Eq, PartialEq)]
1590
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1591
#[non_exhaustive]
1592
pub struct Sender {
1593
    pub user_id: OwnedUserId,
1594
    pub display_name: Option<String>,
1595
    pub avatar: Option<MediaId>,
1596
    /// someone else in the room goes by the same name, so show the user id too
1597
    pub name_ambiguous: bool,
1598
}
1599
1600
impl Sender {
1601
    #[must_use]
1602
    pub fn name(&self) -> &str {
1603
        self.display_name
1604
            .as_deref()
1605
            .unwrap_or_else(|| self.user_id.as_str())
1606
    }
1607
}
1608
1609
#[derive(Clone, Debug, Eq, PartialEq)]
1610
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1611
#[non_exhaustive]
1612
pub enum TimelineContent {
1613
    Message(Message),
1614
    Sticker {
1615
        body: String,
1616
        media: MediaDescriptor,
1617
    },
1618
    Poll {
1619
        question: String,
1620
    },
1621
    Redacted,
1622
    UnableToDecrypt(UtdCause),
1623
    Membership {
1624
        user_id: OwnedUserId,
1625
        display_name: Option<String>,
1626
        change: MembershipChange,
1627
    },
1628
    Profile {
1629
        user_id: OwnedUserId,
1630
        display_name: Option<Change<Option<String>>>,
1631
        avatar: Option<Change<Option<MediaId>>>,
1632
    },
1633
    /// `None` means it was cleared, or the event was redacted
1634
    RoomName(Option<String>),
1635
    RoomTopic(Option<String>),
1636
    RoomAvatar(Option<MediaId>),
1637
    EncryptionEnabled,
1638
    RoomCreated,
1639
    RoomUpgraded {
1640
        successor: OwnedRoomId,
1641
    },
1642
    OtherState {
1643
        event_type: String,
1644
        state_key: String,
1645
    },
1646
    Other {
1647
        event_type: String,
1648
    },
1649
}
1650
1651
#[derive(Clone, Debug, Eq, PartialEq)]
1652
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1653
#[non_exhaustive]
1654
pub struct Message {
1655
    pub kind: MessageKind,
1656
    pub body: String,
1657
    /// only for `MessageKind::Location`
1658
    pub geo_uri: Option<String>,
1659
    pub formatted_body: Option<String>,
1660
    pub mentions: Option<MessageMentions>,
1661
    pub edited: bool,
1662
    pub media: Option<MediaDescriptor>,
1663
    /// previews the sender attached (msc4095), so showing them asks no one about the url.
1664
    /// one for a url the body doesn't contain is dropped
1665
    pub link_previews: Vec<LinkPreview>,
1666
}
1667
1668
/// a link card, from the sender's own message or from `ClientHandle::url_preview`. page
1669
/// text passes through whoever made it unfiltered, so it arrives trimmed to a sane length
1670
#[derive(Clone, Debug, Eq, PartialEq)]
1671
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1672
#[non_exhaustive]
1673
pub struct LinkPreview {
1674
    /// the url as it appears in the message, or as it was asked about
1675
    pub url: String,
1676
    /// where the page says it lives, when that's somewhere else
1677
    pub canonical_url: Option<String>,
1678
    pub title: Option<String>,
1679
    pub description: Option<String>,
1680
    pub site_name: Option<String>,
1681
    pub image: Option<LinkPreviewImage>,
1682
}
1683
1684
#[derive(Clone, Debug, Eq, PartialEq)]
1685
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1686
#[non_exhaustive]
1687
pub struct LinkPreviewImage {
1688
    pub media_id: MediaId,
1689
    pub mimetype: Option<String>,
1690
    pub size_bytes: Option<u64>,
1691
    pub width: Option<u64>,
1692
    pub height: Option<u64>,
1693
}
1694
1695
#[derive(Clone, Debug, Eq, PartialEq)]
1696
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1697
#[non_exhaustive]
1698
pub enum MessageKind {
1699
    Text,
1700
    Emote,
1701
    Notice,
1702
    ServerNotice,
1703
    Image,
1704
    Video,
1705
    Audio,
1706
    File,
1707
    Location,
1708
    VerificationRequest,
1709
    Other(String),
1710
}
1711
1712
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1713
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1714
#[non_exhaustive]
1715
pub enum MembershipChange {
1716
    Joined,
1717
    Left,
1718
    Banned,
1719
    Unbanned,
1720
    Kicked,
1721
    KickedAndBanned,
1722
    Invited,
1723
    InvitationAccepted,
1724
    InvitationRejected,
1725
    InvitationRevoked,
1726
    Knocked,
1727
    KnockAccepted,
1728
    KnockRetracted,
1729
    KnockDenied,
1730
    /// nothing a person would call a change, or something the sdk couldn't make sense of
1731
    Other,
1732
}
1733
1734
#[derive(Clone, Debug, Eq, PartialEq)]
1735
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1736
pub struct Change<T> {
1737
    pub old: T,
1738
    pub new: T,
1739
}
1740
1741
/// why a message couldn't be read, worth telling the user since some of these they can fix
1742
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1743
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1744
#[non_exhaustive]
1745
pub enum UtdCause {
1746
    Unknown,
1747
    SentBeforeWeJoined,
1748
    VerificationViolation,
1749
    UnsignedDevice,
1750
    UnknownDevice,
1751
    HistoricalMessageAndBackupIsDisabled,
1752
    WithheldForUnverifiedOrInsecureDevice,
1753
    WithheldBySender,
1754
    HistoricalMessageAndDeviceIsUnverified,
1755
}
1756
1757
#[derive(Clone, Debug, Eq, PartialEq)]
1758
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1759
#[non_exhaustive]
1760
pub struct ThreadSummary {
1761
    pub reply_count: u32,
1762
    pub latest_event_id: Option<OwnedEventId>,
1763
    pub latest_event_timestamp_ms: Option<u64>,
1764
}
1765
1766
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1767
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1768
#[non_exhaustive]
1769
pub enum SendState {
1770
    Sent,
1771
    Sending,
1772
    /// `will_retry` means macltok has it in hand. otherwise it's wedged until you
1773
    /// `retry_send` or `cancel_send`, so that's when to show a retry button
1774
    Failed {
1775
        will_retry: bool,
1776
    },
1777
}
1778
1779
#[derive(Clone, Debug, Eq, PartialEq)]
1780
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1781
#[non_exhaustive]
1782
pub struct ReplyReference {
1783
    pub event_id: OwnedEventId,
1784
    pub details: ReplyDetails,
1785
}
1786
1787
/// the replied-to event. macltok asks for it when it isn't loaded, `Pending` then turns into one of the others
1788
#[derive(Clone, Debug, Eq, PartialEq)]
1789
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1790
#[non_exhaustive]
1791
pub enum ReplyDetails {
1792
    Pending,
1793
    Ready {
1794
        sender: Sender,
1795
        content: Box<TimelineContent>,
1796
        timestamp_ms: u64,
1797
    },
1798
    Unavailable,
1799
}
1800
1801
#[derive(Clone, Debug, Eq, PartialEq)]
1802
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1803
#[non_exhaustive]
1804
pub struct Reaction {
1805
    pub key: String,
1806
    /// a custom emote's shortcode, from whichever sender gave one
1807
    pub shortcode: Option<String>,
1808
    /// the custom emote itself, when the key is an image
1809
    pub image: Option<MediaId>,
1810
    pub count: usize,
1811
    pub own: bool,
1812
    pub senders: Vec<OwnedUserId>,
1813
}
1814
1815
/// every custom emote and sticker pack the account can reach, from the user's own
1816
/// pack, each joined room's packs, and the rooms the user enabled everywhere
1817
#[derive(Clone, Debug, Default, Eq, PartialEq)]
1818
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1819
#[non_exhaustive]
1820
pub struct ImagePacks {
1821
    /// the user's own pack, then the packs of rooms enabled everywhere
1822
    pub global: Vec<ImagePack>,
1823
    /// each joined room's own packs, rooms without any left out
1824
    pub rooms: imbl::HashMap<OwnedRoomId, Vec<ImagePack>>,
1825
}
1826
1827
impl ImagePacks {
1828
    /// what a picker in this room offers. the user's pack, the room's own, then the
1829
    /// rest of the enabled ones, each pack once
1830
    #[must_use]
1831
    pub fn for_room(&self, room_id: Option<&RoomId>) -> Vec<&ImagePack> {
1832
        let is_user = |pack: &&ImagePack| pack.source == ImagePackSource::User;
1833
        let own = room_id.and_then(|room_id| self.rooms.get(room_id));
1834
        let mut packs: Vec<&ImagePack> = self.global.iter().filter(is_user).collect();
1835
        packs.extend(own.into_iter().flatten());
1836
        for pack in self.global.iter().filter(|pack| !is_user(pack)) {
1837
            if !packs.iter().any(|seen| seen.id == pack.id) {
1838
                packs.push(pack);
1839
            }
1840
        }
1841
        packs
1842
    }
1843
}
1844
1845
#[derive(Clone, Debug, Eq, PartialEq)]
1846
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1847
#[non_exhaustive]
1848
pub struct ImagePack {
1849
    /// `user`, or `room:<room id>:<state key>`, the same across calls
1850
    pub id: String,
1851
    pub display_name: Option<String>,
1852
    pub avatar: Option<MediaId>,
1853
    pub attribution: Option<String>,
1854
    pub source: ImagePackSource,
1855
    pub images: Vec<PackImage>,
1856
    /// your own pack, or a room pack whose state you're allowed to send
1857
    pub editable: bool,
1858
    /// listed in im.ponies.emote_rooms. always true for your own pack
1859
    pub enabled_everywhere: bool,
1860
}
1861
1862
#[derive(Clone, Debug, Eq, PartialEq)]
1863
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1864
#[non_exhaustive]
1865
pub enum ImagePackSource {
1866
    User,
1867
    Room {
1868
        room_id: OwnedRoomId,
1869
        state_key: String,
1870
    },
1871
}
1872
1873
#[derive(Clone, Debug, Eq, PartialEq)]
1874
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1875
#[non_exhaustive]
1876
pub struct PackImage {
1877
    pub shortcode: String,
1878
    pub media_id: MediaId,
1879
    /// what `toggle_reaction` takes to react with this image
1880
    pub reaction_key: String,
1881
    pub body: Option<String>,
1882
    pub mimetype: Option<String>,
1883
    pub width: Option<u64>,
1884
    pub height: Option<u64>,
1885
    /// the image's own, else its pack's, else both
1886
    pub usage: Vec<PackUsage>,
1887
}
1888
1889
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
1890
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1891
#[non_exhaustive]
1892
pub enum PackUsage {
1893
    Emoticon,
1894
    Sticker,
1895
}
1896
1897
#[derive(Clone, Debug, Eq, PartialEq)]
1898
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1899
#[non_exhaustive]
1900
pub struct MediaDescriptor {
1901
    pub id: MediaId,
1902
    pub kind: MediaKind,
1903
    /// only for unencrypted media, an encrypted file's mxc is ciphertext without the keys
1904
    pub mxc: Option<OwnedMxcUri>,
1905
    pub filename: String,
1906
    pub caption: Option<String>,
1907
    pub mimetype: Option<String>,
1908
    pub size_bytes: Option<u64>,
1909
    pub width: Option<u64>,
1910
    pub height: Option<u64>,
1911
    pub duration_ms: Option<u64>,
1912
    pub blurhash: Option<String>,
1913
    /// the uploader sent a thumbnail, so `MediaSize::Thumbnail` won't need the server to resize
1914
    pub has_thumbnail: bool,
1915
}
1916
1917
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
1918
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1919
pub struct MediaId(String);
1920
1921
impl MediaId {
1922
    pub(crate) fn new(value: String) -> Self {
1923
        Self(value)
1924
    }
1925
1926
    /// plain media carries no keys, so it needs no registering and never expires
1927
    pub(crate) fn plain<U: AsRef<str> + ?Sized>(uri: &U) -> Self {
1928
        Self(uri.as_ref().to_owned())
1929
    }
1930
1931
    pub(crate) fn as_plain(&self) -> Option<&MxcUri> {
1932
        self.0.starts_with("mxc://").then(|| self.0.as_str().into())
1933
    }
1934
1935
    #[must_use]
1936
    pub fn as_str(&self) -> &str {
1937
        &self.0
1938
    }
1939
}
1940
1941
/// media ids are opaque but they do have to survive a trip through a browser. a made up one just fails `MediaUnknown`
1942
impl std::str::FromStr for MediaId {
1943
    type Err = std::convert::Infallible;
1944
1945
    fn from_str(value: &str) -> Result<Self, Self::Err> {
1946
        Ok(Self(value.to_owned()))
1947
    }
1948
}
1949
1950
impl From<String> for MediaId {
1951
    fn from(value: String) -> Self {
1952
        Self(value)
1953
    }
1954
}
1955
1956
impl std::fmt::Display for MediaId {
1957
    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1958
        self.0.fmt(formatter)
1959
    }
1960
}
1961
1962
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
1963
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1964
#[non_exhaustive]
1965
pub enum MediaSize {
1966
    Full,
1967
    /// a hint. an uploader's own thumbnail wins over the server resizing anything
1968
    Thumbnail {
1969
        width: u32,
1970
        height: u32,
1971
    },
1972
}
1973
1974
#[derive(Clone, Debug, Eq, PartialEq)]
1975
#[non_exhaustive]
1976
pub struct Media {
1977
    pub bytes: Arc<[u8]>,
1978
    pub mimetype: String,
1979
}
1980
1981
/// where media lives, for a host that fetches and decrypts it itself
1982
#[derive(Clone, Debug, Eq, PartialEq)]
1983
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1984
#[non_exhaustive]
1985
pub enum MediaLocation {
1986
    File(MediaFile),
1987
    /// a resize of plaintext, so never encrypted. plenty of servers refuse some, and `media`
1988
    /// then fetches `fallback` instead, so should you
1989
    ServerThumbnail {
1990
        url: String,
1991
        fallback: MediaFile,
1992
    },
1993
    /// still in the send queue with no url yet. `media` has the bytes
1994
    Local,
1995
}
1996
1997
#[derive(Clone, Debug, Eq, PartialEq)]
1998
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1999
#[non_exhaustive]
2000
pub struct MediaFile {
2001
    pub url: String,
2002
    pub mimetype: Option<String>,
2003
    pub declared_size: Option<u64>,
2004
    pub encryption: Option<AttachmentEncryption>,
2005
}
2006
2007
/// AES-256-CTR, the only attachment cipher matrix has
2008
#[derive(Clone, Debug, Eq, PartialEq)]
2009
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
2010
#[non_exhaustive]
2011
pub struct AttachmentEncryption {
2012
    /// the JWK `k`, unpadded url-safe base64
2013
    pub key: String,
2014
    /// unpadded base64, the initial counter block
2015
    pub iv: String,
2016
    /// unpadded base64 SHA-256 of the whole ciphertext, so a partial read goes unchecked
2017
    pub sha256: String,
2018
}
2019
2020
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
2021
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
2022
#[non_exhaustive]
2023
pub enum MediaKind {
2024
    Image,
2025
    Video,
2026
    Audio,
2027
    File,
2028
    Sticker,
2029
}
2030
2031
#[derive(Clone, Debug, Eq, PartialEq)]
2032
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
2033
#[non_exhaustive]
2034
pub struct TypingState {
2035
    pub room_id: OwnedRoomId,
2036
    pub users: Vec<OwnedUserId>,
2037
}
2038
2039
#[derive(Clone, Debug, Eq, PartialEq)]
2040
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
2041
#[non_exhaustive]
2042
pub enum HistoryState {
2043
    Idle,
2044
    Loading,
2045
    StartReached,
2046
    Failed(String),
2047
}
2048
2049
#[derive(Clone, Debug, Eq, PartialEq)]
2050
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
2051
#[non_exhaustive]
2052
pub struct EncryptionState {
2053
    pub device_id: OwnedDeviceId,
2054
    pub device_verified: bool,
2055
    pub recovery: RecoveryState,
2056
    pub key_backup: KeyBackupState,
2057
}
2058
2059
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
2060
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
2061
#[non_exhaustive]
2062
pub enum KeyBackupState {
2063
    Unknown,
2064
    Creating,
2065
    Enabling,
2066
    Resuming,
2067
    Downloading,
2068
    Enabled,
2069
    Disabling,
2070
}
2071
2072
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
2073
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
2074
#[non_exhaustive]
2075
pub enum RecoveryState {
2076
    Unknown,
2077
    Disabled,
2078
    Incomplete,
2079
    Enabled,
2080
}
2081
2082
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
2083
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
2084
#[non_exhaustive]
2085
pub enum VerificationAction {
2086
    /// take up a request another device sent, which arrives as `VerificationState::Incoming`
2087
    Accept,
2088
    Confirm,
2089
    Reject,
2090
    Cancel,
2091
}
2092
2093
#[derive(Clone, Debug, Eq, PartialEq)]
2094
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
2095
#[non_exhaustive]
2096
pub enum VerificationTarget {
2097
    OwnIdentity,
2098
    User(OwnedUserId),
2099
    Device {
2100
        user_id: OwnedUserId,
2101
        device_id: OwnedDeviceId,
2102
    },
2103
}
2104
2105
#[derive(Clone, Debug, Eq, PartialEq)]
2106
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
2107
#[non_exhaustive]
2108
pub enum VerificationState {
2109
    Waiting,
2110
    /// someone else asked. `Accept` to go ahead, `Cancel` to turn them down
2111
    Incoming {
2112
        user_id: OwnedUserId,
2113
        device_id: Option<OwnedDeviceId>,
2114
    },
2115
    Comparison {
2116
        emojis: Vec<(String, String)>,
2117
        decimals: (u16, u16, u16),
2118
    },
2119
    Confirmed,
2120
    Done,
2121
    Cancelled(String),
2122
    Failed(VerificationFailure),
2123
}
2124
2125
#[derive(Clone, Debug, Eq, PartialEq)]
2126
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
2127
#[non_exhaustive]
2128
pub enum VerificationFailure {
2129
    RequestAcceptance(String),
2130
    SasStartup(String),
2131
    SasAcceptance(String),
2132
    RequestStreamEnded,
2133
    SasStreamEnded,
2134
}
2135
2136
#[derive(Clone, Debug, Eq, PartialEq)]
2137
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
2138
#[non_exhaustive]
2139
pub struct EngineFailure {
2140
    pub kind: EngineErrorKind,
2141
    pub message: String,
2142
}
2143
2144
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
2145
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
2146
#[non_exhaustive]
2147
pub enum EngineErrorKind {
2148
    EventReceiverClosed,
2149
    Watcher,
2150
    Other,
2151
}