atagen

macltok

public
Star0
Watch0
c9effeac326a64a991e3de0049f2ffa1c7a2d18a
3
ssh://brewery@pls.lobotomise.me:33/atagen/macltok.git
src/model.rsRust2363 lines68.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
    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 { delete_from_server: bool },
633
    RecoverKeyBackup(String),
634
    StartVerification(VerificationTarget),
635
    Verify(VerificationAction),
636
}
637
638
impl fmt::Debug for EncryptionCommand {
639
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
640
        match self {
641
            Self::Recover(_) => formatter.debug_tuple("Recover").field(&"...").finish(),
642
            Self::CreateKeyBackup => formatter.write_str("CreateKeyBackup"),
643
            Self::EnableKeyBackup => formatter.write_str("EnableKeyBackup"),
644
            Self::DisableKeyBackup { delete_from_server } => formatter
645
                .debug_struct("DisableKeyBackup")
646
                .field("delete_from_server", delete_from_server)
647
                .finish(),
648
            Self::RecoverKeyBackup(_) => formatter
649
                .debug_tuple("RecoverKeyBackup")
650
                .field(&"...")
651
                .finish(),
652
            Self::StartVerification(target) => formatter
653
                .debug_tuple("StartVerification")
654
                .field(target)
655
                .finish(),
656
            Self::Verify(action) => formatter.debug_tuple("Verify").field(action).finish(),
657
        }
658
    }
659
}
660
661
#[derive(Clone, Debug, Eq, PartialEq)]
662
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
663
#[non_exhaustive]
664
pub enum NotificationCommand {
665
    SetRoomMode {
666
        room_id: OwnedRoomId,
667
        mode: RoomNotificationMode,
668
    },
669
    ResetRoomMode(OwnedRoomId),
670
    SetDefaultMode {
671
        encrypted: bool,
672
        one_to_one: bool,
673
        mode: RoomNotificationMode,
674
    },
675
    AddKeyword(String),
676
    RemoveKeyword(String),
677
}
678
679
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
680
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
681
#[non_exhaustive]
682
pub enum LifecycleCommand {
683
    Shutdown,
684
}
685
686
#[derive(Clone, Debug, PartialEq)]
687
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
688
pub enum CommandOutput {
689
    Done,
690
    Room(OwnedRoomId),
691
    Mxc(OwnedMxcUri),
692
    /// the shortcodes the copies ended up under, and the ones that couldn't be fetched
693
    Cloned {
694
        shortcodes: Vec<String>,
695
        skipped: Vec<String>,
696
    },
697
    EventContext(EventContextSession),
698
    EventContextPage(EventContextPage),
699
    MentionsPage(crate::MentionsPage),
700
    MentionContext(crate::SavedMentionContext),
701
    SearchSession(crate::SearchSession),
702
    SearchPage(crate::SearchPage),
703
    SearchCoverage(crate::SearchCoverage),
704
}
705
706
#[derive(Clone, Debug, Eq, PartialEq)]
707
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
708
#[non_exhaustive]
709
pub struct EventContext {
710
    pub events_before: Vec<TimelineItem>,
711
    pub event: TimelineItem,
712
    pub events_after: Vec<TimelineItem>,
713
    pub start_token: Option<String>,
714
    pub end_token: Option<String>,
715
}
716
717
#[derive(Clone, Debug, Eq, PartialEq)]
718
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
719
pub enum EventContextCloseReason {
720
    /// opening a 33rd session closed the oldest
721
    Evicted,
722
    RoomLeft,
723
}
724
725
/// a session's media stay resolvable until it closes, and past `EngineConfig::max_context_media`
726
/// its least recently used are dropped
727
#[derive(Clone, Debug, Eq, PartialEq)]
728
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
729
#[non_exhaustive]
730
pub struct EventContextSession {
731
    pub context_id: String,
732
    pub context: EventContext,
733
}
734
735
#[derive(Clone, Debug, Eq, PartialEq)]
736
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
737
#[non_exhaustive]
738
pub struct EventContextPage {
739
    pub events: Vec<TimelineItem>,
740
    /// `None` once that end of the room is reached
741
    pub next_token: Option<String>,
742
}
743
744
#[derive(Clone, Debug, Eq, PartialEq)]
745
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
746
#[non_exhaustive]
747
pub enum MessageContent {
748
    Text {
749
        content: RichText,
750
        mentions: MessageMentions,
751
    },
752
    Emote {
753
        content: RichText,
754
        mentions: MessageMentions,
755
    },
756
    /// m.notice, for bots. clients render it muted and never answer it automatically
757
    Notice {
758
        content: RichText,
759
        mentions: MessageMentions,
760
    },
761
    Reply {
762
        event_id: OwnedEventId,
763
        content: RichText,
764
        mentions: MessageMentions,
765
        notify_replied_to: bool,
766
    },
767
}
768
769
impl MessageContent {
770
    #[must_use]
771
    pub fn text(body: impl Into<String>) -> Self {
772
        Self::Text {
773
            content: RichText::plain(body),
774
            mentions: MessageMentions::default(),
775
        }
776
    }
777
778
    #[must_use]
779
    pub fn html(body: impl Into<String>, formatted_body: impl Into<String>) -> Self {
780
        Self::Text {
781
            content: RichText::html(body, formatted_body),
782
            mentions: MessageMentions::default(),
783
        }
784
    }
785
786
    #[must_use]
787
    pub fn emote(content: RichText, mentions: MessageMentions) -> Self {
788
        Self::Emote { content, mentions }
789
    }
790
791
    #[must_use]
792
    pub fn notice(content: RichText, mentions: MessageMentions) -> Self {
793
        Self::Notice { content, mentions }
794
    }
795
796
    #[must_use]
797
    pub fn reply(event_id: OwnedEventId, content: RichText, mentions: MessageMentions) -> Self {
798
        Self::Reply {
799
            event_id,
800
            content,
801
            mentions,
802
            notify_replied_to: true,
803
        }
804
    }
805
806
    #[must_use]
807
    pub fn reply_without_notification(
808
        event_id: OwnedEventId,
809
        content: RichText,
810
        mentions: MessageMentions,
811
    ) -> Self {
812
        Self::Reply {
813
            event_id,
814
            content,
815
            mentions,
816
            notify_replied_to: false,
817
        }
818
    }
819
}
820
821
#[derive(Clone, Debug, Eq, PartialEq)]
822
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
823
#[non_exhaustive]
824
pub enum MessageEditContent {
825
    Text {
826
        content: RichText,
827
        mentions: MessageMentions,
828
    },
829
    Emote {
830
        content: RichText,
831
        mentions: MessageMentions,
832
    },
833
}
834
835
impl MessageEditContent {
836
    #[must_use]
837
    pub fn text(body: impl Into<String>) -> Self {
838
        Self::Text {
839
            content: RichText::plain(body),
840
            mentions: MessageMentions::default(),
841
        }
842
    }
843
844
    #[must_use]
845
    pub fn html(body: impl Into<String>, formatted_body: impl Into<String>) -> Self {
846
        Self::Text {
847
            content: RichText::html(body, formatted_body),
848
            mentions: MessageMentions::default(),
849
        }
850
    }
851
852
    #[must_use]
853
    pub fn emote(content: RichText, mentions: MessageMentions) -> Self {
854
        Self::Emote { content, mentions }
855
    }
856
}
857
858
#[derive(Clone, Debug, Default, Eq, PartialEq)]
859
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
860
#[non_exhaustive]
861
pub struct RichText {
862
    pub body: String,
863
    pub formatted_body: Option<String>,
864
}
865
866
impl RichText {
867
    #[must_use]
868
    pub fn plain(body: impl Into<String>) -> Self {
869
        Self {
870
            body: body.into(),
871
            formatted_body: None,
872
        }
873
    }
874
875
    #[must_use]
876
    pub fn html(body: impl Into<String>, formatted_body: impl Into<String>) -> Self {
877
        Self {
878
            body: body.into(),
879
            formatted_body: Some(formatted_body.into()),
880
        }
881
    }
882
}
883
884
#[derive(Clone, Debug, Default, Eq, PartialEq)]
885
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
886
#[non_exhaustive]
887
pub struct MessageMentions {
888
    pub user_ids: Vec<OwnedUserId>,
889
    pub room: bool,
890
}
891
892
impl MessageMentions {
893
    #[must_use]
894
    pub fn with_user_ids(user_ids: impl IntoIterator<Item = OwnedUserId>) -> Self {
895
        Self {
896
            user_ids: user_ids.into_iter().collect(),
897
            room: false,
898
        }
899
    }
900
901
    #[must_use]
902
    pub fn room() -> Self {
903
        Self {
904
            user_ids: Vec::new(),
905
            room: true,
906
        }
907
    }
908
}
909
910
#[derive(Clone, Debug, PartialEq)]
911
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
912
pub enum Event {
913
    ConnectionChanged(ConnectionState),
914
    Authenticated {
915
        user_id: OwnedUserId,
916
    },
917
    /// the tokens rotated. with a `SessionStore` the record is already saved by now,
918
    /// without one persist a fresh `ClientHandle::session_record()` atomically
919
    SessionTokensChanged,
920
    /// the rotated tokens didn't reach the `SessionStore`, so the saved record may not
921
    /// restore. the engine tries again on the next rotation
922
    SessionSaveFailed {
923
        message: String,
924
    },
925
    /// the server stopped honouring our token and the engine stops. a soft logout
926
    /// names the device to log in again as, keeping the store. otherwise the record
927
    /// and the store are both dead
928
    SessionAccessRejected {
929
        soft_logout: Option<OwnedDeviceId>,
930
    },
931
    NotificationSettingsChanged,
932
    VerificationTargetChanged(VerificationTarget),
933
    RoomsChanged,
934
    MentionsChanged(crate::MentionsUpdate),
935
    /// an image pack came, went or changed somewhere. ask `image_packs` again
936
    ImagePacksChanged,
937
    /// replaces whatever you had for this timeline. every diff after it applies to it
938
    TimelineReset {
939
        timeline_id: TimelineId,
940
        items: Vector<TimelineItem>,
941
    },
942
    TimelineChanged {
943
        timeline_id: TimelineId,
944
        diffs: Vec<TimelineDiff>,
945
    },
946
    HistoryChanged {
947
        timeline_id: TimelineId,
948
        state: HistoryState,
949
    },
950
    RecentHistoryChanged {
951
        room_id: OwnedRoomId,
952
        state: RecentHistoryState,
953
    },
954
    HistoryRepairChanged(crate::HistoryRepairReport),
955
    #[doc(hidden)]
956
    PublicationBarrier {
957
        #[cfg_attr(feature = "serde", serde(skip))]
958
        acknowledgement: Option<crate::PublicationAck>,
959
    },
960
    /// the timeline was closed, or its room was left, and nothing more comes for it
961
    /// until another `TimelineReset`
962
    TimelineClosed {
963
        timeline_id: TimelineId,
964
    },
965
    /// an event context closed by something other than its own `Close`. its id answers
966
    /// `EventContextNotOpen` from now on
967
    EventContextClosed {
968
        context_id: String,
969
        reason: EventContextCloseReason,
970
    },
971
    /// replaces whatever you had for this room's thread list, newest activity first
972
    ThreadListReset {
973
        room_id: OwnedRoomId,
974
        entries: Vector<ThreadListEntry>,
975
    },
976
    /// apply each with `ThreadListEntry::upsert`
977
    ThreadListChanged {
978
        room_id: OwnedRoomId,
979
        upserts: Vec<ThreadListEntry>,
980
    },
981
    /// `StartReached` once the oldest thread is in
982
    ThreadListHistoryChanged {
983
        room_id: OwnedRoomId,
984
        state: HistoryState,
985
    },
986
    /// nothing more comes for it until another `ThreadListReset`
987
    ThreadListClosed {
988
        room_id: OwnedRoomId,
989
        reason: ThreadListCloseReason,
990
    },
991
    /// a room that stops being watched says so with an empty `users`
992
    TypingChanged(TypingState),
993
    MediaUploadProgress {
994
        room_id: OwnedRoomId,
995
        transaction_id: OwnedTransactionId,
996
        uploaded_bytes: u64,
997
        total_bytes: u64,
998
        complete: bool,
999
    },
1000
    /// not the end of it. `will_retry` means the send queue has another go by itself,
1001
    /// otherwise it waits for `retry_send` or `cancel_send`
1002
    MediaUploadFailed {
1003
        room_id: OwnedRoomId,
1004
        transaction_id: OwnedTransactionId,
1005
        kind: crate::ErrorKind,
1006
        will_retry: bool,
1007
    },
1008
    /// an upload that turned up in a progress or failure event gets exactly one of
1009
    /// these while the engine runs, and nothing about it after. stopping ends
1010
    /// nothing, `Stopped` covers whatever is still going and the sdk resumes it next run
1011
    MediaUploadFinished {
1012
        room_id: OwnedRoomId,
1013
        transaction_id: OwnedTransactionId,
1014
        outcome: UploadOutcome,
1015
    },
1016
    EncryptionChanged(EncryptionState),
1017
    VerificationChanged(VerificationState),
1018
    /// logout went through and the engine stops. delete the record and the store
1019
    LoggedOut,
1020
    Failed(EngineFailure),
1021
    /// always the last event. keep the saved record unless `LoggedOut` or a
1022
    /// hard `SessionAccessRejected` came first. commands still in flight answer `Interrupted`
1023
    Stopped,
1024
}
1025
1026
#[derive(Clone, Debug, Eq, PartialEq)]
1027
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1028
pub enum UploadOutcome {
1029
    Sent {
1030
        event_id: OwnedEventId,
1031
    },
1032
    Cancelled,
1033
    /// its room was forgotten, and the queue with it
1034
    RoomForgotten,
1035
    /// the sdk's update channel overflowed while it ended, so whether it was sent is unknown
1036
    Missed,
1037
}
1038
1039
/// why `Engine::run` came back, when nothing went wrong
1040
#[derive(Clone, Debug, Eq, PartialEq)]
1041
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1042
pub enum Termination {
1043
    ShutDown,
1044
    /// every `ClientHandle` was dropped, so nobody could have asked for anything else
1045
    HandlesDropped,
1046
    LoggedOut,
1047
    /// the server rejected our token for good
1048
    AccessRevoked,
1049
    /// the server wants this device to log in again, and its e2ee keys live in the store
1050
    SoftLoggedOut {
1051
        device_id: OwnedDeviceId,
1052
    },
1053
}
1054
1055
impl Termination {
1056
    #[must_use]
1057
    pub const fn retain(&self) -> Retain {
1058
        match self {
1059
            Self::ShutDown | Self::HandlesDropped => Retain::RecordAndStore,
1060
            Self::SoftLoggedOut { .. } => Retain::Store,
1061
            Self::LoggedOut | Self::AccessRevoked => Retain::Nothing,
1062
        }
1063
    }
1064
}
1065
1066
/// what's still worth keeping of the saved record and the store
1067
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1068
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1069
pub enum Retain {
1070
    RecordAndStore,
1071
    /// keep the store and a record naming the device to log in again as
1072
    Store,
1073
    Nothing,
1074
}
1075
1076
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1077
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1078
pub enum ConnectionState {
1079
    Idle,
1080
    Connecting,
1081
    Online,
1082
    Offline,
1083
    Stopped,
1084
}
1085
1086
#[derive(Clone, Debug, Eq, PartialEq)]
1087
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1088
#[non_exhaustive]
1089
pub struct RoomSummary {
1090
    pub room_id: OwnedRoomId,
1091
    pub name: String,
1092
    pub membership: RoomMembership,
1093
    /// the main timeline's, threads have theirs in `thread_unreads`
1094
    pub unread_messages: u64,
1095
    /// thread mentions included
1096
    pub unread_mentions: u64,
1097
    /// unread events the push rules said to notify about, which is neither of the above.
1098
    /// the main timeline's
1099
    pub unread_notifications: u64,
1100
    /// threads with anything unread, by root
1101
    pub thread_unreads: BTreeMap<OwnedEventId, ThreadUnread>,
1102
    pub encrypted: bool,
1103
    pub space: bool,
1104
    pub direct: bool,
1105
    pub favourite: bool,
1106
    pub low_priority: bool,
1107
    pub avatar: Option<MediaId>,
1108
    pub latest_event_timestamp_ms: Option<u64>,
1109
    pub latest_event: Option<LatestEvent>,
1110
    pub notification_mode: Option<RoomNotificationMode>,
1111
    pub notification_mode_custom: bool,
1112
    pub topic: Option<String>,
1113
    pub canonical_alias: Option<OwnedRoomAliasId>,
1114
    pub joined_members: u64,
1115
    pub invited_members: u64,
1116
    /// who a nameless room gets named after
1117
    pub heroes: Vec<RoomHero>,
1118
    /// set once the room has been upgraded, this is where everyone went
1119
    pub successor: Option<OwnedRoomId>,
1120
    pub inviter: Option<OwnedUserId>,
1121
    /// joined spaces that list this room as a child
1122
    pub parent_spaces: Vec<OwnedRoomId>,
1123
    /// for a space, the children it lists, joined or not
1124
    pub children: Vec<OwnedRoomId>,
1125
    /// a space's msc3230 order. ordered spaces sort first by (order, room id), the rest by room id
1126
    pub space_order: Option<String>,
1127
    /// you can send im.ponies.room_emotes here, so you can make and edit its packs
1128
    pub can_edit_packs: bool,
1129
    /// m.room.pinned_events in order, the newest pin last
1130
    pub pinned_events: Vec<OwnedEventId>,
1131
    pub can_pin: bool,
1132
}
1133
1134
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
1135
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1136
#[non_exhaustive]
1137
pub struct ThreadUnread {
1138
    pub messages: u64,
1139
    pub mentions: u64,
1140
    pub notifications: u64,
1141
}
1142
1143
#[derive(Clone, Debug, Eq, PartialEq)]
1144
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1145
#[non_exhaustive]
1146
pub struct RoomHero {
1147
    pub user_id: OwnedUserId,
1148
    pub display_name: Option<String>,
1149
    pub avatar: Option<MediaId>,
1150
}
1151
1152
/// enough for a room list preview line. `kind` and `body` are only set for messages,
1153
/// and an edit shows its new text
1154
#[derive(Clone, Debug, Eq, PartialEq)]
1155
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1156
#[non_exhaustive]
1157
pub struct LatestEvent {
1158
    pub sender: Option<OwnedUserId>,
1159
    pub kind: Option<MessageKind>,
1160
    pub body: Option<String>,
1161
    pub timestamp_ms: Option<u64>,
1162
    pub send_state: SendState,
1163
}
1164
1165
#[derive(Clone, Debug, Eq, PartialEq)]
1166
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1167
#[non_exhaustive]
1168
pub struct Profile {
1169
    pub user_id: OwnedUserId,
1170
    pub display_name: Option<String>,
1171
    pub avatar: Option<MediaId>,
1172
}
1173
1174
/// one room in a space's hierarchy, as the server describes it, which may be a room you're not in
1175
#[derive(Clone, Debug, Eq, PartialEq)]
1176
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1177
#[non_exhaustive]
1178
pub struct SpaceChild {
1179
    pub room_id: OwnedRoomId,
1180
    pub name: String,
1181
    pub topic: Option<String>,
1182
    pub avatar: Option<MediaId>,
1183
    pub canonical_alias: Option<OwnedRoomAliasId>,
1184
    pub joined_members: u64,
1185
    pub space: bool,
1186
    pub children_count: u64,
1187
    pub membership: Option<RoomMembership>,
1188
    pub via: Vec<OwnedServerName>,
1189
    pub suggested: bool,
1190
    pub world_readable: Option<bool>,
1191
    pub guest_can_join: bool,
1192
}
1193
1194
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1195
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1196
pub enum RoomNotificationMode {
1197
    AllMessages,
1198
    MentionsAndKeywordsOnly,
1199
    Mute,
1200
}
1201
1202
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1203
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1204
#[non_exhaustive]
1205
pub struct RoomNotificationSettings {
1206
    pub mode: RoomNotificationMode,
1207
    pub custom: bool,
1208
}
1209
1210
#[derive(Clone, Debug)]
1211
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1212
pub enum PushNotificationStatus {
1213
    Event(Box<PushNotification>),
1214
    NotFound,
1215
    FilteredOut,
1216
    Redacted,
1217
}
1218
1219
#[derive(Clone, Debug)]
1220
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1221
#[non_exhaustive]
1222
pub struct PushNotification {
1223
    pub event_id: OwnedEventId,
1224
    pub event: PushNotificationEvent,
1225
    pub sender: PushNotificationSender,
1226
    pub room: PushNotificationRoom,
1227
    pub noisy: Option<bool>,
1228
    pub has_mention: Option<bool>,
1229
    pub thread_id: Option<OwnedEventId>,
1230
}
1231
1232
#[derive(Clone, Debug, Eq, PartialEq)]
1233
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1234
#[non_exhaustive]
1235
pub struct PushNotificationSender {
1236
    pub user_id: OwnedUserId,
1237
    pub display_name: Option<String>,
1238
    pub avatar_url: Option<MediaId>,
1239
    pub name_ambiguous: bool,
1240
}
1241
1242
#[derive(Clone, Debug, Eq, PartialEq)]
1243
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1244
#[non_exhaustive]
1245
pub struct PushNotificationRoom {
1246
    pub room_id: OwnedRoomId,
1247
    pub name: String,
1248
    pub avatar_url: Option<MediaId>,
1249
    pub canonical_alias: Option<OwnedRoomAliasId>,
1250
    pub topic: Option<String>,
1251
    pub encrypted: Option<bool>,
1252
    pub direct: bool,
1253
    pub joined_members_count: u64,
1254
    pub service_members: Vec<OwnedUserId>,
1255
    pub active_service_members_count: u64,
1256
    pub space: bool,
1257
}
1258
1259
#[derive(Clone, Debug)]
1260
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1261
pub enum PushNotificationEvent {
1262
    Timeline(Raw<AnySyncTimelineEvent>),
1263
    Invite(Raw<StrippedRoomMemberEvent>),
1264
}
1265
1266
#[derive(Clone, Debug, Eq, PartialEq)]
1267
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1268
#[non_exhaustive]
1269
pub struct RoomPowerLevels {
1270
    pub ban: i64,
1271
    pub invite: i64,
1272
    pub kick: i64,
1273
    pub redact: i64,
1274
    pub events_default: i64,
1275
    pub state_default: i64,
1276
    pub users_default: i64,
1277
    pub room_notification: i64,
1278
    pub users: Vec<(OwnedUserId, i64)>,
1279
    pub events: Vec<(String, i64)>,
1280
    /// in rooms from version 12 the creators outrank everyone and aren't listed in `users`
1281
    pub creators: Vec<OwnedUserId>,
1282
}
1283
1284
#[derive(Clone, Debug, Default, Eq, PartialEq)]
1285
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1286
#[non_exhaustive]
1287
pub struct PowerLevelChanges {
1288
    pub ban: Option<i64>,
1289
    pub invite: Option<i64>,
1290
    pub kick: Option<i64>,
1291
    pub redact: Option<i64>,
1292
    pub events_default: Option<i64>,
1293
    pub state_default: Option<i64>,
1294
    pub users_default: Option<i64>,
1295
    pub room_notification: Option<i64>,
1296
    pub room_name: Option<i64>,
1297
    pub room_avatar: Option<i64>,
1298
    pub room_topic: Option<i64>,
1299
    pub space_child: Option<i64>,
1300
    pub beacon: Option<i64>,
1301
    pub beacon_info: Option<i64>,
1302
}
1303
1304
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1305
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1306
pub enum RoomMembership {
1307
    Joined,
1308
    Invited,
1309
    Left,
1310
    Knocked,
1311
    Banned,
1312
}
1313
1314
#[derive(Clone, Debug, Default, Eq, PartialEq)]
1315
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1316
#[non_exhaustive]
1317
pub struct RoomListFilter {
1318
    pub membership: Option<RoomMembership>,
1319
    pub category: Option<RoomCategory>,
1320
    pub favourite: Option<bool>,
1321
    pub unread_only: bool,
1322
    pub search: Option<String>,
1323
}
1324
1325
impl RoomListFilter {
1326
    #[must_use]
1327
    pub fn with_membership(mut self, membership: RoomMembership) -> Self {
1328
        self.membership = Some(membership);
1329
        self
1330
    }
1331
1332
    #[must_use]
1333
    pub fn with_category(mut self, category: RoomCategory) -> Self {
1334
        self.category = Some(category);
1335
        self
1336
    }
1337
1338
    #[must_use]
1339
    pub fn with_favourite(mut self, favourite: bool) -> Self {
1340
        self.favourite = Some(favourite);
1341
        self
1342
    }
1343
1344
    #[must_use]
1345
    pub const fn with_unread_only(mut self, unread_only: bool) -> Self {
1346
        self.unread_only = unread_only;
1347
        self
1348
    }
1349
1350
    #[must_use]
1351
    pub fn with_search(mut self, search: impl Into<String>) -> Self {
1352
        self.search = Some(search.into());
1353
        self
1354
    }
1355
1356
    #[must_use]
1357
    pub fn matches(&self, room: &RoomSummary) -> bool {
1358
        self.matcher().matches(room)
1359
    }
1360
1361
    /// lowercases the search once, for filtering a whole list
1362
    #[must_use]
1363
    pub fn matcher(&self) -> RoomMatcher<'_> {
1364
        RoomMatcher {
1365
            filter: self,
1366
            search: self.search.as_deref().map(str::to_lowercase),
1367
        }
1368
    }
1369
}
1370
1371
#[derive(Debug)]
1372
pub struct RoomMatcher<'a> {
1373
    filter: &'a RoomListFilter,
1374
    search: Option<String>,
1375
}
1376
1377
impl RoomMatcher<'_> {
1378
    #[must_use]
1379
    pub fn matches(&self, room: &RoomSummary) -> bool {
1380
        let filter = self.filter;
1381
        filter
1382
            .membership
1383
            .is_none_or(|membership| room.membership == membership)
1384
            && filter.category.is_none_or(|category| match category {
1385
                RoomCategory::People => room.direct && !room.space,
1386
                RoomCategory::Rooms => !room.direct && !room.space,
1387
                RoomCategory::Spaces => room.space,
1388
            })
1389
            && filter
1390
                .favourite
1391
                .is_none_or(|favourite| room.favourite == favourite)
1392
            && (!filter.unread_only || room.unread_messages > 0 || room.unread_mentions > 0)
1393
            && self.search.as_ref().is_none_or(|search| {
1394
                room.name.to_lowercase().contains(search)
1395
                    || room.room_id.as_str().to_lowercase().contains(search)
1396
            })
1397
    }
1398
}
1399
1400
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1401
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1402
#[non_exhaustive]
1403
pub enum RoomCategory {
1404
    People,
1405
    Rooms,
1406
    Spaces,
1407
}
1408
1409
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1410
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1411
#[non_exhaustive]
1412
pub enum MemberFilter {
1413
    Active,
1414
    Joined,
1415
    Invited,
1416
    Banned,
1417
    Left,
1418
    All,
1419
}
1420
1421
#[derive(Clone, Debug, Eq, PartialEq)]
1422
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1423
pub enum MemberMembership {
1424
    Joined,
1425
    Invited,
1426
    Left,
1427
    Knocked,
1428
    Banned,
1429
    Custom(String),
1430
}
1431
1432
#[derive(Clone, Debug, Eq, PartialEq)]
1433
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1434
#[non_exhaustive]
1435
pub struct MemberSummary {
1436
    pub user_id: OwnedUserId,
1437
    pub name: String,
1438
    pub display_name: Option<String>,
1439
    pub avatar: Option<MediaId>,
1440
    pub membership: MemberMembership,
1441
    pub own: bool,
1442
    pub name_ambiguous: bool,
1443
    pub ignored: bool,
1444
    pub service: bool,
1445
}
1446
1447
#[derive(Clone, Debug, Eq, PartialEq)]
1448
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1449
#[non_exhaustive]
1450
pub struct OwnDevice {
1451
    pub device_id: OwnedDeviceId,
1452
    pub display_name: Option<String>,
1453
    pub last_seen_ip: Option<String>,
1454
    pub last_seen_timestamp_ms: Option<u64>,
1455
    pub current: bool,
1456
}
1457
1458
#[derive(Clone, Debug, Eq, PartialEq)]
1459
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1460
pub enum TimelineItem {
1461
    Event(Box<TimelineEvent>),
1462
    HistoryGap(crate::HistoryGap),
1463
    DayDivider { timestamp_ms: u64 },
1464
    ReadMarker,
1465
    TimelineStart,
1466
}
1467
1468
#[derive(Clone, Debug, Eq, PartialEq)]
1469
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1470
pub enum TimelineDiff {
1471
    Append { values: Vec<TimelineItem> },
1472
    Clear,
1473
    PushFront { value: TimelineItem },
1474
    PushBack { value: TimelineItem },
1475
    PopFront,
1476
    PopBack,
1477
    Insert { index: usize, value: TimelineItem },
1478
    Set { index: usize, value: TimelineItem },
1479
    Remove { index: usize },
1480
    Truncate { length: usize },
1481
    Reset { values: Vec<TimelineItem> },
1482
}
1483
1484
#[derive(Clone, Copy, Debug, Eq, PartialEq, thiserror::Error)]
1485
#[error("timeline diff is incompatible with the current {length}-item snapshot")]
1486
pub struct TimelineDiffError {
1487
    pub length: usize,
1488
}
1489
1490
impl TimelineDiff {
1491
    /// Apply a diff only when its indices match the current snapshot.
1492
    /// On error the snapshot is unchanged; request `TimelineCommand::Resync`
1493
    /// and ignore further diffs until the corresponding `TimelineReset`.
1494
    pub fn try_apply(self, items: &mut Vector<TimelineItem>) -> Result<(), TimelineDiffError> {
1495
        let length = items.len();
1496
        if self.length_after(length).is_none() {
1497
            return Err(TimelineDiffError { length });
1498
        }
1499
        self.apply(items);
1500
        Ok(())
1501
    }
1502
1503
    /// a whole `TimelineChanged` or none of it. whether a diff fits depends only on the
1504
    /// length before it, so the batch is checked against lengths alone before anything
1505
    /// moves. the error carries the length the snapshot would have had at the misfit
1506
    pub fn try_apply_all(
1507
        diffs: Vec<Self>,
1508
        items: &mut Vector<TimelineItem>,
1509
    ) -> Result<(), TimelineDiffError> {
1510
        diffs.iter().try_fold(items.len(), |length, diff| {
1511
            diff.length_after(length)
1512
                .ok_or(TimelineDiffError { length })
1513
        })?;
1514
        for diff in diffs {
1515
            diff.apply(items);
1516
        }
1517
        Ok(())
1518
    }
1519
1520
    fn length_after(&self, length: usize) -> Option<usize> {
1521
        match self {
1522
            Self::Append { values } => Some(length + values.len()),
1523
            Self::Clear => Some(0),
1524
            Self::PushFront { .. } | Self::PushBack { .. } => Some(length + 1),
1525
            Self::PopFront | Self::PopBack => length.checked_sub(1),
1526
            Self::Insert { index, .. } => (*index <= length).then_some(length + 1),
1527
            Self::Set { index, .. } => (*index < length).then_some(length),
1528
            Self::Remove { index } => (*index < length).then(|| length - 1),
1529
            Self::Truncate { length: target } => (*target <= length).then_some(*target),
1530
            Self::Reset { values } => Some(values.len()),
1531
        }
1532
    }
1533
1534
    fn apply(self, items: &mut Vector<TimelineItem>) {
1535
        match self {
1536
            Self::Append { values } => items.extend(values),
1537
            Self::Clear => items.clear(),
1538
            Self::PushFront { value } => items.push_front(value),
1539
            Self::PushBack { value } => items.push_back(value),
1540
            Self::PopFront => {
1541
                items.pop_front();
1542
            }
1543
            Self::PopBack => {
1544
                items.pop_back();
1545
            }
1546
            Self::Insert { index, value } => items.insert(index, value),
1547
            Self::Set { index, value } => {
1548
                items.set(index, value);
1549
            }
1550
            Self::Remove { index } => {
1551
                items.remove(index);
1552
            }
1553
            Self::Truncate { length } => items.truncate(length),
1554
            Self::Reset { values } => *items = values.into_iter().collect(),
1555
        }
1556
    }
1557
}
1558
1559
#[cfg(test)]
1560
mod tests {
1561
    use imbl::{Vector, vector};
1562
1563
    use super::{TimelineDiff, TimelineItem};
1564
1565
    fn item(timestamp_ms: u64) -> TimelineItem {
1566
        TimelineItem::DayDivider { timestamp_ms }
1567
    }
1568
1569
    #[test]
1570
    fn timeline_diff_try_apply_handles_every_variant() {
1571
        let mut items = vector![item(1), item(2)];
1572
1573
        TimelineDiff::Append {
1574
            values: vec![item(3)],
1575
        }
1576
        .try_apply(&mut items)
1577
        .unwrap();
1578
        assert_eq!(items, vector![item(1), item(2), item(3)]);
1579
1580
        TimelineDiff::Clear.try_apply(&mut items).unwrap();
1581
        assert!(items.is_empty());
1582
1583
        TimelineDiff::PushFront { value: item(2) }
1584
            .try_apply(&mut items)
1585
            .unwrap();
1586
        TimelineDiff::PushBack { value: item(3) }
1587
            .try_apply(&mut items)
1588
            .unwrap();
1589
        assert_eq!(items, vector![item(2), item(3)]);
1590
1591
        TimelineDiff::PopFront.try_apply(&mut items).unwrap();
1592
        TimelineDiff::PopBack.try_apply(&mut items).unwrap();
1593
        assert!(items.is_empty());
1594
1595
        TimelineDiff::Reset {
1596
            values: vec![item(1), item(3)],
1597
        }
1598
        .try_apply(&mut items)
1599
        .unwrap();
1600
        TimelineDiff::Insert {
1601
            index: 1,
1602
            value: item(2),
1603
        }
1604
        .try_apply(&mut items)
1605
        .unwrap();
1606
        assert_eq!(items, vector![item(1), item(2), item(3)]);
1607
1608
        TimelineDiff::Set {
1609
            index: 1,
1610
            value: item(4),
1611
        }
1612
        .try_apply(&mut items)
1613
        .unwrap();
1614
        assert_eq!(items, vector![item(1), item(4), item(3)]);
1615
1616
        TimelineDiff::Remove { index: 1 }
1617
            .try_apply(&mut items)
1618
            .unwrap();
1619
        assert_eq!(items, vector![item(1), item(3)]);
1620
1621
        TimelineDiff::Truncate { length: 1 }
1622
            .try_apply(&mut items)
1623
            .unwrap();
1624
        assert_eq!(items, vector![item(1)]);
1625
    }
1626
1627
    #[test]
1628
    fn invalid_timeline_diff_leaves_snapshot_unchanged() {
1629
        let invalid = [
1630
            TimelineDiff::Insert {
1631
                index: 3,
1632
                value: item(3),
1633
            },
1634
            TimelineDiff::Set {
1635
                index: 2,
1636
                value: item(3),
1637
            },
1638
            TimelineDiff::Remove { index: 2 },
1639
            TimelineDiff::Truncate { length: 3 },
1640
        ];
1641
        for diff in invalid {
1642
            let mut items = vector![item(1), item(2)];
1643
            assert_eq!(diff.try_apply(&mut items).unwrap_err().length, 2);
1644
            assert_eq!(items, vector![item(1), item(2)]);
1645
        }
1646
        for diff in [TimelineDiff::PopFront, TimelineDiff::PopBack] {
1647
            let mut items = Vector::new();
1648
            assert_eq!(diff.try_apply(&mut items).unwrap_err().length, 0);
1649
            assert!(items.is_empty());
1650
        }
1651
    }
1652
1653
    #[test]
1654
    fn a_batch_that_goes_wrong_halfway_changes_nothing() {
1655
        let mut items = vector![item(1), item(2)];
1656
        let batch = vec![
1657
            TimelineDiff::PushBack { value: item(3) },
1658
            TimelineDiff::PushBack { value: item(4) },
1659
            TimelineDiff::Remove { index: 0 },
1660
            TimelineDiff::Set {
1661
                index: 3,
1662
                value: item(5),
1663
            },
1664
        ];
1665
        assert_eq!(
1666
            TimelineDiff::try_apply_all(batch, &mut items)
1667
                .unwrap_err()
1668
                .length,
1669
            3
1670
        );
1671
        assert_eq!(items, vector![item(1), item(2)]);
1672
1673
        let batch = vec![
1674
            TimelineDiff::PushBack { value: item(3) },
1675
            TimelineDiff::Remove { index: 0 },
1676
            TimelineDiff::Set {
1677
                index: 1,
1678
                value: item(4),
1679
            },
1680
        ];
1681
        TimelineDiff::try_apply_all(batch, &mut items).unwrap();
1682
        assert_eq!(items, vector![item(2), item(4)]);
1683
    }
1684
}
1685
1686
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
1687
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1688
pub struct TimelineItemId(String);
1689
1690
impl TimelineItemId {
1691
    pub(crate) fn new(value: String) -> Self {
1692
        Self(value)
1693
    }
1694
1695
    #[must_use]
1696
    pub fn as_str(&self) -> &str {
1697
        &self.0
1698
    }
1699
}
1700
1701
#[derive(Clone, Debug, Eq, PartialEq)]
1702
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1703
#[non_exhaustive]
1704
pub struct TimelineEvent {
1705
    pub item_id: TimelineItemId,
1706
    pub event_id: Option<OwnedEventId>,
1707
    pub transaction_id: Option<OwnedTransactionId>,
1708
    pub sender: Sender,
1709
    pub timestamp_ms: u64,
1710
    pub own: bool,
1711
    pub send_state: SendState,
1712
    pub content: TimelineContent,
1713
    pub reply_to: Option<ReplyReference>,
1714
    pub thread_root: Option<OwnedEventId>,
1715
    pub thread_summary: Option<ThreadSummary>,
1716
    pub reactions: Vec<Reaction>,
1717
    pub read_by: Vec<OwnedUserId>,
1718
    pub editable: bool,
1719
    /// the push rules ask for this one to stand out, always false for a local echo
1720
    pub highlight: bool,
1721
}
1722
1723
impl TimelineEvent {
1724
    /// what to hand edit, redact and react. works before the server has echoed it back
1725
    #[must_use]
1726
    pub fn target(&self) -> Option<EventRef> {
1727
        self.event_id
1728
            .clone()
1729
            .map(EventRef::Remote)
1730
            .or_else(|| self.transaction_id.clone().map(EventRef::Local))
1731
    }
1732
}
1733
1734
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
1735
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1736
#[non_exhaustive]
1737
pub enum EventRef {
1738
    Remote(OwnedEventId),
1739
    /// a local echo, by the transaction id it was queued under
1740
    Local(OwnedTransactionId),
1741
}
1742
1743
impl From<OwnedEventId> for EventRef {
1744
    fn from(event_id: OwnedEventId) -> Self {
1745
        Self::Remote(event_id)
1746
    }
1747
}
1748
1749
#[derive(Clone, Debug, Eq, PartialEq)]
1750
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1751
#[non_exhaustive]
1752
pub struct Sender {
1753
    pub user_id: OwnedUserId,
1754
    pub display_name: Option<String>,
1755
    pub avatar: Option<MediaId>,
1756
    /// someone else in the room goes by the same name, so show the user id too
1757
    pub name_ambiguous: bool,
1758
}
1759
1760
impl Sender {
1761
    #[must_use]
1762
    pub fn name(&self) -> &str {
1763
        self.display_name
1764
            .as_deref()
1765
            .unwrap_or_else(|| self.user_id.as_str())
1766
    }
1767
}
1768
1769
#[derive(Clone, Debug, Eq, PartialEq)]
1770
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1771
pub enum TimelineContent {
1772
    Message(Message),
1773
    Sticker {
1774
        body: String,
1775
        media: MediaDescriptor,
1776
    },
1777
    Poll {
1778
        question: String,
1779
    },
1780
    Redacted,
1781
    UnableToDecrypt(UtdCause),
1782
    Membership {
1783
        user_id: OwnedUserId,
1784
        display_name: Option<String>,
1785
        change: MembershipChange,
1786
    },
1787
    Profile {
1788
        user_id: OwnedUserId,
1789
        display_name: Option<Change<Option<String>>>,
1790
        avatar: Option<Change<Option<MediaId>>>,
1791
    },
1792
    /// `None` means it was cleared, or the event was redacted
1793
    RoomName(Option<String>),
1794
    RoomTopic(Option<String>),
1795
    RoomAvatar(Option<MediaId>),
1796
    EncryptionEnabled,
1797
    RoomCreated,
1798
    RoomUpgraded {
1799
        successor: OwnedRoomId,
1800
    },
1801
    OtherState {
1802
        event_type: String,
1803
        state_key: String,
1804
    },
1805
    Other {
1806
        event_type: String,
1807
    },
1808
}
1809
1810
#[derive(Clone, Debug, Eq, PartialEq)]
1811
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1812
#[non_exhaustive]
1813
pub struct Message {
1814
    pub kind: MessageKind,
1815
    pub body: String,
1816
    /// only for `MessageKind::Location`
1817
    pub geo_uri: Option<String>,
1818
    pub formatted_body: Option<String>,
1819
    pub mentions: Option<MessageMentions>,
1820
    pub edited: bool,
1821
    pub media: Option<MediaDescriptor>,
1822
    /// previews the sender attached (msc4095), so showing them asks no one about the url.
1823
    /// one for a url the body doesn't contain is dropped
1824
    pub link_previews: Vec<LinkPreview>,
1825
}
1826
1827
/// a link card, from the sender's own message or from `ClientHandle::url_preview`. page
1828
/// text passes through whoever made it unfiltered, so it arrives trimmed to a sane length
1829
#[derive(Clone, Debug, Eq, PartialEq)]
1830
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1831
#[non_exhaustive]
1832
pub struct LinkPreview {
1833
    /// the url as it appears in the message, or as it was asked about
1834
    pub url: String,
1835
    /// where the page says it lives, when that's somewhere else
1836
    pub canonical_url: Option<String>,
1837
    pub title: Option<String>,
1838
    pub description: Option<String>,
1839
    pub site_name: Option<String>,
1840
    pub image: Option<LinkPreviewImage>,
1841
}
1842
1843
#[derive(Clone, Debug, Eq, PartialEq)]
1844
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1845
#[non_exhaustive]
1846
pub struct LinkPreviewImage {
1847
    pub media_id: MediaId,
1848
    pub mimetype: Option<String>,
1849
    pub size_bytes: Option<u64>,
1850
    pub width: Option<u64>,
1851
    pub height: Option<u64>,
1852
}
1853
1854
#[derive(Clone, Debug, Eq, PartialEq)]
1855
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1856
pub enum MessageKind {
1857
    Text,
1858
    Emote,
1859
    Notice,
1860
    ServerNotice,
1861
    Image,
1862
    Video,
1863
    Audio,
1864
    File,
1865
    Location,
1866
    VerificationRequest,
1867
    Other(String),
1868
}
1869
1870
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1871
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1872
pub enum MembershipChange {
1873
    Joined,
1874
    Left,
1875
    Banned,
1876
    Unbanned,
1877
    Kicked,
1878
    KickedAndBanned,
1879
    Invited,
1880
    InvitationAccepted,
1881
    InvitationRejected,
1882
    InvitationRevoked,
1883
    Knocked,
1884
    KnockAccepted,
1885
    KnockRetracted,
1886
    KnockDenied,
1887
    /// nothing a person would call a change, or something the sdk couldn't make sense of
1888
    Other,
1889
}
1890
1891
#[derive(Clone, Debug, Eq, PartialEq)]
1892
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1893
pub struct Change<T> {
1894
    pub old: T,
1895
    pub new: T,
1896
}
1897
1898
/// why a message couldn't be read, worth telling the user since some of these they can fix
1899
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1900
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1901
pub enum UtdCause {
1902
    Unknown,
1903
    SentBeforeWeJoined,
1904
    VerificationViolation,
1905
    UnsignedDevice,
1906
    UnknownDevice,
1907
    HistoricalMessageAndBackupIsDisabled,
1908
    WithheldForUnverifiedOrInsecureDevice,
1909
    WithheldBySender,
1910
    HistoricalMessageAndDeviceIsUnverified,
1911
}
1912
1913
#[derive(Clone, Debug, Eq, PartialEq)]
1914
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1915
#[non_exhaustive]
1916
pub struct ThreadSummary {
1917
    pub reply_count: u32,
1918
    pub latest_event_id: Option<OwnedEventId>,
1919
    pub latest_event_timestamp_ms: Option<u64>,
1920
}
1921
1922
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1923
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1924
pub enum ThreadListCloseReason {
1925
    /// `Close` asked for it
1926
    Requested,
1927
    /// left, kicked or banned
1928
    RoomLeft,
1929
}
1930
1931
#[derive(Clone, Debug, Eq, PartialEq)]
1932
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1933
#[non_exhaustive]
1934
pub struct ThreadListEntry {
1935
    pub root_event_id: OwnedEventId,
1936
    /// always an event, carrying `summary` as its own
1937
    pub root: TimelineItem,
1938
    pub summary: ThreadSummary,
1939
    pub latest_reply: Option<TimelineItem>,
1940
    /// the latest reply's timestamp, or the root's before any reply
1941
    pub latest_activity_ms: u64,
1942
}
1943
1944
impl ThreadListEntry {
1945
    /// replaces the entry with the same root, or adds it, where its activity puts it.
1946
    /// ties go by root event id
1947
    pub fn upsert(entries: &mut Vector<Self>, entry: Self) {
1948
        if let Some(index) = entries
1949
            .iter()
1950
            .position(|known| known.root_event_id == entry.root_event_id)
1951
        {
1952
            entries.remove(index);
1953
        }
1954
        let index = entries
1955
            .binary_search_by(|known| {
1956
                if known.sorts_before(&entry) {
1957
                    std::cmp::Ordering::Less
1958
                } else {
1959
                    std::cmp::Ordering::Greater
1960
                }
1961
            })
1962
            .unwrap_or_else(|index| index);
1963
        entries.insert(index, entry);
1964
    }
1965
1966
    #[must_use]
1967
    pub fn sorts_before(&self, other: &Self) -> bool {
1968
        (
1969
            std::cmp::Reverse(self.latest_activity_ms),
1970
            &self.root_event_id,
1971
        ) < (
1972
            std::cmp::Reverse(other.latest_activity_ms),
1973
            &other.root_event_id,
1974
        )
1975
    }
1976
}
1977
1978
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1979
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1980
pub enum SendState {
1981
    Sent,
1982
    Sending,
1983
    /// `will_retry` means macltok has it in hand. otherwise it's wedged until you
1984
    /// `retry_send` or `cancel_send`, so that's when to show a retry button
1985
    Failed {
1986
        will_retry: bool,
1987
    },
1988
}
1989
1990
#[derive(Clone, Debug, Eq, PartialEq)]
1991
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1992
#[non_exhaustive]
1993
pub struct ReplyReference {
1994
    pub event_id: OwnedEventId,
1995
    pub details: ReplyDetails,
1996
}
1997
1998
/// the replied-to event. macltok asks for it when it isn't loaded, `Pending` then turns into one of the others
1999
#[derive(Clone, Debug, Eq, PartialEq)]
2000
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
2001
pub enum ReplyDetails {
2002
    Pending,
2003
    Ready {
2004
        sender: Sender,
2005
        content: Box<TimelineContent>,
2006
        timestamp_ms: u64,
2007
    },
2008
    Unavailable,
2009
}
2010
2011
#[derive(Clone, Debug, Eq, PartialEq)]
2012
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
2013
#[non_exhaustive]
2014
pub struct Reaction {
2015
    pub key: String,
2016
    /// a custom emote's shortcode, from whichever sender gave one
2017
    pub shortcode: Option<String>,
2018
    /// the custom emote itself, when the key is an image
2019
    pub image: Option<MediaId>,
2020
    pub count: usize,
2021
    pub own: bool,
2022
    pub senders: Vec<OwnedUserId>,
2023
}
2024
2025
/// every custom emote and sticker pack the account can reach, from the user's own
2026
/// pack, each joined room's packs, and the rooms the user enabled everywhere
2027
#[derive(Clone, Debug, Default, Eq, PartialEq)]
2028
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
2029
#[non_exhaustive]
2030
pub struct ImagePacks {
2031
    /// the user's own pack, then the packs of rooms enabled everywhere
2032
    pub global: Vec<ImagePack>,
2033
    /// each joined room's own packs, rooms without any left out
2034
    pub rooms: imbl::HashMap<OwnedRoomId, Vec<ImagePack>>,
2035
}
2036
2037
impl ImagePacks {
2038
    /// what a picker in this room offers. the user's pack, the room's own, then the
2039
    /// rest of the enabled ones, each pack once
2040
    #[must_use]
2041
    pub fn for_room(&self, room_id: Option<&RoomId>) -> Vec<&ImagePack> {
2042
        let is_user = |pack: &&ImagePack| pack.source == ImagePackSource::User;
2043
        let own = room_id.and_then(|room_id| self.rooms.get(room_id));
2044
        let mut packs: Vec<&ImagePack> = self.global.iter().filter(is_user).collect();
2045
        packs.extend(own.into_iter().flatten());
2046
        for pack in self.global.iter().filter(|pack| !is_user(pack)) {
2047
            if !packs.iter().any(|seen| seen.id == pack.id) {
2048
                packs.push(pack);
2049
            }
2050
        }
2051
        packs
2052
    }
2053
}
2054
2055
#[derive(Clone, Debug, Eq, PartialEq)]
2056
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
2057
#[non_exhaustive]
2058
pub struct ImagePack {
2059
    /// `user`, or `room:<room id>:<state key>`, the same across calls
2060
    pub id: String,
2061
    pub display_name: Option<String>,
2062
    pub avatar: Option<MediaId>,
2063
    pub attribution: Option<String>,
2064
    pub source: ImagePackSource,
2065
    pub images: Vec<PackImage>,
2066
    /// your own pack, or a room pack whose state you're allowed to send
2067
    pub editable: bool,
2068
    /// listed in im.ponies.emote_rooms. always true for your own pack
2069
    pub enabled_everywhere: bool,
2070
}
2071
2072
#[derive(Clone, Debug, Eq, PartialEq)]
2073
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
2074
pub enum ImagePackSource {
2075
    User,
2076
    Room {
2077
        room_id: OwnedRoomId,
2078
        state_key: String,
2079
    },
2080
}
2081
2082
#[derive(Clone, Debug, Eq, PartialEq)]
2083
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
2084
#[non_exhaustive]
2085
pub struct PackImage {
2086
    pub shortcode: String,
2087
    pub media_id: MediaId,
2088
    /// what `toggle_reaction` takes to react with this image
2089
    pub reaction_key: String,
2090
    pub body: Option<String>,
2091
    pub mimetype: Option<String>,
2092
    pub width: Option<u64>,
2093
    pub height: Option<u64>,
2094
    /// the image's own, else its pack's, else both
2095
    pub usage: Vec<PackUsage>,
2096
}
2097
2098
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
2099
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
2100
pub enum PackUsage {
2101
    Emoticon,
2102
    Sticker,
2103
}
2104
2105
#[derive(Clone, Debug, Eq, PartialEq)]
2106
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
2107
#[non_exhaustive]
2108
pub struct MediaDescriptor {
2109
    pub id: MediaId,
2110
    pub kind: MediaKind,
2111
    /// only for unencrypted media, an encrypted file's mxc is ciphertext without the keys
2112
    pub mxc: Option<OwnedMxcUri>,
2113
    pub filename: String,
2114
    pub caption: Option<String>,
2115
    pub mimetype: Option<String>,
2116
    pub size_bytes: Option<u64>,
2117
    pub width: Option<u64>,
2118
    pub height: Option<u64>,
2119
    pub duration_ms: Option<u64>,
2120
    pub blurhash: Option<String>,
2121
    /// the uploader sent a thumbnail, so `MediaSize::Thumbnail` won't need the server to resize
2122
    pub has_thumbnail: bool,
2123
}
2124
2125
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
2126
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
2127
pub struct MediaId(String);
2128
2129
impl MediaId {
2130
    pub(crate) fn new(value: String) -> Self {
2131
        Self(value)
2132
    }
2133
2134
    /// plain media carries no keys, so it needs no registering and never expires
2135
    pub(crate) fn plain<U: AsRef<str> + ?Sized>(uri: &U) -> Self {
2136
        Self(uri.as_ref().to_owned())
2137
    }
2138
2139
    pub(crate) fn as_plain(&self) -> Option<&MxcUri> {
2140
        self.0.starts_with("mxc://").then(|| self.0.as_str().into())
2141
    }
2142
2143
    #[must_use]
2144
    pub fn as_str(&self) -> &str {
2145
        &self.0
2146
    }
2147
}
2148
2149
/// media ids are opaque but they do have to survive a trip through a browser. a made up one just fails `MediaUnknown`
2150
impl std::str::FromStr for MediaId {
2151
    type Err = std::convert::Infallible;
2152
2153
    fn from_str(value: &str) -> Result<Self, Self::Err> {
2154
        Ok(Self(value.to_owned()))
2155
    }
2156
}
2157
2158
impl From<String> for MediaId {
2159
    fn from(value: String) -> Self {
2160
        Self(value)
2161
    }
2162
}
2163
2164
impl std::fmt::Display for MediaId {
2165
    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2166
        self.0.fmt(formatter)
2167
    }
2168
}
2169
2170
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
2171
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
2172
#[non_exhaustive]
2173
pub enum MediaSize {
2174
    Full,
2175
    /// a hint. an uploader's own thumbnail wins over the server resizing anything
2176
    Thumbnail {
2177
        width: u32,
2178
        height: u32,
2179
    },
2180
}
2181
2182
#[derive(Clone, Debug, Eq, PartialEq)]
2183
#[non_exhaustive]
2184
pub struct Media {
2185
    pub bytes: Arc<[u8]>,
2186
    pub mimetype: String,
2187
}
2188
2189
/// where media lives, for a host that fetches and decrypts it itself
2190
#[derive(Clone, Debug, Eq, PartialEq)]
2191
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
2192
pub enum MediaLocation {
2193
    File(MediaFile),
2194
    /// a resize of plaintext, so never encrypted. plenty of servers refuse some, and `media`
2195
    /// then fetches `fallback` instead, so should you
2196
    ServerThumbnail {
2197
        url: String,
2198
        fallback: MediaFile,
2199
    },
2200
    /// still in the send queue with no url yet. `media` has the bytes
2201
    Local,
2202
}
2203
2204
#[derive(Clone, Debug, Eq, PartialEq)]
2205
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
2206
#[non_exhaustive]
2207
pub struct MediaFile {
2208
    pub url: String,
2209
    pub mimetype: Option<String>,
2210
    pub declared_size: Option<u64>,
2211
    pub encryption: Option<AttachmentEncryption>,
2212
}
2213
2214
/// AES-256-CTR, the only attachment cipher matrix has
2215
#[derive(Clone, Debug, Eq, PartialEq)]
2216
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
2217
#[non_exhaustive]
2218
pub struct AttachmentEncryption {
2219
    /// the JWK `k`, unpadded url-safe base64
2220
    pub key: String,
2221
    /// unpadded base64, the initial counter block
2222
    pub iv: String,
2223
    /// unpadded base64 SHA-256 of the whole ciphertext, so a partial read goes unchecked
2224
    pub sha256: String,
2225
}
2226
2227
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
2228
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
2229
pub enum MediaKind {
2230
    Image,
2231
    Video,
2232
    Audio,
2233
    File,
2234
    Sticker,
2235
}
2236
2237
#[derive(Clone, Debug, Eq, PartialEq)]
2238
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
2239
#[non_exhaustive]
2240
pub struct TypingState {
2241
    pub room_id: OwnedRoomId,
2242
    pub users: Vec<OwnedUserId>,
2243
}
2244
2245
#[derive(Clone, Debug, Eq, PartialEq)]
2246
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
2247
pub enum HistoryState {
2248
    Idle,
2249
    Loading,
2250
    StartReached,
2251
    Failed(String),
2252
}
2253
2254
#[derive(Clone, Debug, Eq, PartialEq)]
2255
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
2256
pub enum RecentHistoryState {
2257
    /// nothing running and nothing to show, which is also what closing the room leaves
2258
    Idle,
2259
    Loading,
2260
    Loaded,
2261
    Cancelled,
2262
    /// stays until a retry, or until closing the room makes it `Idle`
2263
    Failed(String),
2264
}
2265
2266
#[derive(Clone, Debug, Eq, PartialEq)]
2267
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
2268
#[non_exhaustive]
2269
pub struct EncryptionState {
2270
    pub device_id: OwnedDeviceId,
2271
    pub device_verified: bool,
2272
    pub recovery: RecoveryState,
2273
    pub key_backup: KeyBackupState,
2274
}
2275
2276
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
2277
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
2278
pub enum KeyBackupState {
2279
    Unknown,
2280
    Creating,
2281
    Enabling,
2282
    Resuming,
2283
    Downloading,
2284
    Enabled,
2285
    Disabling,
2286
}
2287
2288
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
2289
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
2290
pub enum RecoveryState {
2291
    Unknown,
2292
    Disabled,
2293
    Incomplete,
2294
    Enabled,
2295
}
2296
2297
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
2298
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
2299
#[non_exhaustive]
2300
pub enum VerificationAction {
2301
    /// take up a request another device sent, which arrives as `VerificationState::Incoming`
2302
    Accept,
2303
    Confirm,
2304
    Reject,
2305
    Cancel,
2306
}
2307
2308
#[derive(Clone, Debug, Eq, PartialEq)]
2309
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
2310
#[non_exhaustive]
2311
pub enum VerificationTarget {
2312
    OwnIdentity,
2313
    User(OwnedUserId),
2314
    Device {
2315
        user_id: OwnedUserId,
2316
        device_id: OwnedDeviceId,
2317
    },
2318
}
2319
2320
#[derive(Clone, Debug, Eq, PartialEq)]
2321
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
2322
pub enum VerificationState {
2323
    Waiting,
2324
    /// someone else asked. `Accept` to go ahead, `Cancel` to turn them down
2325
    Incoming {
2326
        user_id: OwnedUserId,
2327
        device_id: Option<OwnedDeviceId>,
2328
    },
2329
    Comparison {
2330
        emojis: Vec<(String, String)>,
2331
        decimals: (u16, u16, u16),
2332
    },
2333
    Confirmed,
2334
    Done,
2335
    Cancelled(String),
2336
    Failed(VerificationFailure),
2337
}
2338
2339
#[derive(Clone, Debug, Eq, PartialEq)]
2340
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
2341
pub enum VerificationFailure {
2342
    RequestAcceptance(String),
2343
    SasStartup(String),
2344
    SasAcceptance(String),
2345
    RequestStreamEnded,
2346
    SasStreamEnded,
2347
}
2348
2349
#[derive(Clone, Debug, Eq, PartialEq)]
2350
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
2351
#[non_exhaustive]
2352
pub struct EngineFailure {
2353
    pub kind: EngineErrorKind,
2354
    pub message: String,
2355
}
2356
2357
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
2358
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
2359
pub enum EngineErrorKind {
2360
    EventReceiverClosed,
2361
    Watcher,
2362
    Other,
2363
}