atagen

macltok

public
Star0
Watch0
d8d8a9fe770a74b05da14929c5cb655208549837
3
ssh://brewery@pls.lobotomise.me:33/atagen/macltok.git
src/transport.rsRust1114 lines34.5 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, Media, MediaCommand, MediaId,
32
    MediaLocation, MediaSize, MemberFilter, MemberSummary, MessageContent, MessageEditContent,
33
    NotificationCommand, OwnDevice, PowerLevelChanges, Profile, PushNotification,
34
    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},
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
    /// browse a space the way the server sees it, rooms you haven't joined included
592
    pub async fn space_hierarchy(&self, space_id: OwnedRoomId) -> SpaceHierarchy {
593
        SpaceHierarchy(SpaceRoomList::new(self.client.clone(), space_id).await)
594
    }
595
596
    pub async fn own_profile(&self) -> Result<Profile, Error> {
597
        let user_id = self
598
            .client
599
            .user_id()
600
            .ok_or_else(|| Error::new(ErrorKind::Unauthenticated, "the client is not logged in"))?
601
            .to_owned();
602
        let response = self
603
            .client
604
            .account()
605
            .fetch_user_profile()
606
            .await
607
            .map_err(|source| Error::from_sdk("fetching the profile failed", source))?;
608
        profile(user_id, &response)
609
    }
610
611
    pub async fn user_profile(&self, user_id: &UserId) -> Result<Profile, Error> {
612
        let response = self
613
            .client
614
            .account()
615
            .fetch_user_profile_of(user_id)
616
            .await
617
            .map_err(|source| Error::from_sdk("fetching the profile failed", source))?;
618
        profile(user_id.to_owned(), &response)
619
    }
620
621
    pub async fn set_display_name(&self, name: Option<String>) -> Result<(), Error> {
622
        self.run(Command::Account(AccountCommand::SetDisplayName(name)))
623
            .await
624
    }
625
626
    pub async fn set_avatar(&self, avatar: Option<OwnedMxcUri>) -> Result<(), Error> {
627
        self.run(Command::Account(AccountCommand::SetAvatar(avatar)))
628
            .await
629
    }
630
631
    pub async fn upload_avatar(
632
        &self,
633
        mimetype: impl Into<String>,
634
        bytes: Vec<u8>,
635
    ) -> Result<(), Error> {
636
        self.run(Command::Account(AccountCommand::UploadAvatar {
637
            mimetype: mimetype.into(),
638
            bytes,
639
        }))
640
        .await
641
    }
642
643
    pub async fn rooms_filtered(&self, filter: &RoomListFilter) -> Vec<RoomSummary> {
644
        let matcher = filter.matcher();
645
        self.rooms()
646
            .await
647
            .into_iter()
648
            .filter(|room| matcher.matches(room))
649
            .collect()
650
    }
651
652
    pub async fn devices(&self) -> Result<Vec<OwnDevice>, Error> {
653
        let current_device_id = self.client.device_id();
654
        Ok(self
655
            .client
656
            .devices()
657
            .await
658
            .map_err(|source| Error::from_http("listing devices failed", source))?
659
            .devices
660
            .into_iter()
661
            .map(|device| OwnDevice {
662
                current: current_device_id == Some(device.device_id.as_ref()),
663
                device_id: device.device_id,
664
                display_name: device.display_name,
665
                last_seen_ip: device.last_seen_ip,
666
                last_seen_timestamp_ms: device.last_seen_ts.map(|timestamp| timestamp.get().into()),
667
            })
668
            .collect())
669
    }
670
671
    fn room(&self, room_id: &RoomId) -> Result<matrix_sdk::Room, Error> {
672
        self.client
673
            .get_room(room_id)
674
            .ok_or_else(Error::room_unknown)
675
    }
676
677
    pub async fn room_notification_settings(
678
        &self,
679
        room_id: &RoomId,
680
    ) -> Result<Option<RoomNotificationSettings>, Error> {
681
        let room = self.room(room_id)?;
682
        let custom = room.user_defined_notification_mode().await;
683
        let mode = match custom {
684
            Some(mode) => mode,
685
            None => match room.notification_mode().await {
686
                Some(mode) => mode,
687
                None => return Ok(None),
688
            },
689
        };
690
        Ok(Some(RoomNotificationSettings {
691
            mode: crate::normalise::notification_mode(mode),
692
            custom: custom.is_some(),
693
        }))
694
    }
695
696
    pub async fn notification_keywords(&self) -> Vec<String> {
697
        self.client
698
            .notification_settings()
699
            .await
700
            .enabled_keywords()
701
            .await
702
            .into_iter()
703
            .collect()
704
    }
705
706
    pub async fn resolve_push_notification(
707
        &self,
708
        room_id: &RoomId,
709
        event_id: &EventId,
710
    ) -> Result<PushNotificationStatus, Error> {
711
        let notification_failed = |source| {
712
            Error::with_source(
713
                ErrorKind::Other,
714
                "resolving the push notification failed",
715
                source,
716
            )
717
        };
718
        let client = self
719
            .notification_client
720
            .get_or_try_init(|| async {
721
                NotificationClient::new(
722
                    self.client.clone(),
723
                    NotificationProcessSetup::SingleProcess {
724
                        sync_service: self.sync_service.clone(),
725
                    },
726
                )
727
                .await
728
            })
729
            .await
730
            .map_err(notification_failed)?;
731
        let status = client
732
            .get_notification(room_id, event_id)
733
            .await
734
            .map_err(notification_failed)?;
735
        Ok(normalise_push_notification(room_id, event_id, status))
736
    }
737
738
    pub async fn members(
739
        &self,
740
        room_id: &RoomId,
741
        filter: MemberFilter,
742
    ) -> Result<Vec<MemberSummary>, Error> {
743
        let memberships = match filter {
744
            MemberFilter::Active => RoomMemberships::ACTIVE,
745
            MemberFilter::Joined => RoomMemberships::JOIN,
746
            MemberFilter::Invited => RoomMemberships::INVITE,
747
            MemberFilter::Banned => RoomMemberships::BAN,
748
            MemberFilter::Left => RoomMemberships::LEAVE,
749
            // An empty mask is the SDK's unfiltered wildcard: it matches every
750
            // membership, including custom states that `all()` would exclude.
751
            MemberFilter::All => RoomMemberships::empty(),
752
        };
753
        Ok(self
754
            .room(room_id)?
755
            .members(memberships)
756
            .await
757
            .map_err(|source| Error::from_sdk("listing members failed", source))?
758
            .into_iter()
759
            .map(member_summary)
760
            .collect())
761
    }
762
763
    pub async fn room_power_levels(&self, room_id: &RoomId) -> Result<RoomPowerLevels, Error> {
764
        let levels = self
765
            .room(room_id)?
766
            .power_levels()
767
            .await
768
            .map_err(|source| Error::from_sdk("reading power levels failed", source.into()))?;
769
        Ok(RoomPowerLevels {
770
            ban: levels.ban.into(),
771
            invite: levels.invite.into(),
772
            kick: levels.kick.into(),
773
            redact: levels.redact.into(),
774
            events_default: levels.events_default.into(),
775
            state_default: levels.state_default.into(),
776
            users_default: levels.users_default.into(),
777
            room_notification: levels.notifications.room.into(),
778
            users: levels
779
                .users
780
                .into_iter()
781
                .map(|(user_id, level)| (user_id, level.into()))
782
                .collect(),
783
            events: levels
784
                .events
785
                .into_iter()
786
                .map(|(event_type, level)| (event_type.to_string(), level.into()))
787
                .collect(),
788
            creators: levels
789
                .rules
790
                .privileged_creators
791
                .into_iter()
792
                .flatten()
793
                .collect(),
794
        })
795
    }
796
797
    pub async fn pending_send_ids(
798
        &self,
799
        room_id: &RoomId,
800
    ) -> Result<Vec<OwnedTransactionId>, Error> {
801
        let (echoes, _) = self
802
            .room(room_id)?
803
            .send_queue()
804
            .subscribe()
805
            .await
806
            .map_err(|source| {
807
                Error::with_source(ErrorKind::Store, "reading the send queue failed", source)
808
            })?;
809
        Ok(echoes
810
            .into_iter()
811
            .filter(|echo| {
812
                matches!(
813
                    echo.content,
814
                    matrix_sdk::send_queue::LocalEchoContent::Event { .. }
815
                )
816
            })
817
            .map(|echo| echo.transaction_id)
818
            .collect())
819
    }
820
821
    pub async fn global_account_data(
822
        &self,
823
        event_type: GlobalAccountDataEventType,
824
    ) -> Result<Option<Raw<AnyGlobalAccountDataEventContent>>, Error> {
825
        self.client
826
            .account()
827
            .account_data_raw(event_type)
828
            .await
829
            .map_err(|source| Error::from_sdk("reading account data failed", source))
830
    }
831
832
    pub async fn room_account_data(
833
        &self,
834
        room_id: &RoomId,
835
        event_type: RoomAccountDataEventType,
836
    ) -> Result<Option<Raw<AnyRoomAccountDataEvent>>, Error> {
837
        self.room(room_id)?
838
            .account_data(event_type)
839
            .await
840
            .map_err(|source| Error::from_sdk("reading room account data failed", source))
841
    }
842
843
    #[must_use]
844
    pub fn connection_state(&self) -> watch::Receiver<ConnectionState> {
845
        self.connection.clone()
846
    }
847
848
    #[must_use]
849
    pub fn encryption_state(&self) -> Option<EncryptionState> {
850
        let device_id = self.client.device_id()?.to_owned();
851
        let encryption = self.client.encryption();
852
        Some(encryption_state(
853
            device_id,
854
            encryption.recovery().state(),
855
            encryption.verification_state().get(),
856
            encryption.backups().state(),
857
        ))
858
    }
859
}
860
861
/// one space's hierarchy, a page at a time. hold on to it while you browse, it remembers where it got to
862
pub struct SpaceHierarchy(SpaceRoomList);
863
864
impl fmt::Debug for SpaceHierarchy {
865
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
866
        formatter
867
            .debug_struct("SpaceHierarchy")
868
            .finish_non_exhaustive()
869
    }
870
}
871
872
impl SpaceHierarchy {
873
    /// fetches the next page. a no-op once the end is reached or while a page is in flight
874
    pub async fn paginate(&self) -> Result<(), Error> {
875
        self.0.paginate().await.map_err(|source| {
876
            Error::with_source(
877
                ErrorKind::Server,
878
                "fetching the space hierarchy failed",
879
                source,
880
            )
881
        })
882
    }
883
884
    /// everything fetched so far, the space itself excluded
885
    pub async fn rooms(&self) -> Vec<SpaceChild> {
886
        self.0.rooms().await.into_iter().map(space_child).collect()
887
    }
888
889
    #[must_use]
890
    pub fn end_reached(&self) -> bool {
891
        matches!(
892
            self.0.pagination_state(),
893
            SpaceRoomListPaginationState::Idle { end_reached: true }
894
        )
895
    }
896
}
897
898
fn profile(
899
    user_id: OwnedUserId,
900
    response: &ruma::api::client::profile::get_profile::v3::Response,
901
) -> Result<Profile, Error> {
902
    use ruma::api::client::profile::{AvatarUrl, DisplayName};
903
    let malformed = |source| {
904
        Error::with_source(
905
            ErrorKind::Server,
906
            "the server sent a malformed profile",
907
            source,
908
        )
909
    };
910
    Ok(Profile {
911
        user_id,
912
        display_name: response.get_static::<DisplayName>().map_err(malformed)?,
913
        avatar: response
914
            .get_static::<AvatarUrl>()
915
            .map_err(malformed)?
916
            .as_deref()
917
            .map(MediaId::plain),
918
    })
919
}
920
921
#[derive(Debug)]
922
pub(crate) struct Request {
923
    pub(crate) command: Command,
924
    pub(crate) reply: Responder,
925
}
926
927
impl Request {
928
    fn new(command: Command) -> (Self, Pending) {
929
        let (reply, pending) = oneshot::channel();
930
        (
931
            Self {
932
                command,
933
                reply: Responder(reply),
934
            },
935
            Pending(pending),
936
        )
937
    }
938
}
939
940
/// whoever holds this owes the caller an answer. dropping it answers `Interrupted`
941
#[derive(Debug)]
942
pub(crate) struct Responder(oneshot::Sender<Result<CommandOutput, Error>>);
943
944
impl Responder {
945
    pub(crate) fn send(self, result: Result<CommandOutput, Error>) {
946
        if let Err(error) = &result {
947
            tracing::debug!(%error, kind = ?error.kind, "command failed");
948
        }
949
        let _ = self.0.send(result);
950
    }
951
952
    pub(crate) fn done(self, result: Result<(), Error>) {
953
        self.send(result.map(|()| CommandOutput::Done));
954
    }
955
}
956
957
#[derive(Debug)]
958
pub struct EventStream {
959
    events: mpsc::Receiver<Event>,
960
}
961
962
impl EventStream {
963
    pub async fn recv(&mut self) -> Option<Event> {
964
        self.events.recv().await
965
    }
966
967
    pub fn try_recv(&mut self) -> Result<Event, mpsc::error::TryRecvError> {
968
        self.events.try_recv()
969
    }
970
}
971
972
impl futures_util::Stream for EventStream {
973
    type Item = Event;
974
975
    fn poll_next(mut self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Option<Self::Item>> {
976
        self.events.poll_recv(context)
977
    }
978
}
979
980
#[derive(Debug)]
981
pub(crate) struct DriverPort {
982
    requests: mpsc::Receiver<Request>,
983
    events: mpsc::Sender<Event>,
984
    connection: watch::Sender<ConnectionState>,
985
}
986
987
#[derive(Clone, Debug)]
988
pub(crate) struct EventSender(mpsc::Sender<Event>);
989
990
impl EventSender {
991
    pub(crate) async fn send(&self, event: Event) -> Result<(), SendEventError> {
992
        self.0.send(event).await.map_err(|_| SendEventError)
993
    }
994
995
    pub(crate) async fn reserve(&self) -> Result<mpsc::Permit<'_, Event>, SendEventError> {
996
        self.0.reserve().await.map_err(|_| SendEventError)
997
    }
998
999
    /// resolves once every `EventStream` is gone
1000
    pub(crate) async fn closed(&self) {
1001
        self.0.closed().await;
1002
    }
1003
}
1004
1005
impl DriverPort {
1006
    pub(crate) fn event_sender(&self) -> EventSender {
1007
        EventSender(self.events.clone())
1008
    }
1009
1010
    pub(crate) fn connection_sender(&self) -> watch::Sender<ConnectionState> {
1011
        self.connection.clone()
1012
    }
1013
1014
    pub(crate) async fn recv(&mut self) -> Option<Request> {
1015
        self.requests.recv().await
1016
    }
1017
1018
    pub(crate) fn try_recv(&mut self) -> Option<Request> {
1019
        self.requests.try_recv().ok()
1020
    }
1021
1022
    pub(crate) fn close(&mut self) {
1023
        self.requests.close();
1024
    }
1025
}
1026
1027
#[derive(Clone, Copy, Debug, Eq, PartialEq, thiserror::Error)]
1028
#[error("the event receiver has closed")]
1029
pub(crate) struct SendEventError;
1030
1031
#[must_use]
1032
pub(crate) fn transport_pair(
1033
    client: Client,
1034
    sync_service: Arc<SyncService>,
1035
    media: Arc<MediaService>,
1036
    store_lock: Option<Arc<StoreLock>>,
1037
    command_capacity: usize,
1038
    event_capacity: usize,
1039
) -> (ClientHandle, DriverPort, EventStream) {
1040
    let (request_tx, request_rx) = mpsc::channel(command_capacity);
1041
    let (event_tx, event_rx) = mpsc::channel(event_capacity);
1042
    let (connection_tx, connection_rx) = watch::channel(ConnectionState::Idle);
1043
    (
1044
        ClientHandle {
1045
            requests: request_tx,
1046
            client,
1047
            connection: connection_rx,
1048
            notification_client: Arc::new(OnceCell::new()),
1049
            sync_service,
1050
            media,
1051
            spaces: Arc::new(OnceCell::new()),
1052
            _store_lock: store_lock,
1053
        },
1054
        DriverPort {
1055
            requests: request_rx,
1056
            events: event_tx,
1057
            connection: connection_tx,
1058
        },
1059
        EventStream { events: event_rx },
1060
    )
1061
}
1062
1063
fn normalise_push_notification(
1064
    room_id: &RoomId,
1065
    event_id: &EventId,
1066
    status: NotificationStatus,
1067
) -> PushNotificationStatus {
1068
    match status {
1069
        NotificationStatus::Event(item) => {
1070
            let event = match &item.raw_event {
1071
                RawNotificationEvent::Timeline(event) => {
1072
                    PushNotificationEvent::Timeline(event.clone())
1073
                }
1074
                RawNotificationEvent::Invite(event) => PushNotificationEvent::Invite(event.clone()),
1075
            };
1076
            PushNotificationStatus::Event(Box::new(PushNotification {
1077
                event_id: event_id.to_owned(),
1078
                event,
1079
                sender: PushNotificationSender {
1080
                    user_id: item.event.sender().to_owned(),
1081
                    display_name: item.sender_display_name,
1082
                    avatar_url: item.sender_avatar_url.as_deref().map(MediaId::plain),
1083
                    name_ambiguous: item.is_sender_name_ambiguous,
1084
                },
1085
                room: PushNotificationRoom {
1086
                    room_id: room_id.to_owned(),
1087
                    name: item.room_computed_display_name,
1088
                    avatar_url: item.room_avatar_url.as_deref().map(MediaId::plain),
1089
                    canonical_alias: item
1090
                        .room_canonical_alias
1091
                        .as_deref()
1092
                        .and_then(|alias| alias.parse().ok()),
1093
                    topic: item.room_topic,
1094
                    encrypted: item.is_room_encrypted,
1095
                    direct: item.room_is_dm,
1096
                    joined_members_count: item.joined_members_count,
1097
                    service_members: item
1098
                        .service_members
1099
                        .into_iter()
1100
                        .filter_map(|user_id| user_id.parse().ok())
1101
                        .collect(),
1102
                    active_service_members_count: item.active_service_members_count,
1103
                    space: item.is_space,
1104
                },
1105
                noisy: item.is_noisy,
1106
                has_mention: item.has_mention,
1107
                thread_id: item.thread_id,
1108
            }))
1109
        }
1110
        NotificationStatus::EventNotFound => PushNotificationStatus::NotFound,
1111
        NotificationStatus::EventFilteredOut => PushNotificationStatus::FilteredOut,
1112
        NotificationStatus::EventRedacted => PushNotificationStatus::Redacted,
1113
    }
1114
}