atagen

macltok

public
Star0
Watch0
f137a3c74d1144698237fe4ac1bddd483f9ec85e
3
ssh://brewery@pls.lobotomise.me:33/atagen/macltok.git
src/transport.rsRust1464 lines44.9 KB
1
use std::{
2
    collections::HashSet,
3
    fmt,
4
    future::Future,
5
    pin::Pin,
6
    sync::{Arc, Mutex},
7
    task::{Context, Poll},
8
};
9
10
use matrix_sdk::{Client, RoomMemberships};
11
use matrix_sdk_ui::{
12
    notification_client::{
13
        NotificationClient, NotificationProcessSetup, NotificationStatus, RawNotificationEvent,
14
    },
15
    spaces::{SpaceRoomList, SpaceService, room_list::SpaceRoomListPaginationState},
16
    sync_service::SyncService,
17
};
18
use ruma::{
19
    EventId, OwnedDeviceId, OwnedEventId, OwnedMxcUri, OwnedRoomId, OwnedRoomOrAliasId,
20
    OwnedServerName, OwnedTransactionId, OwnedUserId, RoomId, UserId,
21
    events::{
22
        AnyGlobalAccountDataEventContent, AnyRoomAccountDataEvent, AnyRoomAccountDataEventContent,
23
        GlobalAccountDataEventType, RoomAccountDataEventType,
24
    },
25
    serde::Raw,
26
};
27
use tokio::sync::{OnceCell, mpsc, oneshot, watch};
28
29
use crate::{
30
    AccountCommand, AttachmentUpload, Command, CommandOutput, ConnectionState, CreateRoomOptions,
31
    EncryptionCommand, EncryptionState, Error, ErrorKind, Event, EventRef, ImagePackSource,
32
    ImagePacks, InteractiveAuth, InteractiveAuthOutcome, LifecycleCommand, LinkPreview, Media,
33
    MediaCommand, MediaId, MediaLocation, MediaSize, MemberFilter, MemberSummary, MessageContent,
34
    MessageEditContent, NotificationCommand, OwnDevice, PackCommand, PackImageInfo, PackUsage,
35
    Patch, PowerLevelChanges, Profile, PushNotification, PushNotificationEvent,
36
    PushNotificationStatus, RoomCommand, RoomListFilter, RoomNotificationMode,
37
    RoomNotificationSettings, RoomPowerLevels, RoomSummary, SpaceChild, TimelineCommand,
38
    TimelineId, VerificationAction, VerificationTarget,
39
    encryption::encryption_state,
40
    engine::{MediaService, StoreLock},
41
    model::{PushNotificationRoom, PushNotificationSender},
42
    normalise::{image_packs, member_summary, room_summaries, space_child, url_previews_allowed},
43
};
44
45
#[derive(Clone)]
46
pub struct ClientHandle {
47
    requests: mpsc::Sender<Request>,
48
    client: Client,
49
    connection: watch::Receiver<ConnectionState>,
50
    notification_client: Arc<OnceCell<NotificationClient>>,
51
    sync_service: Arc<SyncService>,
52
    media: Arc<MediaService>,
53
    spaces: Arc<OnceCell<SpaceService>>,
54
    members_checked: Arc<Mutex<HashSet<OwnedRoomId>>>,
55
    _store_lock: Option<Arc<StoreLock>>,
56
}
57
58
impl fmt::Debug for ClientHandle {
59
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
60
        formatter
61
            .debug_struct("ClientHandle")
62
            .finish_non_exhaustive()
63
    }
64
}
65
66
/// a command's answer. drop it if you don't care, the command still runs
67
#[derive(Debug)]
68
#[must_use = "dropping it is fine but then you'll never know"]
69
pub struct Pending(oneshot::Receiver<Result<CommandOutput, Error>>);
70
71
impl Future for Pending {
72
    type Output = Result<CommandOutput, Error>;
73
74
    fn poll(mut self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Self::Output> {
75
        Pin::new(&mut self.0)
76
            .poll(context)
77
            .map(|result| result.unwrap_or_else(|_| Err(Error::interrupted())))
78
    }
79
}
80
81
impl ClientHandle {
82
    /// current credentials, it holds secrets. the engine saves it for you unless the
83
    /// session was built `without_persistence`
84
    #[cfg(any(feature = "sqlite", feature = "indexeddb"))]
85
    pub fn session_record(&self) -> Result<crate::SessionRecord, crate::AccountError> {
86
        crate::account::session_record(&self.client)
87
    }
88
89
    /// the bearer token. it rotates on every refresh so ask again after a 401 rather than
90
    /// keeping it, and keep it in memory and away from all but the homeserver's media endpoints
91
    pub fn access_token(&self) -> Option<String> {
92
        self.client.access_token()
93
    }
94
95
    /// queue a command, waiting for room in the queue if there isn't any
96
    pub async fn send(&self, command: Command) -> Result<Pending, Error> {
97
        let (request, pending) = Request::new(command);
98
        self.requests
99
            .send(request)
100
            .await
101
            .map_err(|_| Error::interrupted())?;
102
        Ok(pending)
103
    }
104
105
    /// queue a command or fail with `QueueFull` right now
106
    pub fn try_send(&self, command: Command) -> Result<Pending, Error> {
107
        let (request, pending) = Request::new(command);
108
        self.requests
109
            .try_send(request)
110
            .map_err(|error| match error {
111
                mpsc::error::TrySendError::Full(_) => {
112
                    Error::new(ErrorKind::QueueFull, "the command queue is full")
113
                }
114
                mpsc::error::TrySendError::Closed(_) => Error::interrupted(),
115
            })?;
116
        Ok(pending)
117
    }
118
119
    /// queue a command and wait for its answer. timeline sends answer once the
120
    /// sdk send queue has them, delivery shows up later as the item's send state
121
    pub async fn execute(&self, command: Command) -> Result<CommandOutput, Error> {
122
        self.send(command).await?.await
123
    }
124
125
    async fn run(&self, command: Command) -> Result<(), Error> {
126
        self.execute(command).await.map(drop)
127
    }
128
129
    async fn run_for_room(&self, command: RoomCommand) -> Result<OwnedRoomId, Error> {
130
        match self.execute(Command::Room(command)).await? {
131
            CommandOutput::Room(room_id) => Ok(room_id),
132
            output => Err(Error::new(
133
                ErrorKind::Other,
134
                format!("expected a room but got {output:?}"),
135
            )),
136
        }
137
    }
138
139
    pub async fn shutdown(&self) -> Result<(), Error> {
140
        self.run(Command::Lifecycle(LifecycleCommand::Shutdown))
141
            .await
142
    }
143
144
    pub async fn logout(&self) -> Result<(), Error> {
145
        self.run(Command::Account(AccountCommand::Logout)).await
146
    }
147
148
    pub async fn delete_devices(
149
        &self,
150
        device_ids: Vec<OwnedDeviceId>,
151
        auth: Option<InteractiveAuth>,
152
    ) -> Result<InteractiveAuthOutcome, Error> {
153
        match self
154
            .run(Command::Account(AccountCommand::DeleteDevices {
155
                device_ids,
156
                auth,
157
            }))
158
            .await
159
        {
160
            Ok(()) => Ok(InteractiveAuthOutcome::Completed),
161
            Err(Error {
162
                challenge: Some(challenge),
163
                ..
164
            }) => Ok(InteractiveAuthOutcome::Challenge(*challenge)),
165
            Err(error) => Err(error),
166
        }
167
    }
168
169
    pub async fn rename_device(
170
        &self,
171
        device_id: OwnedDeviceId,
172
        display_name: impl Into<String>,
173
    ) -> Result<(), Error> {
174
        self.run(Command::Account(AccountCommand::RenameDevice {
175
            device_id,
176
            display_name: display_name.into(),
177
        }))
178
        .await
179
    }
180
181
    pub async fn set_global_account_data(
182
        &self,
183
        event_type: GlobalAccountDataEventType,
184
        content: Raw<AnyGlobalAccountDataEventContent>,
185
    ) -> Result<(), Error> {
186
        self.run(Command::Account(AccountCommand::SetAccountData {
187
            event_type,
188
            content,
189
        }))
190
        .await
191
    }
192
193
    pub async fn set_room_account_data(
194
        &self,
195
        room_id: OwnedRoomId,
196
        event_type: RoomAccountDataEventType,
197
        content: Raw<AnyRoomAccountDataEventContent>,
198
    ) -> Result<(), Error> {
199
        self.run(Command::Room(RoomCommand::SetAccountData {
200
            room_id,
201
            event_type,
202
            content,
203
        }))
204
        .await
205
    }
206
207
    pub async fn set_room_notification_mode(
208
        &self,
209
        room_id: OwnedRoomId,
210
        mode: RoomNotificationMode,
211
    ) -> Result<(), Error> {
212
        self.run(Command::Notification(NotificationCommand::SetRoomMode {
213
            room_id,
214
            mode,
215
        }))
216
        .await
217
    }
218
219
    pub async fn reset_room_notification_mode(&self, room_id: OwnedRoomId) -> Result<(), Error> {
220
        self.run(Command::Notification(NotificationCommand::ResetRoomMode(
221
            room_id,
222
        )))
223
        .await
224
    }
225
226
    pub async fn set_default_notification_mode(
227
        &self,
228
        encrypted: bool,
229
        one_to_one: bool,
230
        mode: RoomNotificationMode,
231
    ) -> Result<(), Error> {
232
        self.run(Command::Notification(NotificationCommand::SetDefaultMode {
233
            encrypted,
234
            one_to_one,
235
            mode,
236
        }))
237
        .await
238
    }
239
240
    pub async fn add_notification_keyword(&self, keyword: impl Into<String>) -> Result<(), Error> {
241
        self.run(Command::Notification(NotificationCommand::AddKeyword(
242
            keyword.into(),
243
        )))
244
        .await
245
    }
246
247
    pub async fn remove_notification_keyword(
248
        &self,
249
        keyword: impl Into<String>,
250
    ) -> Result<(), Error> {
251
        self.run(Command::Notification(NotificationCommand::RemoveKeyword(
252
            keyword.into(),
253
        )))
254
        .await
255
    }
256
257
    pub async fn open_room(&self, room_id: OwnedRoomId) -> Result<(), Error> {
258
        self.run(Command::Room(RoomCommand::Open(room_id))).await
259
    }
260
261
    pub async fn close_room(&self, room_id: OwnedRoomId) -> Result<(), Error> {
262
        self.run(Command::Room(RoomCommand::Close(room_id))).await
263
    }
264
265
    pub async fn open_thread(
266
        &self,
267
        room_id: OwnedRoomId,
268
        root_event_id: OwnedEventId,
269
    ) -> Result<(), Error> {
270
        self.run(Command::Timeline(TimelineCommand::OpenThread {
271
            room_id,
272
            root_event_id,
273
        }))
274
        .await
275
    }
276
277
    pub async fn close_thread(
278
        &self,
279
        room_id: OwnedRoomId,
280
        root_event_id: OwnedEventId,
281
    ) -> Result<(), Error> {
282
        self.run(Command::Timeline(TimelineCommand::CloseThread {
283
            room_id,
284
            root_event_id,
285
        }))
286
        .await
287
    }
288
289
    /// the room's pins as `TimelineId::Pinned`, kept current as they change. the room has to be open
290
    pub async fn open_pinned(&self, room_id: OwnedRoomId) -> Result<(), Error> {
291
        self.run(Command::Timeline(TimelineCommand::OpenPinned(room_id)))
292
            .await
293
    }
294
295
    pub async fn close_pinned(&self, room_id: OwnedRoomId) -> Result<(), Error> {
296
        self.run(Command::Timeline(TimelineCommand::ClosePinned(room_id)))
297
            .await
298
    }
299
300
    /// reads the server's list so a pin made elsewhere just before isn't dropped
301
    pub async fn set_pinned(
302
        &self,
303
        room_id: OwnedRoomId,
304
        event_id: OwnedEventId,
305
        pinned: bool,
306
    ) -> Result<(), Error> {
307
        self.run(Command::Room(RoomCommand::SetPinned {
308
            room_id,
309
            event_id,
310
            pinned,
311
        }))
312
        .await
313
    }
314
315
    pub async fn resync(&self, timeline_id: TimelineId) -> Result<(), Error> {
316
        self.run(Command::Timeline(TimelineCommand::Resync { timeline_id }))
317
            .await
318
    }
319
320
    pub async fn paginate(&self, timeline_id: TimelineId, limit: u16) -> Result<(), Error> {
321
        self.run(Command::Timeline(TimelineCommand::Paginate {
322
            timeline_id,
323
            limit,
324
        }))
325
        .await
326
    }
327
328
    pub async fn send_message(
329
        &self,
330
        timeline_id: TimelineId,
331
        content: MessageContent,
332
    ) -> Result<(), Error> {
333
        self.run(Command::Timeline(TimelineCommand::Send {
334
            timeline_id,
335
            content,
336
        }))
337
        .await
338
    }
339
340
    pub async fn edit_message(
341
        &self,
342
        timeline_id: TimelineId,
343
        target: impl Into<EventRef>,
344
        content: MessageEditContent,
345
    ) -> Result<(), Error> {
346
        self.run(Command::Timeline(TimelineCommand::Edit {
347
            timeline_id,
348
            target: target.into(),
349
            content,
350
        }))
351
        .await
352
    }
353
354
    pub async fn redact(
355
        &self,
356
        timeline_id: TimelineId,
357
        target: impl Into<EventRef>,
358
        reason: Option<String>,
359
    ) -> Result<(), Error> {
360
        self.run(Command::Timeline(TimelineCommand::Redact {
361
            timeline_id,
362
            target: target.into(),
363
            reason,
364
        }))
365
        .await
366
    }
367
368
    pub async fn toggle_reaction(
369
        &self,
370
        timeline_id: TimelineId,
371
        target: impl Into<EventRef>,
372
        key: impl Into<String>,
373
        shortcode: Option<String>,
374
    ) -> Result<(), Error> {
375
        self.run(Command::Timeline(TimelineCommand::ToggleReaction {
376
            timeline_id,
377
            target: target.into(),
378
            key: key.into(),
379
            shortcode,
380
        }))
381
        .await
382
    }
383
384
    pub async fn mark_read(
385
        &self,
386
        timeline_id: TimelineId,
387
        event_id: OwnedEventId,
388
    ) -> Result<(), Error> {
389
        self.run(Command::Timeline(TimelineCommand::MarkRead {
390
            timeline_id,
391
            event_id,
392
        }))
393
        .await
394
    }
395
396
    pub async fn set_typing(&self, room_id: OwnedRoomId, typing: bool) -> Result<(), Error> {
397
        self.run(Command::Room(RoomCommand::SetTyping { room_id, typing }))
398
            .await
399
    }
400
401
    pub async fn prepare_to_send(&self, room_id: OwnedRoomId) -> Result<(), Error> {
402
        self.run(Command::Room(RoomCommand::PrepareToSend(room_id)))
403
            .await
404
    }
405
406
    pub async fn join_room(
407
        &self,
408
        room: OwnedRoomOrAliasId,
409
        via: Vec<OwnedServerName>,
410
    ) -> Result<OwnedRoomId, Error> {
411
        self.run_for_room(RoomCommand::Join { room, via }).await
412
    }
413
414
    pub async fn leave_room(&self, room_id: OwnedRoomId) -> Result<(), Error> {
415
        self.run(Command::Room(RoomCommand::Leave(room_id))).await
416
    }
417
418
    pub async fn forget_room(&self, room_id: OwnedRoomId) -> Result<(), Error> {
419
        self.run(Command::Room(RoomCommand::Forget(room_id))).await
420
    }
421
422
    pub async fn accept_invite(&self, room_id: OwnedRoomId) -> Result<(), Error> {
423
        self.run(Command::Room(RoomCommand::AcceptInvite(room_id)))
424
            .await
425
    }
426
427
    pub async fn reject_invite(&self, room_id: OwnedRoomId) -> Result<(), Error> {
428
        self.run(Command::Room(RoomCommand::RejectInvite(room_id)))
429
            .await
430
    }
431
432
    pub async fn invite_user(
433
        &self,
434
        room_id: OwnedRoomId,
435
        user_id: OwnedUserId,
436
    ) -> Result<(), Error> {
437
        self.run(Command::Room(RoomCommand::InviteUser { room_id, user_id }))
438
            .await
439
    }
440
441
    pub async fn kick_user(
442
        &self,
443
        room_id: OwnedRoomId,
444
        user_id: OwnedUserId,
445
        reason: Option<String>,
446
    ) -> Result<(), Error> {
447
        self.run(Command::Room(RoomCommand::KickUser {
448
            room_id,
449
            user_id,
450
            reason,
451
        }))
452
        .await
453
    }
454
455
    pub async fn ban_user(
456
        &self,
457
        room_id: OwnedRoomId,
458
        user_id: OwnedUserId,
459
        reason: Option<String>,
460
    ) -> Result<(), Error> {
461
        self.run(Command::Room(RoomCommand::BanUser {
462
            room_id,
463
            user_id,
464
            reason,
465
        }))
466
        .await
467
    }
468
469
    pub async fn unban_user(
470
        &self,
471
        room_id: OwnedRoomId,
472
        user_id: OwnedUserId,
473
        reason: Option<String>,
474
    ) -> Result<(), Error> {
475
        self.run(Command::Room(RoomCommand::UnbanUser {
476
            room_id,
477
            user_id,
478
            reason,
479
        }))
480
        .await
481
    }
482
483
    pub async fn set_power_levels(
484
        &self,
485
        room_id: OwnedRoomId,
486
        changes: PowerLevelChanges,
487
    ) -> Result<(), Error> {
488
        self.run(Command::Room(RoomCommand::SetPowerLevels {
489
            room_id,
490
            changes,
491
        }))
492
        .await
493
    }
494
495
    pub async fn set_user_power_level(
496
        &self,
497
        room_id: OwnedRoomId,
498
        user_id: OwnedUserId,
499
        level: i64,
500
    ) -> Result<(), Error> {
501
        self.run(Command::Room(RoomCommand::SetUserPowerLevel {
502
            room_id,
503
            user_id,
504
            level,
505
        }))
506
        .await
507
    }
508
509
    pub async fn create_room(&self, options: CreateRoomOptions) -> Result<OwnedRoomId, Error> {
510
        self.run_for_room(RoomCommand::Create(options)).await
511
    }
512
513
    /// hands back the existing dm with them if there is one
514
    pub async fn create_direct(&self, user_id: OwnedUserId) -> Result<OwnedRoomId, Error> {
515
        self.run_for_room(RoomCommand::CreateDirect(user_id)).await
516
    }
517
518
    pub async fn cancel_send(
519
        &self,
520
        room_id: OwnedRoomId,
521
        transaction_id: OwnedTransactionId,
522
    ) -> Result<(), Error> {
523
        self.run(Command::Room(RoomCommand::CancelSend {
524
            room_id,
525
            transaction_id,
526
        }))
527
        .await
528
    }
529
530
    pub async fn retry_send(
531
        &self,
532
        room_id: OwnedRoomId,
533
        transaction_id: OwnedTransactionId,
534
    ) -> Result<(), Error> {
535
        self.run(Command::Room(RoomCommand::RetrySend {
536
            room_id,
537
            transaction_id,
538
        }))
539
        .await
540
    }
541
542
    pub async fn upload(&self, upload: AttachmentUpload) -> Result<(), Error> {
543
        self.run(Command::Media(MediaCommand::Upload(upload))).await
544
    }
545
546
    pub async fn recover(&self, recovery_key: impl Into<String>) -> Result<(), Error> {
547
        self.run(Command::Encryption(EncryptionCommand::Recover(
548
            recovery_key.into(),
549
        )))
550
        .await
551
    }
552
553
    pub async fn create_key_backup(&self) -> Result<(), Error> {
554
        self.run(Command::Encryption(EncryptionCommand::CreateKeyBackup))
555
            .await
556
    }
557
558
    pub async fn enable_key_backup(&self) -> Result<(), Error> {
559
        self.run(Command::Encryption(EncryptionCommand::EnableKeyBackup))
560
            .await
561
    }
562
563
    pub async fn disable_key_backup(&self, delete_from_server: bool) -> Result<(), Error> {
564
        self.run(Command::Encryption(EncryptionCommand::DisableKeyBackup {
565
            delete_from_server,
566
        }))
567
        .await
568
    }
569
570
    pub async fn recover_key_backup(&self, recovery_key: impl Into<String>) -> Result<(), Error> {
571
        self.run(Command::Encryption(EncryptionCommand::RecoverKeyBackup(
572
            recovery_key.into(),
573
        )))
574
        .await
575
    }
576
577
    pub async fn start_verification(&self, target: VerificationTarget) -> Result<(), Error> {
578
        self.run(Command::Encryption(EncryptionCommand::StartVerification(
579
            target,
580
        )))
581
        .await
582
    }
583
584
    pub async fn verify(&self, action: VerificationAction) -> Result<(), Error> {
585
        self.run(Command::Encryption(EncryptionCommand::Verify(action)))
586
            .await
587
    }
588
589
    /// bytes for an avatar, or for media a retained timeline showed you. concurrent asks share
590
    /// one download
591
    pub async fn media(&self, media_id: &MediaId, size: MediaSize) -> Result<Media, Error> {
592
        self.media.timeline_media(media_id, size).await
593
    }
594
595
    /// where `media` would fetch from, for a host streaming big files itself. the byte cap
596
    /// doesn't apply since nothing is buffered
597
    pub async fn media_source(
598
        &self,
599
        media_id: &MediaId,
600
        size: MediaSize,
601
    ) -> Result<MediaLocation, Error> {
602
        self.media.media_location(media_id, size).await
603
    }
604
605
    /// the space graph builds on first use and then keeps itself current
606
    async fn spaces(&self) -> &SpaceService {
607
        self.spaces
608
            .get_or_init(|| SpaceService::new(self.client.clone()))
609
            .await
610
    }
611
612
    pub async fn rooms(&self) -> Vec<RoomSummary> {
613
        room_summaries(&self.client, self.spaces().await).await
614
    }
615
616
    pub async fn image_packs(&self) -> Result<ImagePacks, Error> {
617
        image_packs(&self.client).await
618
    }
619
620
    /// the homeserver's preview of a page, `None` when it has none. the homeserver sees every
621
    /// url asked about, so a host decides whether encrypted rooms ask at all
622
    pub async fn url_preview(&self, url: &str) -> Result<Option<LinkPreview>, Error> {
623
        self.media.url_preview(url).await
624
    }
625
626
    /// whether the room and the user's settings let link previews show here
627
    pub async fn url_previews_allowed(&self, room_id: &RoomId) -> Result<bool, Error> {
628
        url_previews_allowed(&self.room(room_id)?).await
629
    }
630
631
    /// browse a space the way the server sees it, rooms you haven't joined included
632
    pub async fn space_hierarchy(&self, space_id: OwnedRoomId) -> SpaceHierarchy {
633
        SpaceHierarchy(SpaceRoomList::new(self.client.clone(), space_id).await)
634
    }
635
636
    pub async fn own_profile(&self) -> Result<Profile, Error> {
637
        let user_id = self
638
            .client
639
            .user_id()
640
            .ok_or_else(|| Error::new(ErrorKind::Unauthenticated, "the client is not logged in"))?
641
            .to_owned();
642
        let response = self
643
            .client
644
            .account()
645
            .fetch_user_profile()
646
            .await
647
            .map_err(|source| Error::from_sdk("fetching the profile failed", source))?;
648
        profile(user_id, &response)
649
    }
650
651
    pub async fn user_profile(&self, user_id: &UserId) -> Result<Profile, Error> {
652
        let response = self
653
            .client
654
            .account()
655
            .fetch_user_profile_of(user_id)
656
            .await
657
            .map_err(|source| Error::from_sdk("fetching the profile failed", source))?;
658
        profile(user_id.to_owned(), &response)
659
    }
660
661
    pub async fn set_display_name(&self, name: Option<String>) -> Result<(), Error> {
662
        self.run(Command::Account(AccountCommand::SetDisplayName(name)))
663
            .await
664
    }
665
666
    pub async fn set_avatar(&self, avatar: Option<OwnedMxcUri>) -> Result<(), Error> {
667
        self.run(Command::Account(AccountCommand::SetAvatar(avatar)))
668
            .await
669
    }
670
671
    pub async fn upload_avatar(
672
        &self,
673
        mimetype: impl Into<String>,
674
        bytes: Vec<u8>,
675
    ) -> Result<(), Error> {
676
        self.run(Command::Account(AccountCommand::UploadAvatar {
677
            mimetype: mimetype.into(),
678
            bytes,
679
        }))
680
        .await
681
    }
682
683
    /// unencrypted, for room avatars and anything else that only needs the uri
684
    pub async fn upload_media(
685
        &self,
686
        mimetype: impl Into<String>,
687
        bytes: Vec<u8>,
688
    ) -> Result<OwnedMxcUri, Error> {
689
        let command = Command::Account(AccountCommand::UploadMedia {
690
            mimetype: mimetype.into(),
691
            bytes,
692
        });
693
        match self.execute(command).await? {
694
            CommandOutput::Mxc(uri) => Ok(uri),
695
            output => Err(Error::new(
696
                ErrorKind::Other,
697
                format!("expected an mxc but got {output:?}"),
698
            )),
699
        }
700
    }
701
702
    pub async fn ignore_user(&self, user_id: OwnedUserId) -> Result<(), Error> {
703
        self.run(Command::Account(AccountCommand::IgnoreUser(user_id)))
704
            .await
705
    }
706
707
    pub async fn unignore_user(&self, user_id: OwnedUserId) -> Result<(), Error> {
708
        self.run(Command::Account(AccountCommand::UnignoreUser(user_id)))
709
            .await
710
    }
711
712
    /// writes msc3230 orders so these spaces sort in exactly this sequence, touching as few as it can
713
    pub async fn reorder_spaces(&self, order: Vec<OwnedRoomId>) -> Result<(), Error> {
714
        self.run(Command::Account(AccountCommand::ReorderSpaces(order)))
715
            .await
716
    }
717
718
    /// adding m.favourite drops m.lowpriority, the way Element does
719
    pub async fn set_favourite(&self, room_id: OwnedRoomId, favourite: bool) -> Result<(), Error> {
720
        self.run(Command::Room(RoomCommand::SetFavourite {
721
            room_id,
722
            favourite,
723
        }))
724
        .await
725
    }
726
727
    /// None goes back to your global name
728
    pub async fn set_room_display_name(
729
        &self,
730
        room_id: OwnedRoomId,
731
        name: Option<String>,
732
    ) -> Result<(), Error> {
733
        self.run(Command::Room(RoomCommand::SetOwnDisplayName {
734
            room_id,
735
            name,
736
        }))
737
        .await
738
    }
739
740
    /// None goes back to your global avatar
741
    pub async fn set_room_avatar(
742
        &self,
743
        room_id: OwnedRoomId,
744
        avatar: Option<OwnedMxcUri>,
745
    ) -> Result<(), Error> {
746
        self.run(Command::Room(RoomCommand::SetOwnAvatar { room_id, avatar }))
747
            .await
748
    }
749
750
    /// muting drops them below what sending a message takes, unmuting puts a muted user back
751
    /// at users_default and leaves anyone else alone. `ErrorKind::Forbidden` if you can't
752
    pub async fn mute_user(
753
        &self,
754
        room_id: OwnedRoomId,
755
        user_id: OwnedUserId,
756
        muted: bool,
757
    ) -> Result<(), Error> {
758
        self.run(Command::Room(RoomCommand::MuteUser {
759
            room_id,
760
            user_id,
761
            muted,
762
        }))
763
        .await
764
    }
765
766
    /// body defaults to the shortcode
767
    pub async fn send_sticker(
768
        &self,
769
        room_id: OwnedRoomId,
770
        pack_id: String,
771
        shortcode: String,
772
    ) -> Result<(), Error> {
773
        self.run(Command::Pack(PackCommand::SendSticker {
774
            room_id,
775
            pack_id,
776
            shortcode,
777
        }))
778
        .await
779
    }
780
781
    /// fails if the state key already holds a pack
782
    pub async fn create_room_pack(
783
        &self,
784
        room_id: OwnedRoomId,
785
        state_key: String,
786
        display_name: String,
787
    ) -> Result<(), Error> {
788
        self.run(Command::Pack(PackCommand::CreateRoomPack {
789
            room_id,
790
            state_key,
791
            display_name,
792
        }))
793
        .await
794
    }
795
796
    pub async fn delete_pack(&self, pack: ImagePackSource) -> Result<(), Error> {
797
        self.run(Command::Pack(PackCommand::Delete(pack))).await
798
    }
799
800
    pub async fn set_pack_meta(
801
        &self,
802
        pack: ImagePackSource,
803
        display_name: Patch<String>,
804
        avatar: Patch<OwnedMxcUri>,
805
        usage: Patch<Vec<PackUsage>>,
806
    ) -> Result<(), Error> {
807
        self.run(Command::Pack(PackCommand::SetMeta {
808
            pack,
809
            display_name,
810
            avatar,
811
            usage,
812
        }))
813
        .await
814
    }
815
816
    /// adds or replaces by shortcode. a replaced image keeps its info unless the url changed
817
    pub async fn put_pack_image(
818
        &self,
819
        pack: ImagePackSource,
820
        shortcode: String,
821
        mxc: OwnedMxcUri,
822
        body: Option<String>,
823
        usage: Option<Vec<PackUsage>>,
824
        info: Option<PackImageInfo>,
825
    ) -> Result<(), Error> {
826
        self.run(Command::Pack(PackCommand::PutImage {
827
            pack,
828
            shortcode,
829
            mxc,
830
            body,
831
            usage,
832
            info,
833
        }))
834
        .await
835
    }
836
837
    pub async fn rename_pack_image(
838
        &self,
839
        pack: ImagePackSource,
840
        from: String,
841
        to: String,
842
    ) -> Result<(), Error> {
843
        self.run(Command::Pack(PackCommand::RenameImage { pack, from, to }))
844
            .await
845
    }
846
847
    pub async fn remove_pack_image(
848
        &self,
849
        pack: ImagePackSource,
850
        shortcode: String,
851
    ) -> Result<(), Error> {
852
        self.run(Command::Pack(PackCommand::RemoveImage { pack, shortcode }))
853
            .await
854
    }
855
856
    pub async fn set_pack_enabled_everywhere(
857
        &self,
858
        room_id: OwnedRoomId,
859
        state_key: String,
860
        enabled: bool,
861
    ) -> Result<(), Error> {
862
        self.run(Command::Pack(PackCommand::SetEnabledEverywhere {
863
            room_id,
864
            state_key,
865
            enabled,
866
        }))
867
        .await
868
    }
869
870
    /// the same uri when it already lives on your homeserver
871
    pub async fn rehost_media(&self, mxc: OwnedMxcUri) -> Result<OwnedMxcUri, Error> {
872
        match self
873
            .execute(Command::Pack(PackCommand::Rehost(mxc)))
874
            .await?
875
        {
876
            CommandOutput::Mxc(uri) => Ok(uri),
877
            output => Err(unexpected("an mxc", &output)),
878
        }
879
    }
880
881
    /// hands back the shortcode it ended up under
882
    pub async fn clone_pack_image(
883
        &self,
884
        from_pack: String,
885
        shortcode: String,
886
        to: ImagePackSource,
887
    ) -> Result<String, Error> {
888
        let (mut shortcodes, _) = self
889
            .run_clone(PackCommand::CloneImage {
890
                from_pack,
891
                shortcode,
892
                to,
893
            })
894
            .await?;
895
        shortcodes
896
            .pop()
897
            .ok_or_else(|| unexpected("a shortcode", &CommandOutput::Done))
898
    }
899
900
    /// hands back the shortcode it ended up under
901
    pub async fn clone_sticker(
902
        &self,
903
        room_id: OwnedRoomId,
904
        event_id: OwnedEventId,
905
        to: ImagePackSource,
906
        shortcode: String,
907
    ) -> Result<String, Error> {
908
        let (mut shortcodes, _) = self
909
            .run_clone(PackCommand::CloneSticker {
910
                room_id,
911
                event_id,
912
                to,
913
                shortcode,
914
            })
915
            .await?;
916
        shortcodes
917
            .pop()
918
            .ok_or_else(|| unexpected("a shortcode", &CommandOutput::Done))
919
    }
920
921
    /// hands back the shortcodes that couldn't be fetched, everything else is written
922
    pub async fn clone_pack(
923
        &self,
924
        from_pack: String,
925
        to: ImagePackSource,
926
        display_name: Option<String>,
927
    ) -> Result<Vec<String>, Error> {
928
        let (_, skipped) = self
929
            .run_clone(PackCommand::ClonePack {
930
                from_pack,
931
                to,
932
                display_name,
933
            })
934
            .await?;
935
        Ok(skipped)
936
    }
937
938
    async fn run_clone(&self, command: PackCommand) -> Result<(Vec<String>, Vec<String>), Error> {
939
        match self.execute(Command::Pack(command)).await? {
940
            CommandOutput::Cloned {
941
                shortcodes,
942
                skipped,
943
            } => Ok((shortcodes, skipped)),
944
            output => Err(unexpected("a clone", &output)),
945
        }
946
    }
947
948
    pub async fn rooms_filtered(&self, filter: &RoomListFilter) -> Vec<RoomSummary> {
949
        let matcher = filter.matcher();
950
        self.rooms()
951
            .await
952
            .into_iter()
953
            .filter(|room| matcher.matches(room))
954
            .collect()
955
    }
956
957
    pub async fn devices(&self) -> Result<Vec<OwnDevice>, Error> {
958
        let current_device_id = self.client.device_id();
959
        Ok(self
960
            .client
961
            .devices()
962
            .await
963
            .map_err(|source| Error::from_http("listing devices failed", source))?
964
            .devices
965
            .into_iter()
966
            .map(|device| OwnDevice {
967
                current: current_device_id == Some(device.device_id.as_ref()),
968
                device_id: device.device_id,
969
                display_name: device.display_name,
970
                last_seen_ip: device.last_seen_ip,
971
                last_seen_timestamp_ms: device.last_seen_ts.map(|timestamp| timestamp.get().into()),
972
            })
973
            .collect())
974
    }
975
976
    fn room(&self, room_id: &RoomId) -> Result<matrix_sdk::Room, Error> {
977
        self.client
978
            .get_room(room_id)
979
            .ok_or_else(Error::room_unknown)
980
    }
981
982
    pub async fn room_notification_settings(
983
        &self,
984
        room_id: &RoomId,
985
    ) -> Result<Option<RoomNotificationSettings>, Error> {
986
        let room = self.room(room_id)?;
987
        let custom = room.user_defined_notification_mode().await;
988
        let mode = match custom {
989
            Some(mode) => mode,
990
            None => match room.notification_mode().await {
991
                Some(mode) => mode,
992
                None => return Ok(None),
993
            },
994
        };
995
        Ok(Some(RoomNotificationSettings {
996
            mode: crate::normalise::notification_mode(mode),
997
            custom: custom.is_some(),
998
        }))
999
    }
1000
1001
    pub async fn notification_keywords(&self) -> Vec<String> {
1002
        self.client
1003
            .notification_settings()
1004
            .await
1005
            .enabled_keywords()
1006
            .await
1007
            .into_iter()
1008
            .collect()
1009
    }
1010
1011
    pub async fn resolve_push_notification(
1012
        &self,
1013
        room_id: &RoomId,
1014
        event_id: &EventId,
1015
    ) -> Result<PushNotificationStatus, Error> {
1016
        let notification_failed = |source| {
1017
            Error::with_source(
1018
                ErrorKind::Other,
1019
                "resolving the push notification failed",
1020
                source,
1021
            )
1022
        };
1023
        let client = self
1024
            .notification_client
1025
            .get_or_try_init(|| async {
1026
                NotificationClient::new(
1027
                    self.client.clone(),
1028
                    NotificationProcessSetup::SingleProcess {
1029
                        sync_service: self.sync_service.clone(),
1030
                    },
1031
                )
1032
                .await
1033
            })
1034
            .await
1035
            .map_err(notification_failed)?;
1036
        let status = client
1037
            .get_notification(room_id, event_id)
1038
            .await
1039
            .map_err(notification_failed)?;
1040
        Ok(normalise_push_notification(room_id, event_id, status))
1041
    }
1042
1043
    pub async fn members(
1044
        &self,
1045
        room_id: &RoomId,
1046
        filter: MemberFilter,
1047
    ) -> Result<Vec<MemberSummary>, Error> {
1048
        let room = self.room(room_id)?;
1049
        // stores have been seen with members_synced persisted over a partial
1050
        // roster, and the sdk never asks /members again once it's set
1051
        if self
1052
            .members_checked
1053
            .lock()
1054
            .unwrap()
1055
            .insert(room_id.to_owned())
1056
        {
1057
            room.mark_members_missing();
1058
        }
1059
        list_members(&room, filter).await
1060
    }
1061
1062
    /// drop the cached roster for a room and fetch /members again
1063
    pub async fn refresh_members(
1064
        &self,
1065
        room_id: &RoomId,
1066
        filter: MemberFilter,
1067
    ) -> Result<Vec<MemberSummary>, Error> {
1068
        let room = self.room(room_id)?;
1069
        room.mark_members_missing();
1070
        self.members_checked
1071
            .lock()
1072
            .unwrap()
1073
            .insert(room_id.to_owned());
1074
        list_members(&room, filter).await
1075
    }
1076
1077
    pub async fn room_power_levels(&self, room_id: &RoomId) -> Result<RoomPowerLevels, Error> {
1078
        let levels = self
1079
            .room(room_id)?
1080
            .power_levels()
1081
            .await
1082
            .map_err(|source| Error::from_sdk("reading power levels failed", source.into()))?;
1083
        Ok(RoomPowerLevels {
1084
            ban: levels.ban.into(),
1085
            invite: levels.invite.into(),
1086
            kick: levels.kick.into(),
1087
            redact: levels.redact.into(),
1088
            events_default: levels.events_default.into(),
1089
            state_default: levels.state_default.into(),
1090
            users_default: levels.users_default.into(),
1091
            room_notification: levels.notifications.room.into(),
1092
            users: levels
1093
                .users
1094
                .into_iter()
1095
                .map(|(user_id, level)| (user_id, level.into()))
1096
                .collect(),
1097
            events: levels
1098
                .events
1099
                .into_iter()
1100
                .map(|(event_type, level)| (event_type.to_string(), level.into()))
1101
                .collect(),
1102
            creators: levels
1103
                .rules
1104
                .privileged_creators
1105
                .into_iter()
1106
                .flatten()
1107
                .collect(),
1108
        })
1109
    }
1110
1111
    pub async fn pending_send_ids(
1112
        &self,
1113
        room_id: &RoomId,
1114
    ) -> Result<Vec<OwnedTransactionId>, Error> {
1115
        let (echoes, _) = self
1116
            .room(room_id)?
1117
            .send_queue()
1118
            .subscribe()
1119
            .await
1120
            .map_err(|source| {
1121
                Error::with_source(ErrorKind::Store, "reading the send queue failed", source)
1122
            })?;
1123
        Ok(echoes
1124
            .into_iter()
1125
            .filter(|echo| {
1126
                matches!(
1127
                    echo.content,
1128
                    matrix_sdk::send_queue::LocalEchoContent::Event { .. }
1129
                )
1130
            })
1131
            .map(|echo| echo.transaction_id)
1132
            .collect())
1133
    }
1134
1135
    pub async fn global_account_data(
1136
        &self,
1137
        event_type: GlobalAccountDataEventType,
1138
    ) -> Result<Option<Raw<AnyGlobalAccountDataEventContent>>, Error> {
1139
        self.client
1140
            .account()
1141
            .account_data_raw(event_type)
1142
            .await
1143
            .map_err(|source| Error::from_sdk("reading account data failed", source))
1144
    }
1145
1146
    pub async fn room_account_data(
1147
        &self,
1148
        room_id: &RoomId,
1149
        event_type: RoomAccountDataEventType,
1150
    ) -> Result<Option<Raw<AnyRoomAccountDataEvent>>, Error> {
1151
        self.room(room_id)?
1152
            .account_data(event_type)
1153
            .await
1154
            .map_err(|source| Error::from_sdk("reading room account data failed", source))
1155
    }
1156
1157
    #[must_use]
1158
    pub fn connection_state(&self) -> watch::Receiver<ConnectionState> {
1159
        self.connection.clone()
1160
    }
1161
1162
    #[must_use]
1163
    pub fn encryption_state(&self) -> Option<EncryptionState> {
1164
        let device_id = self.client.device_id()?.to_owned();
1165
        let encryption = self.client.encryption();
1166
        Some(encryption_state(
1167
            device_id,
1168
            encryption.recovery().state(),
1169
            encryption.verification_state().get(),
1170
            encryption.backups().state(),
1171
        ))
1172
    }
1173
}
1174
1175
/// one space's hierarchy, a page at a time. hold on to it while you browse, it remembers where it got to
1176
pub struct SpaceHierarchy(SpaceRoomList);
1177
1178
impl fmt::Debug for SpaceHierarchy {
1179
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1180
        formatter
1181
            .debug_struct("SpaceHierarchy")
1182
            .finish_non_exhaustive()
1183
    }
1184
}
1185
1186
impl SpaceHierarchy {
1187
    /// fetches the next page. a no-op once the end is reached or while a page is in flight
1188
    pub async fn paginate(&self) -> Result<(), Error> {
1189
        self.0.paginate().await.map_err(|source| {
1190
            Error::with_source(
1191
                ErrorKind::Server,
1192
                "fetching the space hierarchy failed",
1193
                source,
1194
            )
1195
        })
1196
    }
1197
1198
    /// everything fetched so far, the space itself excluded
1199
    pub async fn rooms(&self) -> Vec<SpaceChild> {
1200
        self.0.rooms().await.into_iter().map(space_child).collect()
1201
    }
1202
1203
    #[must_use]
1204
    pub fn end_reached(&self) -> bool {
1205
        matches!(
1206
            self.0.pagination_state(),
1207
            SpaceRoomListPaginationState::Idle { end_reached: true }
1208
        )
1209
    }
1210
}
1211
1212
fn profile(
1213
    user_id: OwnedUserId,
1214
    response: &ruma::api::client::profile::get_profile::v3::Response,
1215
) -> Result<Profile, Error> {
1216
    use ruma::api::client::profile::{AvatarUrl, DisplayName};
1217
    let malformed = |source| {
1218
        Error::with_source(
1219
            ErrorKind::Server,
1220
            "the server sent a malformed profile",
1221
            source,
1222
        )
1223
    };
1224
    Ok(Profile {
1225
        user_id,
1226
        display_name: response.get_static::<DisplayName>().map_err(malformed)?,
1227
        avatar: response
1228
            .get_static::<AvatarUrl>()
1229
            .map_err(malformed)?
1230
            .as_deref()
1231
            .map(MediaId::plain),
1232
    })
1233
}
1234
1235
#[derive(Debug)]
1236
pub(crate) struct Request {
1237
    pub(crate) command: Command,
1238
    pub(crate) reply: Responder,
1239
}
1240
1241
impl Request {
1242
    fn new(command: Command) -> (Self, Pending) {
1243
        let (reply, pending) = oneshot::channel();
1244
        (
1245
            Self {
1246
                command,
1247
                reply: Responder(reply),
1248
            },
1249
            Pending(pending),
1250
        )
1251
    }
1252
}
1253
1254
/// whoever holds this owes the caller an answer. dropping it answers `Interrupted`
1255
#[derive(Debug)]
1256
pub(crate) struct Responder(oneshot::Sender<Result<CommandOutput, Error>>);
1257
1258
impl Responder {
1259
    pub(crate) fn channel() -> (Self, Pending) {
1260
        let (reply, pending) = oneshot::channel();
1261
        (Self(reply), Pending(pending))
1262
    }
1263
1264
    pub(crate) fn send(self, result: Result<CommandOutput, Error>) {
1265
        if let Err(error) = &result {
1266
            tracing::debug!(%error, kind = ?error.kind, "command failed");
1267
        }
1268
        let _ = self.0.send(result);
1269
    }
1270
1271
    pub(crate) fn done(self, result: Result<(), Error>) {
1272
        self.send(result.map(|()| CommandOutput::Done));
1273
    }
1274
}
1275
1276
#[derive(Debug)]
1277
pub struct EventStream {
1278
    events: mpsc::Receiver<Event>,
1279
}
1280
1281
impl EventStream {
1282
    pub async fn recv(&mut self) -> Option<Event> {
1283
        self.events.recv().await
1284
    }
1285
1286
    pub fn try_recv(&mut self) -> Result<Event, mpsc::error::TryRecvError> {
1287
        self.events.try_recv()
1288
    }
1289
}
1290
1291
impl futures_util::Stream for EventStream {
1292
    type Item = Event;
1293
1294
    fn poll_next(mut self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Option<Self::Item>> {
1295
        self.events.poll_recv(context)
1296
    }
1297
}
1298
1299
#[derive(Debug)]
1300
pub(crate) struct DriverPort {
1301
    requests: mpsc::Receiver<Request>,
1302
    events: mpsc::Sender<Event>,
1303
    connection: watch::Sender<ConnectionState>,
1304
}
1305
1306
#[derive(Clone, Debug)]
1307
pub(crate) struct EventSender(mpsc::Sender<Event>);
1308
1309
impl EventSender {
1310
    pub(crate) async fn send(&self, event: Event) -> Result<(), SendEventError> {
1311
        self.0.send(event).await.map_err(|_| SendEventError)
1312
    }
1313
1314
    pub(crate) async fn reserve(&self) -> Result<mpsc::Permit<'_, Event>, SendEventError> {
1315
        self.0.reserve().await.map_err(|_| SendEventError)
1316
    }
1317
1318
    /// resolves once every `EventStream` is gone
1319
    pub(crate) async fn closed(&self) {
1320
        self.0.closed().await;
1321
    }
1322
}
1323
1324
impl DriverPort {
1325
    pub(crate) fn event_sender(&self) -> EventSender {
1326
        EventSender(self.events.clone())
1327
    }
1328
1329
    pub(crate) fn connection_sender(&self) -> watch::Sender<ConnectionState> {
1330
        self.connection.clone()
1331
    }
1332
1333
    pub(crate) async fn recv(&mut self) -> Option<Request> {
1334
        self.requests.recv().await
1335
    }
1336
1337
    pub(crate) fn try_recv(&mut self) -> Option<Request> {
1338
        self.requests.try_recv().ok()
1339
    }
1340
1341
    pub(crate) fn close(&mut self) {
1342
        self.requests.close();
1343
    }
1344
}
1345
1346
#[derive(Clone, Copy, Debug, Eq, PartialEq, thiserror::Error)]
1347
#[error("the event receiver has closed")]
1348
pub(crate) struct SendEventError;
1349
1350
#[must_use]
1351
pub(crate) fn transport_pair(
1352
    client: Client,
1353
    sync_service: Arc<SyncService>,
1354
    media: Arc<MediaService>,
1355
    store_lock: Option<Arc<StoreLock>>,
1356
    command_capacity: usize,
1357
    event_capacity: usize,
1358
) -> (ClientHandle, DriverPort, EventStream) {
1359
    let (request_tx, request_rx) = mpsc::channel(command_capacity);
1360
    let (event_tx, event_rx) = mpsc::channel(event_capacity);
1361
    let (connection_tx, connection_rx) = watch::channel(ConnectionState::Idle);
1362
    (
1363
        ClientHandle {
1364
            requests: request_tx,
1365
            client,
1366
            connection: connection_rx,
1367
            notification_client: Arc::new(OnceCell::new()),
1368
            sync_service,
1369
            media,
1370
            spaces: Arc::new(OnceCell::new()),
1371
            members_checked: Arc::default(),
1372
            _store_lock: store_lock,
1373
        },
1374
        DriverPort {
1375
            requests: request_rx,
1376
            events: event_tx,
1377
            connection: connection_tx,
1378
        },
1379
        EventStream { events: event_rx },
1380
    )
1381
}
1382
1383
async fn list_members(
1384
    room: &matrix_sdk::Room,
1385
    filter: MemberFilter,
1386
) -> Result<Vec<MemberSummary>, Error> {
1387
    let memberships = match filter {
1388
        MemberFilter::Active => RoomMemberships::ACTIVE,
1389
        MemberFilter::Joined => RoomMemberships::JOIN,
1390
        MemberFilter::Invited => RoomMemberships::INVITE,
1391
        MemberFilter::Banned => RoomMemberships::BAN,
1392
        MemberFilter::Left => RoomMemberships::LEAVE,
1393
        // An empty mask is the SDK's unfiltered wildcard: it matches every
1394
        // membership, including custom states that `all()` would exclude.
1395
        MemberFilter::All => RoomMemberships::empty(),
1396
    };
1397
    Ok(room
1398
        .members(memberships)
1399
        .await
1400
        .map_err(|source| Error::from_sdk("listing members failed", source))?
1401
        .into_iter()
1402
        .map(member_summary)
1403
        .collect())
1404
}
1405
1406
fn normalise_push_notification(
1407
    room_id: &RoomId,
1408
    event_id: &EventId,
1409
    status: NotificationStatus,
1410
) -> PushNotificationStatus {
1411
    match status {
1412
        NotificationStatus::Event(item) => {
1413
            let event = match &item.raw_event {
1414
                RawNotificationEvent::Timeline(event) => {
1415
                    PushNotificationEvent::Timeline(event.clone())
1416
                }
1417
                RawNotificationEvent::Invite(event) => PushNotificationEvent::Invite(event.clone()),
1418
            };
1419
            PushNotificationStatus::Event(Box::new(PushNotification {
1420
                event_id: event_id.to_owned(),
1421
                event,
1422
                sender: PushNotificationSender {
1423
                    user_id: item.event.sender().to_owned(),
1424
                    display_name: item.sender_display_name,
1425
                    avatar_url: item.sender_avatar_url.as_deref().map(MediaId::plain),
1426
                    name_ambiguous: item.is_sender_name_ambiguous,
1427
                },
1428
                room: PushNotificationRoom {
1429
                    room_id: room_id.to_owned(),
1430
                    name: item.room_computed_display_name,
1431
                    avatar_url: item.room_avatar_url.as_deref().map(MediaId::plain),
1432
                    canonical_alias: item
1433
                        .room_canonical_alias
1434
                        .as_deref()
1435
                        .and_then(|alias| alias.parse().ok()),
1436
                    topic: item.room_topic,
1437
                    encrypted: item.is_room_encrypted,
1438
                    direct: item.room_is_dm,
1439
                    joined_members_count: item.joined_members_count,
1440
                    service_members: item
1441
                        .service_members
1442
                        .into_iter()
1443
                        .filter_map(|user_id| user_id.parse().ok())
1444
                        .collect(),
1445
                    active_service_members_count: item.active_service_members_count,
1446
                    space: item.is_space,
1447
                },
1448
                noisy: item.is_noisy,
1449
                has_mention: item.has_mention,
1450
                thread_id: item.thread_id,
1451
            }))
1452
        }
1453
        NotificationStatus::EventNotFound => PushNotificationStatus::NotFound,
1454
        NotificationStatus::EventFilteredOut => PushNotificationStatus::FilteredOut,
1455
        NotificationStatus::EventRedacted => PushNotificationStatus::Redacted,
1456
    }
1457
}
1458
1459
fn unexpected(wanted: &str, output: &CommandOutput) -> Error {
1460
    Error::new(
1461
        ErrorKind::Other,
1462
        format!("expected {wanted} but got {output:?}"),
1463
    )
1464
}