atagen

macltok

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