atagen

macltok

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

Blame: src/engine/mutations.rs

31ce2453c82fatagen2026-09-24 13:07 UTC1
use std::collections::{HashMap, HashSet, VecDeque};
31ce2453c82fatagen2026-09-24 13:07 UTC2
31ce2453c82fatagen2026-09-24 13:07 UTC3
use matrix_sdk::{
31ce2453c82fatagen2026-09-24 13:07 UTC4
    Client, Room,
31ce2453c82fatagen2026-09-24 13:07 UTC5
    ruma::{
31ce2453c82fatagen2026-09-24 13:07 UTC6
        Int,
31ce2453c82fatagen2026-09-24 13:07 UTC7
        api::client::room::{
31ce2453c82fatagen2026-09-24 13:07 UTC8
            Visibility as SdkVisibility,
31ce2453c82fatagen2026-09-24 13:07 UTC9
            create_room::v3::{Request as CreateRoomRequest, RoomPreset as SdkPreset},
31ce2453c82fatagen2026-09-24 13:07 UTC10
        },
31ce2453c82fatagen2026-09-24 13:07 UTC11
        events::{
31ce2453c82fatagen2026-09-24 13:07 UTC12
            AnyGlobalAccountDataEventContent, GlobalAccountDataEventType, MessageLikeEventType,
31ce2453c82fatagen2026-09-24 13:07 UTC13
            StateEventType, room::power_levels::RoomPowerLevelsEventContent,
31ce2453c82fatagen2026-09-24 13:07 UTC14
        },
31ce2453c82fatagen2026-09-24 13:07 UTC15
        serde::Raw,
31ce2453c82fatagen2026-09-24 13:07 UTC16
    },
31ce2453c82fatagen2026-09-24 13:07 UTC17
};
31ce2453c82fatagen2026-09-24 13:07 UTC18
use ruma::{OwnedRoomId, OwnedUserId};
31ce2453c82fatagen2026-09-24 13:07 UTC19
use tokio::{sync::mpsc, task::JoinSet};
31ce2453c82fatagen2026-09-24 13:07 UTC20
31ce2453c82fatagen2026-09-24 13:07 UTC21
use super::commands::{Job, guarded};
31ce2453c82fatagen2026-09-24 13:07 UTC22
use crate::{
31ce2453c82fatagen2026-09-24 13:07 UTC23
    CommandOutput, CreateRoomOptions, Error, ErrorKind, PowerLevelChanges, RoomCommand, RoomPreset,
31ce2453c82fatagen2026-09-24 13:07 UTC24
    RoomVisibility,
31ce2453c82fatagen2026-09-24 13:07 UTC25
};
31ce2453c82fatagen2026-09-24 13:07 UTC26
31ce2453c82fatagen2026-09-24 13:07 UTC27
/// state changes that must not race each other. same room, same alias, same dm
31ce2453c82fatagen2026-09-24 13:07 UTC28
/// partner or same account data type run in order, everything else runs at once
31ce2453c82fatagen2026-09-24 13:07 UTC29
pub(super) enum Mutation {
31ce2453c82fatagen2026-09-24 13:07 UTC30
    Room(RoomCommand),
31ce2453c82fatagen2026-09-24 13:07 UTC31
    GlobalAccountData {
31ce2453c82fatagen2026-09-24 13:07 UTC32
        event_type: GlobalAccountDataEventType,
31ce2453c82fatagen2026-09-24 13:07 UTC33
        content: Raw<AnyGlobalAccountDataEventContent>,
31ce2453c82fatagen2026-09-24 13:07 UTC34
    },
31ce2453c82fatagen2026-09-24 13:07 UTC35
}
31ce2453c82fatagen2026-09-24 13:07 UTC36
31ce2453c82fatagen2026-09-24 13:07 UTC37
pub(super) type MutationJob = Job<Mutation>;
31ce2453c82fatagen2026-09-24 13:07 UTC38
31ce2453c82fatagen2026-09-24 13:07 UTC39
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
31ce2453c82fatagen2026-09-24 13:07 UTC40
enum Key {
31ce2453c82fatagen2026-09-24 13:07 UTC41
    Room(OwnedRoomId),
31ce2453c82fatagen2026-09-24 13:07 UTC42
    Alias(String),
31ce2453c82fatagen2026-09-24 13:07 UTC43
    Direct(OwnedUserId),
31ce2453c82fatagen2026-09-24 13:07 UTC44
    AccountData(String),
31ce2453c82fatagen2026-09-24 13:07 UTC45
    Independent(u64),
31ce2453c82fatagen2026-09-24 13:07 UTC46
}
31ce2453c82fatagen2026-09-24 13:07 UTC47
31ce2453c82fatagen2026-09-24 13:07 UTC48
pub(super) async fn run_scheduler(
31ce2453c82fatagen2026-09-24 13:07 UTC49
    client: Client,
31ce2453c82fatagen2026-09-24 13:07 UTC50
    mut jobs: mpsc::Receiver<MutationJob>,
31ce2453c82fatagen2026-09-24 13:07 UTC51
    queue_limit: usize,
31ce2453c82fatagen2026-09-24 13:07 UTC52
) {
31ce2453c82fatagen2026-09-24 13:07 UTC53
    let mut waiting = HashMap::<Key, VecDeque<MutationJob>>::new();
31ce2453c82fatagen2026-09-24 13:07 UTC54
    let mut running = HashSet::<Key>::new();
31ce2453c82fatagen2026-09-24 13:07 UTC55
    let mut tasks = JoinSet::<Key>::new();
31ce2453c82fatagen2026-09-24 13:07 UTC56
    let mut queued = 0_usize;
31ce2453c82fatagen2026-09-24 13:07 UTC57
    let mut independent = 0_u64;
31ce2453c82fatagen2026-09-24 13:07 UTC58
    let mut receiving = true;
31ce2453c82fatagen2026-09-24 13:07 UTC59
    while receiving || !tasks.is_empty() {
31ce2453c82fatagen2026-09-24 13:07 UTC60
        tokio::select! {
31ce2453c82fatagen2026-09-24 13:07 UTC61
            biased;
31ce2453c82fatagen2026-09-24 13:07 UTC62
            Some(finished) = tasks.join_next(), if !tasks.is_empty() => {
31ce2453c82fatagen2026-09-24 13:07 UTC63
                // a job's own panic is caught inside it, so this is only ever a key
31ce2453c82fatagen2026-09-24 13:07 UTC64
                let Ok(key) = finished else { continue };
31ce2453c82fatagen2026-09-24 13:07 UTC65
                let next = waiting.get_mut(&key).and_then(VecDeque::pop_front);
31ce2453c82fatagen2026-09-24 13:07 UTC66
                if waiting.get(&key).is_some_and(VecDeque::is_empty) {
31ce2453c82fatagen2026-09-24 13:07 UTC67
                    waiting.remove(&key);
31ce2453c82fatagen2026-09-24 13:07 UTC68
                }
31ce2453c82fatagen2026-09-24 13:07 UTC69
                match next {
31ce2453c82fatagen2026-09-24 13:07 UTC70
                    Some(job) => {
31ce2453c82fatagen2026-09-24 13:07 UTC71
                        queued -= 1;
31ce2453c82fatagen2026-09-24 13:07 UTC72
                        spawn(&mut tasks, &client, key, job);
31ce2453c82fatagen2026-09-24 13:07 UTC73
                    }
31ce2453c82fatagen2026-09-24 13:07 UTC74
                    None => {
31ce2453c82fatagen2026-09-24 13:07 UTC75
                        running.remove(&key);
31ce2453c82fatagen2026-09-24 13:07 UTC76
                    }
31ce2453c82fatagen2026-09-24 13:07 UTC77
                }
31ce2453c82fatagen2026-09-24 13:07 UTC78
            }
31ce2453c82fatagen2026-09-24 13:07 UTC79
            job = jobs.recv(), if receiving => {
31ce2453c82fatagen2026-09-24 13:07 UTC80
                let Some(job) = job else {
31ce2453c82fatagen2026-09-24 13:07 UTC81
                    receiving = false;
31ce2453c82fatagen2026-09-24 13:07 UTC82
                    continue;
31ce2453c82fatagen2026-09-24 13:07 UTC83
                };
31ce2453c82fatagen2026-09-24 13:07 UTC84
                let key = key(&job.command).unwrap_or_else(|| {
31ce2453c82fatagen2026-09-24 13:07 UTC85
                    independent += 1;
31ce2453c82fatagen2026-09-24 13:07 UTC86
                    Key::Independent(independent)
31ce2453c82fatagen2026-09-24 13:07 UTC87
                });
31ce2453c82fatagen2026-09-24 13:07 UTC88
                if running.insert(key.clone()) {
31ce2453c82fatagen2026-09-24 13:07 UTC89
                    spawn(&mut tasks, &client, key, job);
31ce2453c82fatagen2026-09-24 13:07 UTC90
                } else if queued < queue_limit {
31ce2453c82fatagen2026-09-24 13:07 UTC91
                    waiting.entry(key).or_default().push_back(job);
31ce2453c82fatagen2026-09-24 13:07 UTC92
                    queued += 1;
31ce2453c82fatagen2026-09-24 13:07 UTC93
                } else {
31ce2453c82fatagen2026-09-24 13:07 UTC94
                    job.reply.done(Err(Error::new(
31ce2453c82fatagen2026-09-24 13:07 UTC95
                        ErrorKind::QueueFull,
31ce2453c82fatagen2026-09-24 13:07 UTC96
                        "room command queue is full",
31ce2453c82fatagen2026-09-24 13:07 UTC97
                    )));
31ce2453c82fatagen2026-09-24 13:07 UTC98
                }
31ce2453c82fatagen2026-09-24 13:07 UTC99
            }
31ce2453c82fatagen2026-09-24 13:07 UTC100
        }
31ce2453c82fatagen2026-09-24 13:07 UTC101
    }
31ce2453c82fatagen2026-09-24 13:07 UTC102
}
31ce2453c82fatagen2026-09-24 13:07 UTC103
31ce2453c82fatagen2026-09-24 13:07 UTC104
fn key(mutation: &Mutation) -> Option<Key> {
31ce2453c82fatagen2026-09-24 13:07 UTC105
    let room_id = match mutation {
31ce2453c82fatagen2026-09-24 13:07 UTC106
        Mutation::GlobalAccountData { event_type, .. } => {
31ce2453c82fatagen2026-09-24 13:07 UTC107
            return Some(Key::AccountData(event_type.to_string()));
31ce2453c82fatagen2026-09-24 13:07 UTC108
        }
31ce2453c82fatagen2026-09-24 13:07 UTC109
        Mutation::Room(RoomCommand::Join { room, .. }) => {
31ce2453c82fatagen2026-09-24 13:07 UTC110
            return Some(match OwnedRoomId::try_from(room.clone()) {
31ce2453c82fatagen2026-09-24 13:07 UTC111
                Ok(room_id) => Key::Room(room_id),
31ce2453c82fatagen2026-09-24 13:07 UTC112
                Err(alias) => Key::Alias(alias.to_string()),
31ce2453c82fatagen2026-09-24 13:07 UTC113
            });
31ce2453c82fatagen2026-09-24 13:07 UTC114
        }
31ce2453c82fatagen2026-09-24 13:07 UTC115
        Mutation::Room(RoomCommand::CreateDirect(user_id)) => {
31ce2453c82fatagen2026-09-24 13:07 UTC116
            return Some(Key::Direct(user_id.clone()));
31ce2453c82fatagen2026-09-24 13:07 UTC117
        }
31ce2453c82fatagen2026-09-24 13:07 UTC118
        Mutation::Room(
31ce2453c82fatagen2026-09-24 13:07 UTC119
            RoomCommand::Leave(room_id)
31ce2453c82fatagen2026-09-24 13:07 UTC120
            | RoomCommand::Forget(room_id)
31ce2453c82fatagen2026-09-24 13:07 UTC121
            | RoomCommand::AcceptInvite(room_id)
31ce2453c82fatagen2026-09-24 13:07 UTC122
            | RoomCommand::RejectInvite(room_id)
31ce2453c82fatagen2026-09-24 13:07 UTC123
            | RoomCommand::InviteUser { room_id, .. }
31ce2453c82fatagen2026-09-24 13:07 UTC124
            | RoomCommand::KickUser { room_id, .. }
31ce2453c82fatagen2026-09-24 13:07 UTC125
            | RoomCommand::BanUser { room_id, .. }
31ce2453c82fatagen2026-09-24 13:07 UTC126
            | RoomCommand::UnbanUser { room_id, .. }
31ce2453c82fatagen2026-09-24 13:07 UTC127
            | RoomCommand::SetPowerLevels { room_id, .. }
31ce2453c82fatagen2026-09-24 13:07 UTC128
            | RoomCommand::SetUserPowerLevel { room_id, .. }
31ce2453c82fatagen2026-09-24 13:07 UTC129
            | RoomCommand::SetAccountData { room_id, .. },
31ce2453c82fatagen2026-09-24 13:07 UTC130
        ) => room_id,
31ce2453c82fatagen2026-09-24 13:07 UTC131
        Mutation::Room(_) => return None,
31ce2453c82fatagen2026-09-24 13:07 UTC132
    };
31ce2453c82fatagen2026-09-24 13:07 UTC133
    Some(Key::Room(room_id.clone()))
31ce2453c82fatagen2026-09-24 13:07 UTC134
}
31ce2453c82fatagen2026-09-24 13:07 UTC135
31ce2453c82fatagen2026-09-24 13:07 UTC136
fn spawn(tasks: &mut JoinSet<Key>, client: &Client, key: Key, job: MutationJob) {
31ce2453c82fatagen2026-09-24 13:07 UTC137
    let client = client.clone();
31ce2453c82fatagen2026-09-24 13:07 UTC138
    tasks.spawn(async move {
31ce2453c82fatagen2026-09-24 13:07 UTC139
        let Job { command, reply } = job;
31ce2453c82fatagen2026-09-24 13:07 UTC140
        reply.send(guarded("room command", execute(&client, command)).await);
31ce2453c82fatagen2026-09-24 13:07 UTC141
        key
31ce2453c82fatagen2026-09-24 13:07 UTC142
    });
31ce2453c82fatagen2026-09-24 13:07 UTC143
}
31ce2453c82fatagen2026-09-24 13:07 UTC144
31ce2453c82fatagen2026-09-24 13:07 UTC145
async fn execute(client: &Client, mutation: Mutation) -> Result<CommandOutput, Error> {
31ce2453c82fatagen2026-09-24 13:07 UTC146
    let command = match mutation {
31ce2453c82fatagen2026-09-24 13:07 UTC147
        Mutation::GlobalAccountData {
31ce2453c82fatagen2026-09-24 13:07 UTC148
            event_type,
31ce2453c82fatagen2026-09-24 13:07 UTC149
            content,
31ce2453c82fatagen2026-09-24 13:07 UTC150
        } => {
31ce2453c82fatagen2026-09-24 13:07 UTC151
            client
31ce2453c82fatagen2026-09-24 13:07 UTC152
                .account()
31ce2453c82fatagen2026-09-24 13:07 UTC153
                .set_account_data_raw(event_type, content)
31ce2453c82fatagen2026-09-24 13:07 UTC154
                .await
31ce2453c82fatagen2026-09-24 13:07 UTC155
                .map_err(|source| Error::from_sdk("writing account data failed", source))?;
31ce2453c82fatagen2026-09-24 13:07 UTC156
            return Ok(CommandOutput::Done);
31ce2453c82fatagen2026-09-24 13:07 UTC157
        }
31ce2453c82fatagen2026-09-24 13:07 UTC158
        Mutation::Room(command) => command,
31ce2453c82fatagen2026-09-24 13:07 UTC159
    };
31ce2453c82fatagen2026-09-24 13:07 UTC160
    let room = |room_id: &OwnedRoomId| client.get_room(room_id).ok_or_else(Error::room_unknown);
31ce2453c82fatagen2026-09-24 13:07 UTC161
    let joined = |room: Room| CommandOutput::Room(room.room_id().to_owned());
31ce2453c82fatagen2026-09-24 13:07 UTC162
    let sdk = |message: &'static str| move |source| Error::from_sdk(message, source);
31ce2453c82fatagen2026-09-24 13:07 UTC163
    match command {
31ce2453c82fatagen2026-09-24 13:07 UTC164
        RoomCommand::Join { room, via } => client
31ce2453c82fatagen2026-09-24 13:07 UTC165
            .join_room_by_id_or_alias(&room, &via)
31ce2453c82fatagen2026-09-24 13:07 UTC166
            .await
31ce2453c82fatagen2026-09-24 13:07 UTC167
            .map(joined)
31ce2453c82fatagen2026-09-24 13:07 UTC168
            .map_err(sdk("joining room failed")),
31ce2453c82fatagen2026-09-24 13:07 UTC169
        RoomCommand::Leave(room_id) => room(&room_id)?
31ce2453c82fatagen2026-09-24 13:07 UTC170
            .leave()
31ce2453c82fatagen2026-09-24 13:07 UTC171
            .await
31ce2453c82fatagen2026-09-24 13:07 UTC172
            .map(done)
31ce2453c82fatagen2026-09-24 13:07 UTC173
            .map_err(sdk("leaving room failed")),
31ce2453c82fatagen2026-09-24 13:07 UTC174
        RoomCommand::Forget(room_id) => room(&room_id)?
31ce2453c82fatagen2026-09-24 13:07 UTC175
            .forget()
31ce2453c82fatagen2026-09-24 13:07 UTC176
            .await
31ce2453c82fatagen2026-09-24 13:07 UTC177
            .map(done)
31ce2453c82fatagen2026-09-24 13:07 UTC178
            .map_err(sdk("forgetting room failed")),
31ce2453c82fatagen2026-09-24 13:07 UTC179
        RoomCommand::AcceptInvite(room_id) => room(&room_id)?
31ce2453c82fatagen2026-09-24 13:07 UTC180
            .join()
31ce2453c82fatagen2026-09-24 13:07 UTC181
            .await
31ce2453c82fatagen2026-09-24 13:07 UTC182
            .map(done)
31ce2453c82fatagen2026-09-24 13:07 UTC183
            .map_err(sdk("accepting invite failed")),
31ce2453c82fatagen2026-09-24 13:07 UTC184
        RoomCommand::RejectInvite(room_id) => room(&room_id)?
31ce2453c82fatagen2026-09-24 13:07 UTC185
            .leave()
31ce2453c82fatagen2026-09-24 13:07 UTC186
            .await
31ce2453c82fatagen2026-09-24 13:07 UTC187
            .map(done)
31ce2453c82fatagen2026-09-24 13:07 UTC188
            .map_err(sdk("rejecting invite failed")),
31ce2453c82fatagen2026-09-24 13:07 UTC189
        RoomCommand::InviteUser { room_id, user_id } => room(&room_id)?
31ce2453c82fatagen2026-09-24 13:07 UTC190
            .invite_user_by_id(&user_id)
31ce2453c82fatagen2026-09-24 13:07 UTC191
            .await
31ce2453c82fatagen2026-09-24 13:07 UTC192
            .map(done)
31ce2453c82fatagen2026-09-24 13:07 UTC193
            .map_err(sdk("inviting user failed")),
31ce2453c82fatagen2026-09-24 13:07 UTC194
        RoomCommand::KickUser {
31ce2453c82fatagen2026-09-24 13:07 UTC195
            room_id,
31ce2453c82fatagen2026-09-24 13:07 UTC196
            user_id,
31ce2453c82fatagen2026-09-24 13:07 UTC197
            reason,
31ce2453c82fatagen2026-09-24 13:07 UTC198
        } => room(&room_id)?
31ce2453c82fatagen2026-09-24 13:07 UTC199
            .kick_user(&user_id, reason.as_deref())
31ce2453c82fatagen2026-09-24 13:07 UTC200
            .await
31ce2453c82fatagen2026-09-24 13:07 UTC201
            .map(done)
31ce2453c82fatagen2026-09-24 13:07 UTC202
            .map_err(sdk("kicking user failed")),
31ce2453c82fatagen2026-09-24 13:07 UTC203
        RoomCommand::BanUser {
31ce2453c82fatagen2026-09-24 13:07 UTC204
            room_id,
31ce2453c82fatagen2026-09-24 13:07 UTC205
            user_id,
31ce2453c82fatagen2026-09-24 13:07 UTC206
            reason,
31ce2453c82fatagen2026-09-24 13:07 UTC207
        } => room(&room_id)?
31ce2453c82fatagen2026-09-24 13:07 UTC208
            .ban_user(&user_id, reason.as_deref())
31ce2453c82fatagen2026-09-24 13:07 UTC209
            .await
31ce2453c82fatagen2026-09-24 13:07 UTC210
            .map(done)
31ce2453c82fatagen2026-09-24 13:07 UTC211
            .map_err(sdk("banning user failed")),
31ce2453c82fatagen2026-09-24 13:07 UTC212
        RoomCommand::UnbanUser {
31ce2453c82fatagen2026-09-24 13:07 UTC213
            room_id,
31ce2453c82fatagen2026-09-24 13:07 UTC214
            user_id,
31ce2453c82fatagen2026-09-24 13:07 UTC215
            reason,
31ce2453c82fatagen2026-09-24 13:07 UTC216
        } => room(&room_id)?
31ce2453c82fatagen2026-09-24 13:07 UTC217
            .unban_user(&user_id, reason.as_deref())
31ce2453c82fatagen2026-09-24 13:07 UTC218
            .await
31ce2453c82fatagen2026-09-24 13:07 UTC219
            .map(done)
31ce2453c82fatagen2026-09-24 13:07 UTC220
            .map_err(sdk("unbanning user failed")),
31ce2453c82fatagen2026-09-24 13:07 UTC221
        RoomCommand::SetPowerLevels { room_id, changes } => {
31ce2453c82fatagen2026-09-24 13:07 UTC222
            apply_power_level_changes(&room(&room_id)?, changes)
31ce2453c82fatagen2026-09-24 13:07 UTC223
                .await
31ce2453c82fatagen2026-09-24 13:07 UTC224
                .map(done)
31ce2453c82fatagen2026-09-24 13:07 UTC225
        }
31ce2453c82fatagen2026-09-24 13:07 UTC226
        RoomCommand::SetUserPowerLevel {
31ce2453c82fatagen2026-09-24 13:07 UTC227
            room_id,
31ce2453c82fatagen2026-09-24 13:07 UTC228
            user_id,
31ce2453c82fatagen2026-09-24 13:07 UTC229
            level,
31ce2453c82fatagen2026-09-24 13:07 UTC230
        } => room(&room_id)?
31ce2453c82fatagen2026-09-24 13:07 UTC231
            .update_power_levels(vec![(&user_id, power_level(level)?)])
31ce2453c82fatagen2026-09-24 13:07 UTC232
            .await
31ce2453c82fatagen2026-09-24 13:07 UTC233
            .map(done)
31ce2453c82fatagen2026-09-24 13:07 UTC234
            .map_err(sdk("updating user power level failed")),
31ce2453c82fatagen2026-09-24 13:07 UTC235
        RoomCommand::SetAccountData {
31ce2453c82fatagen2026-09-24 13:07 UTC236
            room_id,
31ce2453c82fatagen2026-09-24 13:07 UTC237
            event_type,
31ce2453c82fatagen2026-09-24 13:07 UTC238
            content,
31ce2453c82fatagen2026-09-24 13:07 UTC239
        } => room(&room_id)?
31ce2453c82fatagen2026-09-24 13:07 UTC240
            .set_account_data_raw(event_type, content)
31ce2453c82fatagen2026-09-24 13:07 UTC241
            .await
31ce2453c82fatagen2026-09-24 13:07 UTC242
            .map(done)
31ce2453c82fatagen2026-09-24 13:07 UTC243
            .map_err(sdk("writing room account data failed")),
31ce2453c82fatagen2026-09-24 13:07 UTC244
        RoomCommand::Create(options) => client
31ce2453c82fatagen2026-09-24 13:07 UTC245
            .create_room(create_room_request(options))
31ce2453c82fatagen2026-09-24 13:07 UTC246
            .await
31ce2453c82fatagen2026-09-24 13:07 UTC247
            .map(joined)
31ce2453c82fatagen2026-09-24 13:07 UTC248
            .map_err(sdk("creating room failed")),
31ce2453c82fatagen2026-09-24 13:07 UTC249
        RoomCommand::CreateDirect(user_id) => match client.get_dm_room(&user_id) {
31ce2453c82fatagen2026-09-24 13:07 UTC250
            Some(existing) => Ok(joined(existing)),
31ce2453c82fatagen2026-09-24 13:07 UTC251
            None => client
31ce2453c82fatagen2026-09-24 13:07 UTC252
                .create_dm(&user_id)
31ce2453c82fatagen2026-09-24 13:07 UTC253
                .await
31ce2453c82fatagen2026-09-24 13:07 UTC254
                .map(joined)
31ce2453c82fatagen2026-09-24 13:07 UTC255
                .map_err(sdk("creating direct room failed")),
31ce2453c82fatagen2026-09-24 13:07 UTC256
        },
31ce2453c82fatagen2026-09-24 13:07 UTC257
        _ => Err(Error::new(ErrorKind::Other, "room command was misrouted")),
31ce2453c82fatagen2026-09-24 13:07 UTC258
    }
31ce2453c82fatagen2026-09-24 13:07 UTC259
}
31ce2453c82fatagen2026-09-24 13:07 UTC260
31ce2453c82fatagen2026-09-24 13:07 UTC261
/// one state event carrying every requested change, since the sdk's own patcher is private
31ce2453c82fatagen2026-09-24 13:07 UTC262
async fn apply_power_level_changes(room: &Room, changes: PowerLevelChanges) -> Result<(), Error> {
31ce2453c82fatagen2026-09-24 13:07 UTC263
    let mut levels = room
31ce2453c82fatagen2026-09-24 13:07 UTC264
        .power_levels()
31ce2453c82fatagen2026-09-24 13:07 UTC265
        .await
31ce2453c82fatagen2026-09-24 13:07 UTC266
        .map_err(|source| Error::from_sdk("reading power levels failed", source.into()))?;
31ce2453c82fatagen2026-09-24 13:07 UTC267
    for (target, value) in [
31ce2453c82fatagen2026-09-24 13:07 UTC268
        (&mut levels.ban, changes.ban),
31ce2453c82fatagen2026-09-24 13:07 UTC269
        (&mut levels.invite, changes.invite),
31ce2453c82fatagen2026-09-24 13:07 UTC270
        (&mut levels.kick, changes.kick),
31ce2453c82fatagen2026-09-24 13:07 UTC271
        (&mut levels.redact, changes.redact),
31ce2453c82fatagen2026-09-24 13:07 UTC272
        (&mut levels.events_default, changes.events_default),
31ce2453c82fatagen2026-09-24 13:07 UTC273
        (&mut levels.state_default, changes.state_default),
31ce2453c82fatagen2026-09-24 13:07 UTC274
        (&mut levels.users_default, changes.users_default),
31ce2453c82fatagen2026-09-24 13:07 UTC275
        (&mut levels.notifications.room, changes.room_notification),
31ce2453c82fatagen2026-09-24 13:07 UTC276
    ] {
31ce2453c82fatagen2026-09-24 13:07 UTC277
        if let Some(value) = value {
31ce2453c82fatagen2026-09-24 13:07 UTC278
            *target = power_level(value)?;
31ce2453c82fatagen2026-09-24 13:07 UTC279
        }
31ce2453c82fatagen2026-09-24 13:07 UTC280
    }
31ce2453c82fatagen2026-09-24 13:07 UTC281
    for (event_type, value) in [
31ce2453c82fatagen2026-09-24 13:07 UTC282
        (StateEventType::RoomName.into(), changes.room_name),
31ce2453c82fatagen2026-09-24 13:07 UTC283
        (StateEventType::RoomAvatar.into(), changes.room_avatar),
31ce2453c82fatagen2026-09-24 13:07 UTC284
        (StateEventType::RoomTopic.into(), changes.room_topic),
31ce2453c82fatagen2026-09-24 13:07 UTC285
        (StateEventType::SpaceChild.into(), changes.space_child),
31ce2453c82fatagen2026-09-24 13:07 UTC286
        (MessageLikeEventType::Beacon.into(), changes.beacon),
31ce2453c82fatagen2026-09-24 13:07 UTC287
        (StateEventType::BeaconInfo.into(), changes.beacon_info),
31ce2453c82fatagen2026-09-24 13:07 UTC288
    ] {
31ce2453c82fatagen2026-09-24 13:07 UTC289
        if let Some(value) = value {
31ce2453c82fatagen2026-09-24 13:07 UTC290
            levels.events.insert(event_type, power_level(value)?);
31ce2453c82fatagen2026-09-24 13:07 UTC291
        }
31ce2453c82fatagen2026-09-24 13:07 UTC292
    }
31ce2453c82fatagen2026-09-24 13:07 UTC293
    let content = RoomPowerLevelsEventContent::try_from(levels).map_err(|source| {
31ce2453c82fatagen2026-09-24 13:07 UTC294
        Error::with_source(
31ce2453c82fatagen2026-09-24 13:07 UTC295
            ErrorKind::Invalid,
31ce2453c82fatagen2026-09-24 13:07 UTC296
            "normalising power levels failed",
31ce2453c82fatagen2026-09-24 13:07 UTC297
            source,
31ce2453c82fatagen2026-09-24 13:07 UTC298
        )
31ce2453c82fatagen2026-09-24 13:07 UTC299
    })?;
31ce2453c82fatagen2026-09-24 13:07 UTC300
    room.send_state_event(content)
31ce2453c82fatagen2026-09-24 13:07 UTC301
        .await
31ce2453c82fatagen2026-09-24 13:07 UTC302
        .map(drop)
31ce2453c82fatagen2026-09-24 13:07 UTC303
        .map_err(|source| Error::from_sdk("updating power levels failed", source))
31ce2453c82fatagen2026-09-24 13:07 UTC304
}
31ce2453c82fatagen2026-09-24 13:07 UTC305
31ce2453c82fatagen2026-09-24 13:07 UTC306
fn done<T>(_: T) -> CommandOutput {
31ce2453c82fatagen2026-09-24 13:07 UTC307
    CommandOutput::Done
31ce2453c82fatagen2026-09-24 13:07 UTC308
}
31ce2453c82fatagen2026-09-24 13:07 UTC309
31ce2453c82fatagen2026-09-24 13:07 UTC310
fn power_level(value: i64) -> Result<Int, Error> {
31ce2453c82fatagen2026-09-24 13:07 UTC311
    value.try_into().map_err(|source| {
31ce2453c82fatagen2026-09-24 13:07 UTC312
        Error::with_source(
31ce2453c82fatagen2026-09-24 13:07 UTC313
            ErrorKind::Invalid,
31ce2453c82fatagen2026-09-24 13:07 UTC314
            "power level is outside the Matrix integer range",
31ce2453c82fatagen2026-09-24 13:07 UTC315
            source,
31ce2453c82fatagen2026-09-24 13:07 UTC316
        )
31ce2453c82fatagen2026-09-24 13:07 UTC317
    })
31ce2453c82fatagen2026-09-24 13:07 UTC318
}
31ce2453c82fatagen2026-09-24 13:07 UTC319
31ce2453c82fatagen2026-09-24 13:07 UTC320
fn create_room_request(options: CreateRoomOptions) -> CreateRoomRequest {
31ce2453c82fatagen2026-09-24 13:07 UTC321
    let mut request = CreateRoomRequest::new();
31ce2453c82fatagen2026-09-24 13:07 UTC322
    request.name = options.name;
31ce2453c82fatagen2026-09-24 13:07 UTC323
    request.topic = options.topic;
31ce2453c82fatagen2026-09-24 13:07 UTC324
    request.room_alias_name = options.alias_localpart;
31ce2453c82fatagen2026-09-24 13:07 UTC325
    request.invite = options.invite;
31ce2453c82fatagen2026-09-24 13:07 UTC326
    request.is_direct = options.direct;
31ce2453c82fatagen2026-09-24 13:07 UTC327
    request.visibility = match options.visibility {
31ce2453c82fatagen2026-09-24 13:07 UTC328
        RoomVisibility::Private => SdkVisibility::Private,
31ce2453c82fatagen2026-09-24 13:07 UTC329
        RoomVisibility::Public => SdkVisibility::Public,
31ce2453c82fatagen2026-09-24 13:07 UTC330
    };
31ce2453c82fatagen2026-09-24 13:07 UTC331
    request.preset = options.preset.map(|preset| match preset {
31ce2453c82fatagen2026-09-24 13:07 UTC332
        RoomPreset::PrivateChat => SdkPreset::PrivateChat,
31ce2453c82fatagen2026-09-24 13:07 UTC333
        RoomPreset::TrustedPrivateChat => SdkPreset::TrustedPrivateChat,
31ce2453c82fatagen2026-09-24 13:07 UTC334
        RoomPreset::PublicChat => SdkPreset::PublicChat,
31ce2453c82fatagen2026-09-24 13:07 UTC335
    });
31ce2453c82fatagen2026-09-24 13:07 UTC336
    request
31ce2453c82fatagen2026-09-24 13:07 UTC337
}