atagen

macltok

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