atagen

macltok

public
Star0
Watch0
fa9114fcf75f7bdf1a7cec3b3367d604cf7de472
3
ssh://brewery@pls.lobotomise.me:33/atagen/macltok.git
src/model.rsRust1377 lines36.8 KB
1
use std::{fmt, sync::Arc};
2
3
use ruma::{
4
    OwnedDeviceId, OwnedEventId, OwnedMxcUri, OwnedRoomAliasId, OwnedRoomId, OwnedRoomOrAliasId,
5
    OwnedServerName, OwnedTransactionId, OwnedUserId,
6
    events::{AnySyncTimelineEvent, room::member::StrippedRoomMemberEvent},
7
    serde::Raw,
8
};
9
#[cfg(feature = "serde")]
10
use serde::{Deserialize, Serialize};
11
12
#[derive(Debug, Eq, PartialEq)]
13
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
14
#[non_exhaustive]
15
pub enum Command {
16
    Account(AccountCommand),
17
    Room(RoomCommand),
18
    Timeline(TimelineCommand),
19
    Media(MediaCommand),
20
    Encryption(EncryptionCommand),
21
    Notification(NotificationCommand),
22
    Lifecycle(LifecycleCommand),
23
}
24
25
#[derive(Debug, Eq, PartialEq)]
26
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
27
#[non_exhaustive]
28
pub enum AccountCommand {
29
    Logout,
30
    RenameDevice {
31
        device_id: OwnedDeviceId,
32
        display_name: String,
33
    },
34
    DeleteDevices {
35
        device_ids: Vec<OwnedDeviceId>,
36
        auth: Option<crate::InteractiveAuth>,
37
    },
38
}
39
40
#[derive(Clone, Debug, Eq, PartialEq)]
41
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
42
#[non_exhaustive]
43
pub enum RoomCommand {
44
    Open(OwnedRoomId),
45
    Close(OwnedRoomId),
46
    Join {
47
        room: OwnedRoomOrAliasId,
48
        via: Vec<OwnedServerName>,
49
    },
50
    Leave(OwnedRoomId),
51
    Forget(OwnedRoomId),
52
    AcceptInvite(OwnedRoomId),
53
    RejectInvite(OwnedRoomId),
54
    InviteUser {
55
        room_id: OwnedRoomId,
56
        user_id: OwnedUserId,
57
    },
58
    KickUser {
59
        room_id: OwnedRoomId,
60
        user_id: OwnedUserId,
61
        reason: Option<String>,
62
    },
63
    BanUser {
64
        room_id: OwnedRoomId,
65
        user_id: OwnedUserId,
66
        reason: Option<String>,
67
    },
68
    UnbanUser {
69
        room_id: OwnedRoomId,
70
        user_id: OwnedUserId,
71
        reason: Option<String>,
72
    },
73
    SetPowerLevels {
74
        room_id: OwnedRoomId,
75
        changes: PowerLevelChanges,
76
    },
77
    SetUserPowerLevel {
78
        room_id: OwnedRoomId,
79
        user_id: OwnedUserId,
80
        level: i64,
81
    },
82
    Create(CreateRoomOptions),
83
    /// hands back the existing dm if there is one
84
    CreateDirect(OwnedUserId),
85
    CancelSend {
86
        room_id: OwnedRoomId,
87
        transaction_id: OwnedTransactionId,
88
    },
89
    RetrySend {
90
        room_id: OwnedRoomId,
91
        transaction_id: OwnedTransactionId,
92
    },
93
    SetTyping {
94
        room_id: OwnedRoomId,
95
        typing: bool,
96
    },
97
}
98
99
#[derive(Clone, Debug, Default, Eq, PartialEq)]
100
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
101
pub struct CreateRoomOptions {
102
    pub name: Option<String>,
103
    pub topic: Option<String>,
104
    pub alias_localpart: Option<String>,
105
    pub invite: Vec<OwnedUserId>,
106
    pub direct: bool,
107
    pub visibility: RoomVisibility,
108
    pub preset: Option<RoomPreset>,
109
}
110
111
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
112
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
113
pub enum RoomVisibility {
114
    #[default]
115
    Private,
116
    Public,
117
}
118
119
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
120
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
121
pub enum RoomPreset {
122
    PrivateChat,
123
    TrustedPrivateChat,
124
    PublicChat,
125
}
126
127
#[derive(Clone, Debug, Eq, PartialEq)]
128
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
129
#[non_exhaustive]
130
pub enum TimelineCommand {
131
    OpenThread {
132
        room_id: OwnedRoomId,
133
        root_event_id: OwnedEventId,
134
    },
135
    CloseThread {
136
        room_id: OwnedRoomId,
137
        root_event_id: OwnedEventId,
138
    },
139
    /// Re-emit an authoritative reset for an open room or thread without
140
    /// closing it. The reset arrives before this command's terminal result.
141
    Resync {
142
        timeline_id: TimelineId,
143
    },
144
    Paginate {
145
        timeline_id: TimelineId,
146
        limit: u16,
147
    },
148
    Send {
149
        timeline_id: TimelineId,
150
        content: MessageContent,
151
    },
152
    Edit {
153
        timeline_id: TimelineId,
154
        event_id: OwnedEventId,
155
        content: MessageEditContent,
156
    },
157
    Redact {
158
        timeline_id: TimelineId,
159
        event_id: OwnedEventId,
160
        reason: Option<String>,
161
    },
162
    ToggleReaction {
163
        timeline_id: TimelineId,
164
        event_id: OwnedEventId,
165
        key: String,
166
    },
167
    /// works on a closed room too, it just can't refuse to move the marker backwards
168
    MarkRead {
169
        timeline_id: TimelineId,
170
        event_id: OwnedEventId,
171
    },
172
}
173
174
impl TimelineCommand {
175
    #[must_use]
176
    pub const fn room_id(&self) -> &OwnedRoomId {
177
        match self {
178
            Self::OpenThread { room_id, .. } | Self::CloseThread { room_id, .. } => room_id,
179
            Self::Resync { timeline_id }
180
            | Self::Paginate { timeline_id, .. }
181
            | Self::Send { timeline_id, .. }
182
            | Self::Edit { timeline_id, .. }
183
            | Self::Redact { timeline_id, .. }
184
            | Self::ToggleReaction { timeline_id, .. }
185
            | Self::MarkRead { timeline_id, .. } => timeline_id.room_id(),
186
        }
187
    }
188
189
    #[must_use]
190
    pub const fn timeline_id(&self) -> Option<&TimelineId> {
191
        match self {
192
            Self::OpenThread { .. } | Self::CloseThread { .. } => None,
193
            Self::Resync { timeline_id }
194
            | Self::Paginate { timeline_id, .. }
195
            | Self::Send { timeline_id, .. }
196
            | Self::Edit { timeline_id, .. }
197
            | Self::Redact { timeline_id, .. }
198
            | Self::ToggleReaction { timeline_id, .. }
199
            | Self::MarkRead { timeline_id, .. } => Some(timeline_id),
200
        }
201
    }
202
}
203
204
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
205
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
206
#[non_exhaustive]
207
pub enum TimelineId {
208
    Room(OwnedRoomId),
209
    Thread {
210
        room_id: OwnedRoomId,
211
        root_event_id: OwnedEventId,
212
    },
213
}
214
215
impl TimelineId {
216
    #[must_use]
217
    pub const fn room_id(&self) -> &OwnedRoomId {
218
        match self {
219
            Self::Room(room_id) | Self::Thread { room_id, .. } => room_id,
220
        }
221
    }
222
223
    #[must_use]
224
    pub fn thread(room_id: OwnedRoomId, root_event_id: OwnedEventId) -> Self {
225
        Self::Thread {
226
            room_id,
227
            root_event_id,
228
        }
229
    }
230
231
    #[must_use]
232
    pub fn thread_root(&self) -> Option<&OwnedEventId> {
233
        match self {
234
            Self::Room(_) => None,
235
            Self::Thread { root_event_id, .. } => Some(root_event_id),
236
        }
237
    }
238
}
239
240
impl From<OwnedRoomId> for TimelineId {
241
    fn from(room_id: OwnedRoomId) -> Self {
242
        Self::Room(room_id)
243
    }
244
}
245
246
#[derive(Clone, Debug, Eq, PartialEq)]
247
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
248
#[non_exhaustive]
249
pub enum MediaCommand {
250
    Fetch {
251
        id: MediaId,
252
        size: MediaSize,
253
    },
254
    Upload {
255
        timeline_id: TimelineId,
256
        filename: String,
257
        mimetype: String,
258
        bytes: Vec<u8>,
259
        caption: Option<String>,
260
    },
261
    UploadAttachment(AttachmentUpload),
262
}
263
264
#[derive(Clone, Debug, Eq, PartialEq)]
265
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
266
pub struct AttachmentUpload {
267
    pub timeline_id: TimelineId,
268
    pub filename: String,
269
    pub mimetype: String,
270
    pub bytes: Vec<u8>,
271
    pub caption: Option<String>,
272
    pub details: Option<UploadDetails>,
273
    pub thumbnail: Option<UploadThumbnail>,
274
}
275
276
impl AttachmentUpload {
277
    #[must_use]
278
    pub fn new(
279
        timeline_id: impl Into<TimelineId>,
280
        filename: impl Into<String>,
281
        mimetype: impl Into<String>,
282
        bytes: Vec<u8>,
283
    ) -> Self {
284
        Self {
285
            timeline_id: timeline_id.into(),
286
            filename: filename.into(),
287
            mimetype: mimetype.into(),
288
            bytes,
289
            caption: None,
290
            details: None,
291
            thumbnail: None,
292
        }
293
    }
294
}
295
296
#[derive(Clone, Debug, Eq, PartialEq)]
297
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
298
#[non_exhaustive]
299
pub enum UploadDetails {
300
    Image {
301
        width: Option<u32>,
302
        height: Option<u32>,
303
        blurhash: Option<String>,
304
    },
305
    Video {
306
        width: Option<u32>,
307
        height: Option<u32>,
308
        duration_ms: Option<u64>,
309
        blurhash: Option<String>,
310
    },
311
    Audio {
312
        duration_ms: Option<u64>,
313
    },
314
    Voice {
315
        duration_ms: u64,
316
        waveform: Vec<u16>,
317
    },
318
    File,
319
}
320
321
#[derive(Clone, Debug, Eq, PartialEq)]
322
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
323
pub struct UploadThumbnail {
324
    pub bytes: Vec<u8>,
325
    pub mimetype: String,
326
    pub width: u32,
327
    pub height: u32,
328
}
329
330
#[derive(Eq, PartialEq)]
331
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
332
#[non_exhaustive]
333
pub enum EncryptionCommand {
334
    Recover(String),
335
    CreateKeyBackup,
336
    EnableKeyBackup,
337
    DisableKeyBackup { delete_from_server: bool },
338
    RecoverKeyBackup(String),
339
    StartVerification(VerificationTarget),
340
    Verify(VerificationAction),
341
}
342
343
impl fmt::Debug for EncryptionCommand {
344
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
345
        match self {
346
            Self::Recover(_) => formatter.debug_tuple("Recover").field(&"...").finish(),
347
            Self::CreateKeyBackup => formatter.write_str("CreateKeyBackup"),
348
            Self::EnableKeyBackup => formatter.write_str("EnableKeyBackup"),
349
            Self::DisableKeyBackup { delete_from_server } => formatter
350
                .debug_struct("DisableKeyBackup")
351
                .field("delete_from_server", delete_from_server)
352
                .finish(),
353
            Self::RecoverKeyBackup(_) => formatter
354
                .debug_tuple("RecoverKeyBackup")
355
                .field(&"...")
356
                .finish(),
357
            Self::StartVerification(target) => formatter
358
                .debug_tuple("StartVerification")
359
                .field(target)
360
                .finish(),
361
            Self::Verify(action) => formatter.debug_tuple("Verify").field(action).finish(),
362
        }
363
    }
364
}
365
366
#[derive(Clone, Debug, Eq, PartialEq)]
367
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
368
#[non_exhaustive]
369
pub enum NotificationCommand {
370
    SetRoomMode {
371
        room_id: OwnedRoomId,
372
        mode: RoomNotificationMode,
373
    },
374
    ResetRoomMode(OwnedRoomId),
375
    SetDefaultMode {
376
        encrypted: bool,
377
        one_to_one: bool,
378
        mode: RoomNotificationMode,
379
    },
380
    AddKeyword(String),
381
    RemoveKeyword(String),
382
}
383
384
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
385
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
386
#[non_exhaustive]
387
pub enum LifecycleCommand {
388
    Shutdown,
389
}
390
391
impl Command {
392
    #[must_use]
393
    pub const fn kind(&self) -> CommandKind {
394
        match self {
395
            Self::Account(_) => CommandKind::Account,
396
            Self::Room(_) => CommandKind::Room,
397
            Self::Timeline(_) => CommandKind::Timeline,
398
            Self::Media(_) => CommandKind::Media,
399
            Self::Encryption(_) => CommandKind::Encryption,
400
            Self::Notification(_) => CommandKind::Notification,
401
            Self::Lifecycle(_) => CommandKind::Lifecycle,
402
        }
403
    }
404
}
405
406
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
407
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
408
pub enum CommandKind {
409
    Account,
410
    Room,
411
    Timeline,
412
    Media,
413
    Encryption,
414
    Notification,
415
    Lifecycle,
416
}
417
418
#[derive(Clone, Debug, Eq, PartialEq)]
419
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
420
#[non_exhaustive]
421
pub enum MessageContent {
422
    Text {
423
        content: RichText,
424
        mentions: MessageMentions,
425
    },
426
    Emote {
427
        content: RichText,
428
        mentions: MessageMentions,
429
    },
430
    Reply {
431
        event_id: OwnedEventId,
432
        content: RichText,
433
        mentions: MessageMentions,
434
        notify_replied_to: bool,
435
    },
436
}
437
438
impl MessageContent {
439
    #[must_use]
440
    pub fn text(body: impl Into<String>) -> Self {
441
        Self::Text {
442
            content: RichText::plain(body),
443
            mentions: MessageMentions::default(),
444
        }
445
    }
446
447
    #[must_use]
448
    pub fn html(body: impl Into<String>, formatted_body: impl Into<String>) -> Self {
449
        Self::Text {
450
            content: RichText::html(body, formatted_body),
451
            mentions: MessageMentions::default(),
452
        }
453
    }
454
455
    #[must_use]
456
    pub fn emote(content: RichText, mentions: MessageMentions) -> Self {
457
        Self::Emote { content, mentions }
458
    }
459
460
    #[must_use]
461
    pub fn reply(event_id: OwnedEventId, content: RichText, mentions: MessageMentions) -> Self {
462
        Self::Reply {
463
            event_id,
464
            content,
465
            mentions,
466
            notify_replied_to: true,
467
        }
468
    }
469
470
    #[must_use]
471
    pub fn reply_without_notification(
472
        event_id: OwnedEventId,
473
        content: RichText,
474
        mentions: MessageMentions,
475
    ) -> Self {
476
        Self::Reply {
477
            event_id,
478
            content,
479
            mentions,
480
            notify_replied_to: false,
481
        }
482
    }
483
}
484
485
#[derive(Clone, Debug, Eq, PartialEq)]
486
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
487
#[non_exhaustive]
488
pub enum MessageEditContent {
489
    Text {
490
        content: RichText,
491
        mentions: MessageMentions,
492
    },
493
    Emote {
494
        content: RichText,
495
        mentions: MessageMentions,
496
    },
497
}
498
499
impl MessageEditContent {
500
    #[must_use]
501
    pub fn text(body: impl Into<String>) -> Self {
502
        Self::Text {
503
            content: RichText::plain(body),
504
            mentions: MessageMentions::default(),
505
        }
506
    }
507
508
    #[must_use]
509
    pub fn html(body: impl Into<String>, formatted_body: impl Into<String>) -> Self {
510
        Self::Text {
511
            content: RichText::html(body, formatted_body),
512
            mentions: MessageMentions::default(),
513
        }
514
    }
515
516
    #[must_use]
517
    pub fn emote(content: RichText, mentions: MessageMentions) -> Self {
518
        Self::Emote { content, mentions }
519
    }
520
}
521
522
#[derive(Clone, Debug, Default, Eq, PartialEq)]
523
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
524
#[non_exhaustive]
525
pub struct RichText {
526
    pub body: String,
527
    pub formatted_body: Option<String>,
528
}
529
530
impl RichText {
531
    #[must_use]
532
    pub fn plain(body: impl Into<String>) -> Self {
533
        Self {
534
            body: body.into(),
535
            formatted_body: None,
536
        }
537
    }
538
539
    #[must_use]
540
    pub fn html(body: impl Into<String>, formatted_body: impl Into<String>) -> Self {
541
        Self {
542
            body: body.into(),
543
            formatted_body: Some(formatted_body.into()),
544
        }
545
    }
546
}
547
548
#[derive(Clone, Debug, Default, Eq, PartialEq)]
549
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
550
#[non_exhaustive]
551
pub struct MessageMentions {
552
    pub user_ids: Vec<OwnedUserId>,
553
    pub room: bool,
554
}
555
556
impl MessageMentions {
557
    #[must_use]
558
    pub fn with_user_ids(user_ids: impl IntoIterator<Item = OwnedUserId>) -> Self {
559
        Self {
560
            user_ids: user_ids.into_iter().collect(),
561
            room: false,
562
        }
563
    }
564
565
    #[must_use]
566
    pub fn room() -> Self {
567
        Self {
568
            user_ids: Vec::new(),
569
            room: true,
570
        }
571
    }
572
}
573
574
#[derive(Clone, Debug, Eq, PartialEq)]
575
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
576
#[non_exhaustive]
577
pub enum Event {
578
    ConnectionChanged(ConnectionState),
579
    Authenticated {
580
        user_id: OwnedUserId,
581
    },
582
    /// Refresh tokens changed. Persist a fresh `ClientHandle::session_record()`
583
    /// atomically; a record captured before this event may no longer restore.
584
    SessionTokensChanged,
585
    /// the server stopped honouring our token and the engine stops. on a soft
586
    /// logout keep the store and log in again with the same device, otherwise
587
    /// the record and the store are both dead
588
    SessionAccessRejected {
589
        soft_logout: bool,
590
    },
591
    NotificationSettingsChanged,
592
    VerificationTargetChanged(VerificationTarget),
593
    InteractiveAuthRequired {
594
        id: CommandId,
595
        command: CommandKind,
596
        challenge: crate::InteractiveAuthChallenge,
597
    },
598
    RoomsChanged,
599
    /// Authoritative snapshot on open or resync. A resync reset precedes the
600
    /// corresponding terminal command event on this stream.
601
    TimelineReset {
602
        timeline_id: TimelineId,
603
        items: Vec<TimelineItem>,
604
    },
605
    TimelineChanged {
606
        timeline_id: TimelineId,
607
        diffs: Vec<TimelineDiff>,
608
    },
609
    HistoryChanged {
610
        timeline_id: TimelineId,
611
        state: HistoryState,
612
    },
613
    TypingChanged(TypingState),
614
    MediaReady {
615
        media_id: MediaId,
616
        size: MediaSize,
617
        bytes: Arc<[u8]>,
618
        mimetype: String,
619
    },
620
    MediaUploadProgress {
621
        room_id: OwnedRoomId,
622
        transaction_id: OwnedTransactionId,
623
        uploaded_bytes: u64,
624
        total_bytes: u64,
625
        complete: bool,
626
    },
627
    MediaUploadFailed {
628
        room_id: OwnedRoomId,
629
        transaction_id: OwnedTransactionId,
630
        kind: CommandErrorKind,
631
        recoverable: bool,
632
    },
633
    EncryptionChanged(EncryptionState),
634
    VerificationChanged(VerificationState),
635
    /// Terminal command success. For sends, this means the SDK send queue
636
    /// accepted the item; network delivery is reflected by its send state.
637
    CommandSucceeded {
638
        id: CommandId,
639
        command: CommandKind,
640
    },
641
    CommandSucceededWithRoom {
642
        id: CommandId,
643
        command: CommandKind,
644
        room_id: OwnedRoomId,
645
    },
646
    /// Terminal failure correlated by `CommandFailure::id`.
647
    CommandFailed(CommandFailure),
648
    /// logout went through and the engine stops. delete the record and the store
649
    LoggedOut,
650
    Failed(EngineFailure),
651
    /// always the last event. keep the saved record unless `LoggedOut` or a
652
    /// hard `SessionAccessRejected` came first. commands still in flight get no result
653
    Stopped,
654
}
655
656
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
657
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
658
pub struct CommandId(u64);
659
660
impl CommandId {
661
    pub(crate) const fn new(value: u64) -> Self {
662
        Self(value)
663
    }
664
665
    #[must_use]
666
    pub const fn get(self) -> u64 {
667
        self.0
668
    }
669
}
670
671
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
672
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
673
pub enum ConnectionState {
674
    Idle,
675
    Connecting,
676
    Online,
677
    Offline,
678
    Stopped,
679
}
680
681
#[derive(Clone, Debug, Eq, PartialEq)]
682
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
683
#[non_exhaustive]
684
pub struct RoomSummary {
685
    pub room_id: OwnedRoomId,
686
    pub name: String,
687
    pub membership: RoomMembership,
688
    pub unread_messages: u64,
689
    pub unread_mentions: u64,
690
    pub encrypted: bool,
691
    pub space: bool,
692
    pub direct: bool,
693
    pub favourite: bool,
694
    pub low_priority: bool,
695
    pub avatar: Option<OwnedMxcUri>,
696
    pub latest_event_timestamp_ms: Option<u64>,
697
    pub notification_mode: Option<RoomNotificationMode>,
698
    pub notification_mode_custom: bool,
699
}
700
701
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
702
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
703
pub enum RoomNotificationMode {
704
    AllMessages,
705
    MentionsAndKeywordsOnly,
706
    Mute,
707
}
708
709
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
710
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
711
#[non_exhaustive]
712
pub struct RoomNotificationSettings {
713
    pub mode: RoomNotificationMode,
714
    pub custom: bool,
715
}
716
717
#[derive(Clone, Debug)]
718
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
719
#[non_exhaustive]
720
pub enum PushNotificationStatus {
721
    Event(Box<PushNotification>),
722
    NotFound,
723
    FilteredOut,
724
    Redacted,
725
}
726
727
#[derive(Clone, Debug)]
728
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
729
#[non_exhaustive]
730
pub struct PushNotification {
731
    pub event_id: OwnedEventId,
732
    pub event: PushNotificationEvent,
733
    pub sender: PushNotificationSender,
734
    pub room: PushNotificationRoom,
735
    pub noisy: Option<bool>,
736
    pub has_mention: Option<bool>,
737
    pub thread_id: Option<OwnedEventId>,
738
}
739
740
#[derive(Clone, Debug, Eq, PartialEq)]
741
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
742
#[non_exhaustive]
743
pub struct PushNotificationSender {
744
    pub user_id: OwnedUserId,
745
    pub display_name: Option<String>,
746
    pub avatar_url: Option<OwnedMxcUri>,
747
    pub name_ambiguous: bool,
748
}
749
750
#[derive(Clone, Debug, Eq, PartialEq)]
751
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
752
#[non_exhaustive]
753
pub struct PushNotificationRoom {
754
    pub room_id: OwnedRoomId,
755
    pub name: String,
756
    pub avatar_url: Option<OwnedMxcUri>,
757
    pub canonical_alias: Option<OwnedRoomAliasId>,
758
    pub topic: Option<String>,
759
    pub encrypted: Option<bool>,
760
    pub direct: bool,
761
    pub joined_members_count: u64,
762
    pub service_members: Vec<OwnedUserId>,
763
    pub active_service_members_count: u64,
764
    pub space: bool,
765
}
766
767
#[derive(Clone, Debug)]
768
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
769
#[non_exhaustive]
770
pub enum PushNotificationEvent {
771
    Timeline(Raw<AnySyncTimelineEvent>),
772
    Invite(Raw<StrippedRoomMemberEvent>),
773
}
774
775
#[derive(Clone, Debug, Eq, PartialEq)]
776
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
777
#[non_exhaustive]
778
pub struct RoomPowerLevels {
779
    pub ban: i64,
780
    pub invite: i64,
781
    pub kick: i64,
782
    pub redact: i64,
783
    pub events_default: i64,
784
    pub state_default: i64,
785
    pub users_default: i64,
786
    pub room_notification: i64,
787
    pub users: Vec<(OwnedUserId, i64)>,
788
    pub events: Vec<(String, i64)>,
789
}
790
791
#[derive(Clone, Debug, Default, Eq, PartialEq)]
792
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
793
#[non_exhaustive]
794
pub struct PowerLevelChanges {
795
    pub ban: Option<i64>,
796
    pub invite: Option<i64>,
797
    pub kick: Option<i64>,
798
    pub redact: Option<i64>,
799
    pub events_default: Option<i64>,
800
    pub state_default: Option<i64>,
801
    pub users_default: Option<i64>,
802
    pub room_notification: Option<i64>,
803
    pub room_name: Option<i64>,
804
    pub room_avatar: Option<i64>,
805
    pub room_topic: Option<i64>,
806
    pub space_child: Option<i64>,
807
    pub beacon: Option<i64>,
808
    pub beacon_info: Option<i64>,
809
}
810
811
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
812
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
813
pub enum RoomMembership {
814
    Joined,
815
    Invited,
816
    Left,
817
    Knocked,
818
    Banned,
819
}
820
821
#[derive(Clone, Debug, Default, Eq, PartialEq)]
822
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
823
#[non_exhaustive]
824
pub struct RoomListFilter {
825
    pub membership: Option<RoomMembership>,
826
    pub category: Option<RoomCategory>,
827
    pub favourite: Option<bool>,
828
    pub unread_only: bool,
829
    pub search: Option<String>,
830
}
831
832
impl RoomListFilter {
833
    #[must_use]
834
    pub fn with_membership(mut self, membership: RoomMembership) -> Self {
835
        self.membership = Some(membership);
836
        self
837
    }
838
839
    #[must_use]
840
    pub fn with_category(mut self, category: RoomCategory) -> Self {
841
        self.category = Some(category);
842
        self
843
    }
844
845
    #[must_use]
846
    pub fn with_favourite(mut self, favourite: bool) -> Self {
847
        self.favourite = Some(favourite);
848
        self
849
    }
850
851
    #[must_use]
852
    pub const fn with_unread_only(mut self, unread_only: bool) -> Self {
853
        self.unread_only = unread_only;
854
        self
855
    }
856
857
    #[must_use]
858
    pub fn with_search(mut self, search: impl Into<String>) -> Self {
859
        self.search = Some(search.into());
860
        self
861
    }
862
863
    #[must_use]
864
    pub fn matches(&self, room: &RoomSummary) -> bool {
865
        self.membership
866
            .is_none_or(|membership| room.membership == membership)
867
            && self.category.is_none_or(|category| match category {
868
                RoomCategory::People => room.direct && !room.space,
869
                RoomCategory::Rooms => !room.direct && !room.space,
870
                RoomCategory::Spaces => room.space,
871
            })
872
            && self
873
                .favourite
874
                .is_none_or(|favourite| room.favourite == favourite)
875
            && (!self.unread_only || room.unread_messages > 0 || room.unread_mentions > 0)
876
            && self.search.as_ref().is_none_or(|search| {
877
                let search = search.to_lowercase();
878
                room.name.to_lowercase().contains(&search)
879
                    || room.room_id.as_str().to_lowercase().contains(&search)
880
            })
881
    }
882
}
883
884
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
885
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
886
pub enum RoomCategory {
887
    People,
888
    Rooms,
889
    Spaces,
890
}
891
892
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
893
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
894
pub enum MemberFilter {
895
    Active,
896
    Joined,
897
    Invited,
898
    All,
899
}
900
901
#[derive(Clone, Debug, Eq, PartialEq)]
902
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
903
#[non_exhaustive]
904
pub enum MemberMembership {
905
    Joined,
906
    Invited,
907
    Left,
908
    Knocked,
909
    Banned,
910
    Custom(String),
911
}
912
913
#[derive(Clone, Debug, Eq, PartialEq)]
914
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
915
#[non_exhaustive]
916
pub struct MemberSummary {
917
    pub user_id: OwnedUserId,
918
    pub name: String,
919
    pub display_name: Option<String>,
920
    pub avatar: Option<OwnedMxcUri>,
921
    pub membership: MemberMembership,
922
    pub own: bool,
923
    pub name_ambiguous: bool,
924
    pub ignored: bool,
925
    pub service: bool,
926
}
927
928
#[derive(Clone, Debug, Eq, PartialEq)]
929
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
930
#[non_exhaustive]
931
pub struct OwnDevice {
932
    pub device_id: OwnedDeviceId,
933
    pub display_name: Option<String>,
934
    pub last_seen_ip: Option<String>,
935
    pub last_seen_timestamp_ms: Option<u64>,
936
    pub current: bool,
937
}
938
939
#[derive(Clone, Debug, Eq, PartialEq)]
940
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
941
pub enum TimelineItem {
942
    Message(Box<TimelineMessage>),
943
    DayDivider { timestamp_ms: u64 },
944
    ReadMarker,
945
    TimelineStart,
946
}
947
948
#[derive(Clone, Debug, Eq, PartialEq)]
949
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
950
#[non_exhaustive]
951
pub enum TimelineDiff {
952
    Append { values: Vec<TimelineItem> },
953
    Clear,
954
    PushFront { value: TimelineItem },
955
    PushBack { value: TimelineItem },
956
    PopFront,
957
    PopBack,
958
    Insert { index: usize, value: TimelineItem },
959
    Set { index: usize, value: TimelineItem },
960
    Remove { index: usize },
961
    Truncate { length: usize },
962
    Reset { values: Vec<TimelineItem> },
963
}
964
965
#[derive(Clone, Copy, Debug, Eq, PartialEq, thiserror::Error)]
966
#[error("timeline diff is incompatible with the current {length}-item snapshot")]
967
pub struct TimelineDiffError {
968
    pub length: usize,
969
}
970
971
impl TimelineDiff {
972
    /// Apply a diff only when its indices match the current snapshot.
973
    /// On error the snapshot is unchanged; request `TimelineCommand::Resync`
974
    /// and ignore further diffs until the corresponding `TimelineReset`.
975
    pub fn try_apply(self, items: &mut Vec<TimelineItem>) -> Result<(), TimelineDiffError> {
976
        let length = items.len();
977
        match self {
978
            Self::Append { values } => items.extend(values),
979
            Self::Clear => items.clear(),
980
            Self::PushFront { value } => items.insert(0, value),
981
            Self::PushBack { value } => items.push(value),
982
            Self::PopFront if length == 0 => return Err(TimelineDiffError { length }),
983
            Self::PopFront => {
984
                items.remove(0);
985
            }
986
            Self::PopBack if length == 0 => return Err(TimelineDiffError { length }),
987
            Self::PopBack => {
988
                items.pop();
989
            }
990
            Self::Insert { index, .. } if index > length => {
991
                return Err(TimelineDiffError { length });
992
            }
993
            Self::Insert { index, value } => items.insert(index, value),
994
            Self::Set { index, .. } if index >= length => return Err(TimelineDiffError { length }),
995
            Self::Set { index, value } => items[index] = value,
996
            Self::Remove { index } if index >= length => return Err(TimelineDiffError { length }),
997
            Self::Remove { index } => {
998
                items.remove(index);
999
            }
1000
            Self::Truncate { length: target } if target > length => {
1001
                return Err(TimelineDiffError { length });
1002
            }
1003
            Self::Truncate { length } => items.truncate(length),
1004
            Self::Reset { values } => *items = values,
1005
        }
1006
        Ok(())
1007
    }
1008
}
1009
1010
#[cfg(test)]
1011
mod tests {
1012
    use super::{TimelineDiff, TimelineItem};
1013
1014
    fn item(timestamp_ms: u64) -> TimelineItem {
1015
        TimelineItem::DayDivider { timestamp_ms }
1016
    }
1017
1018
    #[test]
1019
    fn timeline_diff_try_apply_handles_every_variant() {
1020
        let mut items = vec![item(1), item(2)];
1021
1022
        TimelineDiff::Append {
1023
            values: vec![item(3)],
1024
        }
1025
        .try_apply(&mut items)
1026
        .unwrap();
1027
        assert_eq!(items, vec![item(1), item(2), item(3)]);
1028
1029
        TimelineDiff::Clear.try_apply(&mut items).unwrap();
1030
        assert!(items.is_empty());
1031
1032
        TimelineDiff::PushFront { value: item(2) }
1033
            .try_apply(&mut items)
1034
            .unwrap();
1035
        TimelineDiff::PushBack { value: item(3) }
1036
            .try_apply(&mut items)
1037
            .unwrap();
1038
        assert_eq!(items, vec![item(2), item(3)]);
1039
1040
        TimelineDiff::PopFront.try_apply(&mut items).unwrap();
1041
        TimelineDiff::PopBack.try_apply(&mut items).unwrap();
1042
        assert!(items.is_empty());
1043
1044
        TimelineDiff::Reset {
1045
            values: vec![item(1), item(3)],
1046
        }
1047
        .try_apply(&mut items)
1048
        .unwrap();
1049
        TimelineDiff::Insert {
1050
            index: 1,
1051
            value: item(2),
1052
        }
1053
        .try_apply(&mut items)
1054
        .unwrap();
1055
        assert_eq!(items, vec![item(1), item(2), item(3)]);
1056
1057
        TimelineDiff::Set {
1058
            index: 1,
1059
            value: item(4),
1060
        }
1061
        .try_apply(&mut items)
1062
        .unwrap();
1063
        assert_eq!(items, vec![item(1), item(4), item(3)]);
1064
1065
        TimelineDiff::Remove { index: 1 }
1066
            .try_apply(&mut items)
1067
            .unwrap();
1068
        assert_eq!(items, vec![item(1), item(3)]);
1069
1070
        TimelineDiff::Truncate { length: 1 }
1071
            .try_apply(&mut items)
1072
            .unwrap();
1073
        assert_eq!(items, vec![item(1)]);
1074
    }
1075
1076
    #[test]
1077
    fn invalid_timeline_diff_leaves_snapshot_unchanged() {
1078
        let invalid = [
1079
            TimelineDiff::Insert {
1080
                index: 3,
1081
                value: item(3),
1082
            },
1083
            TimelineDiff::Set {
1084
                index: 2,
1085
                value: item(3),
1086
            },
1087
            TimelineDiff::Remove { index: 2 },
1088
            TimelineDiff::Truncate { length: 3 },
1089
        ];
1090
        for diff in invalid {
1091
            let mut items = vec![item(1), item(2)];
1092
            assert_eq!(diff.try_apply(&mut items).unwrap_err().length, 2);
1093
            assert_eq!(items, vec![item(1), item(2)]);
1094
        }
1095
        for diff in [TimelineDiff::PopFront, TimelineDiff::PopBack] {
1096
            let mut items = Vec::new();
1097
            assert_eq!(diff.try_apply(&mut items).unwrap_err().length, 0);
1098
            assert!(items.is_empty());
1099
        }
1100
    }
1101
}
1102
1103
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
1104
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1105
pub struct TimelineItemId(String);
1106
1107
impl TimelineItemId {
1108
    pub(crate) fn new(value: String) -> Self {
1109
        Self(value)
1110
    }
1111
1112
    #[must_use]
1113
    pub fn as_str(&self) -> &str {
1114
        &self.0
1115
    }
1116
}
1117
1118
#[derive(Clone, Debug, Eq, PartialEq)]
1119
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1120
#[non_exhaustive]
1121
pub struct TimelineMessage {
1122
    pub item_id: TimelineItemId,
1123
    pub event_id: Option<OwnedEventId>,
1124
    pub transaction_id: Option<OwnedTransactionId>,
1125
    pub sender_id: OwnedUserId,
1126
    pub sender_name: String,
1127
    pub body: String,
1128
    pub formatted_body: Option<String>,
1129
    pub mentions: Option<MessageMentions>,
1130
    pub thread_root: Option<OwnedEventId>,
1131
    pub thread_summary: Option<ThreadSummary>,
1132
    pub timestamp_ms: u64,
1133
    pub own: bool,
1134
    pub send_state: SendState,
1135
    pub edited: bool,
1136
    pub unable_to_decrypt: bool,
1137
    pub media: Option<MediaDescriptor>,
1138
    pub reply_to: Option<ReplyReference>,
1139
    pub reactions: Vec<Reaction>,
1140
    pub read_by: Vec<OwnedUserId>,
1141
}
1142
1143
#[derive(Clone, Debug, Eq, PartialEq)]
1144
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1145
#[non_exhaustive]
1146
pub struct ThreadSummary {
1147
    pub reply_count: u32,
1148
    pub latest_event_id: Option<OwnedEventId>,
1149
    pub latest_event_timestamp_ms: Option<u64>,
1150
}
1151
1152
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1153
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1154
#[non_exhaustive]
1155
pub enum SendState {
1156
    Sent,
1157
    Queued,
1158
    Sending,
1159
    Failed { retriable: bool },
1160
}
1161
1162
#[derive(Clone, Debug, Eq, PartialEq)]
1163
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1164
pub struct ReplyReference {
1165
    pub event_id: OwnedEventId,
1166
    pub sender_id: Option<OwnedUserId>,
1167
    pub sender_name: Option<String>,
1168
}
1169
1170
#[derive(Clone, Debug, Eq, PartialEq)]
1171
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1172
#[non_exhaustive]
1173
pub struct Reaction {
1174
    pub key: String,
1175
    pub count: usize,
1176
    pub own: bool,
1177
    pub senders: Vec<OwnedUserId>,
1178
}
1179
1180
#[derive(Clone, Debug, Eq, PartialEq)]
1181
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1182
#[non_exhaustive]
1183
pub struct MediaDescriptor {
1184
    pub id: MediaId,
1185
    pub kind: MediaKind,
1186
    pub filename: String,
1187
    pub caption: Option<String>,
1188
    pub mimetype: Option<String>,
1189
    pub size_bytes: Option<u64>,
1190
    pub width: Option<u64>,
1191
    pub height: Option<u64>,
1192
}
1193
1194
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
1195
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1196
pub struct MediaId(String);
1197
1198
impl MediaId {
1199
    pub(crate) fn new(value: String) -> Self {
1200
        Self(value)
1201
    }
1202
1203
    #[must_use]
1204
    pub fn as_str(&self) -> &str {
1205
        &self.0
1206
    }
1207
}
1208
1209
impl std::fmt::Display for MediaId {
1210
    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1211
        self.0.fmt(formatter)
1212
    }
1213
}
1214
1215
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
1216
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1217
pub enum MediaSize {
1218
    Thumbnail,
1219
    Full,
1220
}
1221
1222
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1223
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1224
#[non_exhaustive]
1225
pub enum MediaKind {
1226
    Image,
1227
    Video,
1228
    Audio,
1229
    File,
1230
    Sticker,
1231
}
1232
1233
#[derive(Clone, Debug, Eq, PartialEq)]
1234
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1235
pub struct TypingState {
1236
    pub room_id: OwnedRoomId,
1237
    pub users: Vec<OwnedUserId>,
1238
}
1239
1240
#[derive(Clone, Debug, Eq, PartialEq)]
1241
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1242
pub enum HistoryState {
1243
    Idle,
1244
    Loading,
1245
    StartReached,
1246
    Failed(String),
1247
}
1248
1249
#[derive(Clone, Debug, Eq, PartialEq)]
1250
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1251
#[non_exhaustive]
1252
pub struct EncryptionState {
1253
    pub device_id: OwnedDeviceId,
1254
    pub device_verified: bool,
1255
    pub recovery: RecoveryState,
1256
    pub key_backup: KeyBackupState,
1257
}
1258
1259
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1260
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1261
pub enum KeyBackupState {
1262
    Unknown,
1263
    Creating,
1264
    Enabling,
1265
    Resuming,
1266
    Downloading,
1267
    Enabled,
1268
    Disabling,
1269
}
1270
1271
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1272
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1273
pub enum RecoveryState {
1274
    Unknown,
1275
    Disabled,
1276
    Incomplete,
1277
    Enabled,
1278
}
1279
1280
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1281
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1282
pub enum VerificationAction {
1283
    Confirm,
1284
    Reject,
1285
    Cancel,
1286
}
1287
1288
#[derive(Clone, Debug, Eq, PartialEq)]
1289
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1290
#[non_exhaustive]
1291
pub enum VerificationTarget {
1292
    OwnIdentity,
1293
    User(OwnedUserId),
1294
    Device {
1295
        user_id: OwnedUserId,
1296
        device_id: OwnedDeviceId,
1297
    },
1298
}
1299
1300
#[derive(Clone, Debug, Eq, PartialEq)]
1301
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1302
pub enum VerificationState {
1303
    Waiting,
1304
    Comparison {
1305
        emojis: Vec<(String, String)>,
1306
        decimals: (u16, u16, u16),
1307
    },
1308
    Confirmed,
1309
    Done,
1310
    Cancelled(String),
1311
    Failed(VerificationFailure),
1312
}
1313
1314
#[derive(Clone, Debug, Eq, PartialEq)]
1315
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1316
#[non_exhaustive]
1317
pub enum VerificationFailure {
1318
    RequestAcceptance(String),
1319
    SasStartup(String),
1320
    SasAcceptance(String),
1321
    RequestStreamEnded,
1322
    SasStreamEnded,
1323
}
1324
1325
#[derive(Clone, Debug, Eq, PartialEq)]
1326
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1327
#[non_exhaustive]
1328
pub struct CommandFailure {
1329
    pub id: CommandId,
1330
    pub command: CommandKind,
1331
    pub kind: CommandErrorKind,
1332
    pub message: String,
1333
}
1334
1335
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1336
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1337
#[non_exhaustive]
1338
pub enum CommandErrorKind {
1339
    Unauthenticated,
1340
    RoomUnknown,
1341
    RoomNotOpen,
1342
    TimelineNotOpen,
1343
    RoomBusy,
1344
    QueueFull,
1345
    InteractiveAuthRequired,
1346
    EmptyBody,
1347
    MediaUnknown,
1348
    MediaTooLarge,
1349
    MediaUnavailable,
1350
    VerificationInactive,
1351
    VerificationUnsupported,
1352
    NoCrossSigningIdentity,
1353
    RecoveryRejected,
1354
    EventReceiverClosed,
1355
    Network,
1356
    Server,
1357
    Store,
1358
    Timeline,
1359
    Encryption,
1360
    Other,
1361
}
1362
1363
#[derive(Clone, Debug, Eq, PartialEq)]
1364
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1365
pub struct EngineFailure {
1366
    pub kind: EngineErrorKind,
1367
    pub message: String,
1368
}
1369
1370
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1371
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1372
#[non_exhaustive]
1373
pub enum EngineErrorKind {
1374
    EventReceiverClosed,
1375
    Watcher,
1376
    Other,
1377
}