atagen

macltok

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