atagen

macltok

public
Star0
Watch0
d5d8097c3afd94c250affd252a3e9ea63ecec8a5
3
ssh://brewery@pls.lobotomise.me:33/atagen/macltok.git
src/model.rsRust1803 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. on a soft
641
    /// logout keep the store and log in again with the same device, otherwise
642
    /// the record and the store are both dead
643
    SessionAccessRejected {
644
        soft_logout: bool,
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
    Store,
748
    Nothing,
749
}
750
751
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
752
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
753
#[non_exhaustive]
754
pub enum ConnectionState {
755
    Idle,
756
    Connecting,
757
    Online,
758
    Offline,
759
    Stopped,
760
}
761
762
#[derive(Clone, Debug, Eq, PartialEq)]
763
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
764
#[non_exhaustive]
765
pub struct RoomSummary {
766
    pub room_id: OwnedRoomId,
767
    pub name: String,
768
    pub membership: RoomMembership,
769
    pub unread_messages: u64,
770
    pub unread_mentions: u64,
771
    /// unread events the push rules said to notify about, which is neither of the above
772
    pub unread_notifications: u64,
773
    pub encrypted: bool,
774
    pub space: bool,
775
    pub direct: bool,
776
    pub favourite: bool,
777
    pub low_priority: bool,
778
    pub avatar: Option<OwnedMxcUri>,
779
    pub latest_event_timestamp_ms: Option<u64>,
780
    pub latest_event: Option<LatestEvent>,
781
    pub notification_mode: Option<RoomNotificationMode>,
782
    pub notification_mode_custom: bool,
783
    pub topic: Option<String>,
784
    pub canonical_alias: Option<OwnedRoomAliasId>,
785
    pub joined_members: u64,
786
    pub invited_members: u64,
787
    /// who a nameless room gets named after
788
    pub heroes: Vec<RoomHero>,
789
    /// set once the room has been upgraded, this is where everyone went
790
    pub successor: Option<OwnedRoomId>,
791
    pub inviter: Option<OwnedUserId>,
792
    /// joined spaces that list this room as a child
793
    pub parent_spaces: Vec<OwnedRoomId>,
794
    /// for a space, the children it lists, joined or not
795
    pub children: Vec<OwnedRoomId>,
796
}
797
798
#[derive(Clone, Debug, Eq, PartialEq)]
799
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
800
#[non_exhaustive]
801
pub struct RoomHero {
802
    pub user_id: OwnedUserId,
803
    pub display_name: Option<String>,
804
    pub avatar: Option<OwnedMxcUri>,
805
}
806
807
/// enough for a room list preview line. `kind` and `body` are only set for messages,
808
/// and an edit shows its new text
809
#[derive(Clone, Debug, Eq, PartialEq)]
810
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
811
#[non_exhaustive]
812
pub struct LatestEvent {
813
    pub sender: Option<OwnedUserId>,
814
    pub kind: Option<MessageKind>,
815
    pub body: Option<String>,
816
    pub timestamp_ms: Option<u64>,
817
    pub send_state: SendState,
818
}
819
820
#[derive(Clone, Debug, Eq, PartialEq)]
821
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
822
#[non_exhaustive]
823
pub struct Profile {
824
    pub user_id: OwnedUserId,
825
    pub display_name: Option<String>,
826
    pub avatar: Option<OwnedMxcUri>,
827
}
828
829
/// one room in a space's hierarchy, as the server describes it, which may be a room you're not in
830
#[derive(Clone, Debug, Eq, PartialEq)]
831
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
832
#[non_exhaustive]
833
pub struct SpaceChild {
834
    pub room_id: OwnedRoomId,
835
    pub name: String,
836
    pub topic: Option<String>,
837
    pub avatar: Option<OwnedMxcUri>,
838
    pub canonical_alias: Option<OwnedRoomAliasId>,
839
    pub joined_members: u64,
840
    pub space: bool,
841
    pub children_count: u64,
842
    pub membership: Option<RoomMembership>,
843
    pub via: Vec<OwnedServerName>,
844
    pub suggested: bool,
845
    pub world_readable: Option<bool>,
846
    pub guest_can_join: bool,
847
}
848
849
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
850
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
851
#[non_exhaustive]
852
pub enum RoomNotificationMode {
853
    AllMessages,
854
    MentionsAndKeywordsOnly,
855
    Mute,
856
}
857
858
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
859
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
860
#[non_exhaustive]
861
pub struct RoomNotificationSettings {
862
    pub mode: RoomNotificationMode,
863
    pub custom: bool,
864
}
865
866
#[derive(Clone, Debug)]
867
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
868
#[non_exhaustive]
869
pub enum PushNotificationStatus {
870
    Event(Box<PushNotification>),
871
    NotFound,
872
    FilteredOut,
873
    Redacted,
874
}
875
876
#[derive(Clone, Debug)]
877
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
878
#[non_exhaustive]
879
pub struct PushNotification {
880
    pub event_id: OwnedEventId,
881
    pub event: PushNotificationEvent,
882
    pub sender: PushNotificationSender,
883
    pub room: PushNotificationRoom,
884
    pub noisy: Option<bool>,
885
    pub has_mention: Option<bool>,
886
    pub thread_id: Option<OwnedEventId>,
887
}
888
889
#[derive(Clone, Debug, Eq, PartialEq)]
890
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
891
#[non_exhaustive]
892
pub struct PushNotificationSender {
893
    pub user_id: OwnedUserId,
894
    pub display_name: Option<String>,
895
    pub avatar_url: Option<OwnedMxcUri>,
896
    pub name_ambiguous: bool,
897
}
898
899
#[derive(Clone, Debug, Eq, PartialEq)]
900
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
901
#[non_exhaustive]
902
pub struct PushNotificationRoom {
903
    pub room_id: OwnedRoomId,
904
    pub name: String,
905
    pub avatar_url: Option<OwnedMxcUri>,
906
    pub canonical_alias: Option<OwnedRoomAliasId>,
907
    pub topic: Option<String>,
908
    pub encrypted: Option<bool>,
909
    pub direct: bool,
910
    pub joined_members_count: u64,
911
    pub service_members: Vec<OwnedUserId>,
912
    pub active_service_members_count: u64,
913
    pub space: bool,
914
}
915
916
#[derive(Clone, Debug)]
917
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
918
#[non_exhaustive]
919
pub enum PushNotificationEvent {
920
    Timeline(Raw<AnySyncTimelineEvent>),
921
    Invite(Raw<StrippedRoomMemberEvent>),
922
}
923
924
#[derive(Clone, Debug, Eq, PartialEq)]
925
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
926
#[non_exhaustive]
927
pub struct RoomPowerLevels {
928
    pub ban: i64,
929
    pub invite: i64,
930
    pub kick: i64,
931
    pub redact: i64,
932
    pub events_default: i64,
933
    pub state_default: i64,
934
    pub users_default: i64,
935
    pub room_notification: i64,
936
    pub users: Vec<(OwnedUserId, i64)>,
937
    pub events: Vec<(String, i64)>,
938
    /// in rooms from version 12 the creators outrank everyone and aren't listed in `users`
939
    pub creators: Vec<OwnedUserId>,
940
}
941
942
#[derive(Clone, Debug, Default, Eq, PartialEq)]
943
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
944
#[non_exhaustive]
945
pub struct PowerLevelChanges {
946
    pub ban: Option<i64>,
947
    pub invite: Option<i64>,
948
    pub kick: Option<i64>,
949
    pub redact: Option<i64>,
950
    pub events_default: Option<i64>,
951
    pub state_default: Option<i64>,
952
    pub users_default: Option<i64>,
953
    pub room_notification: Option<i64>,
954
    pub room_name: Option<i64>,
955
    pub room_avatar: Option<i64>,
956
    pub room_topic: Option<i64>,
957
    pub space_child: Option<i64>,
958
    pub beacon: Option<i64>,
959
    pub beacon_info: Option<i64>,
960
}
961
962
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
963
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
964
#[non_exhaustive]
965
pub enum RoomMembership {
966
    Joined,
967
    Invited,
968
    Left,
969
    Knocked,
970
    Banned,
971
}
972
973
#[derive(Clone, Debug, Default, Eq, PartialEq)]
974
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
975
#[non_exhaustive]
976
pub struct RoomListFilter {
977
    pub membership: Option<RoomMembership>,
978
    pub category: Option<RoomCategory>,
979
    pub favourite: Option<bool>,
980
    pub unread_only: bool,
981
    pub search: Option<String>,
982
}
983
984
impl RoomListFilter {
985
    #[must_use]
986
    pub fn with_membership(mut self, membership: RoomMembership) -> Self {
987
        self.membership = Some(membership);
988
        self
989
    }
990
991
    #[must_use]
992
    pub fn with_category(mut self, category: RoomCategory) -> Self {
993
        self.category = Some(category);
994
        self
995
    }
996
997
    #[must_use]
998
    pub fn with_favourite(mut self, favourite: bool) -> Self {
999
        self.favourite = Some(favourite);
1000
        self
1001
    }
1002
1003
    #[must_use]
1004
    pub const fn with_unread_only(mut self, unread_only: bool) -> Self {
1005
        self.unread_only = unread_only;
1006
        self
1007
    }
1008
1009
    #[must_use]
1010
    pub fn with_search(mut self, search: impl Into<String>) -> Self {
1011
        self.search = Some(search.into());
1012
        self
1013
    }
1014
1015
    #[must_use]
1016
    pub fn matches(&self, room: &RoomSummary) -> bool {
1017
        self.matcher().matches(room)
1018
    }
1019
1020
    /// lowercases the search once, for filtering a whole list
1021
    #[must_use]
1022
    pub fn matcher(&self) -> RoomMatcher<'_> {
1023
        RoomMatcher {
1024
            filter: self,
1025
            search: self.search.as_deref().map(str::to_lowercase),
1026
        }
1027
    }
1028
}
1029
1030
#[derive(Debug)]
1031
pub struct RoomMatcher<'a> {
1032
    filter: &'a RoomListFilter,
1033
    search: Option<String>,
1034
}
1035
1036
impl RoomMatcher<'_> {
1037
    #[must_use]
1038
    pub fn matches(&self, room: &RoomSummary) -> bool {
1039
        let filter = self.filter;
1040
        filter
1041
            .membership
1042
            .is_none_or(|membership| room.membership == membership)
1043
            && filter.category.is_none_or(|category| match category {
1044
                RoomCategory::People => room.direct && !room.space,
1045
                RoomCategory::Rooms => !room.direct && !room.space,
1046
                RoomCategory::Spaces => room.space,
1047
            })
1048
            && filter
1049
                .favourite
1050
                .is_none_or(|favourite| room.favourite == favourite)
1051
            && (!filter.unread_only || room.unread_messages > 0 || room.unread_mentions > 0)
1052
            && self.search.as_ref().is_none_or(|search| {
1053
                room.name.to_lowercase().contains(search)
1054
                    || room.room_id.as_str().to_lowercase().contains(search)
1055
            })
1056
    }
1057
}
1058
1059
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1060
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1061
#[non_exhaustive]
1062
pub enum RoomCategory {
1063
    People,
1064
    Rooms,
1065
    Spaces,
1066
}
1067
1068
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1069
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1070
#[non_exhaustive]
1071
pub enum MemberFilter {
1072
    Active,
1073
    Joined,
1074
    Invited,
1075
    Banned,
1076
    Left,
1077
    All,
1078
}
1079
1080
#[derive(Clone, Debug, Eq, PartialEq)]
1081
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1082
#[non_exhaustive]
1083
pub enum MemberMembership {
1084
    Joined,
1085
    Invited,
1086
    Left,
1087
    Knocked,
1088
    Banned,
1089
    Custom(String),
1090
}
1091
1092
#[derive(Clone, Debug, Eq, PartialEq)]
1093
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1094
#[non_exhaustive]
1095
pub struct MemberSummary {
1096
    pub user_id: OwnedUserId,
1097
    pub name: String,
1098
    pub display_name: Option<String>,
1099
    pub avatar: Option<OwnedMxcUri>,
1100
    pub membership: MemberMembership,
1101
    pub own: bool,
1102
    pub name_ambiguous: bool,
1103
    pub ignored: bool,
1104
    pub service: bool,
1105
}
1106
1107
#[derive(Clone, Debug, Eq, PartialEq)]
1108
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1109
#[non_exhaustive]
1110
pub struct OwnDevice {
1111
    pub device_id: OwnedDeviceId,
1112
    pub display_name: Option<String>,
1113
    pub last_seen_ip: Option<String>,
1114
    pub last_seen_timestamp_ms: Option<u64>,
1115
    pub current: bool,
1116
}
1117
1118
#[derive(Clone, Debug, Eq, PartialEq)]
1119
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1120
#[non_exhaustive]
1121
pub enum TimelineItem {
1122
    Event(Box<TimelineEvent>),
1123
    DayDivider { timestamp_ms: u64 },
1124
    ReadMarker,
1125
    TimelineStart,
1126
}
1127
1128
#[derive(Clone, Debug, Eq, PartialEq)]
1129
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1130
#[non_exhaustive]
1131
pub enum TimelineDiff {
1132
    Append { values: Vec<TimelineItem> },
1133
    Clear,
1134
    PushFront { value: TimelineItem },
1135
    PushBack { value: TimelineItem },
1136
    PopFront,
1137
    PopBack,
1138
    Insert { index: usize, value: TimelineItem },
1139
    Set { index: usize, value: TimelineItem },
1140
    Remove { index: usize },
1141
    Truncate { length: usize },
1142
    Reset { values: Vec<TimelineItem> },
1143
}
1144
1145
#[derive(Clone, Copy, Debug, Eq, PartialEq, thiserror::Error)]
1146
#[error("timeline diff is incompatible with the current {length}-item snapshot")]
1147
pub struct TimelineDiffError {
1148
    pub length: usize,
1149
}
1150
1151
impl TimelineDiff {
1152
    /// Apply a diff only when its indices match the current snapshot.
1153
    /// On error the snapshot is unchanged; request `TimelineCommand::Resync`
1154
    /// and ignore further diffs until the corresponding `TimelineReset`.
1155
    pub fn try_apply(self, items: &mut Vector<TimelineItem>) -> Result<(), TimelineDiffError> {
1156
        let length = items.len();
1157
        if self.length_after(length).is_none() {
1158
            return Err(TimelineDiffError { length });
1159
        }
1160
        self.apply(items);
1161
        Ok(())
1162
    }
1163
1164
    /// a whole `TimelineChanged` or none of it. whether a diff fits depends only on the
1165
    /// length before it, so the batch is checked against lengths alone before anything
1166
    /// moves. the error carries the length the snapshot would have had at the misfit
1167
    pub fn try_apply_all(
1168
        diffs: Vec<Self>,
1169
        items: &mut Vector<TimelineItem>,
1170
    ) -> Result<(), TimelineDiffError> {
1171
        diffs.iter().try_fold(items.len(), |length, diff| {
1172
            diff.length_after(length)
1173
                .ok_or(TimelineDiffError { length })
1174
        })?;
1175
        for diff in diffs {
1176
            diff.apply(items);
1177
        }
1178
        Ok(())
1179
    }
1180
1181
    fn length_after(&self, length: usize) -> Option<usize> {
1182
        match self {
1183
            Self::Append { values } => Some(length + values.len()),
1184
            Self::Clear => Some(0),
1185
            Self::PushFront { .. } | Self::PushBack { .. } => Some(length + 1),
1186
            Self::PopFront | Self::PopBack => length.checked_sub(1),
1187
            Self::Insert { index, .. } => (*index <= length).then_some(length + 1),
1188
            Self::Set { index, .. } => (*index < length).then_some(length),
1189
            Self::Remove { index } => (*index < length).then(|| length - 1),
1190
            Self::Truncate { length: target } => (*target <= length).then_some(*target),
1191
            Self::Reset { values } => Some(values.len()),
1192
        }
1193
    }
1194
1195
    fn apply(self, items: &mut Vector<TimelineItem>) {
1196
        match self {
1197
            Self::Append { values } => items.extend(values),
1198
            Self::Clear => items.clear(),
1199
            Self::PushFront { value } => items.push_front(value),
1200
            Self::PushBack { value } => items.push_back(value),
1201
            Self::PopFront => {
1202
                items.pop_front();
1203
            }
1204
            Self::PopBack => {
1205
                items.pop_back();
1206
            }
1207
            Self::Insert { index, value } => items.insert(index, value),
1208
            Self::Set { index, value } => {
1209
                items.set(index, value);
1210
            }
1211
            Self::Remove { index } => {
1212
                items.remove(index);
1213
            }
1214
            Self::Truncate { length } => items.truncate(length),
1215
            Self::Reset { values } => *items = values.into_iter().collect(),
1216
        }
1217
    }
1218
}
1219
1220
#[cfg(test)]
1221
mod tests {
1222
    use imbl::{Vector, vector};
1223
1224
    use super::{TimelineDiff, TimelineItem};
1225
1226
    fn item(timestamp_ms: u64) -> TimelineItem {
1227
        TimelineItem::DayDivider { timestamp_ms }
1228
    }
1229
1230
    #[test]
1231
    fn timeline_diff_try_apply_handles_every_variant() {
1232
        let mut items = vector![item(1), item(2)];
1233
1234
        TimelineDiff::Append {
1235
            values: vec![item(3)],
1236
        }
1237
        .try_apply(&mut items)
1238
        .unwrap();
1239
        assert_eq!(items, vector![item(1), item(2), item(3)]);
1240
1241
        TimelineDiff::Clear.try_apply(&mut items).unwrap();
1242
        assert!(items.is_empty());
1243
1244
        TimelineDiff::PushFront { value: item(2) }
1245
            .try_apply(&mut items)
1246
            .unwrap();
1247
        TimelineDiff::PushBack { value: item(3) }
1248
            .try_apply(&mut items)
1249
            .unwrap();
1250
        assert_eq!(items, vector![item(2), item(3)]);
1251
1252
        TimelineDiff::PopFront.try_apply(&mut items).unwrap();
1253
        TimelineDiff::PopBack.try_apply(&mut items).unwrap();
1254
        assert!(items.is_empty());
1255
1256
        TimelineDiff::Reset {
1257
            values: vec![item(1), item(3)],
1258
        }
1259
        .try_apply(&mut items)
1260
        .unwrap();
1261
        TimelineDiff::Insert {
1262
            index: 1,
1263
            value: item(2),
1264
        }
1265
        .try_apply(&mut items)
1266
        .unwrap();
1267
        assert_eq!(items, vector![item(1), item(2), item(3)]);
1268
1269
        TimelineDiff::Set {
1270
            index: 1,
1271
            value: item(4),
1272
        }
1273
        .try_apply(&mut items)
1274
        .unwrap();
1275
        assert_eq!(items, vector![item(1), item(4), item(3)]);
1276
1277
        TimelineDiff::Remove { index: 1 }
1278
            .try_apply(&mut items)
1279
            .unwrap();
1280
        assert_eq!(items, vector![item(1), item(3)]);
1281
1282
        TimelineDiff::Truncate { length: 1 }
1283
            .try_apply(&mut items)
1284
            .unwrap();
1285
        assert_eq!(items, vector![item(1)]);
1286
    }
1287
1288
    #[test]
1289
    fn invalid_timeline_diff_leaves_snapshot_unchanged() {
1290
        let invalid = [
1291
            TimelineDiff::Insert {
1292
                index: 3,
1293
                value: item(3),
1294
            },
1295
            TimelineDiff::Set {
1296
                index: 2,
1297
                value: item(3),
1298
            },
1299
            TimelineDiff::Remove { index: 2 },
1300
            TimelineDiff::Truncate { length: 3 },
1301
        ];
1302
        for diff in invalid {
1303
            let mut items = vector![item(1), item(2)];
1304
            assert_eq!(diff.try_apply(&mut items).unwrap_err().length, 2);
1305
            assert_eq!(items, vector![item(1), item(2)]);
1306
        }
1307
        for diff in [TimelineDiff::PopFront, TimelineDiff::PopBack] {
1308
            let mut items = Vector::new();
1309
            assert_eq!(diff.try_apply(&mut items).unwrap_err().length, 0);
1310
            assert!(items.is_empty());
1311
        }
1312
    }
1313
1314
    #[test]
1315
    fn a_batch_that_goes_wrong_halfway_changes_nothing() {
1316
        let mut items = vector![item(1), item(2)];
1317
        let batch = vec![
1318
            TimelineDiff::PushBack { value: item(3) },
1319
            TimelineDiff::Remove { index: 0 },
1320
            TimelineDiff::Set {
1321
                index: 2,
1322
                value: item(4),
1323
            },
1324
        ];
1325
        assert_eq!(
1326
            TimelineDiff::try_apply_all(batch, &mut items)
1327
                .unwrap_err()
1328
                .length,
1329
            2
1330
        );
1331
        assert_eq!(items, vector![item(1), item(2)]);
1332
1333
        let batch = vec![
1334
            TimelineDiff::PushBack { value: item(3) },
1335
            TimelineDiff::Remove { index: 0 },
1336
            TimelineDiff::Set {
1337
                index: 1,
1338
                value: item(4),
1339
            },
1340
        ];
1341
        TimelineDiff::try_apply_all(batch, &mut items).unwrap();
1342
        assert_eq!(items, vector![item(2), item(4)]);
1343
    }
1344
}
1345
1346
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
1347
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1348
pub struct TimelineItemId(String);
1349
1350
impl TimelineItemId {
1351
    pub(crate) fn new(value: String) -> Self {
1352
        Self(value)
1353
    }
1354
1355
    #[must_use]
1356
    pub fn as_str(&self) -> &str {
1357
        &self.0
1358
    }
1359
}
1360
1361
#[derive(Clone, Debug, Eq, PartialEq)]
1362
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1363
#[non_exhaustive]
1364
pub struct TimelineEvent {
1365
    pub item_id: TimelineItemId,
1366
    pub event_id: Option<OwnedEventId>,
1367
    pub transaction_id: Option<OwnedTransactionId>,
1368
    pub sender: Sender,
1369
    pub timestamp_ms: u64,
1370
    pub own: bool,
1371
    pub send_state: SendState,
1372
    pub content: TimelineContent,
1373
    pub reply_to: Option<ReplyReference>,
1374
    pub thread_root: Option<OwnedEventId>,
1375
    pub thread_summary: Option<ThreadSummary>,
1376
    pub reactions: Vec<Reaction>,
1377
    pub read_by: Vec<OwnedUserId>,
1378
    pub editable: bool,
1379
}
1380
1381
impl TimelineEvent {
1382
    /// what to hand edit, redact and react. works before the server has echoed it back
1383
    #[must_use]
1384
    pub fn target(&self) -> Option<EventRef> {
1385
        self.event_id
1386
            .clone()
1387
            .map(EventRef::Remote)
1388
            .or_else(|| self.transaction_id.clone().map(EventRef::Local))
1389
    }
1390
}
1391
1392
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
1393
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1394
#[non_exhaustive]
1395
pub enum EventRef {
1396
    Remote(OwnedEventId),
1397
    /// a local echo, by the transaction id it was queued under
1398
    Local(OwnedTransactionId),
1399
}
1400
1401
impl From<OwnedEventId> for EventRef {
1402
    fn from(event_id: OwnedEventId) -> Self {
1403
        Self::Remote(event_id)
1404
    }
1405
}
1406
1407
#[derive(Clone, Debug, Eq, PartialEq)]
1408
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1409
#[non_exhaustive]
1410
pub struct Sender {
1411
    pub user_id: OwnedUserId,
1412
    pub display_name: Option<String>,
1413
    pub avatar: Option<OwnedMxcUri>,
1414
    /// someone else in the room goes by the same name, so show the user id too
1415
    pub name_ambiguous: bool,
1416
}
1417
1418
impl Sender {
1419
    #[must_use]
1420
    pub fn name(&self) -> &str {
1421
        self.display_name
1422
            .as_deref()
1423
            .unwrap_or_else(|| self.user_id.as_str())
1424
    }
1425
}
1426
1427
#[derive(Clone, Debug, Eq, PartialEq)]
1428
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1429
#[non_exhaustive]
1430
pub enum TimelineContent {
1431
    Message(Message),
1432
    Sticker {
1433
        body: String,
1434
        media: MediaDescriptor,
1435
    },
1436
    Poll {
1437
        question: String,
1438
    },
1439
    Redacted,
1440
    UnableToDecrypt(UtdCause),
1441
    Membership {
1442
        user_id: OwnedUserId,
1443
        display_name: Option<String>,
1444
        change: MembershipChange,
1445
    },
1446
    Profile {
1447
        user_id: OwnedUserId,
1448
        display_name: Option<Change<Option<String>>>,
1449
        avatar: Option<Change<Option<OwnedMxcUri>>>,
1450
    },
1451
    /// `None` means it was cleared, or the event was redacted
1452
    RoomName(Option<String>),
1453
    RoomTopic(Option<String>),
1454
    RoomAvatar(Option<OwnedMxcUri>),
1455
    EncryptionEnabled,
1456
    RoomCreated,
1457
    RoomUpgraded {
1458
        successor: OwnedRoomId,
1459
    },
1460
    OtherState {
1461
        event_type: String,
1462
        state_key: String,
1463
    },
1464
    Other {
1465
        event_type: String,
1466
    },
1467
}
1468
1469
#[derive(Clone, Debug, Eq, PartialEq)]
1470
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1471
#[non_exhaustive]
1472
pub struct Message {
1473
    pub kind: MessageKind,
1474
    pub body: String,
1475
    /// only for `MessageKind::Location`
1476
    pub geo_uri: Option<String>,
1477
    pub formatted_body: Option<String>,
1478
    pub mentions: Option<MessageMentions>,
1479
    pub edited: bool,
1480
    pub media: Option<MediaDescriptor>,
1481
}
1482
1483
#[derive(Clone, Debug, Eq, PartialEq)]
1484
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1485
#[non_exhaustive]
1486
pub enum MessageKind {
1487
    Text,
1488
    Emote,
1489
    Notice,
1490
    ServerNotice,
1491
    Image,
1492
    Video,
1493
    Audio,
1494
    File,
1495
    Location,
1496
    VerificationRequest,
1497
    Other(String),
1498
}
1499
1500
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1501
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1502
#[non_exhaustive]
1503
pub enum MembershipChange {
1504
    Joined,
1505
    Left,
1506
    Banned,
1507
    Unbanned,
1508
    Kicked,
1509
    KickedAndBanned,
1510
    Invited,
1511
    InvitationAccepted,
1512
    InvitationRejected,
1513
    InvitationRevoked,
1514
    Knocked,
1515
    KnockAccepted,
1516
    KnockRetracted,
1517
    KnockDenied,
1518
    /// nothing a person would call a change, or something the sdk couldn't make sense of
1519
    Other,
1520
}
1521
1522
#[derive(Clone, Debug, Eq, PartialEq)]
1523
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1524
pub struct Change<T> {
1525
    pub old: T,
1526
    pub new: T,
1527
}
1528
1529
/// why a message couldn't be read, worth telling the user since some of these they can fix
1530
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1531
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1532
#[non_exhaustive]
1533
pub enum UtdCause {
1534
    Unknown,
1535
    SentBeforeWeJoined,
1536
    VerificationViolation,
1537
    UnsignedDevice,
1538
    UnknownDevice,
1539
    HistoricalMessageAndBackupIsDisabled,
1540
    WithheldForUnverifiedOrInsecureDevice,
1541
    WithheldBySender,
1542
    HistoricalMessageAndDeviceIsUnverified,
1543
}
1544
1545
#[derive(Clone, Debug, Eq, PartialEq)]
1546
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1547
#[non_exhaustive]
1548
pub struct ThreadSummary {
1549
    pub reply_count: u32,
1550
    pub latest_event_id: Option<OwnedEventId>,
1551
    pub latest_event_timestamp_ms: Option<u64>,
1552
}
1553
1554
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1555
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1556
#[non_exhaustive]
1557
pub enum SendState {
1558
    Sent,
1559
    Sending,
1560
    /// `will_retry` means macltok has it in hand. otherwise it's wedged until you
1561
    /// `retry_send` or `cancel_send`, so that's when to show a retry button
1562
    Failed {
1563
        will_retry: bool,
1564
    },
1565
}
1566
1567
#[derive(Clone, Debug, Eq, PartialEq)]
1568
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1569
#[non_exhaustive]
1570
pub struct ReplyReference {
1571
    pub event_id: OwnedEventId,
1572
    pub details: ReplyDetails,
1573
}
1574
1575
/// the replied-to event. macltok asks for it when it isn't loaded, `Pending` then turns into one of the others
1576
#[derive(Clone, Debug, Eq, PartialEq)]
1577
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1578
#[non_exhaustive]
1579
pub enum ReplyDetails {
1580
    Pending,
1581
    Ready {
1582
        sender: Sender,
1583
        content: Box<TimelineContent>,
1584
        timestamp_ms: u64,
1585
    },
1586
    Unavailable,
1587
}
1588
1589
#[derive(Clone, Debug, Eq, PartialEq)]
1590
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1591
#[non_exhaustive]
1592
pub struct Reaction {
1593
    pub key: String,
1594
    pub count: usize,
1595
    pub own: bool,
1596
    pub senders: Vec<OwnedUserId>,
1597
}
1598
1599
#[derive(Clone, Debug, Eq, PartialEq)]
1600
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1601
#[non_exhaustive]
1602
pub struct MediaDescriptor {
1603
    pub id: MediaId,
1604
    pub kind: MediaKind,
1605
    pub filename: String,
1606
    pub caption: Option<String>,
1607
    pub mimetype: Option<String>,
1608
    pub size_bytes: Option<u64>,
1609
    pub width: Option<u64>,
1610
    pub height: Option<u64>,
1611
    pub duration_ms: Option<u64>,
1612
    pub blurhash: Option<String>,
1613
    /// the uploader sent a thumbnail, so `MediaSize::Thumbnail` won't need the server to resize
1614
    pub has_thumbnail: bool,
1615
}
1616
1617
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
1618
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1619
pub struct MediaId(String);
1620
1621
impl MediaId {
1622
    pub(crate) fn new(value: String) -> Self {
1623
        Self(value)
1624
    }
1625
1626
    #[must_use]
1627
    pub fn as_str(&self) -> &str {
1628
        &self.0
1629
    }
1630
}
1631
1632
/// media ids are opaque but they do have to survive a trip through a browser. a made up one just fails `MediaUnknown`
1633
impl std::str::FromStr for MediaId {
1634
    type Err = std::convert::Infallible;
1635
1636
    fn from_str(value: &str) -> Result<Self, Self::Err> {
1637
        Ok(Self(value.to_owned()))
1638
    }
1639
}
1640
1641
impl From<String> for MediaId {
1642
    fn from(value: String) -> Self {
1643
        Self(value)
1644
    }
1645
}
1646
1647
impl std::fmt::Display for MediaId {
1648
    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1649
        self.0.fmt(formatter)
1650
    }
1651
}
1652
1653
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
1654
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1655
#[non_exhaustive]
1656
pub enum MediaSize {
1657
    Full,
1658
    /// a hint. an uploader's own thumbnail wins over the server resizing anything
1659
    Thumbnail {
1660
        width: u32,
1661
        height: u32,
1662
    },
1663
}
1664
1665
#[derive(Clone, Debug, Eq, PartialEq)]
1666
#[non_exhaustive]
1667
pub struct Media {
1668
    pub bytes: Arc<[u8]>,
1669
    pub mimetype: String,
1670
}
1671
1672
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1673
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1674
#[non_exhaustive]
1675
pub enum MediaKind {
1676
    Image,
1677
    Video,
1678
    Audio,
1679
    File,
1680
    Sticker,
1681
}
1682
1683
#[derive(Clone, Debug, Eq, PartialEq)]
1684
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1685
#[non_exhaustive]
1686
pub struct TypingState {
1687
    pub room_id: OwnedRoomId,
1688
    pub users: Vec<OwnedUserId>,
1689
}
1690
1691
#[derive(Clone, Debug, Eq, PartialEq)]
1692
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1693
#[non_exhaustive]
1694
pub enum HistoryState {
1695
    Idle,
1696
    Loading,
1697
    StartReached,
1698
    Failed(String),
1699
}
1700
1701
#[derive(Clone, Debug, Eq, PartialEq)]
1702
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1703
#[non_exhaustive]
1704
pub struct EncryptionState {
1705
    pub device_id: OwnedDeviceId,
1706
    pub device_verified: bool,
1707
    pub recovery: RecoveryState,
1708
    pub key_backup: KeyBackupState,
1709
}
1710
1711
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1712
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1713
#[non_exhaustive]
1714
pub enum KeyBackupState {
1715
    Unknown,
1716
    Creating,
1717
    Enabling,
1718
    Resuming,
1719
    Downloading,
1720
    Enabled,
1721
    Disabling,
1722
}
1723
1724
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1725
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1726
#[non_exhaustive]
1727
pub enum RecoveryState {
1728
    Unknown,
1729
    Disabled,
1730
    Incomplete,
1731
    Enabled,
1732
}
1733
1734
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1735
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1736
#[non_exhaustive]
1737
pub enum VerificationAction {
1738
    /// take up a request another device sent, which arrives as `VerificationState::Incoming`
1739
    Accept,
1740
    Confirm,
1741
    Reject,
1742
    Cancel,
1743
}
1744
1745
#[derive(Clone, Debug, Eq, PartialEq)]
1746
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1747
#[non_exhaustive]
1748
pub enum VerificationTarget {
1749
    OwnIdentity,
1750
    User(OwnedUserId),
1751
    Device {
1752
        user_id: OwnedUserId,
1753
        device_id: OwnedDeviceId,
1754
    },
1755
}
1756
1757
#[derive(Clone, Debug, Eq, PartialEq)]
1758
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1759
#[non_exhaustive]
1760
pub enum VerificationState {
1761
    Waiting,
1762
    /// someone else asked. `Accept` to go ahead, `Cancel` to turn them down
1763
    Incoming {
1764
        user_id: OwnedUserId,
1765
        device_id: Option<OwnedDeviceId>,
1766
    },
1767
    Comparison {
1768
        emojis: Vec<(String, String)>,
1769
        decimals: (u16, u16, u16),
1770
    },
1771
    Confirmed,
1772
    Done,
1773
    Cancelled(String),
1774
    Failed(VerificationFailure),
1775
}
1776
1777
#[derive(Clone, Debug, Eq, PartialEq)]
1778
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1779
#[non_exhaustive]
1780
pub enum VerificationFailure {
1781
    RequestAcceptance(String),
1782
    SasStartup(String),
1783
    SasAcceptance(String),
1784
    RequestStreamEnded,
1785
    SasStreamEnded,
1786
}
1787
1788
#[derive(Clone, Debug, Eq, PartialEq)]
1789
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1790
#[non_exhaustive]
1791
pub struct EngineFailure {
1792
    pub kind: EngineErrorKind,
1793
    pub message: String,
1794
}
1795
1796
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1797
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1798
#[non_exhaustive]
1799
pub enum EngineErrorKind {
1800
    EventReceiverClosed,
1801
    Watcher,
1802
    Other,
1803
}