atagen

macltok

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