atagen

macltok

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