atagen

macltok

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