atagen

macltok

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