atagen

macltok

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