atagen

macltok

public
Star0
Watch0
3dab4a4e73f48986ea8e34215bdeb7d5d3870773
3
ssh://brewery@pls.lobotomise.me:33/atagen/macltok.git

Blame: src/engine/commands.rs

31ce2453c82fatagen2026-09-24 13:07 UTC1
use std::{future::Future, panic::AssertUnwindSafe};
31ce2453c82fatagen2026-09-24 13:07 UTC2
31ce2453c82fatagen2026-09-24 13:07 UTC3
use futures_util::FutureExt;
5a52012c9b1datagen2026-09-24 23:45 UTC4
use matrix_sdk::encryption::verification::VerificationRequest;
31ce2453c82fatagen2026-09-24 13:07 UTC5
use matrix_sdk::{
31ce2453c82fatagen2026-09-24 13:07 UTC6
    Client,
e9af9e0e0f31atagen2026-10-25 10:55 UTC7
    notification_settings::{IsEncrypted, IsOneToOne, RoomNotificationMode as SdkMode},
31ce2453c82fatagen2026-09-24 13:07 UTC8
    ruma::{
9bd34e25bb86atagen2026-10-24 07:23 UTC9
        OwnedDeviceId, OwnedEventId, OwnedMxcUri, RoomId, TransactionId,
e9af9e0e0f31atagen2026-10-25 10:55 UTC10
        api::client::{
e9af9e0e0f31atagen2026-10-25 10:55 UTC11
            push::{get_pushrule, set_pushrule_actions},
e9af9e0e0f31atagen2026-10-25 10:55 UTC12
            receipt::create_receipt::v3::ReceiptType,
e9af9e0e0f31atagen2026-10-25 10:55 UTC13
        },
5b585cd970fdatagen2026-10-25 09:20 UTC14
        events::receipt::ReceiptThread,
e9af9e0e0f31atagen2026-10-25 10:55 UTC15
        push::{Action, HighlightTweakValue, RuleKind, Tweak},
31ce2453c82fatagen2026-09-24 13:07 UTC16
    },
31ce2453c82fatagen2026-09-24 13:07 UTC17
    send_queue::{LocalEchoContent, SendHandle},
31ce2453c82fatagen2026-09-24 13:07 UTC18
};
743123167e29atagen2026-10-26 04:02 UTC19
use tokio::sync::{mpsc, watch};
31ce2453c82fatagen2026-09-24 13:07 UTC20
31ce2453c82fatagen2026-09-24 13:07 UTC21
use crate::{
743123167e29atagen2026-10-26 04:02 UTC22
    CommandOutput, EncryptionCommand, Error, ErrorKind, InteractiveAuth, InteractiveAuthChallenge,
743123167e29atagen2026-10-26 04:02 UTC23
    NotificationCommand, RecoveryKey, RoomNotificationMode, TimelineId,
31ce2453c82fatagen2026-09-24 13:07 UTC24
    encryption::VerificationController,
31ce2453c82fatagen2026-09-24 13:07 UTC25
    transport::{EventSender, Responder},
31ce2453c82fatagen2026-09-24 13:07 UTC26
};
31ce2453c82fatagen2026-09-24 13:07 UTC27
31ce2453c82fatagen2026-09-24 13:07 UTC28
pub(super) struct Job<T> {
31ce2453c82fatagen2026-09-24 13:07 UTC29
    pub(super) command: T,
31ce2453c82fatagen2026-09-24 13:07 UTC30
    pub(super) reply: Responder,
31ce2453c82fatagen2026-09-24 13:07 UTC31
}
31ce2453c82fatagen2026-09-24 13:07 UTC32
31ce2453c82fatagen2026-09-24 13:07 UTC33
/// a panicking command answers with an error instead of taking its worker down with it
31ce2453c82fatagen2026-09-24 13:07 UTC34
pub(super) async fn guarded<T>(
31ce2453c82fatagen2026-09-24 13:07 UTC35
    label: &'static str,
31ce2453c82fatagen2026-09-24 13:07 UTC36
    work: impl Future<Output = Result<T, Error>>,
31ce2453c82fatagen2026-09-24 13:07 UTC37
) -> Result<T, Error> {
31ce2453c82fatagen2026-09-24 13:07 UTC38
    AssertUnwindSafe(work)
31ce2453c82fatagen2026-09-24 13:07 UTC39
        .catch_unwind()
31ce2453c82fatagen2026-09-24 13:07 UTC40
        .await
31ce2453c82fatagen2026-09-24 13:07 UTC41
        .unwrap_or_else(|_| Err(Error::new(ErrorKind::Other, format!("{label} panicked"))))
31ce2453c82fatagen2026-09-24 13:07 UTC42
}
31ce2453c82fatagen2026-09-24 13:07 UTC43
31ce2453c82fatagen2026-09-24 13:07 UTC44
pub(super) async fn run_notifications(
31ce2453c82fatagen2026-09-24 13:07 UTC45
    client: Client,
31ce2453c82fatagen2026-09-24 13:07 UTC46
    mut jobs: mpsc::Receiver<Job<NotificationCommand>>,
31ce2453c82fatagen2026-09-24 13:07 UTC47
) {
31ce2453c82fatagen2026-09-24 13:07 UTC48
    while let Some(Job { command, reply }) = jobs.recv().await {
31ce2453c82fatagen2026-09-24 13:07 UTC49
        reply.done(guarded("notification command", notification(&client, command)).await);
31ce2453c82fatagen2026-09-24 13:07 UTC50
    }
31ce2453c82fatagen2026-09-24 13:07 UTC51
}
31ce2453c82fatagen2026-09-24 13:07 UTC52
31ce2453c82fatagen2026-09-24 13:07 UTC53
pub(super) async fn run_encryption(
31ce2453c82fatagen2026-09-24 13:07 UTC54
    client: Client,
31ce2453c82fatagen2026-09-24 13:07 UTC55
    events: EventSender,
31ce2453c82fatagen2026-09-24 13:07 UTC56
    mut jobs: mpsc::Receiver<Job<EncryptionCommand>>,
5a52012c9b1datagen2026-09-24 23:45 UTC57
    mut incoming: mpsc::UnboundedReceiver<VerificationRequest>,
743123167e29atagen2026-10-26 04:02 UTC58
    enabling: watch::Sender<bool>,
31ce2453c82fatagen2026-09-24 13:07 UTC59
) {
31ce2453c82fatagen2026-09-24 13:07 UTC60
    let mut verification = VerificationController::default();
5a52012c9b1datagen2026-09-24 23:45 UTC61
    loop {
5a52012c9b1datagen2026-09-24 23:45 UTC62
        tokio::select! {
5a52012c9b1datagen2026-09-24 23:45 UTC63
            job = jobs.recv() => {
5a52012c9b1datagen2026-09-24 23:45 UTC64
                let Some(Job { command, reply }) = job else { return };
743123167e29atagen2026-10-26 04:02 UTC65
                let work = encryption(&client, &events, &mut verification, &enabling, command);
743123167e29atagen2026-10-26 04:02 UTC66
                reply.send(guarded("encryption command", work).await);
5a52012c9b1datagen2026-09-24 23:45 UTC67
            }
5a52012c9b1datagen2026-09-24 23:45 UTC68
            Some(request) = incoming.recv() => {
5a52012c9b1datagen2026-09-24 23:45 UTC69
                verification.adopt_incoming(events.clone(), request).await;
5a52012c9b1datagen2026-09-24 23:45 UTC70
            }
5a52012c9b1datagen2026-09-24 23:45 UTC71
        }
31ce2453c82fatagen2026-09-24 13:07 UTC72
    }
31ce2453c82fatagen2026-09-24 13:07 UTC73
}
31ce2453c82fatagen2026-09-24 13:07 UTC74
31ce2453c82fatagen2026-09-24 13:07 UTC75
async fn encryption(
31ce2453c82fatagen2026-09-24 13:07 UTC76
    client: &Client,
31ce2453c82fatagen2026-09-24 13:07 UTC77
    events: &EventSender,
31ce2453c82fatagen2026-09-24 13:07 UTC78
    verification: &mut VerificationController,
743123167e29atagen2026-10-26 04:02 UTC79
    enabling: &watch::Sender<bool>,
31ce2453c82fatagen2026-09-24 13:07 UTC80
    command: EncryptionCommand,
743123167e29atagen2026-10-26 04:02 UTC81
) -> Result<CommandOutput, Error> {
31ce2453c82fatagen2026-09-24 13:07 UTC82
    let encryption = client.encryption();
743123167e29atagen2026-10-26 04:02 UTC83
    let recovery = encryption.recovery();
743123167e29atagen2026-10-26 04:02 UTC84
    let key = |key: String| CommandOutput::RecoveryKey(RecoveryKey::new(key));
743123167e29atagen2026-10-26 04:02 UTC85
    let done = CommandOutput::Done;
31ce2453c82fatagen2026-09-24 13:07 UTC86
    match command {
743123167e29atagen2026-10-26 04:02 UTC87
        EncryptionCommand::EnableRecovery { passphrase } => {
743123167e29atagen2026-10-26 04:02 UTC88
            let _enabling = Enabling::start(enabling);
743123167e29atagen2026-10-26 04:02 UTC89
            let enable = recovery.enable();
743123167e29atagen2026-10-26 04:02 UTC90
            match passphrase.as_deref() {
743123167e29atagen2026-10-26 04:02 UTC91
                Some(passphrase) => enable.with_passphrase(passphrase).await,
743123167e29atagen2026-10-26 04:02 UTC92
                None => enable.await,
743123167e29atagen2026-10-26 04:02 UTC93
            }
743123167e29atagen2026-10-26 04:02 UTC94
            .map(key)
743123167e29atagen2026-10-26 04:02 UTC95
            .map_err(|source| Error::from_recovery("enabling recovery failed", source))
743123167e29atagen2026-10-26 04:02 UTC96
        }
743123167e29atagen2026-10-26 04:02 UTC97
        EncryptionCommand::ResetRecoveryKey { passphrase } => {
743123167e29atagen2026-10-26 04:02 UTC98
            let reset = recovery.reset_key();
743123167e29atagen2026-10-26 04:02 UTC99
            match passphrase.as_deref() {
743123167e29atagen2026-10-26 04:02 UTC100
                Some(passphrase) => reset.with_passphrase(passphrase).await,
743123167e29atagen2026-10-26 04:02 UTC101
                None => reset.await,
743123167e29atagen2026-10-26 04:02 UTC102
            }
743123167e29atagen2026-10-26 04:02 UTC103
            .map(key)
743123167e29atagen2026-10-26 04:02 UTC104
            .map_err(|source| Error::from_recovery("resetting the recovery key failed", source))
743123167e29atagen2026-10-26 04:02 UTC105
        }
743123167e29atagen2026-10-26 04:02 UTC106
        EncryptionCommand::DisableRecovery => recovery
743123167e29atagen2026-10-26 04:02 UTC107
            .disable()
743123167e29atagen2026-10-26 04:02 UTC108
            .await
743123167e29atagen2026-10-26 04:02 UTC109
            .map(|()| done)
743123167e29atagen2026-10-26 04:02 UTC110
            .map_err(|source| Error::from_recovery("disabling recovery failed", source)),
743123167e29atagen2026-10-26 04:02 UTC111
        EncryptionCommand::Recover(key) => recovery
31ce2453c82fatagen2026-09-24 13:07 UTC112
            .recover(&key)
31ce2453c82fatagen2026-09-24 13:07 UTC113
            .await
743123167e29atagen2026-10-26 04:02 UTC114
            .map(|()| done)
31ce2453c82fatagen2026-09-24 13:07 UTC115
            .map_err(|source| Error::from_recovery("account recovery failed", source)),
743123167e29atagen2026-10-26 04:02 UTC116
        EncryptionCommand::RecoverKeyBackup(key) => recovery
31ce2453c82fatagen2026-09-24 13:07 UTC117
            .recover_and_fix_backup(&key)
31ce2453c82fatagen2026-09-24 13:07 UTC118
            .await
743123167e29atagen2026-10-26 04:02 UTC119
            .map(|()| done)
31ce2453c82fatagen2026-09-24 13:07 UTC120
            .map_err(|source| Error::from_recovery("recovering key backup failed", source)),
31ce2453c82fatagen2026-09-24 13:07 UTC121
        EncryptionCommand::CreateKeyBackup => encryption
31ce2453c82fatagen2026-09-24 13:07 UTC122
            .backups()
31ce2453c82fatagen2026-09-24 13:07 UTC123
            .create()
31ce2453c82fatagen2026-09-24 13:07 UTC124
            .await
743123167e29atagen2026-10-26 04:02 UTC125
            .map(|()| done)
31ce2453c82fatagen2026-09-24 13:07 UTC126
            .map_err(|source| Error::from_sdk("creating key backup failed", source)),
743123167e29atagen2026-10-26 04:02 UTC127
        EncryptionCommand::EnableKeyBackup => recovery
31ce2453c82fatagen2026-09-24 13:07 UTC128
            .enable_backup()
31ce2453c82fatagen2026-09-24 13:07 UTC129
            .await
743123167e29atagen2026-10-26 04:02 UTC130
            .map(|()| done)
31ce2453c82fatagen2026-09-24 13:07 UTC131
            .map_err(|source| Error::from_recovery("enabling key backup failed", source)),
31ce2453c82fatagen2026-09-24 13:07 UTC132
        EncryptionCommand::DisableKeyBackup { delete_from_server } => {
31ce2453c82fatagen2026-09-24 13:07 UTC133
            let backups = encryption.backups();
31ce2453c82fatagen2026-09-24 13:07 UTC134
            if delete_from_server {
31ce2453c82fatagen2026-09-24 13:07 UTC135
                backups.disable_and_delete().await
31ce2453c82fatagen2026-09-24 13:07 UTC136
            } else {
31ce2453c82fatagen2026-09-24 13:07 UTC137
                backups.disable().await
31ce2453c82fatagen2026-09-24 13:07 UTC138
            }
743123167e29atagen2026-10-26 04:02 UTC139
            .map(|()| done)
31ce2453c82fatagen2026-09-24 13:07 UTC140
            .map_err(|source| Error::from_sdk("disabling key backup failed", source))
31ce2453c82fatagen2026-09-24 13:07 UTC141
        }
743123167e29atagen2026-10-26 04:02 UTC142
        EncryptionCommand::StartVerification(target) => verification
743123167e29atagen2026-10-26 04:02 UTC143
            .start(client, events.clone(), target)
743123167e29atagen2026-10-26 04:02 UTC144
            .await
743123167e29atagen2026-10-26 04:02 UTC145
            .map(|()| done),
743123167e29atagen2026-10-26 04:02 UTC146
        EncryptionCommand::Verify(action) => verification
743123167e29atagen2026-10-26 04:02 UTC147
            .apply(events.clone(), action)
743123167e29atagen2026-10-26 04:02 UTC148
            .await
743123167e29atagen2026-10-26 04:02 UTC149
            .map(|()| done),
743123167e29atagen2026-10-26 04:02 UTC150
    }
743123167e29atagen2026-10-26 04:02 UTC151
}
743123167e29atagen2026-10-26 04:02 UTC152
743123167e29atagen2026-10-26 04:02 UTC153
/// a panicking enable must not leave the state saying it is still running
743123167e29atagen2026-10-26 04:02 UTC154
struct Enabling<'a>(&'a watch::Sender<bool>);
743123167e29atagen2026-10-26 04:02 UTC155
743123167e29atagen2026-10-26 04:02 UTC156
impl<'a> Enabling<'a> {
743123167e29atagen2026-10-26 04:02 UTC157
    fn start(sender: &'a watch::Sender<bool>) -> Self {
743123167e29atagen2026-10-26 04:02 UTC158
        sender.send_replace(true);
743123167e29atagen2026-10-26 04:02 UTC159
        Self(sender)
743123167e29atagen2026-10-26 04:02 UTC160
    }
743123167e29atagen2026-10-26 04:02 UTC161
}
743123167e29atagen2026-10-26 04:02 UTC162
743123167e29atagen2026-10-26 04:02 UTC163
impl Drop for Enabling<'_> {
743123167e29atagen2026-10-26 04:02 UTC164
    fn drop(&mut self) {
743123167e29atagen2026-10-26 04:02 UTC165
        self.0.send_replace(false);
31ce2453c82fatagen2026-09-24 13:07 UTC166
    }
31ce2453c82fatagen2026-09-24 13:07 UTC167
}
31ce2453c82fatagen2026-09-24 13:07 UTC168
31ce2453c82fatagen2026-09-24 13:07 UTC169
async fn notification(client: &Client, command: NotificationCommand) -> Result<(), Error> {
31ce2453c82fatagen2026-09-24 13:07 UTC170
    let settings = client.notification_settings().await;
31ce2453c82fatagen2026-09-24 13:07 UTC171
    match command {
31ce2453c82fatagen2026-09-24 13:07 UTC172
        NotificationCommand::SetRoomMode { room_id, mode } => {
31ce2453c82fatagen2026-09-24 13:07 UTC173
            settings
31ce2453c82fatagen2026-09-24 13:07 UTC174
                .set_room_notification_mode(&room_id, sdk_mode(mode))
31ce2453c82fatagen2026-09-24 13:07 UTC175
                .await
31ce2453c82fatagen2026-09-24 13:07 UTC176
        }
31ce2453c82fatagen2026-09-24 13:07 UTC177
        NotificationCommand::ResetRoomMode(room_id) => {
31ce2453c82fatagen2026-09-24 13:07 UTC178
            settings.delete_user_defined_room_rules(&room_id).await
31ce2453c82fatagen2026-09-24 13:07 UTC179
        }
31ce2453c82fatagen2026-09-24 13:07 UTC180
        NotificationCommand::SetDefaultMode {
31ce2453c82fatagen2026-09-24 13:07 UTC181
            encrypted,
31ce2453c82fatagen2026-09-24 13:07 UTC182
            one_to_one,
31ce2453c82fatagen2026-09-24 13:07 UTC183
            mode,
31ce2453c82fatagen2026-09-24 13:07 UTC184
        } => {
31ce2453c82fatagen2026-09-24 13:07 UTC185
            settings
31ce2453c82fatagen2026-09-24 13:07 UTC186
                .set_default_room_notification_mode(
31ce2453c82fatagen2026-09-24 13:07 UTC187
                    IsEncrypted::from(encrypted),
31ce2453c82fatagen2026-09-24 13:07 UTC188
                    IsOneToOne::from(one_to_one),
31ce2453c82fatagen2026-09-24 13:07 UTC189
                    sdk_mode(mode),
31ce2453c82fatagen2026-09-24 13:07 UTC190
                )
31ce2453c82fatagen2026-09-24 13:07 UTC191
                .await
31ce2453c82fatagen2026-09-24 13:07 UTC192
        }
5b585cd970fdatagen2026-10-25 09:20 UTC193
        NotificationCommand::AddKeyword(keyword) => {
5b585cd970fdatagen2026-10-25 09:20 UTC194
            match settings.add_keyword(keyword.clone()).await {
e9af9e0e0f31atagen2026-10-25 10:55 UTC195
                Ok(()) => return highlight_keyword(client, &keyword).await,
5b585cd970fdatagen2026-10-25 09:20 UTC196
                Err(error) => Err(error),
5b585cd970fdatagen2026-10-25 09:20 UTC197
            }
5b585cd970fdatagen2026-10-25 09:20 UTC198
        }
31ce2453c82fatagen2026-09-24 13:07 UTC199
        NotificationCommand::RemoveKeyword(keyword) => settings.remove_keyword(&keyword).await,
31ce2453c82fatagen2026-09-24 13:07 UTC200
    }
31ce2453c82fatagen2026-09-24 13:07 UTC201
    .map_err(|source| {
31ce2453c82fatagen2026-09-24 13:07 UTC202
        Error::with_source(
31ce2453c82fatagen2026-09-24 13:07 UTC203
            ErrorKind::Server,
31ce2453c82fatagen2026-09-24 13:07 UTC204
            "updating notification settings failed",
31ce2453c82fatagen2026-09-24 13:07 UTC205
            source,
31ce2453c82fatagen2026-09-24 13:07 UTC206
        )
31ce2453c82fatagen2026-09-24 13:07 UTC207
    })
31ce2453c82fatagen2026-09-24 13:07 UTC208
}
31ce2453c82fatagen2026-09-24 13:07 UTC209
5b585cd970fdatagen2026-10-25 09:20 UTC210
/// sdk keyword rules carry only notify and a sound, while Element's loud keywords also
e9af9e0e0f31atagen2026-10-25 10:55 UTC211
/// highlight, which is what lets the inbox keep them as mentions. The sdk names a
e9af9e0e0f31atagen2026-10-25 10:55 UTC212
/// keyword's content rule after the keyword itself, a rule under another id is left
e9af9e0e0f31atagen2026-10-25 10:55 UTC213
/// alone.
e9af9e0e0f31atagen2026-10-25 10:55 UTC214
async fn highlight_keyword(client: &Client, keyword: &str) -> Result<(), Error> {
e9af9e0e0f31atagen2026-10-25 10:55 UTC215
    let failed = |source| Error::from_http("highlighting a notification keyword failed", source);
e9af9e0e0f31atagen2026-10-25 10:55 UTC216
    let rule = match client
e9af9e0e0f31atagen2026-10-25 10:55 UTC217
        .send(get_pushrule::v3::Request::new(
e9af9e0e0f31atagen2026-10-25 10:55 UTC218
            RuleKind::Content,
e9af9e0e0f31atagen2026-10-25 10:55 UTC219
            keyword.to_owned(),
e9af9e0e0f31atagen2026-10-25 10:55 UTC220
        ))
e9af9e0e0f31atagen2026-10-25 10:55 UTC221
        .await
5b585cd970fdatagen2026-10-25 09:20 UTC222
    {
e9af9e0e0f31atagen2026-10-25 10:55 UTC223
        Ok(response) => response.rule,
e9af9e0e0f31atagen2026-10-25 10:55 UTC224
        Err(error)
e9af9e0e0f31atagen2026-10-25 10:55 UTC225
            if error
e9af9e0e0f31atagen2026-10-25 10:55 UTC226
                .as_client_api_error()
e9af9e0e0f31atagen2026-10-25 10:55 UTC227
                .is_some_and(|error| error.status_code.as_u16() == 404) =>
e9af9e0e0f31atagen2026-10-25 10:55 UTC228
        {
e9af9e0e0f31atagen2026-10-25 10:55 UTC229
            return Ok(());
e9af9e0e0f31atagen2026-10-25 10:55 UTC230
        }
e9af9e0e0f31atagen2026-10-25 10:55 UTC231
        Err(error) => return Err(failed(error)),
e9af9e0e0f31atagen2026-10-25 10:55 UTC232
    };
e9af9e0e0f31atagen2026-10-25 10:55 UTC233
    if rule.actions.iter().any(Action::is_highlight) {
e9af9e0e0f31atagen2026-10-25 10:55 UTC234
        return Ok(());
5b585cd970fdatagen2026-10-25 09:20 UTC235
    }
e9af9e0e0f31atagen2026-10-25 10:55 UTC236
    let mut actions: Vec<_> = rule
e9af9e0e0f31atagen2026-10-25 10:55 UTC237
        .actions
e9af9e0e0f31atagen2026-10-25 10:55 UTC238
        .into_iter()
5b585cd970fdatagen2026-10-25 09:20 UTC239
        .filter(|action| !matches!(action, Action::SetTweak(Tweak::Highlight(_))))
5b585cd970fdatagen2026-10-25 09:20 UTC240
        .collect();
5b585cd970fdatagen2026-10-25 09:20 UTC241
    actions.push(Action::SetTweak(Tweak::Highlight(HighlightTweakValue::Yes)));
e9af9e0e0f31atagen2026-10-25 10:55 UTC242
    client
e9af9e0e0f31atagen2026-10-25 10:55 UTC243
        .send(set_pushrule_actions::v3::Request::new(
e9af9e0e0f31atagen2026-10-25 10:55 UTC244
            RuleKind::Content,
e9af9e0e0f31atagen2026-10-25 10:55 UTC245
            keyword.to_owned(),
e9af9e0e0f31atagen2026-10-25 10:55 UTC246
            actions,
e9af9e0e0f31atagen2026-10-25 10:55 UTC247
        ))
e9af9e0e0f31atagen2026-10-25 10:55 UTC248
        .await
e9af9e0e0f31atagen2026-10-25 10:55 UTC249
        .map_err(failed)?;
e9af9e0e0f31atagen2026-10-25 10:55 UTC250
    Ok(())
5b585cd970fdatagen2026-10-25 09:20 UTC251
}
5b585cd970fdatagen2026-10-25 09:20 UTC252
31ce2453c82fatagen2026-09-24 13:07 UTC253
const fn sdk_mode(mode: RoomNotificationMode) -> SdkMode {
31ce2453c82fatagen2026-09-24 13:07 UTC254
    match mode {
31ce2453c82fatagen2026-09-24 13:07 UTC255
        RoomNotificationMode::AllMessages => SdkMode::AllMessages,
31ce2453c82fatagen2026-09-24 13:07 UTC256
        RoomNotificationMode::MentionsAndKeywordsOnly => SdkMode::MentionsAndKeywordsOnly,
31ce2453c82fatagen2026-09-24 13:07 UTC257
        RoomNotificationMode::Mute => SdkMode::Mute,
31ce2453c82fatagen2026-09-24 13:07 UTC258
    }
31ce2453c82fatagen2026-09-24 13:07 UTC259
}
31ce2453c82fatagen2026-09-24 13:07 UTC260
31ce2453c82fatagen2026-09-24 13:07 UTC261
fn room(client: &Client, room_id: &RoomId) -> Result<matrix_sdk::Room, Error> {
31ce2453c82fatagen2026-09-24 13:07 UTC262
    client.get_room(room_id).ok_or_else(Error::room_unknown)
31ce2453c82fatagen2026-09-24 13:07 UTC263
}
31ce2453c82fatagen2026-09-24 13:07 UTC264
31ce2453c82fatagen2026-09-24 13:07 UTC265
/// the closed-room path. without a timeline nothing stops the marker going backwards
31ce2453c82fatagen2026-09-24 13:07 UTC266
pub(super) async fn mark_read(
31ce2453c82fatagen2026-09-24 13:07 UTC267
    client: &Client,
31ce2453c82fatagen2026-09-24 13:07 UTC268
    timeline_id: &TimelineId,
31ce2453c82fatagen2026-09-24 13:07 UTC269
    event_id: OwnedEventId,
31ce2453c82fatagen2026-09-24 13:07 UTC270
) -> Result<(), Error> {
31ce2453c82fatagen2026-09-24 13:07 UTC271
    let room = room(client, timeline_id.room_id())?;
31ce2453c82fatagen2026-09-24 13:07 UTC272
    let receipt_failed = |source| Error::from_sdk("sending read receipt failed", source);
31ce2453c82fatagen2026-09-24 13:07 UTC273
    let thread = match timeline_id.thread_root() {
31ce2453c82fatagen2026-09-24 13:07 UTC274
        None => ReceiptThread::Main,
31ce2453c82fatagen2026-09-24 13:07 UTC275
        Some(root) => ReceiptThread::Thread(root.clone()),
31ce2453c82fatagen2026-09-24 13:07 UTC276
    };
31ce2453c82fatagen2026-09-24 13:07 UTC277
    room.send_single_receipt(ReceiptType::Read, thread, event_id.clone())
31ce2453c82fatagen2026-09-24 13:07 UTC278
        .await
31ce2453c82fatagen2026-09-24 13:07 UTC279
        .map_err(receipt_failed)?;
31ce2453c82fatagen2026-09-24 13:07 UTC280
    if timeline_id.thread_root().is_none() {
31ce2453c82fatagen2026-09-24 13:07 UTC281
        room.send_single_receipt(ReceiptType::FullyRead, ReceiptThread::Unthreaded, event_id)
31ce2453c82fatagen2026-09-24 13:07 UTC282
            .await
31ce2453c82fatagen2026-09-24 13:07 UTC283
            .map_err(receipt_failed)?;
31ce2453c82fatagen2026-09-24 13:07 UTC284
    }
31ce2453c82fatagen2026-09-24 13:07 UTC285
    Ok(())
31ce2453c82fatagen2026-09-24 13:07 UTC286
}
31ce2453c82fatagen2026-09-24 13:07 UTC287
31ce2453c82fatagen2026-09-24 13:07 UTC288
pub(super) async fn set_typing(
31ce2453c82fatagen2026-09-24 13:07 UTC289
    client: &Client,
31ce2453c82fatagen2026-09-24 13:07 UTC290
    room_id: &RoomId,
31ce2453c82fatagen2026-09-24 13:07 UTC291
    typing: bool,
31ce2453c82fatagen2026-09-24 13:07 UTC292
) -> Result<(), Error> {
31ce2453c82fatagen2026-09-24 13:07 UTC293
    room(client, room_id)?
31ce2453c82fatagen2026-09-24 13:07 UTC294
        .typing_notice(typing)
31ce2453c82fatagen2026-09-24 13:07 UTC295
        .await
31ce2453c82fatagen2026-09-24 13:07 UTC296
        .map_err(|source| Error::from_sdk("typing update failed", source))
31ce2453c82fatagen2026-09-24 13:07 UTC297
}
31ce2453c82fatagen2026-09-24 13:07 UTC298
31ce2453c82fatagen2026-09-24 13:07 UTC299
async fn send_handle(
31ce2453c82fatagen2026-09-24 13:07 UTC300
    client: &Client,
31ce2453c82fatagen2026-09-24 13:07 UTC301
    room_id: &RoomId,
31ce2453c82fatagen2026-09-24 13:07 UTC302
    transaction_id: &TransactionId,
31ce2453c82fatagen2026-09-24 13:07 UTC303
) -> Result<SendHandle, Error> {
31ce2453c82fatagen2026-09-24 13:07 UTC304
    let (echoes, _) = room(client, room_id)?
31ce2453c82fatagen2026-09-24 13:07 UTC305
        .send_queue()
31ce2453c82fatagen2026-09-24 13:07 UTC306
        .subscribe()
31ce2453c82fatagen2026-09-24 13:07 UTC307
        .await
31ce2453c82fatagen2026-09-24 13:07 UTC308
        .map_err(|source| {
31ce2453c82fatagen2026-09-24 13:07 UTC309
            Error::with_source(ErrorKind::Store, "reading send queue failed", source)
31ce2453c82fatagen2026-09-24 13:07 UTC310
        })?;
31ce2453c82fatagen2026-09-24 13:07 UTC311
    echoes
31ce2453c82fatagen2026-09-24 13:07 UTC312
        .into_iter()
31ce2453c82fatagen2026-09-24 13:07 UTC313
        .find_map(|echo| match echo.content {
31ce2453c82fatagen2026-09-24 13:07 UTC314
            LocalEchoContent::Event { send_handle, .. }
31ce2453c82fatagen2026-09-24 13:07 UTC315
                if echo.transaction_id.as_str() == transaction_id.as_str() =>
31ce2453c82fatagen2026-09-24 13:07 UTC316
            {
31ce2453c82fatagen2026-09-24 13:07 UTC317
                Some(send_handle)
31ce2453c82fatagen2026-09-24 13:07 UTC318
            }
31ce2453c82fatagen2026-09-24 13:07 UTC319
            _ => None,
31ce2453c82fatagen2026-09-24 13:07 UTC320
        })
31ce2453c82fatagen2026-09-24 13:07 UTC321
        .ok_or_else(|| {
31ce2453c82fatagen2026-09-24 13:07 UTC322
            Error::new(
31ce2453c82fatagen2026-09-24 13:07 UTC323
                ErrorKind::Invalid,
31ce2453c82fatagen2026-09-24 13:07 UTC324
                "send is no longer pending or is not a message",
31ce2453c82fatagen2026-09-24 13:07 UTC325
            )
31ce2453c82fatagen2026-09-24 13:07 UTC326
        })
31ce2453c82fatagen2026-09-24 13:07 UTC327
}
31ce2453c82fatagen2026-09-24 13:07 UTC328
31ce2453c82fatagen2026-09-24 13:07 UTC329
pub(super) async fn cancel_send(
31ce2453c82fatagen2026-09-24 13:07 UTC330
    client: &Client,
31ce2453c82fatagen2026-09-24 13:07 UTC331
    room_id: &RoomId,
31ce2453c82fatagen2026-09-24 13:07 UTC332
    transaction_id: &TransactionId,
31ce2453c82fatagen2026-09-24 13:07 UTC333
) -> Result<(), Error> {
31ce2453c82fatagen2026-09-24 13:07 UTC334
    match send_handle(client, room_id, transaction_id)
31ce2453c82fatagen2026-09-24 13:07 UTC335
        .await?
31ce2453c82fatagen2026-09-24 13:07 UTC336
        .abort()
31ce2453c82fatagen2026-09-24 13:07 UTC337
        .await
31ce2453c82fatagen2026-09-24 13:07 UTC338
    {
31ce2453c82fatagen2026-09-24 13:07 UTC339
        Ok(true) => Ok(()),
31ce2453c82fatagen2026-09-24 13:07 UTC340
        Ok(false) => Err(Error::new(
31ce2453c82fatagen2026-09-24 13:07 UTC341
            ErrorKind::Invalid,
31ce2453c82fatagen2026-09-24 13:07 UTC342
            "send could not be cancelled because it has already been sent",
31ce2453c82fatagen2026-09-24 13:07 UTC343
        )),
31ce2453c82fatagen2026-09-24 13:07 UTC344
        Err(source) => Err(Error::with_source(
31ce2453c82fatagen2026-09-24 13:07 UTC345
            ErrorKind::Store,
31ce2453c82fatagen2026-09-24 13:07 UTC346
            "cancelling send failed",
31ce2453c82fatagen2026-09-24 13:07 UTC347
            source,
31ce2453c82fatagen2026-09-24 13:07 UTC348
        )),
31ce2453c82fatagen2026-09-24 13:07 UTC349
    }
31ce2453c82fatagen2026-09-24 13:07 UTC350
}
31ce2453c82fatagen2026-09-24 13:07 UTC351
31ce2453c82fatagen2026-09-24 13:07 UTC352
pub(super) async fn retry_send(
31ce2453c82fatagen2026-09-24 13:07 UTC353
    client: &Client,
31ce2453c82fatagen2026-09-24 13:07 UTC354
    room_id: &RoomId,
31ce2453c82fatagen2026-09-24 13:07 UTC355
    transaction_id: &TransactionId,
31ce2453c82fatagen2026-09-24 13:07 UTC356
) -> Result<(), Error> {
31ce2453c82fatagen2026-09-24 13:07 UTC357
    send_handle(client, room_id, transaction_id)
31ce2453c82fatagen2026-09-24 13:07 UTC358
        .await?
31ce2453c82fatagen2026-09-24 13:07 UTC359
        .unwedge()
31ce2453c82fatagen2026-09-24 13:07 UTC360
        .await
31ce2453c82fatagen2026-09-24 13:07 UTC361
        .map_err(|source| Error::with_source(ErrorKind::Store, "retrying send failed", source))?;
31ce2453c82fatagen2026-09-24 13:07 UTC362
    room(client, room_id)?.send_queue().set_enabled(true);
31ce2453c82fatagen2026-09-24 13:07 UTC363
    Ok(())
31ce2453c82fatagen2026-09-24 13:07 UTC364
}
31ce2453c82fatagen2026-09-24 13:07 UTC365
31ce2453c82fatagen2026-09-24 13:07 UTC366
pub(super) async fn upload_avatar(
31ce2453c82fatagen2026-09-24 13:07 UTC367
    client: &Client,
31ce2453c82fatagen2026-09-24 13:07 UTC368
    mimetype: &str,
31ce2453c82fatagen2026-09-24 13:07 UTC369
    bytes: Vec<u8>,
31ce2453c82fatagen2026-09-24 13:07 UTC370
    max_bytes: usize,
31ce2453c82fatagen2026-09-24 13:07 UTC371
) -> Result<(), Error> {
31ce2453c82fatagen2026-09-24 13:07 UTC372
    if bytes.len() > max_bytes {
31ce2453c82fatagen2026-09-24 13:07 UTC373
        return Err(Error::new(
31ce2453c82fatagen2026-09-24 13:07 UTC374
            ErrorKind::MediaTooLarge,
31ce2453c82fatagen2026-09-24 13:07 UTC375
            "avatar exceeds the configured byte limit",
31ce2453c82fatagen2026-09-24 13:07 UTC376
        ));
31ce2453c82fatagen2026-09-24 13:07 UTC377
    }
31ce2453c82fatagen2026-09-24 13:07 UTC378
    let mimetype: mime::Mime = mimetype
31ce2453c82fatagen2026-09-24 13:07 UTC379
        .parse()
31ce2453c82fatagen2026-09-24 13:07 UTC380
        .map_err(|_| Error::new(ErrorKind::Invalid, "avatar MIME type is invalid"))?;
31ce2453c82fatagen2026-09-24 13:07 UTC381
    if mimetype.type_() != mime::IMAGE {
31ce2453c82fatagen2026-09-24 13:07 UTC382
        return Err(Error::new(
31ce2453c82fatagen2026-09-24 13:07 UTC383
            ErrorKind::Invalid,
31ce2453c82fatagen2026-09-24 13:07 UTC384
            "an avatar has to be an image",
31ce2453c82fatagen2026-09-24 13:07 UTC385
        ));
31ce2453c82fatagen2026-09-24 13:07 UTC386
    }
31ce2453c82fatagen2026-09-24 13:07 UTC387
    client
31ce2453c82fatagen2026-09-24 13:07 UTC388
        .account()
31ce2453c82fatagen2026-09-24 13:07 UTC389
        .upload_avatar(&mimetype, bytes)
31ce2453c82fatagen2026-09-24 13:07 UTC390
        .await
31ce2453c82fatagen2026-09-24 13:07 UTC391
        .map(drop)
31ce2453c82fatagen2026-09-24 13:07 UTC392
        .map_err(|source| Error::from_sdk("uploading avatar failed", source))
31ce2453c82fatagen2026-09-24 13:07 UTC393
}
31ce2453c82fatagen2026-09-24 13:07 UTC394
9bd34e25bb86atagen2026-10-24 07:23 UTC395
pub(super) async fn upload_media(
9bd34e25bb86atagen2026-10-24 07:23 UTC396
    client: &Client,
9bd34e25bb86atagen2026-10-24 07:23 UTC397
    mimetype: &str,
9bd34e25bb86atagen2026-10-24 07:23 UTC398
    bytes: Vec<u8>,
9bd34e25bb86atagen2026-10-24 07:23 UTC399
    max_bytes: usize,
9bd34e25bb86atagen2026-10-24 07:23 UTC400
) -> Result<OwnedMxcUri, Error> {
9bd34e25bb86atagen2026-10-24 07:23 UTC401
    if bytes.len() > max_bytes {
9bd34e25bb86atagen2026-10-24 07:23 UTC402
        return Err(Error::new(
9bd34e25bb86atagen2026-10-24 07:23 UTC403
            ErrorKind::MediaTooLarge,
9bd34e25bb86atagen2026-10-24 07:23 UTC404
            "media exceeds the configured byte limit",
9bd34e25bb86atagen2026-10-24 07:23 UTC405
        ));
9bd34e25bb86atagen2026-10-24 07:23 UTC406
    }
9bd34e25bb86atagen2026-10-24 07:23 UTC407
    let mimetype: mime::Mime = mimetype
9bd34e25bb86atagen2026-10-24 07:23 UTC408
        .parse()
9bd34e25bb86atagen2026-10-24 07:23 UTC409
        .map_err(|_| Error::new(ErrorKind::Invalid, "media MIME type is invalid"))?;
9bd34e25bb86atagen2026-10-24 07:23 UTC410
    client
9bd34e25bb86atagen2026-10-24 07:23 UTC411
        .media()
9bd34e25bb86atagen2026-10-24 07:23 UTC412
        .upload(&mimetype, bytes, None)
9bd34e25bb86atagen2026-10-24 07:23 UTC413
        .await
9bd34e25bb86atagen2026-10-24 07:23 UTC414
        .map(|response| response.content_uri)
9bd34e25bb86atagen2026-10-24 07:23 UTC415
        .map_err(|source| Error::from_sdk("uploading media failed", source))
9bd34e25bb86atagen2026-10-24 07:23 UTC416
}
9bd34e25bb86atagen2026-10-24 07:23 UTC417
31ce2453c82fatagen2026-09-24 13:07 UTC418
pub(super) async fn rename_device(
31ce2453c82fatagen2026-09-24 13:07 UTC419
    client: &Client,
31ce2453c82fatagen2026-09-24 13:07 UTC420
    device_id: &OwnedDeviceId,
31ce2453c82fatagen2026-09-24 13:07 UTC421
    display_name: &str,
31ce2453c82fatagen2026-09-24 13:07 UTC422
) -> Result<(), Error> {
31ce2453c82fatagen2026-09-24 13:07 UTC423
    client
31ce2453c82fatagen2026-09-24 13:07 UTC424
        .rename_device(device_id, display_name)
31ce2453c82fatagen2026-09-24 13:07 UTC425
        .await
31ce2453c82fatagen2026-09-24 13:07 UTC426
        .map(drop)
31ce2453c82fatagen2026-09-24 13:07 UTC427
        .map_err(|source| Error::from_http("renaming device failed", source))
31ce2453c82fatagen2026-09-24 13:07 UTC428
}
31ce2453c82fatagen2026-09-24 13:07 UTC429
31ce2453c82fatagen2026-09-24 13:07 UTC430
pub(super) async fn delete_devices(
31ce2453c82fatagen2026-09-24 13:07 UTC431
    client: &Client,
31ce2453c82fatagen2026-09-24 13:07 UTC432
    device_ids: &[OwnedDeviceId],
31ce2453c82fatagen2026-09-24 13:07 UTC433
    auth: Option<InteractiveAuth>,
31ce2453c82fatagen2026-09-24 13:07 UTC434
) -> Result<(), Error> {
31ce2453c82fatagen2026-09-24 13:07 UTC435
    let user_id = client
31ce2453c82fatagen2026-09-24 13:07 UTC436
        .user_id()
31ce2453c82fatagen2026-09-24 13:07 UTC437
        .ok_or_else(|| Error::new(ErrorKind::Unauthenticated, "the client is not logged in"))?;
31ce2453c82fatagen2026-09-24 13:07 UTC438
    let auth = auth
31ce2453c82fatagen2026-09-24 13:07 UTC439
        .map(|auth| auth.into_sdk(user_id))
31ce2453c82fatagen2026-09-24 13:07 UTC440
        .transpose()
31ce2453c82fatagen2026-09-24 13:07 UTC441
        .map_err(|message| {
31ce2453c82fatagen2026-09-24 13:07 UTC442
            Error::new(
31ce2453c82fatagen2026-09-24 13:07 UTC443
                ErrorKind::Invalid,
31ce2453c82fatagen2026-09-24 13:07 UTC444
                format!("interactive authentication data is invalid, {message}"),
31ce2453c82fatagen2026-09-24 13:07 UTC445
            )
31ce2453c82fatagen2026-09-24 13:07 UTC446
        })?;
31ce2453c82fatagen2026-09-24 13:07 UTC447
    match client.delete_devices(device_ids, auth).await {
31ce2453c82fatagen2026-09-24 13:07 UTC448
        Ok(_) => Ok(()),
31ce2453c82fatagen2026-09-24 13:07 UTC449
        Err(source) => match source.as_uiaa_response() {
31ce2453c82fatagen2026-09-24 13:07 UTC450
            Some(info) => Err(Error::interactive_auth(InteractiveAuthChallenge::from(
31ce2453c82fatagen2026-09-24 13:07 UTC451
                info,
31ce2453c82fatagen2026-09-24 13:07 UTC452
            ))),
31ce2453c82fatagen2026-09-24 13:07 UTC453
            None => Err(Error::from_http("deleting devices failed", source)),
31ce2453c82fatagen2026-09-24 13:07 UTC454
        },
31ce2453c82fatagen2026-09-24 13:07 UTC455
    }
31ce2453c82fatagen2026-09-24 13:07 UTC456
}