atagen

macltok

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