atagen

macltok

public
Star0
Watch0
8b67d612ef83460158bb0d15eaa9797bdb89a79c
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,
31ce2453c82fatagen2026-09-24 13:07 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,
31ce2453c82fatagen2026-09-24 13:07 UTC10
        api::client::receipt::create_receipt::v3::ReceiptType, events::receipt::ReceiptThread,
31ce2453c82fatagen2026-09-24 13:07 UTC11
    },
31ce2453c82fatagen2026-09-24 13:07 UTC12
    send_queue::{LocalEchoContent, SendHandle},
31ce2453c82fatagen2026-09-24 13:07 UTC13
};
31ce2453c82fatagen2026-09-24 13:07 UTC14
use tokio::sync::mpsc;
31ce2453c82fatagen2026-09-24 13:07 UTC15
31ce2453c82fatagen2026-09-24 13:07 UTC16
use crate::{
31ce2453c82fatagen2026-09-24 13:07 UTC17
    EncryptionCommand, Error, ErrorKind, InteractiveAuth, InteractiveAuthChallenge,
31ce2453c82fatagen2026-09-24 13:07 UTC18
    NotificationCommand, RoomNotificationMode, TimelineId,
31ce2453c82fatagen2026-09-24 13:07 UTC19
    encryption::VerificationController,
31ce2453c82fatagen2026-09-24 13:07 UTC20
    transport::{EventSender, Responder},
31ce2453c82fatagen2026-09-24 13:07 UTC21
};
31ce2453c82fatagen2026-09-24 13:07 UTC22
31ce2453c82fatagen2026-09-24 13:07 UTC23
pub(super) struct Job<T> {
31ce2453c82fatagen2026-09-24 13:07 UTC24
    pub(super) command: T,
31ce2453c82fatagen2026-09-24 13:07 UTC25
    pub(super) reply: Responder,
31ce2453c82fatagen2026-09-24 13:07 UTC26
}
31ce2453c82fatagen2026-09-24 13:07 UTC27
31ce2453c82fatagen2026-09-24 13:07 UTC28
/// a panicking command answers with an error instead of taking its worker down with it
31ce2453c82fatagen2026-09-24 13:07 UTC29
pub(super) async fn guarded<T>(
31ce2453c82fatagen2026-09-24 13:07 UTC30
    label: &'static str,
31ce2453c82fatagen2026-09-24 13:07 UTC31
    work: impl Future<Output = Result<T, Error>>,
31ce2453c82fatagen2026-09-24 13:07 UTC32
) -> Result<T, Error> {
31ce2453c82fatagen2026-09-24 13:07 UTC33
    AssertUnwindSafe(work)
31ce2453c82fatagen2026-09-24 13:07 UTC34
        .catch_unwind()
31ce2453c82fatagen2026-09-24 13:07 UTC35
        .await
31ce2453c82fatagen2026-09-24 13:07 UTC36
        .unwrap_or_else(|_| Err(Error::new(ErrorKind::Other, format!("{label} panicked"))))
31ce2453c82fatagen2026-09-24 13:07 UTC37
}
31ce2453c82fatagen2026-09-24 13:07 UTC38
31ce2453c82fatagen2026-09-24 13:07 UTC39
pub(super) async fn run_notifications(
31ce2453c82fatagen2026-09-24 13:07 UTC40
    client: Client,
31ce2453c82fatagen2026-09-24 13:07 UTC41
    mut jobs: mpsc::Receiver<Job<NotificationCommand>>,
31ce2453c82fatagen2026-09-24 13:07 UTC42
) {
31ce2453c82fatagen2026-09-24 13:07 UTC43
    while let Some(Job { command, reply }) = jobs.recv().await {
31ce2453c82fatagen2026-09-24 13:07 UTC44
        reply.done(guarded("notification command", notification(&client, command)).await);
31ce2453c82fatagen2026-09-24 13:07 UTC45
    }
31ce2453c82fatagen2026-09-24 13:07 UTC46
}
31ce2453c82fatagen2026-09-24 13:07 UTC47
31ce2453c82fatagen2026-09-24 13:07 UTC48
pub(super) async fn run_encryption(
31ce2453c82fatagen2026-09-24 13:07 UTC49
    client: Client,
31ce2453c82fatagen2026-09-24 13:07 UTC50
    events: EventSender,
31ce2453c82fatagen2026-09-24 13:07 UTC51
    mut jobs: mpsc::Receiver<Job<EncryptionCommand>>,
5a52012c9b1datagen2026-09-24 23:45 UTC52
    mut incoming: mpsc::UnboundedReceiver<VerificationRequest>,
31ce2453c82fatagen2026-09-24 13:07 UTC53
) {
31ce2453c82fatagen2026-09-24 13:07 UTC54
    let mut verification = VerificationController::default();
5a52012c9b1datagen2026-09-24 23:45 UTC55
    loop {
5a52012c9b1datagen2026-09-24 23:45 UTC56
        tokio::select! {
5a52012c9b1datagen2026-09-24 23:45 UTC57
            job = jobs.recv() => {
5a52012c9b1datagen2026-09-24 23:45 UTC58
                let Some(Job { command, reply }) = job else { return };
5a52012c9b1datagen2026-09-24 23:45 UTC59
                let work = encryption(&client, &events, &mut verification, command);
5a52012c9b1datagen2026-09-24 23:45 UTC60
                reply.done(guarded("encryption command", work).await);
5a52012c9b1datagen2026-09-24 23:45 UTC61
            }
5a52012c9b1datagen2026-09-24 23:45 UTC62
            Some(request) = incoming.recv() => {
5a52012c9b1datagen2026-09-24 23:45 UTC63
                verification.adopt_incoming(events.clone(), request).await;
5a52012c9b1datagen2026-09-24 23:45 UTC64
            }
5a52012c9b1datagen2026-09-24 23:45 UTC65
        }
31ce2453c82fatagen2026-09-24 13:07 UTC66
    }
31ce2453c82fatagen2026-09-24 13:07 UTC67
}
31ce2453c82fatagen2026-09-24 13:07 UTC68
31ce2453c82fatagen2026-09-24 13:07 UTC69
async fn encryption(
31ce2453c82fatagen2026-09-24 13:07 UTC70
    client: &Client,
31ce2453c82fatagen2026-09-24 13:07 UTC71
    events: &EventSender,
31ce2453c82fatagen2026-09-24 13:07 UTC72
    verification: &mut VerificationController,
31ce2453c82fatagen2026-09-24 13:07 UTC73
    command: EncryptionCommand,
31ce2453c82fatagen2026-09-24 13:07 UTC74
) -> Result<(), Error> {
31ce2453c82fatagen2026-09-24 13:07 UTC75
    let encryption = client.encryption();
31ce2453c82fatagen2026-09-24 13:07 UTC76
    match command {
31ce2453c82fatagen2026-09-24 13:07 UTC77
        EncryptionCommand::Recover(key) => encryption
31ce2453c82fatagen2026-09-24 13:07 UTC78
            .recovery()
31ce2453c82fatagen2026-09-24 13:07 UTC79
            .recover(&key)
31ce2453c82fatagen2026-09-24 13:07 UTC80
            .await
31ce2453c82fatagen2026-09-24 13:07 UTC81
            .map_err(|source| Error::from_recovery("account recovery failed", source)),
31ce2453c82fatagen2026-09-24 13:07 UTC82
        EncryptionCommand::RecoverKeyBackup(key) => encryption
31ce2453c82fatagen2026-09-24 13:07 UTC83
            .recovery()
31ce2453c82fatagen2026-09-24 13:07 UTC84
            .recover_and_fix_backup(&key)
31ce2453c82fatagen2026-09-24 13:07 UTC85
            .await
31ce2453c82fatagen2026-09-24 13:07 UTC86
            .map_err(|source| Error::from_recovery("recovering key backup failed", source)),
31ce2453c82fatagen2026-09-24 13:07 UTC87
        EncryptionCommand::CreateKeyBackup => encryption
31ce2453c82fatagen2026-09-24 13:07 UTC88
            .backups()
31ce2453c82fatagen2026-09-24 13:07 UTC89
            .create()
31ce2453c82fatagen2026-09-24 13:07 UTC90
            .await
31ce2453c82fatagen2026-09-24 13:07 UTC91
            .map_err(|source| Error::from_sdk("creating key backup failed", source)),
31ce2453c82fatagen2026-09-24 13:07 UTC92
        EncryptionCommand::EnableKeyBackup => encryption
31ce2453c82fatagen2026-09-24 13:07 UTC93
            .recovery()
31ce2453c82fatagen2026-09-24 13:07 UTC94
            .enable_backup()
31ce2453c82fatagen2026-09-24 13:07 UTC95
            .await
31ce2453c82fatagen2026-09-24 13:07 UTC96
            .map_err(|source| Error::from_recovery("enabling key backup failed", source)),
31ce2453c82fatagen2026-09-24 13:07 UTC97
        EncryptionCommand::DisableKeyBackup { delete_from_server } => {
31ce2453c82fatagen2026-09-24 13:07 UTC98
            let backups = encryption.backups();
31ce2453c82fatagen2026-09-24 13:07 UTC99
            if delete_from_server {
31ce2453c82fatagen2026-09-24 13:07 UTC100
                backups.disable_and_delete().await
31ce2453c82fatagen2026-09-24 13:07 UTC101
            } else {
31ce2453c82fatagen2026-09-24 13:07 UTC102
                backups.disable().await
31ce2453c82fatagen2026-09-24 13:07 UTC103
            }
31ce2453c82fatagen2026-09-24 13:07 UTC104
            .map_err(|source| Error::from_sdk("disabling key backup failed", source))
31ce2453c82fatagen2026-09-24 13:07 UTC105
        }
31ce2453c82fatagen2026-09-24 13:07 UTC106
        EncryptionCommand::StartVerification(target) => {
31ce2453c82fatagen2026-09-24 13:07 UTC107
            verification.start(client, events.clone(), target).await
31ce2453c82fatagen2026-09-24 13:07 UTC108
        }
31ce2453c82fatagen2026-09-24 13:07 UTC109
        EncryptionCommand::Verify(action) => verification.apply(events.clone(), action).await,
31ce2453c82fatagen2026-09-24 13:07 UTC110
    }
31ce2453c82fatagen2026-09-24 13:07 UTC111
}
31ce2453c82fatagen2026-09-24 13:07 UTC112
31ce2453c82fatagen2026-09-24 13:07 UTC113
async fn notification(client: &Client, command: NotificationCommand) -> Result<(), Error> {
31ce2453c82fatagen2026-09-24 13:07 UTC114
    let settings = client.notification_settings().await;
31ce2453c82fatagen2026-09-24 13:07 UTC115
    match command {
31ce2453c82fatagen2026-09-24 13:07 UTC116
        NotificationCommand::SetRoomMode { room_id, mode } => {
31ce2453c82fatagen2026-09-24 13:07 UTC117
            settings
31ce2453c82fatagen2026-09-24 13:07 UTC118
                .set_room_notification_mode(&room_id, sdk_mode(mode))
31ce2453c82fatagen2026-09-24 13:07 UTC119
                .await
31ce2453c82fatagen2026-09-24 13:07 UTC120
        }
31ce2453c82fatagen2026-09-24 13:07 UTC121
        NotificationCommand::ResetRoomMode(room_id) => {
31ce2453c82fatagen2026-09-24 13:07 UTC122
            settings.delete_user_defined_room_rules(&room_id).await
31ce2453c82fatagen2026-09-24 13:07 UTC123
        }
31ce2453c82fatagen2026-09-24 13:07 UTC124
        NotificationCommand::SetDefaultMode {
31ce2453c82fatagen2026-09-24 13:07 UTC125
            encrypted,
31ce2453c82fatagen2026-09-24 13:07 UTC126
            one_to_one,
31ce2453c82fatagen2026-09-24 13:07 UTC127
            mode,
31ce2453c82fatagen2026-09-24 13:07 UTC128
        } => {
31ce2453c82fatagen2026-09-24 13:07 UTC129
            settings
31ce2453c82fatagen2026-09-24 13:07 UTC130
                .set_default_room_notification_mode(
31ce2453c82fatagen2026-09-24 13:07 UTC131
                    IsEncrypted::from(encrypted),
31ce2453c82fatagen2026-09-24 13:07 UTC132
                    IsOneToOne::from(one_to_one),
31ce2453c82fatagen2026-09-24 13:07 UTC133
                    sdk_mode(mode),
31ce2453c82fatagen2026-09-24 13:07 UTC134
                )
31ce2453c82fatagen2026-09-24 13:07 UTC135
                .await
31ce2453c82fatagen2026-09-24 13:07 UTC136
        }
31ce2453c82fatagen2026-09-24 13:07 UTC137
        NotificationCommand::AddKeyword(keyword) => settings.add_keyword(keyword).await,
31ce2453c82fatagen2026-09-24 13:07 UTC138
        NotificationCommand::RemoveKeyword(keyword) => settings.remove_keyword(&keyword).await,
31ce2453c82fatagen2026-09-24 13:07 UTC139
    }
31ce2453c82fatagen2026-09-24 13:07 UTC140
    .map_err(|source| {
31ce2453c82fatagen2026-09-24 13:07 UTC141
        Error::with_source(
31ce2453c82fatagen2026-09-24 13:07 UTC142
            ErrorKind::Server,
31ce2453c82fatagen2026-09-24 13:07 UTC143
            "updating notification settings failed",
31ce2453c82fatagen2026-09-24 13:07 UTC144
            source,
31ce2453c82fatagen2026-09-24 13:07 UTC145
        )
31ce2453c82fatagen2026-09-24 13:07 UTC146
    })
31ce2453c82fatagen2026-09-24 13:07 UTC147
}
31ce2453c82fatagen2026-09-24 13:07 UTC148
31ce2453c82fatagen2026-09-24 13:07 UTC149
const fn sdk_mode(mode: RoomNotificationMode) -> SdkMode {
31ce2453c82fatagen2026-09-24 13:07 UTC150
    match mode {
31ce2453c82fatagen2026-09-24 13:07 UTC151
        RoomNotificationMode::AllMessages => SdkMode::AllMessages,
31ce2453c82fatagen2026-09-24 13:07 UTC152
        RoomNotificationMode::MentionsAndKeywordsOnly => SdkMode::MentionsAndKeywordsOnly,
31ce2453c82fatagen2026-09-24 13:07 UTC153
        RoomNotificationMode::Mute => SdkMode::Mute,
31ce2453c82fatagen2026-09-24 13:07 UTC154
    }
31ce2453c82fatagen2026-09-24 13:07 UTC155
}
31ce2453c82fatagen2026-09-24 13:07 UTC156
31ce2453c82fatagen2026-09-24 13:07 UTC157
fn room(client: &Client, room_id: &RoomId) -> Result<matrix_sdk::Room, Error> {
31ce2453c82fatagen2026-09-24 13:07 UTC158
    client.get_room(room_id).ok_or_else(Error::room_unknown)
31ce2453c82fatagen2026-09-24 13:07 UTC159
}
31ce2453c82fatagen2026-09-24 13:07 UTC160
31ce2453c82fatagen2026-09-24 13:07 UTC161
/// the closed-room path. without a timeline nothing stops the marker going backwards
31ce2453c82fatagen2026-09-24 13:07 UTC162
pub(super) async fn mark_read(
31ce2453c82fatagen2026-09-24 13:07 UTC163
    client: &Client,
31ce2453c82fatagen2026-09-24 13:07 UTC164
    timeline_id: &TimelineId,
31ce2453c82fatagen2026-09-24 13:07 UTC165
    event_id: OwnedEventId,
31ce2453c82fatagen2026-09-24 13:07 UTC166
) -> Result<(), Error> {
31ce2453c82fatagen2026-09-24 13:07 UTC167
    let room = room(client, timeline_id.room_id())?;
31ce2453c82fatagen2026-09-24 13:07 UTC168
    let receipt_failed = |source| Error::from_sdk("sending read receipt failed", source);
31ce2453c82fatagen2026-09-24 13:07 UTC169
    let thread = match timeline_id.thread_root() {
31ce2453c82fatagen2026-09-24 13:07 UTC170
        None => ReceiptThread::Main,
31ce2453c82fatagen2026-09-24 13:07 UTC171
        Some(root) => ReceiptThread::Thread(root.clone()),
31ce2453c82fatagen2026-09-24 13:07 UTC172
    };
31ce2453c82fatagen2026-09-24 13:07 UTC173
    room.send_single_receipt(ReceiptType::Read, thread, event_id.clone())
31ce2453c82fatagen2026-09-24 13:07 UTC174
        .await
31ce2453c82fatagen2026-09-24 13:07 UTC175
        .map_err(receipt_failed)?;
31ce2453c82fatagen2026-09-24 13:07 UTC176
    if timeline_id.thread_root().is_none() {
31ce2453c82fatagen2026-09-24 13:07 UTC177
        room.send_single_receipt(ReceiptType::FullyRead, ReceiptThread::Unthreaded, event_id)
31ce2453c82fatagen2026-09-24 13:07 UTC178
            .await
31ce2453c82fatagen2026-09-24 13:07 UTC179
            .map_err(receipt_failed)?;
31ce2453c82fatagen2026-09-24 13:07 UTC180
    }
31ce2453c82fatagen2026-09-24 13:07 UTC181
    Ok(())
31ce2453c82fatagen2026-09-24 13:07 UTC182
}
31ce2453c82fatagen2026-09-24 13:07 UTC183
31ce2453c82fatagen2026-09-24 13:07 UTC184
pub(super) async fn set_typing(
31ce2453c82fatagen2026-09-24 13:07 UTC185
    client: &Client,
31ce2453c82fatagen2026-09-24 13:07 UTC186
    room_id: &RoomId,
31ce2453c82fatagen2026-09-24 13:07 UTC187
    typing: bool,
31ce2453c82fatagen2026-09-24 13:07 UTC188
) -> Result<(), Error> {
31ce2453c82fatagen2026-09-24 13:07 UTC189
    room(client, room_id)?
31ce2453c82fatagen2026-09-24 13:07 UTC190
        .typing_notice(typing)
31ce2453c82fatagen2026-09-24 13:07 UTC191
        .await
31ce2453c82fatagen2026-09-24 13:07 UTC192
        .map_err(|source| Error::from_sdk("typing update failed", source))
31ce2453c82fatagen2026-09-24 13:07 UTC193
}
31ce2453c82fatagen2026-09-24 13:07 UTC194
31ce2453c82fatagen2026-09-24 13:07 UTC195
async fn send_handle(
31ce2453c82fatagen2026-09-24 13:07 UTC196
    client: &Client,
31ce2453c82fatagen2026-09-24 13:07 UTC197
    room_id: &RoomId,
31ce2453c82fatagen2026-09-24 13:07 UTC198
    transaction_id: &TransactionId,
31ce2453c82fatagen2026-09-24 13:07 UTC199
) -> Result<SendHandle, Error> {
31ce2453c82fatagen2026-09-24 13:07 UTC200
    let (echoes, _) = room(client, room_id)?
31ce2453c82fatagen2026-09-24 13:07 UTC201
        .send_queue()
31ce2453c82fatagen2026-09-24 13:07 UTC202
        .subscribe()
31ce2453c82fatagen2026-09-24 13:07 UTC203
        .await
31ce2453c82fatagen2026-09-24 13:07 UTC204
        .map_err(|source| {
31ce2453c82fatagen2026-09-24 13:07 UTC205
            Error::with_source(ErrorKind::Store, "reading send queue failed", source)
31ce2453c82fatagen2026-09-24 13:07 UTC206
        })?;
31ce2453c82fatagen2026-09-24 13:07 UTC207
    echoes
31ce2453c82fatagen2026-09-24 13:07 UTC208
        .into_iter()
31ce2453c82fatagen2026-09-24 13:07 UTC209
        .find_map(|echo| match echo.content {
31ce2453c82fatagen2026-09-24 13:07 UTC210
            LocalEchoContent::Event { send_handle, .. }
31ce2453c82fatagen2026-09-24 13:07 UTC211
                if echo.transaction_id.as_str() == transaction_id.as_str() =>
31ce2453c82fatagen2026-09-24 13:07 UTC212
            {
31ce2453c82fatagen2026-09-24 13:07 UTC213
                Some(send_handle)
31ce2453c82fatagen2026-09-24 13:07 UTC214
            }
31ce2453c82fatagen2026-09-24 13:07 UTC215
            _ => None,
31ce2453c82fatagen2026-09-24 13:07 UTC216
        })
31ce2453c82fatagen2026-09-24 13:07 UTC217
        .ok_or_else(|| {
31ce2453c82fatagen2026-09-24 13:07 UTC218
            Error::new(
31ce2453c82fatagen2026-09-24 13:07 UTC219
                ErrorKind::Invalid,
31ce2453c82fatagen2026-09-24 13:07 UTC220
                "send is no longer pending or is not a message",
31ce2453c82fatagen2026-09-24 13:07 UTC221
            )
31ce2453c82fatagen2026-09-24 13:07 UTC222
        })
31ce2453c82fatagen2026-09-24 13:07 UTC223
}
31ce2453c82fatagen2026-09-24 13:07 UTC224
31ce2453c82fatagen2026-09-24 13:07 UTC225
pub(super) async fn cancel_send(
31ce2453c82fatagen2026-09-24 13:07 UTC226
    client: &Client,
31ce2453c82fatagen2026-09-24 13:07 UTC227
    room_id: &RoomId,
31ce2453c82fatagen2026-09-24 13:07 UTC228
    transaction_id: &TransactionId,
31ce2453c82fatagen2026-09-24 13:07 UTC229
) -> Result<(), Error> {
31ce2453c82fatagen2026-09-24 13:07 UTC230
    match send_handle(client, room_id, transaction_id)
31ce2453c82fatagen2026-09-24 13:07 UTC231
        .await?
31ce2453c82fatagen2026-09-24 13:07 UTC232
        .abort()
31ce2453c82fatagen2026-09-24 13:07 UTC233
        .await
31ce2453c82fatagen2026-09-24 13:07 UTC234
    {
31ce2453c82fatagen2026-09-24 13:07 UTC235
        Ok(true) => Ok(()),
31ce2453c82fatagen2026-09-24 13:07 UTC236
        Ok(false) => Err(Error::new(
31ce2453c82fatagen2026-09-24 13:07 UTC237
            ErrorKind::Invalid,
31ce2453c82fatagen2026-09-24 13:07 UTC238
            "send could not be cancelled because it has already been sent",
31ce2453c82fatagen2026-09-24 13:07 UTC239
        )),
31ce2453c82fatagen2026-09-24 13:07 UTC240
        Err(source) => Err(Error::with_source(
31ce2453c82fatagen2026-09-24 13:07 UTC241
            ErrorKind::Store,
31ce2453c82fatagen2026-09-24 13:07 UTC242
            "cancelling send failed",
31ce2453c82fatagen2026-09-24 13:07 UTC243
            source,
31ce2453c82fatagen2026-09-24 13:07 UTC244
        )),
31ce2453c82fatagen2026-09-24 13:07 UTC245
    }
31ce2453c82fatagen2026-09-24 13:07 UTC246
}
31ce2453c82fatagen2026-09-24 13:07 UTC247
31ce2453c82fatagen2026-09-24 13:07 UTC248
pub(super) async fn retry_send(
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<(), Error> {
31ce2453c82fatagen2026-09-24 13:07 UTC253
    send_handle(client, room_id, transaction_id)
31ce2453c82fatagen2026-09-24 13:07 UTC254
        .await?
31ce2453c82fatagen2026-09-24 13:07 UTC255
        .unwedge()
31ce2453c82fatagen2026-09-24 13:07 UTC256
        .await
31ce2453c82fatagen2026-09-24 13:07 UTC257
        .map_err(|source| Error::with_source(ErrorKind::Store, "retrying send failed", source))?;
31ce2453c82fatagen2026-09-24 13:07 UTC258
    room(client, room_id)?.send_queue().set_enabled(true);
31ce2453c82fatagen2026-09-24 13:07 UTC259
    Ok(())
31ce2453c82fatagen2026-09-24 13:07 UTC260
}
31ce2453c82fatagen2026-09-24 13:07 UTC261
31ce2453c82fatagen2026-09-24 13:07 UTC262
pub(super) async fn upload_avatar(
31ce2453c82fatagen2026-09-24 13:07 UTC263
    client: &Client,
31ce2453c82fatagen2026-09-24 13:07 UTC264
    mimetype: &str,
31ce2453c82fatagen2026-09-24 13:07 UTC265
    bytes: Vec<u8>,
31ce2453c82fatagen2026-09-24 13:07 UTC266
    max_bytes: usize,
31ce2453c82fatagen2026-09-24 13:07 UTC267
) -> Result<(), Error> {
31ce2453c82fatagen2026-09-24 13:07 UTC268
    if bytes.len() > max_bytes {
31ce2453c82fatagen2026-09-24 13:07 UTC269
        return Err(Error::new(
31ce2453c82fatagen2026-09-24 13:07 UTC270
            ErrorKind::MediaTooLarge,
31ce2453c82fatagen2026-09-24 13:07 UTC271
            "avatar exceeds the configured byte limit",
31ce2453c82fatagen2026-09-24 13:07 UTC272
        ));
31ce2453c82fatagen2026-09-24 13:07 UTC273
    }
31ce2453c82fatagen2026-09-24 13:07 UTC274
    let mimetype: mime::Mime = mimetype
31ce2453c82fatagen2026-09-24 13:07 UTC275
        .parse()
31ce2453c82fatagen2026-09-24 13:07 UTC276
        .map_err(|_| Error::new(ErrorKind::Invalid, "avatar MIME type is invalid"))?;
31ce2453c82fatagen2026-09-24 13:07 UTC277
    if mimetype.type_() != mime::IMAGE {
31ce2453c82fatagen2026-09-24 13:07 UTC278
        return Err(Error::new(
31ce2453c82fatagen2026-09-24 13:07 UTC279
            ErrorKind::Invalid,
31ce2453c82fatagen2026-09-24 13:07 UTC280
            "an avatar has to be an image",
31ce2453c82fatagen2026-09-24 13:07 UTC281
        ));
31ce2453c82fatagen2026-09-24 13:07 UTC282
    }
31ce2453c82fatagen2026-09-24 13:07 UTC283
    client
31ce2453c82fatagen2026-09-24 13:07 UTC284
        .account()
31ce2453c82fatagen2026-09-24 13:07 UTC285
        .upload_avatar(&mimetype, bytes)
31ce2453c82fatagen2026-09-24 13:07 UTC286
        .await
31ce2453c82fatagen2026-09-24 13:07 UTC287
        .map(drop)
31ce2453c82fatagen2026-09-24 13:07 UTC288
        .map_err(|source| Error::from_sdk("uploading avatar failed", source))
31ce2453c82fatagen2026-09-24 13:07 UTC289
}
31ce2453c82fatagen2026-09-24 13:07 UTC290
9bd34e25bb86atagen2026-10-24 07:23 UTC291
pub(super) async fn upload_media(
9bd34e25bb86atagen2026-10-24 07:23 UTC292
    client: &Client,
9bd34e25bb86atagen2026-10-24 07:23 UTC293
    mimetype: &str,
9bd34e25bb86atagen2026-10-24 07:23 UTC294
    bytes: Vec<u8>,
9bd34e25bb86atagen2026-10-24 07:23 UTC295
    max_bytes: usize,
9bd34e25bb86atagen2026-10-24 07:23 UTC296
) -> Result<OwnedMxcUri, Error> {
9bd34e25bb86atagen2026-10-24 07:23 UTC297
    if bytes.len() > max_bytes {
9bd34e25bb86atagen2026-10-24 07:23 UTC298
        return Err(Error::new(
9bd34e25bb86atagen2026-10-24 07:23 UTC299
            ErrorKind::MediaTooLarge,
9bd34e25bb86atagen2026-10-24 07:23 UTC300
            "media exceeds the configured byte limit",
9bd34e25bb86atagen2026-10-24 07:23 UTC301
        ));
9bd34e25bb86atagen2026-10-24 07:23 UTC302
    }
9bd34e25bb86atagen2026-10-24 07:23 UTC303
    let mimetype: mime::Mime = mimetype
9bd34e25bb86atagen2026-10-24 07:23 UTC304
        .parse()
9bd34e25bb86atagen2026-10-24 07:23 UTC305
        .map_err(|_| Error::new(ErrorKind::Invalid, "media MIME type is invalid"))?;
9bd34e25bb86atagen2026-10-24 07:23 UTC306
    client
9bd34e25bb86atagen2026-10-24 07:23 UTC307
        .media()
9bd34e25bb86atagen2026-10-24 07:23 UTC308
        .upload(&mimetype, bytes, None)
9bd34e25bb86atagen2026-10-24 07:23 UTC309
        .await
9bd34e25bb86atagen2026-10-24 07:23 UTC310
        .map(|response| response.content_uri)
9bd34e25bb86atagen2026-10-24 07:23 UTC311
        .map_err(|source| Error::from_sdk("uploading media failed", source))
9bd34e25bb86atagen2026-10-24 07:23 UTC312
}
9bd34e25bb86atagen2026-10-24 07:23 UTC313
31ce2453c82fatagen2026-09-24 13:07 UTC314
pub(super) async fn rename_device(
31ce2453c82fatagen2026-09-24 13:07 UTC315
    client: &Client,
31ce2453c82fatagen2026-09-24 13:07 UTC316
    device_id: &OwnedDeviceId,
31ce2453c82fatagen2026-09-24 13:07 UTC317
    display_name: &str,
31ce2453c82fatagen2026-09-24 13:07 UTC318
) -> Result<(), Error> {
31ce2453c82fatagen2026-09-24 13:07 UTC319
    client
31ce2453c82fatagen2026-09-24 13:07 UTC320
        .rename_device(device_id, display_name)
31ce2453c82fatagen2026-09-24 13:07 UTC321
        .await
31ce2453c82fatagen2026-09-24 13:07 UTC322
        .map(drop)
31ce2453c82fatagen2026-09-24 13:07 UTC323
        .map_err(|source| Error::from_http("renaming device failed", source))
31ce2453c82fatagen2026-09-24 13:07 UTC324
}
31ce2453c82fatagen2026-09-24 13:07 UTC325
31ce2453c82fatagen2026-09-24 13:07 UTC326
pub(super) async fn delete_devices(
31ce2453c82fatagen2026-09-24 13:07 UTC327
    client: &Client,
31ce2453c82fatagen2026-09-24 13:07 UTC328
    device_ids: &[OwnedDeviceId],
31ce2453c82fatagen2026-09-24 13:07 UTC329
    auth: Option<InteractiveAuth>,
31ce2453c82fatagen2026-09-24 13:07 UTC330
) -> Result<(), Error> {
31ce2453c82fatagen2026-09-24 13:07 UTC331
    let user_id = client
31ce2453c82fatagen2026-09-24 13:07 UTC332
        .user_id()
31ce2453c82fatagen2026-09-24 13:07 UTC333
        .ok_or_else(|| Error::new(ErrorKind::Unauthenticated, "the client is not logged in"))?;
31ce2453c82fatagen2026-09-24 13:07 UTC334
    let auth = auth
31ce2453c82fatagen2026-09-24 13:07 UTC335
        .map(|auth| auth.into_sdk(user_id))
31ce2453c82fatagen2026-09-24 13:07 UTC336
        .transpose()
31ce2453c82fatagen2026-09-24 13:07 UTC337
        .map_err(|message| {
31ce2453c82fatagen2026-09-24 13:07 UTC338
            Error::new(
31ce2453c82fatagen2026-09-24 13:07 UTC339
                ErrorKind::Invalid,
31ce2453c82fatagen2026-09-24 13:07 UTC340
                format!("interactive authentication data is invalid, {message}"),
31ce2453c82fatagen2026-09-24 13:07 UTC341
            )
31ce2453c82fatagen2026-09-24 13:07 UTC342
        })?;
31ce2453c82fatagen2026-09-24 13:07 UTC343
    match client.delete_devices(device_ids, auth).await {
31ce2453c82fatagen2026-09-24 13:07 UTC344
        Ok(_) => Ok(()),
31ce2453c82fatagen2026-09-24 13:07 UTC345
        Err(source) => match source.as_uiaa_response() {
31ce2453c82fatagen2026-09-24 13:07 UTC346
            Some(info) => Err(Error::interactive_auth(InteractiveAuthChallenge::from(
31ce2453c82fatagen2026-09-24 13:07 UTC347
                info,
31ce2453c82fatagen2026-09-24 13:07 UTC348
            ))),
31ce2453c82fatagen2026-09-24 13:07 UTC349
            None => Err(Error::from_http("deleting devices failed", source)),
31ce2453c82fatagen2026-09-24 13:07 UTC350
        },
31ce2453c82fatagen2026-09-24 13:07 UTC351
    }
31ce2453c82fatagen2026-09-24 13:07 UTC352
}