atagen

macltok

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