atagen

macltok

public
Star0
Watch0
419c65426a62191a26b47d07a0e0661e047317da
3
ssh://brewery@pls.lobotomise.me:33/atagen/macltok.git
src/transport.rsRust2036 lines63.1 KB
1
use std::{
2
    collections::HashSet,
3
    fmt,
4
    future::Future,
5
    pin::Pin,
6
    sync::{Arc, Mutex, Weak},
7
    task::{Context, Poll},
8
};
9
10
use matrix_sdk::{Client, RoomMemberships};
11
use matrix_sdk_ui::{
12
    notification_client::{
13
        NotificationClient, NotificationProcessSetup, NotificationStatus, RawNotificationEvent,
14
    },
15
    spaces::{SpaceRoomList, SpaceService, room_list::SpaceRoomListPaginationState},
16
    sync_service::SyncService,
17
};
18
use ruma::{
19
    EventId, OwnedDeviceId, OwnedEventId, OwnedMxcUri, OwnedRoomId, OwnedRoomOrAliasId,
20
    OwnedServerName, OwnedTransactionId, OwnedUserId, RoomId, UserId,
21
    events::{
22
        AnyGlobalAccountDataEventContent, AnyRoomAccountDataEvent, AnyRoomAccountDataEventContent,
23
        GlobalAccountDataEventType, RoomAccountDataEventType,
24
    },
25
    serde::Raw,
26
};
27
use tokio::sync::{OnceCell, mpsc, oneshot, watch};
28
29
use crate::{
30
    AccountCommand, AttachmentUpload, Command, CommandOutput, ConnectionState, CreateRoomOptions,
31
    EncryptionCommand, EncryptionState, Error, ErrorKind, Event, EventContextCommand,
32
    EventContextPage, EventContextSession, EventRef, HistoryRepairCommand, HttpPusher,
33
    ImagePackSource, ImagePacks, InteractiveAuth, InteractiveAuthOutcome, LifecycleCommand,
34
    LinkPreview, Media, MediaCommand, MediaId, MediaLocation, MediaSize, MemberFilter,
35
    MemberSummary, MentionKey, MentionsBoundary, MentionsCommand, MentionsPage, MessageContent,
36
    MessageEditContent, NotificationCommand, OwnDevice, PackCommand, PackImageInfo, PackUsage,
37
    Patch, PowerLevelChanges, Profile, PushFormat, PushNotification, PushNotificationEvent,
38
    PushNotificationStatus, PusherKey, RecoveryKey, RoomCommand, RoomListFilter,
39
    RoomNotificationMode, RoomNotificationSettings, RoomPowerLevels, RoomSummary,
40
    SavedMentionContext, SearchCommand, SearchCoverage, SearchPage, SearchScope, SearchSession,
41
    SpaceChild, ThreadListCommand, TimelineCommand, TimelineId, VerificationAction,
42
    VerificationTarget,
43
    encryption::encryption_state,
44
    engine::{MediaService, StoreLock},
45
    model::{PushNotificationRoom, PushNotificationSender},
46
    normalise::{image_packs, member_summary, room_summaries, space_child, url_previews_allowed},
47
};
48
49
#[derive(Clone)]
50
pub struct ClientHandle {
51
    requests: mpsc::Sender<Request>,
52
    client: Client,
53
    connection: watch::Receiver<ConnectionState>,
54
    notification_client: Arc<OnceCell<NotificationClient>>,
55
    sync_service: Arc<SyncService>,
56
    media: Arc<MediaService>,
57
    spaces: Arc<OnceCell<SpaceService>>,
58
    members_checked: Arc<Mutex<HashSet<OwnedRoomId>>>,
59
    publication: Arc<PublicationRegistry>,
60
    _store_lock: Option<Arc<StoreLock>>,
61
}
62
63
impl fmt::Debug for ClientHandle {
64
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
65
        formatter
66
            .debug_struct("ClientHandle")
67
            .finish_non_exhaustive()
68
    }
69
}
70
71
/// a command's answer. drop it if you don't care, the command still runs
72
#[derive(Debug)]
73
#[must_use = "dropping it is fine but then you'll never know"]
74
pub struct Pending(oneshot::Receiver<Result<CommandOutput, Error>>);
75
76
impl Future for Pending {
77
    type Output = Result<CommandOutput, Error>;
78
79
    fn poll(mut self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Self::Output> {
80
        Pin::new(&mut self.0)
81
            .poll(context)
82
            .map(|result| result.unwrap_or_else(|_| Err(Error::interrupted())))
83
    }
84
}
85
86
impl ClientHandle {
87
    pub(crate) fn register_publication_mirror(&self) -> PublicationRegistration {
88
        self.publication.register_mirror()
89
    }
90
91
    /// current credentials, it holds secrets. the engine saves it for you unless the
92
    /// session was built `without_persistence`
93
    #[cfg(any(feature = "sqlite", feature = "indexeddb"))]
94
    pub fn session_record(&self) -> Result<crate::SessionRecord, crate::AccountError> {
95
        crate::account::session_record(&self.client)
96
    }
97
98
    /// the bearer token. it rotates on every refresh so ask again after a 401 rather than
99
    /// keeping it, and keep it in memory and away from all but the homeserver's media endpoints
100
    pub fn access_token(&self) -> Option<String> {
101
        self.client.access_token()
102
    }
103
104
    /// queue a command, waiting for room in the queue if there isn't any
105
    pub async fn send(&self, command: Command) -> Result<Pending, Error> {
106
        let (request, pending) = Request::new(command);
107
        self.requests
108
            .send(request)
109
            .await
110
            .map_err(|_| Error::interrupted())?;
111
        Ok(pending)
112
    }
113
114
    /// queue a command or fail with `QueueFull` right now
115
    pub fn try_send(&self, command: Command) -> Result<Pending, Error> {
116
        let (request, pending) = Request::new(command);
117
        self.requests
118
            .try_send(request)
119
            .map_err(|error| match error {
120
                mpsc::error::TrySendError::Full(_) => {
121
                    Error::new(ErrorKind::QueueFull, "the command queue is full")
122
                }
123
                mpsc::error::TrySendError::Closed(_) => Error::interrupted(),
124
            })?;
125
        Ok(pending)
126
    }
127
128
    /// queue a command and wait for its answer. timeline sends answer once the
129
    /// sdk send queue has them, delivery shows up later as the item's send state
130
    pub async fn execute(&self, command: Command) -> Result<CommandOutput, Error> {
131
        self.send(command).await?.await
132
    }
133
134
    async fn run(&self, command: Command) -> Result<(), Error> {
135
        self.execute(command).await.map(drop)
136
    }
137
138
    async fn run_for_room(&self, command: RoomCommand) -> Result<OwnedRoomId, Error> {
139
        match self.execute(Command::Room(command)).await? {
140
            CommandOutput::Room(room_id) => Ok(room_id),
141
            output => Err(Error::new(
142
                ErrorKind::Other,
143
                format!("expected a room but got {output:?}"),
144
            )),
145
        }
146
    }
147
148
    pub async fn shutdown(&self) -> Result<(), Error> {
149
        self.run(Command::Lifecycle(LifecycleCommand::Shutdown))
150
            .await
151
    }
152
153
    pub async fn logout(&self, remove_pushers: Vec<PusherKey>) -> Result<(), Error> {
154
        self.run(Command::Account(AccountCommand::Logout { remove_pushers }))
155
            .await
156
    }
157
158
    /// replaces any pusher with the same app id and pushkey
159
    pub async fn set_pusher(&self, pusher: HttpPusher) -> Result<(), Error> {
160
        use ruma::{
161
            api::client::push::{PusherIds, PusherInit, PusherKind},
162
            push::{HttpPusherData, PushFormat as SdkFormat},
163
        };
164
        let mut data = HttpPusherData::new(pusher.url);
165
        data.format = match pusher.format {
166
            PushFormat::Full => None,
167
            PushFormat::EventIdOnly => Some(SdkFormat::EventIdOnly),
168
        };
169
        data.data = pusher.data;
170
        let pusher = PusherInit {
171
            ids: PusherIds::new(pusher.pushkey, pusher.app_id),
172
            kind: PusherKind::Http(data),
173
            app_display_name: pusher.app_display_name,
174
            device_display_name: pusher.device_display_name,
175
            profile_tag: None,
176
            lang: pusher.lang,
177
        };
178
        self.client
179
            .pusher()
180
            .set(pusher.into(), false)
181
            .await
182
            .map_err(|source| Error::from_sdk("setting the pusher failed", source))
183
    }
184
185
    pub async fn remove_pusher(&self, pusher: PusherKey) -> Result<(), Error> {
186
        remove_pusher(&self.client, pusher).await
187
    }
188
189
    /// the account's http pushers, every device's alike
190
    pub async fn pushers(&self) -> Result<Vec<HttpPusher>, Error> {
191
        use ruma::{
192
            api::client::push::{PusherKind, get_pushers},
193
            push::PushFormat as SdkFormat,
194
        };
195
        let response = self
196
            .client
197
            .send(get_pushers::v3::Request::new())
198
            .await
199
            .map_err(|source| Error::from_sdk("fetching pushers failed", source.into()))?;
200
        Ok(response
201
            .pushers
202
            .into_iter()
203
            .filter_map(|pusher| match pusher.kind {
204
                PusherKind::Http(data) => Some(HttpPusher {
205
                    app_id: pusher.ids.app_id,
206
                    pushkey: pusher.ids.pushkey,
207
                    app_display_name: pusher.app_display_name,
208
                    device_display_name: pusher.device_display_name,
209
                    lang: pusher.lang,
210
                    url: data.url,
211
                    format: match data.format {
212
                        Some(SdkFormat::EventIdOnly) => PushFormat::EventIdOnly,
213
                        _ => PushFormat::Full,
214
                    },
215
                    data: data.data,
216
                }),
217
                _ => None,
218
            })
219
            .collect())
220
    }
221
222
    pub async fn delete_devices(
223
        &self,
224
        device_ids: Vec<OwnedDeviceId>,
225
        auth: Option<InteractiveAuth>,
226
    ) -> Result<InteractiveAuthOutcome, Error> {
227
        match self
228
            .run(Command::Account(AccountCommand::DeleteDevices {
229
                device_ids,
230
                auth,
231
            }))
232
            .await
233
        {
234
            Ok(()) => Ok(InteractiveAuthOutcome::Completed),
235
            Err(Error {
236
                challenge: Some(challenge),
237
                ..
238
            }) => Ok(InteractiveAuthOutcome::Challenge(*challenge)),
239
            Err(error) => Err(error),
240
        }
241
    }
242
243
    pub async fn rename_device(
244
        &self,
245
        device_id: OwnedDeviceId,
246
        display_name: impl Into<String>,
247
    ) -> Result<(), Error> {
248
        self.run(Command::Account(AccountCommand::RenameDevice {
249
            device_id,
250
            display_name: display_name.into(),
251
        }))
252
        .await
253
    }
254
255
    pub async fn set_global_account_data(
256
        &self,
257
        event_type: GlobalAccountDataEventType,
258
        content: Raw<AnyGlobalAccountDataEventContent>,
259
    ) -> Result<(), Error> {
260
        self.run(Command::Account(AccountCommand::SetAccountData {
261
            event_type,
262
            content,
263
        }))
264
        .await
265
    }
266
267
    pub async fn set_room_account_data(
268
        &self,
269
        room_id: OwnedRoomId,
270
        event_type: RoomAccountDataEventType,
271
        content: Raw<AnyRoomAccountDataEventContent>,
272
    ) -> Result<(), Error> {
273
        self.run(Command::Room(RoomCommand::SetAccountData {
274
            room_id,
275
            event_type,
276
            content,
277
        }))
278
        .await
279
    }
280
281
    pub async fn set_room_notification_mode(
282
        &self,
283
        room_id: OwnedRoomId,
284
        mode: RoomNotificationMode,
285
    ) -> Result<(), Error> {
286
        self.run(Command::Notification(NotificationCommand::SetRoomMode {
287
            room_id,
288
            mode,
289
        }))
290
        .await
291
    }
292
293
    pub async fn reset_room_notification_mode(&self, room_id: OwnedRoomId) -> Result<(), Error> {
294
        self.run(Command::Notification(NotificationCommand::ResetRoomMode(
295
            room_id,
296
        )))
297
        .await
298
    }
299
300
    pub async fn set_default_notification_mode(
301
        &self,
302
        encrypted: bool,
303
        one_to_one: bool,
304
        mode: RoomNotificationMode,
305
    ) -> Result<(), Error> {
306
        self.run(Command::Notification(NotificationCommand::SetDefaultMode {
307
            encrypted,
308
            one_to_one,
309
            mode,
310
        }))
311
        .await
312
    }
313
314
    pub async fn add_notification_keyword(&self, keyword: impl Into<String>) -> Result<(), Error> {
315
        self.run(Command::Notification(NotificationCommand::AddKeyword(
316
            keyword.into(),
317
        )))
318
        .await
319
    }
320
321
    pub async fn remove_notification_keyword(
322
        &self,
323
        keyword: impl Into<String>,
324
    ) -> Result<(), Error> {
325
        self.run(Command::Notification(NotificationCommand::RemoveKeyword(
326
            keyword.into(),
327
        )))
328
        .await
329
    }
330
331
    pub async fn open_room(&self, room_id: OwnedRoomId) -> Result<(), Error> {
332
        self.run(Command::Room(RoomCommand::Open(room_id))).await
333
    }
334
335
    pub async fn close_room(&self, room_id: OwnedRoomId) -> Result<(), Error> {
336
        self.run(Command::Room(RoomCommand::Close(room_id))).await
337
    }
338
339
    pub async fn open_thread(
340
        &self,
341
        room_id: OwnedRoomId,
342
        root_event_id: OwnedEventId,
343
    ) -> Result<(), Error> {
344
        self.run(Command::Timeline(TimelineCommand::OpenThread {
345
            room_id,
346
            root_event_id,
347
        }))
348
        .await
349
    }
350
351
    pub async fn close_thread(
352
        &self,
353
        room_id: OwnedRoomId,
354
        root_event_id: OwnedEventId,
355
    ) -> Result<(), Error> {
356
        self.run(Command::Timeline(TimelineCommand::CloseThread {
357
            room_id,
358
            root_event_id,
359
        }))
360
        .await
361
    }
362
363
    /// the room's pins as `TimelineId::Pinned`, kept current as they change. the room has to be open
364
    pub async fn open_pinned(&self, room_id: OwnedRoomId) -> Result<(), Error> {
365
        self.run(Command::Timeline(TimelineCommand::OpenPinned(room_id)))
366
            .await
367
    }
368
369
    pub async fn close_pinned(&self, room_id: OwnedRoomId) -> Result<(), Error> {
370
        self.run(Command::Timeline(TimelineCommand::ClosePinned(room_id)))
371
            .await
372
    }
373
374
    /// the room's threads, newest activity first, kept current as they gain replies.
375
    /// the room needn't be open. it starts empty, `paginate_thread_list` fills it
376
    pub async fn open_thread_list(&self, room_id: OwnedRoomId) -> Result<(), Error> {
377
        self.run(Command::ThreadList(ThreadListCommand::Open(room_id)))
378
            .await
379
    }
380
381
    pub async fn paginate_thread_list(
382
        &self,
383
        room_id: OwnedRoomId,
384
        limit: u16,
385
    ) -> Result<(), Error> {
386
        self.run(Command::ThreadList(ThreadListCommand::Paginate {
387
            room_id,
388
            limit,
389
        }))
390
        .await
391
    }
392
393
    pub async fn close_thread_list(&self, room_id: OwnedRoomId) -> Result<(), Error> {
394
        self.run(Command::ThreadList(ThreadListCommand::Close(room_id)))
395
            .await
396
    }
397
398
    /// reads the server's list so a pin made elsewhere just before isn't dropped
399
    pub async fn set_pinned(
400
        &self,
401
        room_id: OwnedRoomId,
402
        event_id: OwnedEventId,
403
        pinned: bool,
404
    ) -> Result<(), Error> {
405
        self.run(Command::Room(RoomCommand::SetPinned {
406
            room_id,
407
            event_id,
408
            pinned,
409
        }))
410
        .await
411
    }
412
413
    pub async fn resync(&self, timeline_id: TimelineId) -> Result<(), Error> {
414
        self.run(Command::Timeline(TimelineCommand::Resync { timeline_id }))
415
            .await
416
    }
417
418
    pub async fn paginate(&self, timeline_id: TimelineId, limit: u16) -> Result<(), Error> {
419
        self.run(Command::Timeline(TimelineCommand::Paginate {
420
            timeline_id,
421
            limit,
422
        }))
423
        .await
424
    }
425
426
    pub async fn cancel_history_repair(&self, room_id: OwnedRoomId) -> Result<(), Error> {
427
        self.run(Command::HistoryRepair(HistoryRepairCommand::Cancel {
428
            room_id,
429
        }))
430
        .await
431
    }
432
433
    pub async fn fill_history_gap(
434
        &self,
435
        room_id: OwnedRoomId,
436
        gap_id: ruma::OwnedTransactionId,
437
    ) -> Result<(), Error> {
438
        self.run(Command::HistoryRepair(HistoryRepairCommand::FillGap {
439
            room_id,
440
            gap_id,
441
        }))
442
        .await
443
    }
444
445
    pub async fn retry_recent_history(&self, room_id: OwnedRoomId) -> Result<(), Error> {
446
        self.run(Command::HistoryRepair(HistoryRepairCommand::RetryRecent {
447
            room_id,
448
        }))
449
        .await
450
    }
451
452
    /// close the session when the view goes, or its media stay registered
453
    pub async fn event_context(
454
        &self,
455
        room_id: &RoomId,
456
        event_id: &EventId,
457
        before_limit: u16,
458
        after_limit: u16,
459
    ) -> Result<EventContextSession, Error> {
460
        match self
461
            .execute(Command::EventContext(EventContextCommand::Fetch {
462
                room_id: room_id.to_owned(),
463
                event_id: event_id.to_owned(),
464
                before_limit,
465
                after_limit,
466
            }))
467
            .await?
468
        {
469
            CommandOutput::EventContext(session) => Ok(session),
470
            output => Err(unexpected("event context", &output)),
471
        }
472
    }
473
474
    pub async fn event_context_before(
475
        &self,
476
        context_id: &str,
477
        token: &str,
478
        limit: u16,
479
    ) -> Result<EventContextPage, Error> {
480
        self.event_context_page(EventContextCommand::Before {
481
            context_id: context_id.to_owned(),
482
            limit,
483
            token: token.to_owned(),
484
        })
485
        .await
486
    }
487
488
    pub async fn event_context_after(
489
        &self,
490
        context_id: &str,
491
        token: &str,
492
        limit: u16,
493
    ) -> Result<EventContextPage, Error> {
494
        self.event_context_page(EventContextCommand::After {
495
            context_id: context_id.to_owned(),
496
            limit,
497
            token: token.to_owned(),
498
        })
499
        .await
500
    }
501
502
    pub async fn close_event_context(&self, context_id: &str) -> Result<(), Error> {
503
        self.run(Command::EventContext(EventContextCommand::Close {
504
            context_id: context_id.to_owned(),
505
        }))
506
        .await
507
    }
508
509
    async fn event_context_page(
510
        &self,
511
        command: EventContextCommand,
512
    ) -> Result<EventContextPage, Error> {
513
        match self.execute(Command::EventContext(command)).await? {
514
            CommandOutput::EventContextPage(page) => Ok(page),
515
            output => Err(unexpected("event context page", &output)),
516
        }
517
    }
518
519
    pub async fn mentions_page(
520
        &self,
521
        cursor: Option<String>,
522
        limit: u32,
523
    ) -> Result<MentionsPage, Error> {
524
        match self
525
            .execute(Command::Mentions(MentionsCommand::Page {
526
                cursor,
527
                limit: limit.min(50) as u16,
528
            }))
529
            .await?
530
        {
531
            CommandOutput::MentionsPage(page) => Ok(page),
532
            output => Err(unexpected("mentions page", &output)),
533
        }
534
    }
535
536
    pub async fn mention_context(
537
        &self,
538
        room_id: &RoomId,
539
        event_id: &EventId,
540
    ) -> Result<SavedMentionContext, Error> {
541
        self.mention_context_command(MentionsCommand::Context(MentionKey {
542
            room_id: room_id.to_owned(),
543
            event_id: event_id.to_owned(),
544
        }))
545
        .await
546
    }
547
548
    pub async fn retry_mention_context(
549
        &self,
550
        room_id: &RoomId,
551
        event_id: &EventId,
552
    ) -> Result<SavedMentionContext, Error> {
553
        self.mention_context_command(MentionsCommand::RetryContext(MentionKey {
554
            room_id: room_id.to_owned(),
555
            event_id: event_id.to_owned(),
556
        }))
557
        .await
558
    }
559
560
    async fn mention_context_command(
561
        &self,
562
        command: MentionsCommand,
563
    ) -> Result<SavedMentionContext, Error> {
564
        match self.execute(Command::Mentions(command)).await? {
565
            CommandOutput::MentionContext(context) => Ok(context),
566
            output => Err(unexpected("mention context", &output)),
567
        }
568
    }
569
570
    pub async fn set_mentions_read(&self, keys: Vec<MentionKey>, read: bool) -> Result<(), Error> {
571
        self.run(Command::Mentions(MentionsCommand::SetRead { keys, read }))
572
            .await
573
    }
574
575
    pub async fn mark_mentions_read_through(
576
        &self,
577
        boundary: MentionsBoundary,
578
    ) -> Result<(), Error> {
579
        self.run(Command::Mentions(MentionsCommand::ReadThrough(boundary)))
580
            .await
581
    }
582
583
    pub async fn recover_mentions(&self) -> Result<(), Error> {
584
        self.run(Command::Mentions(MentionsCommand::Recover)).await
585
    }
586
587
    /// newest first, at most 50 a page. close the search when its results go, or its media
588
    /// stay registered
589
    pub async fn search_messages(
590
        &self,
591
        query: &str,
592
        scope: SearchScope,
593
        limit: u16,
594
    ) -> Result<SearchSession, Error> {
595
        match self
596
            .execute(Command::Search(SearchCommand::Search {
597
                query: query.to_owned(),
598
                scope,
599
                limit,
600
            }))
601
            .await?
602
        {
603
            CommandOutput::SearchSession(session) => Ok(session),
604
            output => Err(unexpected("search session", &output)),
605
        }
606
    }
607
608
    pub async fn search_more(
609
        &self,
610
        search_id: &str,
611
        cursor: &str,
612
        limit: u16,
613
    ) -> Result<SearchPage, Error> {
614
        match self
615
            .execute(Command::Search(SearchCommand::More {
616
                search_id: search_id.to_owned(),
617
                cursor: cursor.to_owned(),
618
                limit,
619
            }))
620
            .await?
621
        {
622
            CommandOutput::SearchPage(page) => Ok(page),
623
            output => Err(unexpected("search page", &output)),
624
        }
625
    }
626
627
    pub async fn close_search(&self, search_id: &str) -> Result<(), Error> {
628
        self.run(Command::Search(SearchCommand::Close {
629
            search_id: search_id.to_owned(),
630
        }))
631
        .await
632
    }
633
634
    pub async fn search_coverage(&self, scope: SearchScope) -> Result<SearchCoverage, Error> {
635
        match self
636
            .execute(Command::Search(SearchCommand::Coverage(scope)))
637
            .await?
638
        {
639
            CommandOutput::SearchCoverage(coverage) => Ok(coverage),
640
            output => Err(unexpected("search coverage", &output)),
641
        }
642
    }
643
644
    pub async fn send_message(
645
        &self,
646
        timeline_id: TimelineId,
647
        content: MessageContent,
648
    ) -> Result<(), Error> {
649
        self.run(Command::Timeline(TimelineCommand::Send {
650
            timeline_id,
651
            content,
652
        }))
653
        .await
654
    }
655
656
    pub async fn edit_message(
657
        &self,
658
        timeline_id: TimelineId,
659
        target: impl Into<EventRef>,
660
        content: MessageEditContent,
661
    ) -> Result<(), Error> {
662
        self.run(Command::Timeline(TimelineCommand::Edit {
663
            timeline_id,
664
            target: target.into(),
665
            content,
666
        }))
667
        .await
668
    }
669
670
    pub async fn redact(
671
        &self,
672
        timeline_id: TimelineId,
673
        target: impl Into<EventRef>,
674
        reason: Option<String>,
675
    ) -> Result<(), Error> {
676
        self.run(Command::Timeline(TimelineCommand::Redact {
677
            timeline_id,
678
            target: target.into(),
679
            reason,
680
        }))
681
        .await
682
    }
683
684
    pub async fn toggle_reaction(
685
        &self,
686
        timeline_id: TimelineId,
687
        target: impl Into<EventRef>,
688
        key: impl Into<String>,
689
        shortcode: Option<String>,
690
    ) -> Result<(), Error> {
691
        self.run(Command::Timeline(TimelineCommand::ToggleReaction {
692
            timeline_id,
693
            target: target.into(),
694
            key: key.into(),
695
            shortcode,
696
        }))
697
        .await
698
    }
699
700
    pub async fn mark_read(
701
        &self,
702
        timeline_id: TimelineId,
703
        event_id: OwnedEventId,
704
    ) -> Result<(), Error> {
705
        self.run(Command::Timeline(TimelineCommand::MarkRead {
706
            timeline_id,
707
            event_id,
708
        }))
709
        .await
710
    }
711
712
    pub async fn set_typing(&self, room_id: OwnedRoomId, typing: bool) -> Result<(), Error> {
713
        self.run(Command::Room(RoomCommand::SetTyping { room_id, typing }))
714
            .await
715
    }
716
717
    pub async fn prepare_to_send(&self, room_id: OwnedRoomId) -> Result<(), Error> {
718
        self.run(Command::Room(RoomCommand::PrepareToSend(room_id)))
719
            .await
720
    }
721
722
    pub async fn join_room(
723
        &self,
724
        room: OwnedRoomOrAliasId,
725
        via: Vec<OwnedServerName>,
726
    ) -> Result<OwnedRoomId, Error> {
727
        self.run_for_room(RoomCommand::Join { room, via }).await
728
    }
729
730
    pub async fn leave_room(&self, room_id: OwnedRoomId) -> Result<(), Error> {
731
        self.run(Command::Room(RoomCommand::Leave(room_id))).await
732
    }
733
734
    pub async fn forget_room(&self, room_id: OwnedRoomId) -> Result<(), Error> {
735
        self.run(Command::Room(RoomCommand::Forget(room_id))).await
736
    }
737
738
    pub async fn accept_invite(&self, room_id: OwnedRoomId) -> Result<(), Error> {
739
        self.run(Command::Room(RoomCommand::AcceptInvite(room_id)))
740
            .await
741
    }
742
743
    pub async fn reject_invite(&self, room_id: OwnedRoomId) -> Result<(), Error> {
744
        self.run(Command::Room(RoomCommand::RejectInvite(room_id)))
745
            .await
746
    }
747
748
    pub async fn invite_user(
749
        &self,
750
        room_id: OwnedRoomId,
751
        user_id: OwnedUserId,
752
    ) -> Result<(), Error> {
753
        self.run(Command::Room(RoomCommand::InviteUser { room_id, user_id }))
754
            .await
755
    }
756
757
    pub async fn kick_user(
758
        &self,
759
        room_id: OwnedRoomId,
760
        user_id: OwnedUserId,
761
        reason: Option<String>,
762
    ) -> Result<(), Error> {
763
        self.run(Command::Room(RoomCommand::KickUser {
764
            room_id,
765
            user_id,
766
            reason,
767
        }))
768
        .await
769
    }
770
771
    pub async fn ban_user(
772
        &self,
773
        room_id: OwnedRoomId,
774
        user_id: OwnedUserId,
775
        reason: Option<String>,
776
    ) -> Result<(), Error> {
777
        self.run(Command::Room(RoomCommand::BanUser {
778
            room_id,
779
            user_id,
780
            reason,
781
        }))
782
        .await
783
    }
784
785
    pub async fn unban_user(
786
        &self,
787
        room_id: OwnedRoomId,
788
        user_id: OwnedUserId,
789
        reason: Option<String>,
790
    ) -> Result<(), Error> {
791
        self.run(Command::Room(RoomCommand::UnbanUser {
792
            room_id,
793
            user_id,
794
            reason,
795
        }))
796
        .await
797
    }
798
799
    pub async fn set_power_levels(
800
        &self,
801
        room_id: OwnedRoomId,
802
        changes: PowerLevelChanges,
803
    ) -> Result<(), Error> {
804
        self.run(Command::Room(RoomCommand::SetPowerLevels {
805
            room_id,
806
            changes,
807
        }))
808
        .await
809
    }
810
811
    pub async fn set_user_power_level(
812
        &self,
813
        room_id: OwnedRoomId,
814
        user_id: OwnedUserId,
815
        level: i64,
816
    ) -> Result<(), Error> {
817
        self.run(Command::Room(RoomCommand::SetUserPowerLevel {
818
            room_id,
819
            user_id,
820
            level,
821
        }))
822
        .await
823
    }
824
825
    pub async fn create_room(&self, options: CreateRoomOptions) -> Result<OwnedRoomId, Error> {
826
        self.run_for_room(RoomCommand::Create(options)).await
827
    }
828
829
    /// hands back the existing dm with them if there is one
830
    pub async fn create_direct(&self, user_id: OwnedUserId) -> Result<OwnedRoomId, Error> {
831
        self.run_for_room(RoomCommand::CreateDirect(user_id)).await
832
    }
833
834
    pub async fn cancel_send(
835
        &self,
836
        room_id: OwnedRoomId,
837
        transaction_id: OwnedTransactionId,
838
    ) -> Result<(), Error> {
839
        self.run(Command::Room(RoomCommand::CancelSend {
840
            room_id,
841
            transaction_id,
842
        }))
843
        .await
844
    }
845
846
    pub async fn retry_send(
847
        &self,
848
        room_id: OwnedRoomId,
849
        transaction_id: OwnedTransactionId,
850
    ) -> Result<(), Error> {
851
        self.run(Command::Room(RoomCommand::RetrySend {
852
            room_id,
853
            transaction_id,
854
        }))
855
        .await
856
    }
857
858
    pub async fn upload(&self, upload: AttachmentUpload) -> Result<(), Error> {
859
        self.run(Command::Media(MediaCommand::Upload(upload))).await
860
    }
861
862
    pub async fn recover(&self, recovery_key: impl Into<String>) -> Result<(), Error> {
863
        self.run(Command::Encryption(EncryptionCommand::Recover(
864
            recovery_key.into(),
865
        )))
866
        .await
867
    }
868
869
    /// the key is in the answer and nowhere else, so show it before dropping it
870
    pub async fn enable_recovery(&self, passphrase: Option<String>) -> Result<RecoveryKey, Error> {
871
        self.recovery_key(EncryptionCommand::EnableRecovery { passphrase })
872
            .await
873
    }
874
875
    pub async fn reset_recovery_key(
876
        &self,
877
        passphrase: Option<String>,
878
    ) -> Result<RecoveryKey, Error> {
879
        self.recovery_key(EncryptionCommand::ResetRecoveryKey { passphrase })
880
            .await
881
    }
882
883
    async fn recovery_key(&self, command: EncryptionCommand) -> Result<RecoveryKey, Error> {
884
        match self.execute(Command::Encryption(command)).await? {
885
            CommandOutput::RecoveryKey(key) => Ok(key),
886
            output => Err(unexpected("recovery key", &output)),
887
        }
888
    }
889
890
    pub async fn disable_recovery(&self) -> Result<(), Error> {
891
        self.run(Command::Encryption(EncryptionCommand::DisableRecovery))
892
            .await
893
    }
894
895
    /// checks a typed key or passphrase against secret storage without importing anything
896
    pub async fn verify_recovery_key(&self, recovery_key: &str) -> Result<bool, Error> {
897
        use matrix_sdk::encryption::secret_storage::SecretStorageError;
898
        match self
899
            .client
900
            .encryption()
901
            .secret_storage()
902
            .open_secret_store(recovery_key)
903
            .await
904
        {
905
            Ok(_) => Ok(true),
906
            Err(SecretStorageError::SecretStorageKey(_)) => Ok(false),
907
            Err(SecretStorageError::MissingKeyInfo { .. }) => {
908
                Err(Error::new(ErrorKind::Invalid, "recovery is not set up"))
909
            }
910
            Err(source) => Err(Error::from_recovery(
911
                "checking the recovery key failed",
912
                source.into(),
913
            )),
914
        }
915
    }
916
917
    pub async fn create_key_backup(&self) -> Result<(), Error> {
918
        self.run(Command::Encryption(EncryptionCommand::CreateKeyBackup))
919
            .await
920
    }
921
922
    pub async fn enable_key_backup(&self) -> Result<(), Error> {
923
        self.run(Command::Encryption(EncryptionCommand::EnableKeyBackup))
924
            .await
925
    }
926
927
    pub async fn disable_key_backup(&self, delete_from_server: bool) -> Result<(), Error> {
928
        self.run(Command::Encryption(EncryptionCommand::DisableKeyBackup {
929
            delete_from_server,
930
        }))
931
        .await
932
    }
933
934
    pub async fn recover_key_backup(&self, recovery_key: impl Into<String>) -> Result<(), Error> {
935
        self.run(Command::Encryption(EncryptionCommand::RecoverKeyBackup(
936
            recovery_key.into(),
937
        )))
938
        .await
939
    }
940
941
    pub async fn start_verification(&self, target: VerificationTarget) -> Result<(), Error> {
942
        self.run(Command::Encryption(EncryptionCommand::StartVerification(
943
            target,
944
        )))
945
        .await
946
    }
947
948
    pub async fn verify(&self, action: VerificationAction) -> Result<(), Error> {
949
        self.run(Command::Encryption(EncryptionCommand::Verify(action)))
950
            .await
951
    }
952
953
    /// bytes for an avatar, or for media a retained timeline showed you. concurrent asks share
954
    /// one download
955
    pub async fn media(&self, media_id: &MediaId, size: MediaSize) -> Result<Media, Error> {
956
        self.media.timeline_media(media_id, size).await
957
    }
958
959
    /// where `media` would fetch from, for a host streaming big files itself. the byte cap
960
    /// doesn't apply since nothing is buffered
961
    pub async fn media_source(
962
        &self,
963
        media_id: &MediaId,
964
        size: MediaSize,
965
    ) -> Result<MediaLocation, Error> {
966
        self.media.media_location(media_id, size).await
967
    }
968
969
    /// the space graph builds on first use and then keeps itself current
970
    async fn spaces(&self) -> &SpaceService {
971
        self.spaces
972
            .get_or_init(|| SpaceService::new(self.client.clone()))
973
            .await
974
    }
975
976
    pub async fn rooms(&self) -> Vec<RoomSummary> {
977
        room_summaries(&self.client, self.spaces().await).await
978
    }
979
980
    pub async fn image_packs(&self) -> Result<ImagePacks, Error> {
981
        image_packs(&self.client).await
982
    }
983
984
    /// the homeserver's preview of a page, `None` when it has none. the homeserver sees every
985
    /// url asked about, so a host decides whether encrypted rooms ask at all
986
    pub async fn url_preview(&self, url: &str) -> Result<Option<LinkPreview>, Error> {
987
        self.media.url_preview(url).await
988
    }
989
990
    /// whether the room and the user's settings let link previews show here
991
    pub async fn url_previews_allowed(&self, room_id: &RoomId) -> Result<bool, Error> {
992
        url_previews_allowed(&self.room(room_id)?).await
993
    }
994
995
    /// browse a space the way the server sees it, rooms you haven't joined included
996
    pub async fn space_hierarchy(&self, space_id: OwnedRoomId) -> SpaceHierarchy {
997
        SpaceHierarchy(SpaceRoomList::new(self.client.clone(), space_id).await)
998
    }
999
1000
    pub async fn own_profile(&self) -> Result<Profile, Error> {
1001
        let user_id = self
1002
            .client
1003
            .user_id()
1004
            .ok_or_else(|| Error::new(ErrorKind::Unauthenticated, "the client is not logged in"))?
1005
            .to_owned();
1006
        let response = self
1007
            .client
1008
            .account()
1009
            .fetch_user_profile()
1010
            .await
1011
            .map_err(|source| Error::from_sdk("fetching the profile failed", source))?;
1012
        profile(user_id, &response)
1013
    }
1014
1015
    pub async fn user_profile(&self, user_id: &UserId) -> Result<Profile, Error> {
1016
        let response = self
1017
            .client
1018
            .account()
1019
            .fetch_user_profile_of(user_id)
1020
            .await
1021
            .map_err(|source| Error::from_sdk("fetching the profile failed", source))?;
1022
        profile(user_id.to_owned(), &response)
1023
    }
1024
1025
    pub async fn set_display_name(&self, name: Option<String>) -> Result<(), Error> {
1026
        self.run(Command::Account(AccountCommand::SetDisplayName(name)))
1027
            .await
1028
    }
1029
1030
    pub async fn set_avatar(&self, avatar: Option<OwnedMxcUri>) -> Result<(), Error> {
1031
        self.run(Command::Account(AccountCommand::SetAvatar(avatar)))
1032
            .await
1033
    }
1034
1035
    pub async fn upload_avatar(
1036
        &self,
1037
        mimetype: impl Into<String>,
1038
        bytes: Vec<u8>,
1039
    ) -> Result<(), Error> {
1040
        self.run(Command::Account(AccountCommand::UploadAvatar {
1041
            mimetype: mimetype.into(),
1042
            bytes,
1043
        }))
1044
        .await
1045
    }
1046
1047
    /// unencrypted, for room avatars and anything else that only needs the uri
1048
    pub async fn upload_media(
1049
        &self,
1050
        mimetype: impl Into<String>,
1051
        bytes: Vec<u8>,
1052
    ) -> Result<OwnedMxcUri, Error> {
1053
        let command = Command::Account(AccountCommand::UploadMedia {
1054
            mimetype: mimetype.into(),
1055
            bytes,
1056
        });
1057
        match self.execute(command).await? {
1058
            CommandOutput::Mxc(uri) => Ok(uri),
1059
            output => Err(Error::new(
1060
                ErrorKind::Other,
1061
                format!("expected an mxc but got {output:?}"),
1062
            )),
1063
        }
1064
    }
1065
1066
    pub async fn ignore_user(&self, user_id: OwnedUserId) -> Result<(), Error> {
1067
        self.run(Command::Account(AccountCommand::IgnoreUser(user_id)))
1068
            .await
1069
    }
1070
1071
    pub async fn unignore_user(&self, user_id: OwnedUserId) -> Result<(), Error> {
1072
        self.run(Command::Account(AccountCommand::UnignoreUser(user_id)))
1073
            .await
1074
    }
1075
1076
    /// writes msc3230 orders so these spaces sort in exactly this sequence, touching as few as it can
1077
    pub async fn reorder_spaces(&self, order: Vec<OwnedRoomId>) -> Result<(), Error> {
1078
        self.run(Command::Account(AccountCommand::ReorderSpaces(order)))
1079
            .await
1080
    }
1081
1082
    /// adding m.favourite drops m.lowpriority, the way Element does
1083
    pub async fn set_favourite(&self, room_id: OwnedRoomId, favourite: bool) -> Result<(), Error> {
1084
        self.run(Command::Room(RoomCommand::SetFavourite {
1085
            room_id,
1086
            favourite,
1087
        }))
1088
        .await
1089
    }
1090
1091
    /// None goes back to your global name
1092
    pub async fn set_room_display_name(
1093
        &self,
1094
        room_id: OwnedRoomId,
1095
        name: Option<String>,
1096
    ) -> Result<(), Error> {
1097
        self.run(Command::Room(RoomCommand::SetOwnDisplayName {
1098
            room_id,
1099
            name,
1100
        }))
1101
        .await
1102
    }
1103
1104
    /// None goes back to your global avatar
1105
    pub async fn set_room_avatar(
1106
        &self,
1107
        room_id: OwnedRoomId,
1108
        avatar: Option<OwnedMxcUri>,
1109
    ) -> Result<(), Error> {
1110
        self.run(Command::Room(RoomCommand::SetOwnAvatar { room_id, avatar }))
1111
            .await
1112
    }
1113
1114
    /// muting drops them below what sending a message takes, unmuting puts a muted user back
1115
    /// at users_default and leaves anyone else alone. `ErrorKind::Forbidden` if you can't
1116
    pub async fn mute_user(
1117
        &self,
1118
        room_id: OwnedRoomId,
1119
        user_id: OwnedUserId,
1120
        muted: bool,
1121
    ) -> Result<(), Error> {
1122
        self.run(Command::Room(RoomCommand::MuteUser {
1123
            room_id,
1124
            user_id,
1125
            muted,
1126
        }))
1127
        .await
1128
    }
1129
1130
    /// body defaults to the shortcode
1131
    pub async fn send_sticker(
1132
        &self,
1133
        timeline_id: TimelineId,
1134
        pack_id: String,
1135
        shortcode: String,
1136
    ) -> Result<(), Error> {
1137
        self.run(Command::Pack(PackCommand::SendSticker {
1138
            timeline_id,
1139
            pack_id,
1140
            shortcode,
1141
        }))
1142
        .await
1143
    }
1144
1145
    /// fails if the state key already holds a pack
1146
    pub async fn create_room_pack(
1147
        &self,
1148
        room_id: OwnedRoomId,
1149
        state_key: String,
1150
        display_name: String,
1151
    ) -> Result<(), Error> {
1152
        self.run(Command::Pack(PackCommand::CreateRoomPack {
1153
            room_id,
1154
            state_key,
1155
            display_name,
1156
        }))
1157
        .await
1158
    }
1159
1160
    pub async fn delete_pack(&self, pack: ImagePackSource) -> Result<(), Error> {
1161
        self.run(Command::Pack(PackCommand::Delete(pack))).await
1162
    }
1163
1164
    pub async fn set_pack_meta(
1165
        &self,
1166
        pack: ImagePackSource,
1167
        display_name: Patch<String>,
1168
        avatar: Patch<OwnedMxcUri>,
1169
        usage: Patch<Vec<PackUsage>>,
1170
    ) -> Result<(), Error> {
1171
        self.run(Command::Pack(PackCommand::SetMeta {
1172
            pack,
1173
            display_name,
1174
            avatar,
1175
            usage,
1176
        }))
1177
        .await
1178
    }
1179
1180
    /// adds or replaces by shortcode. a replaced image keeps its info unless the url changed
1181
    pub async fn put_pack_image(
1182
        &self,
1183
        pack: ImagePackSource,
1184
        shortcode: String,
1185
        mxc: OwnedMxcUri,
1186
        body: Option<String>,
1187
        usage: Option<Vec<PackUsage>>,
1188
        info: Option<PackImageInfo>,
1189
    ) -> Result<(), Error> {
1190
        self.run(Command::Pack(PackCommand::PutImage {
1191
            pack,
1192
            shortcode,
1193
            mxc,
1194
            body,
1195
            usage,
1196
            info,
1197
        }))
1198
        .await
1199
    }
1200
1201
    pub async fn rename_pack_image(
1202
        &self,
1203
        pack: ImagePackSource,
1204
        from: String,
1205
        to: String,
1206
    ) -> Result<(), Error> {
1207
        self.run(Command::Pack(PackCommand::RenameImage { pack, from, to }))
1208
            .await
1209
    }
1210
1211
    pub async fn remove_pack_image(
1212
        &self,
1213
        pack: ImagePackSource,
1214
        shortcode: String,
1215
    ) -> Result<(), Error> {
1216
        self.run(Command::Pack(PackCommand::RemoveImage { pack, shortcode }))
1217
            .await
1218
    }
1219
1220
    pub async fn set_pack_enabled_everywhere(
1221
        &self,
1222
        room_id: OwnedRoomId,
1223
        state_key: String,
1224
        enabled: bool,
1225
    ) -> Result<(), Error> {
1226
        self.run(Command::Pack(PackCommand::SetEnabledEverywhere {
1227
            room_id,
1228
            state_key,
1229
            enabled,
1230
        }))
1231
        .await
1232
    }
1233
1234
    /// the same uri when it already lives on your homeserver
1235
    pub async fn rehost_media(&self, mxc: OwnedMxcUri) -> Result<OwnedMxcUri, Error> {
1236
        match self
1237
            .execute(Command::Pack(PackCommand::Rehost(mxc)))
1238
            .await?
1239
        {
1240
            CommandOutput::Mxc(uri) => Ok(uri),
1241
            output => Err(unexpected("an mxc", &output)),
1242
        }
1243
    }
1244
1245
    /// hands back the shortcode it ended up under. `usage` overrides what the image had
1246
    pub async fn clone_pack_image(
1247
        &self,
1248
        from_pack: String,
1249
        shortcode: String,
1250
        to: ImagePackSource,
1251
        usage: Option<Vec<PackUsage>>,
1252
    ) -> Result<String, Error> {
1253
        let (mut shortcodes, _) = self
1254
            .run_clone(PackCommand::CloneImage {
1255
                from_pack,
1256
                shortcode,
1257
                to,
1258
                usage,
1259
            })
1260
            .await?;
1261
        shortcodes
1262
            .pop()
1263
            .ok_or_else(|| unexpected("a shortcode", &CommandOutput::Done))
1264
    }
1265
1266
    /// hands back the shortcode it ended up under
1267
    pub async fn clone_sticker(
1268
        &self,
1269
        room_id: OwnedRoomId,
1270
        event_id: OwnedEventId,
1271
        to: ImagePackSource,
1272
        shortcode: String,
1273
        usage: Option<Vec<PackUsage>>,
1274
    ) -> Result<String, Error> {
1275
        let (mut shortcodes, _) = self
1276
            .run_clone(PackCommand::CloneSticker {
1277
                room_id,
1278
                event_id,
1279
                to,
1280
                shortcode,
1281
                usage,
1282
            })
1283
            .await?;
1284
        shortcodes
1285
            .pop()
1286
            .ok_or_else(|| unexpected("a shortcode", &CommandOutput::Done))
1287
    }
1288
1289
    /// hands back the shortcodes that couldn't be fetched, everything else is written
1290
    pub async fn clone_pack(
1291
        &self,
1292
        from_pack: String,
1293
        to: ImagePackSource,
1294
        display_name: Option<String>,
1295
        usage: Option<Vec<PackUsage>>,
1296
    ) -> Result<Vec<String>, Error> {
1297
        let (_, skipped) = self
1298
            .run_clone(PackCommand::ClonePack {
1299
                from_pack,
1300
                to,
1301
                display_name,
1302
                usage,
1303
            })
1304
            .await?;
1305
        Ok(skipped)
1306
    }
1307
1308
    async fn run_clone(&self, command: PackCommand) -> Result<(Vec<String>, Vec<String>), Error> {
1309
        match self.execute(Command::Pack(command)).await? {
1310
            CommandOutput::Cloned {
1311
                shortcodes,
1312
                skipped,
1313
            } => Ok((shortcodes, skipped)),
1314
            output => Err(unexpected("a clone", &output)),
1315
        }
1316
    }
1317
1318
    pub async fn rooms_filtered(&self, filter: &RoomListFilter) -> Vec<RoomSummary> {
1319
        let matcher = filter.matcher();
1320
        self.rooms()
1321
            .await
1322
            .into_iter()
1323
            .filter(|room| matcher.matches(room))
1324
            .collect()
1325
    }
1326
1327
    pub async fn devices(&self) -> Result<Vec<OwnDevice>, Error> {
1328
        let current_device_id = self.client.device_id();
1329
        Ok(self
1330
            .client
1331
            .devices()
1332
            .await
1333
            .map_err(|source| Error::from_http("listing devices failed", source))?
1334
            .devices
1335
            .into_iter()
1336
            .map(|device| OwnDevice {
1337
                current: current_device_id == Some(device.device_id.as_ref()),
1338
                device_id: device.device_id,
1339
                display_name: device.display_name,
1340
                last_seen_ip: device.last_seen_ip,
1341
                last_seen_timestamp_ms: device.last_seen_ts.map(|timestamp| timestamp.get().into()),
1342
            })
1343
            .collect())
1344
    }
1345
1346
    fn room(&self, room_id: &RoomId) -> Result<matrix_sdk::Room, Error> {
1347
        self.client
1348
            .get_room(room_id)
1349
            .ok_or_else(Error::room_unknown)
1350
    }
1351
1352
    pub async fn room_notification_settings(
1353
        &self,
1354
        room_id: &RoomId,
1355
    ) -> Result<Option<RoomNotificationSettings>, Error> {
1356
        let room = self.room(room_id)?;
1357
        let custom = room.user_defined_notification_mode().await;
1358
        let mode = match custom {
1359
            Some(mode) => mode,
1360
            None => match room.notification_mode().await {
1361
                Some(mode) => mode,
1362
                None => return Ok(None),
1363
            },
1364
        };
1365
        Ok(Some(RoomNotificationSettings {
1366
            mode: crate::normalise::notification_mode(mode),
1367
            custom: custom.is_some(),
1368
        }))
1369
    }
1370
1371
    pub async fn notification_keywords(&self) -> Vec<String> {
1372
        self.client
1373
            .notification_settings()
1374
            .await
1375
            .enabled_keywords()
1376
            .await
1377
            .into_iter()
1378
            .collect()
1379
    }
1380
1381
    pub async fn resolve_push_notification(
1382
        &self,
1383
        room_id: &RoomId,
1384
        event_id: &EventId,
1385
    ) -> Result<PushNotificationStatus, Error> {
1386
        let notification_failed = |source| {
1387
            Error::with_source(
1388
                ErrorKind::Other,
1389
                "resolving the push notification failed",
1390
                source,
1391
            )
1392
        };
1393
        let client = self
1394
            .notification_client
1395
            .get_or_try_init(|| async {
1396
                NotificationClient::new(
1397
                    self.client.clone(),
1398
                    NotificationProcessSetup::SingleProcess {
1399
                        sync_service: self.sync_service.clone(),
1400
                    },
1401
                )
1402
                .await
1403
            })
1404
            .await
1405
            .map_err(notification_failed)?;
1406
        let status = client
1407
            .get_notification(room_id, event_id)
1408
            .await
1409
            .map_err(notification_failed)?;
1410
        let unread = self
1411
            .client
1412
            .get_room(room_id)
1413
            .map(|room| (room.num_unread_notifications(), room.num_unread_mentions()));
1414
        Ok(normalise_push_notification(
1415
            room_id, event_id, status, unread,
1416
        ))
1417
    }
1418
1419
    pub async fn members(
1420
        &self,
1421
        room_id: &RoomId,
1422
        filter: MemberFilter,
1423
    ) -> Result<Vec<MemberSummary>, Error> {
1424
        let room = self.room(room_id)?;
1425
        // stores have been seen with members_synced persisted over a partial
1426
        // roster, and the sdk never asks /members again once it's set
1427
        if self
1428
            .members_checked
1429
            .lock()
1430
            .unwrap()
1431
            .insert(room_id.to_owned())
1432
        {
1433
            room.mark_members_missing();
1434
        }
1435
        list_members(&room, filter).await
1436
    }
1437
1438
    /// drop the cached roster for a room and fetch /members again
1439
    pub async fn refresh_members(
1440
        &self,
1441
        room_id: &RoomId,
1442
        filter: MemberFilter,
1443
    ) -> Result<Vec<MemberSummary>, Error> {
1444
        let room = self.room(room_id)?;
1445
        room.mark_members_missing();
1446
        self.members_checked
1447
            .lock()
1448
            .unwrap()
1449
            .insert(room_id.to_owned());
1450
        list_members(&room, filter).await
1451
    }
1452
1453
    pub async fn room_power_levels(&self, room_id: &RoomId) -> Result<RoomPowerLevels, Error> {
1454
        let levels = self
1455
            .room(room_id)?
1456
            .power_levels()
1457
            .await
1458
            .map_err(|source| Error::from_sdk("reading power levels failed", source.into()))?;
1459
        Ok(RoomPowerLevels {
1460
            ban: levels.ban.into(),
1461
            invite: levels.invite.into(),
1462
            kick: levels.kick.into(),
1463
            redact: levels.redact.into(),
1464
            events_default: levels.events_default.into(),
1465
            state_default: levels.state_default.into(),
1466
            users_default: levels.users_default.into(),
1467
            room_notification: levels.notifications.room.into(),
1468
            users: levels
1469
                .users
1470
                .into_iter()
1471
                .map(|(user_id, level)| (user_id, level.into()))
1472
                .collect(),
1473
            events: levels
1474
                .events
1475
                .into_iter()
1476
                .map(|(event_type, level)| (event_type.to_string(), level.into()))
1477
                .collect(),
1478
            creators: levels
1479
                .rules
1480
                .privileged_creators
1481
                .into_iter()
1482
                .flatten()
1483
                .collect(),
1484
        })
1485
    }
1486
1487
    pub async fn pending_send_ids(
1488
        &self,
1489
        room_id: &RoomId,
1490
    ) -> Result<Vec<OwnedTransactionId>, Error> {
1491
        let (echoes, _) = self
1492
            .room(room_id)?
1493
            .send_queue()
1494
            .subscribe()
1495
            .await
1496
            .map_err(|source| {
1497
                Error::with_source(ErrorKind::Store, "reading the send queue failed", source)
1498
            })?;
1499
        Ok(echoes
1500
            .into_iter()
1501
            .filter(|echo| {
1502
                matches!(
1503
                    echo.content,
1504
                    matrix_sdk::send_queue::LocalEchoContent::Event { .. }
1505
                )
1506
            })
1507
            .map(|echo| echo.transaction_id)
1508
            .collect())
1509
    }
1510
1511
    pub async fn global_account_data(
1512
        &self,
1513
        event_type: GlobalAccountDataEventType,
1514
    ) -> Result<Option<Raw<AnyGlobalAccountDataEventContent>>, Error> {
1515
        self.client
1516
            .account()
1517
            .account_data_raw(event_type)
1518
            .await
1519
            .map_err(|source| Error::from_sdk("reading account data failed", source))
1520
    }
1521
1522
    pub async fn room_account_data(
1523
        &self,
1524
        room_id: &RoomId,
1525
        event_type: RoomAccountDataEventType,
1526
    ) -> Result<Option<Raw<AnyRoomAccountDataEvent>>, Error> {
1527
        self.room(room_id)?
1528
            .account_data(event_type)
1529
            .await
1530
            .map_err(|source| Error::from_sdk("reading room account data failed", source))
1531
    }
1532
1533
    #[must_use]
1534
    pub fn connection_state(&self) -> watch::Receiver<ConnectionState> {
1535
        self.connection.clone()
1536
    }
1537
1538
    #[must_use]
1539
    pub fn encryption_state(&self) -> Option<EncryptionState> {
1540
        let device_id = self.client.device_id()?.to_owned();
1541
        let encryption = self.client.encryption();
1542
        Some(encryption_state(
1543
            device_id,
1544
            encryption.recovery().state(),
1545
            encryption.verification_state().get(),
1546
            encryption.backups().state(),
1547
        ))
1548
    }
1549
}
1550
1551
/// one space's hierarchy, a page at a time. hold on to it while you browse, it remembers where it got to
1552
pub struct SpaceHierarchy(SpaceRoomList);
1553
1554
impl fmt::Debug for SpaceHierarchy {
1555
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1556
        formatter
1557
            .debug_struct("SpaceHierarchy")
1558
            .finish_non_exhaustive()
1559
    }
1560
}
1561
1562
impl SpaceHierarchy {
1563
    /// fetches the next page. a no-op once the end is reached or while a page is in flight
1564
    pub async fn paginate(&self) -> Result<(), Error> {
1565
        self.0.paginate().await.map_err(|source| {
1566
            Error::with_source(
1567
                ErrorKind::Server,
1568
                "fetching the space hierarchy failed",
1569
                source,
1570
            )
1571
        })
1572
    }
1573
1574
    /// everything fetched so far, the space itself excluded
1575
    pub async fn rooms(&self) -> Vec<SpaceChild> {
1576
        self.0.rooms().await.into_iter().map(space_child).collect()
1577
    }
1578
1579
    #[must_use]
1580
    pub fn end_reached(&self) -> bool {
1581
        matches!(
1582
            self.0.pagination_state(),
1583
            SpaceRoomListPaginationState::Idle { end_reached: true }
1584
        )
1585
    }
1586
}
1587
1588
fn profile(
1589
    user_id: OwnedUserId,
1590
    response: &ruma::api::client::profile::get_profile::v3::Response,
1591
) -> Result<Profile, Error> {
1592
    use ruma::api::client::profile::{AvatarUrl, DisplayName};
1593
    let malformed = |source| {
1594
        Error::with_source(
1595
            ErrorKind::Server,
1596
            "the server sent a malformed profile",
1597
            source,
1598
        )
1599
    };
1600
    Ok(Profile {
1601
        user_id,
1602
        display_name: response.get_static::<DisplayName>().map_err(malformed)?,
1603
        avatar: response
1604
            .get_static::<AvatarUrl>()
1605
            .map_err(malformed)?
1606
            .as_deref()
1607
            .map(MediaId::plain),
1608
    })
1609
}
1610
1611
#[derive(Debug)]
1612
pub(crate) struct Request {
1613
    pub(crate) command: Command,
1614
    pub(crate) reply: Responder,
1615
}
1616
1617
impl Request {
1618
    fn new(command: Command) -> (Self, Pending) {
1619
        let (reply, pending) = oneshot::channel();
1620
        (
1621
            Self {
1622
                command,
1623
                reply: Responder(reply),
1624
            },
1625
            Pending(pending),
1626
        )
1627
    }
1628
}
1629
1630
/// whoever holds this owes the caller an answer. dropping it answers `Interrupted`
1631
#[derive(Debug)]
1632
pub(crate) struct Responder(oneshot::Sender<Result<CommandOutput, Error>>);
1633
1634
impl Responder {
1635
    pub(crate) fn channel() -> (Self, Pending) {
1636
        let (reply, pending) = oneshot::channel();
1637
        (Self(reply), Pending(pending))
1638
    }
1639
1640
    pub(crate) fn send(self, result: Result<CommandOutput, Error>) {
1641
        if let Err(error) = &result {
1642
            tracing::debug!(%error, kind = ?error.kind, "command failed");
1643
        }
1644
        let _ = self.0.send(result);
1645
    }
1646
1647
    pub(crate) fn done(self, result: Result<(), Error>) {
1648
        self.send(result.map(|()| CommandOutput::Done));
1649
    }
1650
}
1651
1652
#[derive(Debug)]
1653
pub struct EventStream {
1654
    events: mpsc::Receiver<Event>,
1655
}
1656
1657
impl EventStream {
1658
    pub async fn recv(&mut self) -> Option<Event> {
1659
        self.events.recv().await
1660
    }
1661
1662
    pub fn try_recv(&mut self) -> Result<Event, mpsc::error::TryRecvError> {
1663
        self.events.try_recv()
1664
    }
1665
}
1666
1667
impl futures_util::Stream for EventStream {
1668
    type Item = Event;
1669
1670
    fn poll_next(mut self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Option<Self::Item>> {
1671
        self.events.poll_recv(context)
1672
    }
1673
}
1674
1675
#[derive(Debug)]
1676
pub(crate) struct DriverPort {
1677
    requests: mpsc::Receiver<Request>,
1678
    events: mpsc::Sender<Event>,
1679
    connection: watch::Sender<ConnectionState>,
1680
    publication: Arc<PublicationRegistry>,
1681
}
1682
1683
#[doc(hidden)]
1684
#[derive(Clone, Debug)]
1685
pub struct PublicationAck(Arc<PublicationAckInner>);
1686
1687
#[derive(Debug)]
1688
struct PublicationAckInner {
1689
    sender: Mutex<Option<oneshot::Sender<bool>>>,
1690
}
1691
1692
impl PartialEq for PublicationAck {
1693
    fn eq(&self, other: &Self) -> bool {
1694
        Arc::ptr_eq(&self.0, &other.0)
1695
    }
1696
}
1697
1698
impl Eq for PublicationAck {}
1699
1700
impl PublicationAck {
1701
    pub(crate) fn complete(&self, published: bool) {
1702
        let sender = self
1703
            .0
1704
            .sender
1705
            .lock()
1706
            .unwrap_or_else(|error| error.into_inner())
1707
            .take();
1708
        if let Some(sender) = sender {
1709
            let _ = sender.send(published);
1710
        }
1711
    }
1712
}
1713
1714
#[derive(Debug, Default)]
1715
struct PublicationRegistry {
1716
    state: Mutex<PublicationRegistryState>,
1717
}
1718
1719
#[derive(Debug, Default)]
1720
struct PublicationRegistryState {
1721
    mirrors: usize,
1722
    pending: Vec<Weak<PublicationAckInner>>,
1723
}
1724
1725
impl PublicationRegistry {
1726
    fn register_mirror(self: &Arc<Self>) -> PublicationRegistration {
1727
        self.state
1728
            .lock()
1729
            .unwrap_or_else(|error| error.into_inner())
1730
            .mirrors += 1;
1731
        PublicationRegistration {
1732
            registry: self.clone(),
1733
        }
1734
    }
1735
1736
    fn acknowledgement(&self) -> Option<(PublicationAck, oneshot::Receiver<bool>)> {
1737
        let mut state = self.state.lock().unwrap_or_else(|error| error.into_inner());
1738
        if state.mirrors == 0 {
1739
            return None;
1740
        }
1741
        state.pending.retain(|pending| pending.strong_count() > 0);
1742
        let (sender, receiver) = oneshot::channel();
1743
        let acknowledgement = PublicationAck(Arc::new(PublicationAckInner {
1744
            sender: Mutex::new(Some(sender)),
1745
        }));
1746
        state.pending.push(Arc::downgrade(&acknowledgement.0));
1747
        Some((acknowledgement, receiver))
1748
    }
1749
}
1750
1751
pub(crate) struct PublicationRegistration {
1752
    registry: Arc<PublicationRegistry>,
1753
}
1754
1755
impl Drop for PublicationRegistration {
1756
    fn drop(&mut self) {
1757
        let pending = {
1758
            let mut state = self
1759
                .registry
1760
                .state
1761
                .lock()
1762
                .unwrap_or_else(|error| error.into_inner());
1763
            state.mirrors -= 1;
1764
            if state.mirrors == 0 {
1765
                std::mem::take(&mut state.pending)
1766
            } else {
1767
                Vec::new()
1768
            }
1769
        };
1770
        for acknowledgement in pending.into_iter().filter_map(|ack| ack.upgrade()) {
1771
            PublicationAck(acknowledgement).complete(false);
1772
        }
1773
    }
1774
}
1775
1776
#[derive(Clone, Debug)]
1777
pub(crate) struct EventSender {
1778
    events: mpsc::Sender<Event>,
1779
    publication: Arc<PublicationRegistry>,
1780
}
1781
1782
impl EventSender {
1783
    #[cfg(all(test, feature = "sqlite"))]
1784
    pub(crate) fn detached(capacity: usize) -> (Self, mpsc::Receiver<Event>) {
1785
        let (events, receiver) = mpsc::channel(capacity);
1786
        let sender = Self {
1787
            events,
1788
            publication: Arc::new(PublicationRegistry::default()),
1789
        };
1790
        (sender, receiver)
1791
    }
1792
1793
    pub(crate) async fn send(&self, event: Event) -> Result<(), SendEventError> {
1794
        self.events.send(event).await.map_err(|_| SendEventError)
1795
    }
1796
1797
    pub(crate) async fn publish_barrier(&self) -> Result<(), SendEventError> {
1798
        let Some((acknowledgement, published)) = self.publication.acknowledgement() else {
1799
            return Ok(());
1800
        };
1801
        if let Err(error) = self
1802
            .send(Event::PublicationBarrier {
1803
                acknowledgement: Some(acknowledgement.clone()),
1804
            })
1805
            .await
1806
        {
1807
            acknowledgement.complete(false);
1808
            return Err(error);
1809
        }
1810
        drop(acknowledgement);
1811
        match published.await {
1812
            Ok(true) => Ok(()),
1813
            Ok(false) | Err(_) => Err(SendEventError),
1814
        }
1815
    }
1816
1817
    pub(crate) async fn reserve(&self) -> Result<mpsc::Permit<'_, Event>, SendEventError> {
1818
        self.events.reserve().await.map_err(|_| SendEventError)
1819
    }
1820
1821
    /// resolves once every `EventStream` is gone
1822
    pub(crate) async fn closed(&self) {
1823
        self.events.closed().await;
1824
    }
1825
}
1826
1827
impl DriverPort {
1828
    pub(crate) fn event_sender(&self) -> EventSender {
1829
        EventSender {
1830
            events: self.events.clone(),
1831
            publication: self.publication.clone(),
1832
        }
1833
    }
1834
1835
    pub(crate) fn connection_sender(&self) -> watch::Sender<ConnectionState> {
1836
        self.connection.clone()
1837
    }
1838
1839
    pub(crate) async fn recv(&mut self) -> Option<Request> {
1840
        self.requests.recv().await
1841
    }
1842
1843
    pub(crate) fn try_recv(&mut self) -> Option<Request> {
1844
        self.requests.try_recv().ok()
1845
    }
1846
1847
    pub(crate) fn close(&mut self) {
1848
        self.requests.close();
1849
    }
1850
}
1851
1852
#[derive(Clone, Copy, Debug, Eq, PartialEq, thiserror::Error)]
1853
#[error("the event receiver has closed")]
1854
pub(crate) struct SendEventError;
1855
1856
#[must_use]
1857
pub(crate) fn transport_pair(
1858
    client: Client,
1859
    sync_service: Arc<SyncService>,
1860
    media: Arc<MediaService>,
1861
    store_lock: Option<Arc<StoreLock>>,
1862
    command_capacity: usize,
1863
    event_capacity: usize,
1864
) -> (ClientHandle, DriverPort, EventStream) {
1865
    let (request_tx, request_rx) = mpsc::channel(command_capacity);
1866
    let (event_tx, event_rx) = mpsc::channel(event_capacity);
1867
    let (connection_tx, connection_rx) = watch::channel(ConnectionState::Idle);
1868
    let publication = Arc::new(PublicationRegistry::default());
1869
    (
1870
        ClientHandle {
1871
            requests: request_tx,
1872
            client,
1873
            connection: connection_rx,
1874
            notification_client: Arc::new(OnceCell::new()),
1875
            sync_service,
1876
            media,
1877
            spaces: Arc::new(OnceCell::new()),
1878
            members_checked: Arc::default(),
1879
            publication: publication.clone(),
1880
            _store_lock: store_lock,
1881
        },
1882
        DriverPort {
1883
            requests: request_rx,
1884
            events: event_tx,
1885
            connection: connection_tx,
1886
            publication,
1887
        },
1888
        EventStream { events: event_rx },
1889
    )
1890
}
1891
1892
async fn list_members(
1893
    room: &matrix_sdk::Room,
1894
    filter: MemberFilter,
1895
) -> Result<Vec<MemberSummary>, Error> {
1896
    let memberships = match filter {
1897
        MemberFilter::Active => RoomMemberships::ACTIVE,
1898
        MemberFilter::Joined => RoomMemberships::JOIN,
1899
        MemberFilter::Invited => RoomMemberships::INVITE,
1900
        MemberFilter::Banned => RoomMemberships::BAN,
1901
        MemberFilter::Left => RoomMemberships::LEAVE,
1902
        // An empty mask is the SDK's unfiltered wildcard: it matches every
1903
        // membership, including custom states that `all()` would exclude.
1904
        MemberFilter::All => RoomMemberships::empty(),
1905
    };
1906
    Ok(room
1907
        .members(memberships)
1908
        .await
1909
        .map_err(|source| Error::from_sdk("listing members failed", source))?
1910
        .into_iter()
1911
        .map(member_summary)
1912
        .collect())
1913
}
1914
1915
fn normalise_push_notification(
1916
    room_id: &RoomId,
1917
    event_id: &EventId,
1918
    status: NotificationStatus,
1919
    unread: Option<(u64, u64)>,
1920
) -> PushNotificationStatus {
1921
    match status {
1922
        NotificationStatus::Event(item) => {
1923
            let event = match &item.raw_event {
1924
                RawNotificationEvent::Timeline(event) => {
1925
                    PushNotificationEvent::Timeline(event.clone())
1926
                }
1927
                RawNotificationEvent::Invite(event) => PushNotificationEvent::Invite(event.clone()),
1928
            };
1929
            PushNotificationStatus::Event(Box::new(PushNotification {
1930
                event_id: event_id.to_owned(),
1931
                event,
1932
                sender: PushNotificationSender {
1933
                    user_id: item.event.sender().to_owned(),
1934
                    display_name: item.sender_display_name,
1935
                    avatar_url: item.sender_avatar_url.as_deref().map(MediaId::plain),
1936
                    name_ambiguous: item.is_sender_name_ambiguous,
1937
                },
1938
                room: PushNotificationRoom {
1939
                    room_id: room_id.to_owned(),
1940
                    unread_notifications: unread.map(|(notifications, _)| notifications),
1941
                    unread_mentions: unread.map(|(_, mentions)| mentions),
1942
                    name: item.room_computed_display_name,
1943
                    avatar_url: item.room_avatar_url.as_deref().map(MediaId::plain),
1944
                    canonical_alias: item
1945
                        .room_canonical_alias
1946
                        .as_deref()
1947
                        .and_then(|alias| alias.parse().ok()),
1948
                    topic: item.room_topic,
1949
                    encrypted: item.is_room_encrypted,
1950
                    direct: item.room_is_dm,
1951
                    joined_members_count: item.joined_members_count,
1952
                    service_members: item
1953
                        .service_members
1954
                        .into_iter()
1955
                        .filter_map(|user_id| user_id.parse().ok())
1956
                        .collect(),
1957
                    active_service_members_count: item.active_service_members_count,
1958
                    space: item.is_space,
1959
                },
1960
                noisy: item.is_noisy,
1961
                has_mention: item.has_mention,
1962
                thread_id: item.thread_id,
1963
            }))
1964
        }
1965
        NotificationStatus::EventNotFound => PushNotificationStatus::NotFound,
1966
        NotificationStatus::EventFilteredOut => PushNotificationStatus::FilteredOut,
1967
        NotificationStatus::EventRedacted => PushNotificationStatus::Redacted,
1968
    }
1969
}
1970
1971
pub(crate) async fn remove_pusher(client: &Client, pusher: PusherKey) -> Result<(), Error> {
1972
    use ruma::api::client::push::PusherIds;
1973
    client
1974
        .pusher()
1975
        .delete(PusherIds::new(pusher.pushkey, pusher.app_id))
1976
        .await
1977
        .map_err(|source| Error::from_sdk("removing the pusher failed", source))
1978
}
1979
1980
fn unexpected(wanted: &str, output: &CommandOutput) -> Error {
1981
    Error::new(
1982
        ErrorKind::Other,
1983
        format!("expected {wanted} but got {output:?}"),
1984
    )
1985
}
1986
1987
#[cfg(test)]
1988
mod tests {
1989
    use super::*;
1990
1991
    #[tokio::test]
1992
    async fn publish_barrier_waits_for_mirror_publication() {
1993
        let publication = Arc::new(PublicationRegistry::default());
1994
        let _registration = publication.register_mirror();
1995
        let (events, mut received) = mpsc::channel(1);
1996
        let sender = EventSender {
1997
            events,
1998
            publication,
1999
        };
2000
        let publisher = tokio::spawn(async move { sender.publish_barrier().await });
2001
2002
        let Some(Event::PublicationBarrier {
2003
            acknowledgement: Some(acknowledgement),
2004
        }) = received.recv().await
2005
        else {
2006
            panic!("barrier was not published");
2007
        };
2008
        tokio::task::yield_now().await;
2009
        assert!(!publisher.is_finished());
2010
2011
        acknowledgement.complete(true);
2012
        assert!(matches!(publisher.await.unwrap(), Ok(())));
2013
    }
2014
2015
    #[tokio::test]
2016
    async fn publish_barrier_fails_when_last_mirror_drops() {
2017
        let publication = Arc::new(PublicationRegistry::default());
2018
        let registration = publication.register_mirror();
2019
        let (events, mut received) = mpsc::channel(1);
2020
        let sender = EventSender {
2021
            events,
2022
            publication,
2023
        };
2024
        let publisher = tokio::spawn(async move { sender.publish_barrier().await });
2025
2026
        assert!(matches!(
2027
            received.recv().await,
2028
            Some(Event::PublicationBarrier {
2029
                acknowledgement: Some(_),
2030
            })
2031
        ));
2032
        drop(registration);
2033
2034
        assert!(matches!(publisher.await.unwrap(), Err(SendEventError)));
2035
    }
2036
}