atagen

macltok

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