atagen

macltok

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