atagen

macltok

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