atagen

macltok

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