atagen

macltok

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