atagen

macltok

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

Blame: src/engine/mutations.rs

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