atagen

macltok

public
Star0
Watch0
b34c2df6d896a1eeab5c20e1e759fbb0192aa5fc
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
};
31ce2453c82fatagen2026-09-24 13:07 UTC19
use tokio::sync::mpsc;
31ce2453c82fatagen2026-09-24 13:07 UTC20
31ce2453c82fatagen2026-09-24 13:07 UTC21
use crate::{
31ce2453c82fatagen2026-09-24 13:07 UTC22
    EncryptionCommand, Error, ErrorKind, InteractiveAuth, InteractiveAuthChallenge,
31ce2453c82fatagen2026-09-24 13:07 UTC23
    NotificationCommand, 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>,
31ce2453c82fatagen2026-09-24 13:07 UTC58
) {
31ce2453c82fatagen2026-09-24 13:07 UTC59
    let mut verification = VerificationController::default();
5a52012c9b1datagen2026-09-24 23:45 UTC60
    loop {
5a52012c9b1datagen2026-09-24 23:45 UTC61
        tokio::select! {
5a52012c9b1datagen2026-09-24 23:45 UTC62
            job = jobs.recv() => {
5a52012c9b1datagen2026-09-24 23:45 UTC63
                let Some(Job { command, reply }) = job else { return };
5a52012c9b1datagen2026-09-24 23:45 UTC64
                let work = encryption(&client, &events, &mut verification, command);
5a52012c9b1datagen2026-09-24 23:45 UTC65
                reply.done(guarded("encryption command", work).await);
5a52012c9b1datagen2026-09-24 23:45 UTC66
            }
5a52012c9b1datagen2026-09-24 23:45 UTC67
            Some(request) = incoming.recv() => {
5a52012c9b1datagen2026-09-24 23:45 UTC68
                verification.adopt_incoming(events.clone(), request).await;
5a52012c9b1datagen2026-09-24 23:45 UTC69
            }
5a52012c9b1datagen2026-09-24 23:45 UTC70
        }
31ce2453c82fatagen2026-09-24 13:07 UTC71
    }
31ce2453c82fatagen2026-09-24 13:07 UTC72
}
31ce2453c82fatagen2026-09-24 13:07 UTC73
31ce2453c82fatagen2026-09-24 13:07 UTC74
async fn encryption(
31ce2453c82fatagen2026-09-24 13:07 UTC75
    client: &Client,
31ce2453c82fatagen2026-09-24 13:07 UTC76
    events: &EventSender,
31ce2453c82fatagen2026-09-24 13:07 UTC77
    verification: &mut VerificationController,
31ce2453c82fatagen2026-09-24 13:07 UTC78
    command: EncryptionCommand,
31ce2453c82fatagen2026-09-24 13:07 UTC79
) -> Result<(), Error> {
31ce2453c82fatagen2026-09-24 13:07 UTC80
    let encryption = client.encryption();
31ce2453c82fatagen2026-09-24 13:07 UTC81
    match command {
31ce2453c82fatagen2026-09-24 13:07 UTC82
        EncryptionCommand::Recover(key) => encryption
31ce2453c82fatagen2026-09-24 13:07 UTC83
            .recovery()
31ce2453c82fatagen2026-09-24 13:07 UTC84
            .recover(&key)
31ce2453c82fatagen2026-09-24 13:07 UTC85
            .await
31ce2453c82fatagen2026-09-24 13:07 UTC86
            .map_err(|source| Error::from_recovery("account recovery failed", source)),
31ce2453c82fatagen2026-09-24 13:07 UTC87
        EncryptionCommand::RecoverKeyBackup(key) => encryption
31ce2453c82fatagen2026-09-24 13:07 UTC88
            .recovery()
31ce2453c82fatagen2026-09-24 13:07 UTC89
            .recover_and_fix_backup(&key)
31ce2453c82fatagen2026-09-24 13:07 UTC90
            .await
31ce2453c82fatagen2026-09-24 13:07 UTC91
            .map_err(|source| Error::from_recovery("recovering key backup failed", source)),
31ce2453c82fatagen2026-09-24 13:07 UTC92
        EncryptionCommand::CreateKeyBackup => encryption
31ce2453c82fatagen2026-09-24 13:07 UTC93
            .backups()
31ce2453c82fatagen2026-09-24 13:07 UTC94
            .create()
31ce2453c82fatagen2026-09-24 13:07 UTC95
            .await
31ce2453c82fatagen2026-09-24 13:07 UTC96
            .map_err(|source| Error::from_sdk("creating key backup failed", source)),
31ce2453c82fatagen2026-09-24 13:07 UTC97
        EncryptionCommand::EnableKeyBackup => encryption
31ce2453c82fatagen2026-09-24 13:07 UTC98
            .recovery()
31ce2453c82fatagen2026-09-24 13:07 UTC99
            .enable_backup()
31ce2453c82fatagen2026-09-24 13:07 UTC100
            .await
31ce2453c82fatagen2026-09-24 13:07 UTC101
            .map_err(|source| Error::from_recovery("enabling key backup failed", source)),
31ce2453c82fatagen2026-09-24 13:07 UTC102
        EncryptionCommand::DisableKeyBackup { delete_from_server } => {
31ce2453c82fatagen2026-09-24 13:07 UTC103
            let backups = encryption.backups();
31ce2453c82fatagen2026-09-24 13:07 UTC104
            if delete_from_server {
31ce2453c82fatagen2026-09-24 13:07 UTC105
                backups.disable_and_delete().await
31ce2453c82fatagen2026-09-24 13:07 UTC106
            } else {
31ce2453c82fatagen2026-09-24 13:07 UTC107
                backups.disable().await
31ce2453c82fatagen2026-09-24 13:07 UTC108
            }
31ce2453c82fatagen2026-09-24 13:07 UTC109
            .map_err(|source| Error::from_sdk("disabling key backup failed", source))
31ce2453c82fatagen2026-09-24 13:07 UTC110
        }
31ce2453c82fatagen2026-09-24 13:07 UTC111
        EncryptionCommand::StartVerification(target) => {
31ce2453c82fatagen2026-09-24 13:07 UTC112
            verification.start(client, events.clone(), target).await
31ce2453c82fatagen2026-09-24 13:07 UTC113
        }
31ce2453c82fatagen2026-09-24 13:07 UTC114
        EncryptionCommand::Verify(action) => verification.apply(events.clone(), action).await,
31ce2453c82fatagen2026-09-24 13:07 UTC115
    }
31ce2453c82fatagen2026-09-24 13:07 UTC116
}
31ce2453c82fatagen2026-09-24 13:07 UTC117
31ce2453c82fatagen2026-09-24 13:07 UTC118
async fn notification(client: &Client, command: NotificationCommand) -> Result<(), Error> {
31ce2453c82fatagen2026-09-24 13:07 UTC119
    let settings = client.notification_settings().await;
31ce2453c82fatagen2026-09-24 13:07 UTC120
    match command {
31ce2453c82fatagen2026-09-24 13:07 UTC121
        NotificationCommand::SetRoomMode { room_id, mode } => {
31ce2453c82fatagen2026-09-24 13:07 UTC122
            settings
31ce2453c82fatagen2026-09-24 13:07 UTC123
                .set_room_notification_mode(&room_id, sdk_mode(mode))
31ce2453c82fatagen2026-09-24 13:07 UTC124
                .await
31ce2453c82fatagen2026-09-24 13:07 UTC125
        }
31ce2453c82fatagen2026-09-24 13:07 UTC126
        NotificationCommand::ResetRoomMode(room_id) => {
31ce2453c82fatagen2026-09-24 13:07 UTC127
            settings.delete_user_defined_room_rules(&room_id).await
31ce2453c82fatagen2026-09-24 13:07 UTC128
        }
31ce2453c82fatagen2026-09-24 13:07 UTC129
        NotificationCommand::SetDefaultMode {
31ce2453c82fatagen2026-09-24 13:07 UTC130
            encrypted,
31ce2453c82fatagen2026-09-24 13:07 UTC131
            one_to_one,
31ce2453c82fatagen2026-09-24 13:07 UTC132
            mode,
31ce2453c82fatagen2026-09-24 13:07 UTC133
        } => {
31ce2453c82fatagen2026-09-24 13:07 UTC134
            settings
31ce2453c82fatagen2026-09-24 13:07 UTC135
                .set_default_room_notification_mode(
31ce2453c82fatagen2026-09-24 13:07 UTC136
                    IsEncrypted::from(encrypted),
31ce2453c82fatagen2026-09-24 13:07 UTC137
                    IsOneToOne::from(one_to_one),
31ce2453c82fatagen2026-09-24 13:07 UTC138
                    sdk_mode(mode),
31ce2453c82fatagen2026-09-24 13:07 UTC139
                )
31ce2453c82fatagen2026-09-24 13:07 UTC140
                .await
31ce2453c82fatagen2026-09-24 13:07 UTC141
        }
5b585cd970fdatagen2026-10-25 09:20 UTC142
        NotificationCommand::AddKeyword(keyword) => {
5b585cd970fdatagen2026-10-25 09:20 UTC143
            match settings.add_keyword(keyword.clone()).await {
e9af9e0e0f31atagen2026-10-25 10:55 UTC144
                Ok(()) => return highlight_keyword(client, &keyword).await,
5b585cd970fdatagen2026-10-25 09:20 UTC145
                Err(error) => Err(error),
5b585cd970fdatagen2026-10-25 09:20 UTC146
            }
5b585cd970fdatagen2026-10-25 09:20 UTC147
        }
31ce2453c82fatagen2026-09-24 13:07 UTC148
        NotificationCommand::RemoveKeyword(keyword) => settings.remove_keyword(&keyword).await,
31ce2453c82fatagen2026-09-24 13:07 UTC149
    }
31ce2453c82fatagen2026-09-24 13:07 UTC150
    .map_err(|source| {
31ce2453c82fatagen2026-09-24 13:07 UTC151
        Error::with_source(
31ce2453c82fatagen2026-09-24 13:07 UTC152
            ErrorKind::Server,
31ce2453c82fatagen2026-09-24 13:07 UTC153
            "updating notification settings failed",
31ce2453c82fatagen2026-09-24 13:07 UTC154
            source,
31ce2453c82fatagen2026-09-24 13:07 UTC155
        )
31ce2453c82fatagen2026-09-24 13:07 UTC156
    })
31ce2453c82fatagen2026-09-24 13:07 UTC157
}
31ce2453c82fatagen2026-09-24 13:07 UTC158
5b585cd970fdatagen2026-10-25 09:20 UTC159
/// sdk keyword rules carry only notify and a sound, while Element's loud keywords also
e9af9e0e0f31atagen2026-10-25 10:55 UTC160
/// highlight, which is what lets the inbox keep them as mentions. The sdk names a
e9af9e0e0f31atagen2026-10-25 10:55 UTC161
/// keyword's content rule after the keyword itself, a rule under another id is left
e9af9e0e0f31atagen2026-10-25 10:55 UTC162
/// alone.
e9af9e0e0f31atagen2026-10-25 10:55 UTC163
async fn highlight_keyword(client: &Client, keyword: &str) -> Result<(), Error> {
e9af9e0e0f31atagen2026-10-25 10:55 UTC164
    let failed = |source| Error::from_http("highlighting a notification keyword failed", source);
e9af9e0e0f31atagen2026-10-25 10:55 UTC165
    let rule = match client
e9af9e0e0f31atagen2026-10-25 10:55 UTC166
        .send(get_pushrule::v3::Request::new(
e9af9e0e0f31atagen2026-10-25 10:55 UTC167
            RuleKind::Content,
e9af9e0e0f31atagen2026-10-25 10:55 UTC168
            keyword.to_owned(),
e9af9e0e0f31atagen2026-10-25 10:55 UTC169
        ))
e9af9e0e0f31atagen2026-10-25 10:55 UTC170
        .await
5b585cd970fdatagen2026-10-25 09:20 UTC171
    {
e9af9e0e0f31atagen2026-10-25 10:55 UTC172
        Ok(response) => response.rule,
e9af9e0e0f31atagen2026-10-25 10:55 UTC173
        Err(error)
e9af9e0e0f31atagen2026-10-25 10:55 UTC174
            if error
e9af9e0e0f31atagen2026-10-25 10:55 UTC175
                .as_client_api_error()
e9af9e0e0f31atagen2026-10-25 10:55 UTC176
                .is_some_and(|error| error.status_code.as_u16() == 404) =>
e9af9e0e0f31atagen2026-10-25 10:55 UTC177
        {
e9af9e0e0f31atagen2026-10-25 10:55 UTC178
            return Ok(());
e9af9e0e0f31atagen2026-10-25 10:55 UTC179
        }
e9af9e0e0f31atagen2026-10-25 10:55 UTC180
        Err(error) => return Err(failed(error)),
e9af9e0e0f31atagen2026-10-25 10:55 UTC181
    };
e9af9e0e0f31atagen2026-10-25 10:55 UTC182
    if rule.actions.iter().any(Action::is_highlight) {
e9af9e0e0f31atagen2026-10-25 10:55 UTC183
        return Ok(());
5b585cd970fdatagen2026-10-25 09:20 UTC184
    }
e9af9e0e0f31atagen2026-10-25 10:55 UTC185
    let mut actions: Vec<_> = rule
e9af9e0e0f31atagen2026-10-25 10:55 UTC186
        .actions
e9af9e0e0f31atagen2026-10-25 10:55 UTC187
        .into_iter()
5b585cd970fdatagen2026-10-25 09:20 UTC188
        .filter(|action| !matches!(action, Action::SetTweak(Tweak::Highlight(_))))
5b585cd970fdatagen2026-10-25 09:20 UTC189
        .collect();
5b585cd970fdatagen2026-10-25 09:20 UTC190
    actions.push(Action::SetTweak(Tweak::Highlight(HighlightTweakValue::Yes)));
e9af9e0e0f31atagen2026-10-25 10:55 UTC191
    client
e9af9e0e0f31atagen2026-10-25 10:55 UTC192
        .send(set_pushrule_actions::v3::Request::new(
e9af9e0e0f31atagen2026-10-25 10:55 UTC193
            RuleKind::Content,
e9af9e0e0f31atagen2026-10-25 10:55 UTC194
            keyword.to_owned(),
e9af9e0e0f31atagen2026-10-25 10:55 UTC195
            actions,
e9af9e0e0f31atagen2026-10-25 10:55 UTC196
        ))
e9af9e0e0f31atagen2026-10-25 10:55 UTC197
        .await
e9af9e0e0f31atagen2026-10-25 10:55 UTC198
        .map_err(failed)?;
e9af9e0e0f31atagen2026-10-25 10:55 UTC199
    Ok(())
5b585cd970fdatagen2026-10-25 09:20 UTC200
}
5b585cd970fdatagen2026-10-25 09:20 UTC201
31ce2453c82fatagen2026-09-24 13:07 UTC202
const fn sdk_mode(mode: RoomNotificationMode) -> SdkMode {
31ce2453c82fatagen2026-09-24 13:07 UTC203
    match mode {
31ce2453c82fatagen2026-09-24 13:07 UTC204
        RoomNotificationMode::AllMessages => SdkMode::AllMessages,
31ce2453c82fatagen2026-09-24 13:07 UTC205
        RoomNotificationMode::MentionsAndKeywordsOnly => SdkMode::MentionsAndKeywordsOnly,
31ce2453c82fatagen2026-09-24 13:07 UTC206
        RoomNotificationMode::Mute => SdkMode::Mute,
31ce2453c82fatagen2026-09-24 13:07 UTC207
    }
31ce2453c82fatagen2026-09-24 13:07 UTC208
}
31ce2453c82fatagen2026-09-24 13:07 UTC209
31ce2453c82fatagen2026-09-24 13:07 UTC210
fn room(client: &Client, room_id: &RoomId) -> Result<matrix_sdk::Room, Error> {
31ce2453c82fatagen2026-09-24 13:07 UTC211
    client.get_room(room_id).ok_or_else(Error::room_unknown)
31ce2453c82fatagen2026-09-24 13:07 UTC212
}
31ce2453c82fatagen2026-09-24 13:07 UTC213
31ce2453c82fatagen2026-09-24 13:07 UTC214
/// the closed-room path. without a timeline nothing stops the marker going backwards
31ce2453c82fatagen2026-09-24 13:07 UTC215
pub(super) async fn mark_read(
31ce2453c82fatagen2026-09-24 13:07 UTC216
    client: &Client,
31ce2453c82fatagen2026-09-24 13:07 UTC217
    timeline_id: &TimelineId,
31ce2453c82fatagen2026-09-24 13:07 UTC218
    event_id: OwnedEventId,
31ce2453c82fatagen2026-09-24 13:07 UTC219
) -> Result<(), Error> {
31ce2453c82fatagen2026-09-24 13:07 UTC220
    let room = room(client, timeline_id.room_id())?;
31ce2453c82fatagen2026-09-24 13:07 UTC221
    let receipt_failed = |source| Error::from_sdk("sending read receipt failed", source);
31ce2453c82fatagen2026-09-24 13:07 UTC222
    let thread = match timeline_id.thread_root() {
31ce2453c82fatagen2026-09-24 13:07 UTC223
        None => ReceiptThread::Main,
31ce2453c82fatagen2026-09-24 13:07 UTC224
        Some(root) => ReceiptThread::Thread(root.clone()),
31ce2453c82fatagen2026-09-24 13:07 UTC225
    };
31ce2453c82fatagen2026-09-24 13:07 UTC226
    room.send_single_receipt(ReceiptType::Read, thread, event_id.clone())
31ce2453c82fatagen2026-09-24 13:07 UTC227
        .await
31ce2453c82fatagen2026-09-24 13:07 UTC228
        .map_err(receipt_failed)?;
31ce2453c82fatagen2026-09-24 13:07 UTC229
    if timeline_id.thread_root().is_none() {
31ce2453c82fatagen2026-09-24 13:07 UTC230
        room.send_single_receipt(ReceiptType::FullyRead, ReceiptThread::Unthreaded, event_id)
31ce2453c82fatagen2026-09-24 13:07 UTC231
            .await
31ce2453c82fatagen2026-09-24 13:07 UTC232
            .map_err(receipt_failed)?;
31ce2453c82fatagen2026-09-24 13:07 UTC233
    }
31ce2453c82fatagen2026-09-24 13:07 UTC234
    Ok(())
31ce2453c82fatagen2026-09-24 13:07 UTC235
}
31ce2453c82fatagen2026-09-24 13:07 UTC236
31ce2453c82fatagen2026-09-24 13:07 UTC237
pub(super) async fn set_typing(
31ce2453c82fatagen2026-09-24 13:07 UTC238
    client: &Client,
31ce2453c82fatagen2026-09-24 13:07 UTC239
    room_id: &RoomId,
31ce2453c82fatagen2026-09-24 13:07 UTC240
    typing: bool,
31ce2453c82fatagen2026-09-24 13:07 UTC241
) -> Result<(), Error> {
31ce2453c82fatagen2026-09-24 13:07 UTC242
    room(client, room_id)?
31ce2453c82fatagen2026-09-24 13:07 UTC243
        .typing_notice(typing)
31ce2453c82fatagen2026-09-24 13:07 UTC244
        .await
31ce2453c82fatagen2026-09-24 13:07 UTC245
        .map_err(|source| Error::from_sdk("typing update failed", source))
31ce2453c82fatagen2026-09-24 13:07 UTC246
}
31ce2453c82fatagen2026-09-24 13:07 UTC247
31ce2453c82fatagen2026-09-24 13:07 UTC248
async fn send_handle(
31ce2453c82fatagen2026-09-24 13:07 UTC249
    client: &Client,
31ce2453c82fatagen2026-09-24 13:07 UTC250
    room_id: &RoomId,
31ce2453c82fatagen2026-09-24 13:07 UTC251
    transaction_id: &TransactionId,
31ce2453c82fatagen2026-09-24 13:07 UTC252
) -> Result<SendHandle, Error> {
31ce2453c82fatagen2026-09-24 13:07 UTC253
    let (echoes, _) = room(client, room_id)?
31ce2453c82fatagen2026-09-24 13:07 UTC254
        .send_queue()
31ce2453c82fatagen2026-09-24 13:07 UTC255
        .subscribe()
31ce2453c82fatagen2026-09-24 13:07 UTC256
        .await
31ce2453c82fatagen2026-09-24 13:07 UTC257
        .map_err(|source| {
31ce2453c82fatagen2026-09-24 13:07 UTC258
            Error::with_source(ErrorKind::Store, "reading send queue failed", source)
31ce2453c82fatagen2026-09-24 13:07 UTC259
        })?;
31ce2453c82fatagen2026-09-24 13:07 UTC260
    echoes
31ce2453c82fatagen2026-09-24 13:07 UTC261
        .into_iter()
31ce2453c82fatagen2026-09-24 13:07 UTC262
        .find_map(|echo| match echo.content {
31ce2453c82fatagen2026-09-24 13:07 UTC263
            LocalEchoContent::Event { send_handle, .. }
31ce2453c82fatagen2026-09-24 13:07 UTC264
                if echo.transaction_id.as_str() == transaction_id.as_str() =>
31ce2453c82fatagen2026-09-24 13:07 UTC265
            {
31ce2453c82fatagen2026-09-24 13:07 UTC266
                Some(send_handle)
31ce2453c82fatagen2026-09-24 13:07 UTC267
            }
31ce2453c82fatagen2026-09-24 13:07 UTC268
            _ => None,
31ce2453c82fatagen2026-09-24 13:07 UTC269
        })
31ce2453c82fatagen2026-09-24 13:07 UTC270
        .ok_or_else(|| {
31ce2453c82fatagen2026-09-24 13:07 UTC271
            Error::new(
31ce2453c82fatagen2026-09-24 13:07 UTC272
                ErrorKind::Invalid,
31ce2453c82fatagen2026-09-24 13:07 UTC273
                "send is no longer pending or is not a message",
31ce2453c82fatagen2026-09-24 13:07 UTC274
            )
31ce2453c82fatagen2026-09-24 13:07 UTC275
        })
31ce2453c82fatagen2026-09-24 13:07 UTC276
}
31ce2453c82fatagen2026-09-24 13:07 UTC277
31ce2453c82fatagen2026-09-24 13:07 UTC278
pub(super) async fn cancel_send(
31ce2453c82fatagen2026-09-24 13:07 UTC279
    client: &Client,
31ce2453c82fatagen2026-09-24 13:07 UTC280
    room_id: &RoomId,
31ce2453c82fatagen2026-09-24 13:07 UTC281
    transaction_id: &TransactionId,
31ce2453c82fatagen2026-09-24 13:07 UTC282
) -> Result<(), Error> {
31ce2453c82fatagen2026-09-24 13:07 UTC283
    match send_handle(client, room_id, transaction_id)
31ce2453c82fatagen2026-09-24 13:07 UTC284
        .await?
31ce2453c82fatagen2026-09-24 13:07 UTC285
        .abort()
31ce2453c82fatagen2026-09-24 13:07 UTC286
        .await
31ce2453c82fatagen2026-09-24 13:07 UTC287
    {
31ce2453c82fatagen2026-09-24 13:07 UTC288
        Ok(true) => Ok(()),
31ce2453c82fatagen2026-09-24 13:07 UTC289
        Ok(false) => Err(Error::new(
31ce2453c82fatagen2026-09-24 13:07 UTC290
            ErrorKind::Invalid,
31ce2453c82fatagen2026-09-24 13:07 UTC291
            "send could not be cancelled because it has already been sent",
31ce2453c82fatagen2026-09-24 13:07 UTC292
        )),
31ce2453c82fatagen2026-09-24 13:07 UTC293
        Err(source) => Err(Error::with_source(
31ce2453c82fatagen2026-09-24 13:07 UTC294
            ErrorKind::Store,
31ce2453c82fatagen2026-09-24 13:07 UTC295
            "cancelling send failed",
31ce2453c82fatagen2026-09-24 13:07 UTC296
            source,
31ce2453c82fatagen2026-09-24 13:07 UTC297
        )),
31ce2453c82fatagen2026-09-24 13:07 UTC298
    }
31ce2453c82fatagen2026-09-24 13:07 UTC299
}
31ce2453c82fatagen2026-09-24 13:07 UTC300
31ce2453c82fatagen2026-09-24 13:07 UTC301
pub(super) async fn retry_send(
31ce2453c82fatagen2026-09-24 13:07 UTC302
    client: &Client,
31ce2453c82fatagen2026-09-24 13:07 UTC303
    room_id: &RoomId,
31ce2453c82fatagen2026-09-24 13:07 UTC304
    transaction_id: &TransactionId,
31ce2453c82fatagen2026-09-24 13:07 UTC305
) -> Result<(), Error> {
31ce2453c82fatagen2026-09-24 13:07 UTC306
    send_handle(client, room_id, transaction_id)
31ce2453c82fatagen2026-09-24 13:07 UTC307
        .await?
31ce2453c82fatagen2026-09-24 13:07 UTC308
        .unwedge()
31ce2453c82fatagen2026-09-24 13:07 UTC309
        .await
31ce2453c82fatagen2026-09-24 13:07 UTC310
        .map_err(|source| Error::with_source(ErrorKind::Store, "retrying send failed", source))?;
31ce2453c82fatagen2026-09-24 13:07 UTC311
    room(client, room_id)?.send_queue().set_enabled(true);
31ce2453c82fatagen2026-09-24 13:07 UTC312
    Ok(())
31ce2453c82fatagen2026-09-24 13:07 UTC313
}
31ce2453c82fatagen2026-09-24 13:07 UTC314
31ce2453c82fatagen2026-09-24 13:07 UTC315
pub(super) async fn upload_avatar(
31ce2453c82fatagen2026-09-24 13:07 UTC316
    client: &Client,
31ce2453c82fatagen2026-09-24 13:07 UTC317
    mimetype: &str,
31ce2453c82fatagen2026-09-24 13:07 UTC318
    bytes: Vec<u8>,
31ce2453c82fatagen2026-09-24 13:07 UTC319
    max_bytes: usize,
31ce2453c82fatagen2026-09-24 13:07 UTC320
) -> Result<(), Error> {
31ce2453c82fatagen2026-09-24 13:07 UTC321
    if bytes.len() > max_bytes {
31ce2453c82fatagen2026-09-24 13:07 UTC322
        return Err(Error::new(
31ce2453c82fatagen2026-09-24 13:07 UTC323
            ErrorKind::MediaTooLarge,
31ce2453c82fatagen2026-09-24 13:07 UTC324
            "avatar exceeds the configured byte limit",
31ce2453c82fatagen2026-09-24 13:07 UTC325
        ));
31ce2453c82fatagen2026-09-24 13:07 UTC326
    }
31ce2453c82fatagen2026-09-24 13:07 UTC327
    let mimetype: mime::Mime = mimetype
31ce2453c82fatagen2026-09-24 13:07 UTC328
        .parse()
31ce2453c82fatagen2026-09-24 13:07 UTC329
        .map_err(|_| Error::new(ErrorKind::Invalid, "avatar MIME type is invalid"))?;
31ce2453c82fatagen2026-09-24 13:07 UTC330
    if mimetype.type_() != mime::IMAGE {
31ce2453c82fatagen2026-09-24 13:07 UTC331
        return Err(Error::new(
31ce2453c82fatagen2026-09-24 13:07 UTC332
            ErrorKind::Invalid,
31ce2453c82fatagen2026-09-24 13:07 UTC333
            "an avatar has to be an image",
31ce2453c82fatagen2026-09-24 13:07 UTC334
        ));
31ce2453c82fatagen2026-09-24 13:07 UTC335
    }
31ce2453c82fatagen2026-09-24 13:07 UTC336
    client
31ce2453c82fatagen2026-09-24 13:07 UTC337
        .account()
31ce2453c82fatagen2026-09-24 13:07 UTC338
        .upload_avatar(&mimetype, bytes)
31ce2453c82fatagen2026-09-24 13:07 UTC339
        .await
31ce2453c82fatagen2026-09-24 13:07 UTC340
        .map(drop)
31ce2453c82fatagen2026-09-24 13:07 UTC341
        .map_err(|source| Error::from_sdk("uploading avatar failed", source))
31ce2453c82fatagen2026-09-24 13:07 UTC342
}
31ce2453c82fatagen2026-09-24 13:07 UTC343
9bd34e25bb86atagen2026-10-24 07:23 UTC344
pub(super) async fn upload_media(
9bd34e25bb86atagen2026-10-24 07:23 UTC345
    client: &Client,
9bd34e25bb86atagen2026-10-24 07:23 UTC346
    mimetype: &str,
9bd34e25bb86atagen2026-10-24 07:23 UTC347
    bytes: Vec<u8>,
9bd34e25bb86atagen2026-10-24 07:23 UTC348
    max_bytes: usize,
9bd34e25bb86atagen2026-10-24 07:23 UTC349
) -> Result<OwnedMxcUri, Error> {
9bd34e25bb86atagen2026-10-24 07:23 UTC350
    if bytes.len() > max_bytes {
9bd34e25bb86atagen2026-10-24 07:23 UTC351
        return Err(Error::new(
9bd34e25bb86atagen2026-10-24 07:23 UTC352
            ErrorKind::MediaTooLarge,
9bd34e25bb86atagen2026-10-24 07:23 UTC353
            "media exceeds the configured byte limit",
9bd34e25bb86atagen2026-10-24 07:23 UTC354
        ));
9bd34e25bb86atagen2026-10-24 07:23 UTC355
    }
9bd34e25bb86atagen2026-10-24 07:23 UTC356
    let mimetype: mime::Mime = mimetype
9bd34e25bb86atagen2026-10-24 07:23 UTC357
        .parse()
9bd34e25bb86atagen2026-10-24 07:23 UTC358
        .map_err(|_| Error::new(ErrorKind::Invalid, "media MIME type is invalid"))?;
9bd34e25bb86atagen2026-10-24 07:23 UTC359
    client
9bd34e25bb86atagen2026-10-24 07:23 UTC360
        .media()
9bd34e25bb86atagen2026-10-24 07:23 UTC361
        .upload(&mimetype, bytes, None)
9bd34e25bb86atagen2026-10-24 07:23 UTC362
        .await
9bd34e25bb86atagen2026-10-24 07:23 UTC363
        .map(|response| response.content_uri)
9bd34e25bb86atagen2026-10-24 07:23 UTC364
        .map_err(|source| Error::from_sdk("uploading media failed", source))
9bd34e25bb86atagen2026-10-24 07:23 UTC365
}
9bd34e25bb86atagen2026-10-24 07:23 UTC366
31ce2453c82fatagen2026-09-24 13:07 UTC367
pub(super) async fn rename_device(
31ce2453c82fatagen2026-09-24 13:07 UTC368
    client: &Client,
31ce2453c82fatagen2026-09-24 13:07 UTC369
    device_id: &OwnedDeviceId,
31ce2453c82fatagen2026-09-24 13:07 UTC370
    display_name: &str,
31ce2453c82fatagen2026-09-24 13:07 UTC371
) -> Result<(), Error> {
31ce2453c82fatagen2026-09-24 13:07 UTC372
    client
31ce2453c82fatagen2026-09-24 13:07 UTC373
        .rename_device(device_id, display_name)
31ce2453c82fatagen2026-09-24 13:07 UTC374
        .await
31ce2453c82fatagen2026-09-24 13:07 UTC375
        .map(drop)
31ce2453c82fatagen2026-09-24 13:07 UTC376
        .map_err(|source| Error::from_http("renaming device failed", source))
31ce2453c82fatagen2026-09-24 13:07 UTC377
}
31ce2453c82fatagen2026-09-24 13:07 UTC378
31ce2453c82fatagen2026-09-24 13:07 UTC379
pub(super) async fn delete_devices(
31ce2453c82fatagen2026-09-24 13:07 UTC380
    client: &Client,
31ce2453c82fatagen2026-09-24 13:07 UTC381
    device_ids: &[OwnedDeviceId],
31ce2453c82fatagen2026-09-24 13:07 UTC382
    auth: Option<InteractiveAuth>,
31ce2453c82fatagen2026-09-24 13:07 UTC383
) -> Result<(), Error> {
31ce2453c82fatagen2026-09-24 13:07 UTC384
    let user_id = client
31ce2453c82fatagen2026-09-24 13:07 UTC385
        .user_id()
31ce2453c82fatagen2026-09-24 13:07 UTC386
        .ok_or_else(|| Error::new(ErrorKind::Unauthenticated, "the client is not logged in"))?;
31ce2453c82fatagen2026-09-24 13:07 UTC387
    let auth = auth
31ce2453c82fatagen2026-09-24 13:07 UTC388
        .map(|auth| auth.into_sdk(user_id))
31ce2453c82fatagen2026-09-24 13:07 UTC389
        .transpose()
31ce2453c82fatagen2026-09-24 13:07 UTC390
        .map_err(|message| {
31ce2453c82fatagen2026-09-24 13:07 UTC391
            Error::new(
31ce2453c82fatagen2026-09-24 13:07 UTC392
                ErrorKind::Invalid,
31ce2453c82fatagen2026-09-24 13:07 UTC393
                format!("interactive authentication data is invalid, {message}"),
31ce2453c82fatagen2026-09-24 13:07 UTC394
            )
31ce2453c82fatagen2026-09-24 13:07 UTC395
        })?;
31ce2453c82fatagen2026-09-24 13:07 UTC396
    match client.delete_devices(device_ids, auth).await {
31ce2453c82fatagen2026-09-24 13:07 UTC397
        Ok(_) => Ok(()),
31ce2453c82fatagen2026-09-24 13:07 UTC398
        Err(source) => match source.as_uiaa_response() {
31ce2453c82fatagen2026-09-24 13:07 UTC399
            Some(info) => Err(Error::interactive_auth(InteractiveAuthChallenge::from(
31ce2453c82fatagen2026-09-24 13:07 UTC400
                info,
31ce2453c82fatagen2026-09-24 13:07 UTC401
            ))),
31ce2453c82fatagen2026-09-24 13:07 UTC402
            None => Err(Error::from_http("deleting devices failed", source)),
31ce2453c82fatagen2026-09-24 13:07 UTC403
        },
31ce2453c82fatagen2026-09-24 13:07 UTC404
    }
31ce2453c82fatagen2026-09-24 13:07 UTC405
}