atagen

macltok

public
Star0
Watch0
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,
9bd34e25bb86atagen2026-10-24 07:23 UTC10
        api::client::{
9bd34e25bb86atagen2026-10-24 07:23 UTC11
            profile::{AvatarUrl, DisplayName},
9bd34e25bb86atagen2026-10-24 07:23 UTC12
            room::{
9bd34e25bb86atagen2026-10-24 07:23 UTC13
                Visibility as SdkVisibility,
9bd34e25bb86atagen2026-10-24 07:23 UTC14
                create_room::v3::{Request as CreateRoomRequest, RoomPreset as SdkPreset},
9bd34e25bb86atagen2026-10-24 07:23 UTC15
            },
9bd34e25bb86atagen2026-10-24 07:23 UTC16
            state::get_state_event_for_key,
31ce2453c82fatagen2026-09-24 13:07 UTC17
        },
fa5b4e09943aatagen2026-10-24 09:33 UTC18
        api::error::ErrorKind as ApiErrorKind,
31ce2453c82fatagen2026-09-24 13:07 UTC19
        events::{
31ce2453c82fatagen2026-09-24 13:07 UTC20
            AnyGlobalAccountDataEventContent, GlobalAccountDataEventType, MessageLikeEventType,
9bd34e25bb86atagen2026-10-24 07:23 UTC21
            StateEventType,
9bd34e25bb86atagen2026-10-24 07:23 UTC22
            ignored_user_list::{IgnoredUser, IgnoredUserListEventContent},
9bd34e25bb86atagen2026-10-24 07:23 UTC23
            room::power_levels::RoomPowerLevelsEventContent,
31ce2453c82fatagen2026-09-24 13:07 UTC24
        },
9bd34e25bb86atagen2026-10-24 07:23 UTC25
        serde::{JsonObject, Raw},
31ce2453c82fatagen2026-09-24 13:07 UTC26
    },
31ce2453c82fatagen2026-09-24 13:07 UTC27
};
fa5b4e09943aatagen2026-10-24 09:33 UTC28
use ruma::{EventId, OwnedRoomId, OwnedUserId};
9bd34e25bb86atagen2026-10-24 07:23 UTC29
use serde_json::Value as JsonValue;
1fe480c16a3catagen2026-09-26 08:44 UTC30
use tokio::sync::mpsc;
31ce2453c82fatagen2026-09-24 13:07 UTC31
31ce2453c82fatagen2026-09-24 13:07 UTC32
use super::commands::{Job, guarded};
1fe480c16a3catagen2026-09-26 08:44 UTC33
use super::executor::TaskSet;
fa51490b6ae9atagen2026-10-24 08:41 UTC34
use super::packs::{self, PackEdit};
31ce2453c82fatagen2026-09-24 13:07 UTC35
use crate::{
fa51490b6ae9atagen2026-10-24 08:41 UTC36
    CommandOutput, CreateRoomOptions, Error, ErrorKind, ImagePackSource, PowerLevelChanges,
fa51490b6ae9atagen2026-10-24 08:41 UTC37
    RoomCommand, RoomPreset, RoomVisibility,
fa51490b6ae9atagen2026-10-24 08:41 UTC38
    normalise::{ENABLED_ROOMS, USER_PACK, stored_space_order},
fa51490b6ae9atagen2026-10-24 08:41 UTC39
    space_order,
31ce2453c82fatagen2026-09-24 13:07 UTC40
};
31ce2453c82fatagen2026-09-24 13:07 UTC41
31ce2453c82fatagen2026-09-24 13:07 UTC42
/// state changes that must not race each other. same room, same alias, same dm
31ce2453c82fatagen2026-09-24 13:07 UTC43
/// partner or same account data type run in order, everything else runs at once
31ce2453c82fatagen2026-09-24 13:07 UTC44
pub(super) enum Mutation {
31ce2453c82fatagen2026-09-24 13:07 UTC45
    Room(RoomCommand),
31ce2453c82fatagen2026-09-24 13:07 UTC46
    GlobalAccountData {
31ce2453c82fatagen2026-09-24 13:07 UTC47
        event_type: GlobalAccountDataEventType,
31ce2453c82fatagen2026-09-24 13:07 UTC48
        content: Raw<AnyGlobalAccountDataEventContent>,
31ce2453c82fatagen2026-09-24 13:07 UTC49
    },
9bd34e25bb86atagen2026-10-24 07:23 UTC50
    Ignore {
9bd34e25bb86atagen2026-10-24 07:23 UTC51
        user_id: OwnedUserId,
9bd34e25bb86atagen2026-10-24 07:23 UTC52
        ignored: bool,
9bd34e25bb86atagen2026-10-24 07:23 UTC53
    },
9bd34e25bb86atagen2026-10-24 07:23 UTC54
    ReorderSpaces(Vec<OwnedRoomId>),
fa51490b6ae9atagen2026-10-24 08:41 UTC55
    Pack {
fa51490b6ae9atagen2026-10-24 08:41 UTC56
        pack: ImagePackSource,
fa51490b6ae9atagen2026-10-24 08:41 UTC57
        edit: PackEdit,
fa51490b6ae9atagen2026-10-24 08:41 UTC58
    },
fa51490b6ae9atagen2026-10-24 08:41 UTC59
    PackEnabledEverywhere {
fa51490b6ae9atagen2026-10-24 08:41 UTC60
        room_id: OwnedRoomId,
fa51490b6ae9atagen2026-10-24 08:41 UTC61
        state_key: String,
fa51490b6ae9atagen2026-10-24 08:41 UTC62
        enabled: bool,
fa51490b6ae9atagen2026-10-24 08:41 UTC63
    },
31ce2453c82fatagen2026-09-24 13:07 UTC64
}
31ce2453c82fatagen2026-09-24 13:07 UTC65
31ce2453c82fatagen2026-09-24 13:07 UTC66
pub(super) type MutationJob = Job<Mutation>;
31ce2453c82fatagen2026-09-24 13:07 UTC67
31ce2453c82fatagen2026-09-24 13:07 UTC68
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
31ce2453c82fatagen2026-09-24 13:07 UTC69
enum Key {
31ce2453c82fatagen2026-09-24 13:07 UTC70
    Room(OwnedRoomId),
31ce2453c82fatagen2026-09-24 13:07 UTC71
    Alias(String),
31ce2453c82fatagen2026-09-24 13:07 UTC72
    Direct(OwnedUserId),
31ce2453c82fatagen2026-09-24 13:07 UTC73
    AccountData(String),
31ce2453c82fatagen2026-09-24 13:07 UTC74
    Independent(u64),
31ce2453c82fatagen2026-09-24 13:07 UTC75
}
31ce2453c82fatagen2026-09-24 13:07 UTC76
31ce2453c82fatagen2026-09-24 13:07 UTC77
pub(super) async fn run_scheduler(
31ce2453c82fatagen2026-09-24 13:07 UTC78
    client: Client,
31ce2453c82fatagen2026-09-24 13:07 UTC79
    mut jobs: mpsc::Receiver<MutationJob>,
31ce2453c82fatagen2026-09-24 13:07 UTC80
    queue_limit: usize,
31ce2453c82fatagen2026-09-24 13:07 UTC81
) {
9bd34e25bb86atagen2026-10-24 07:23 UTC82
    let fresh = Fresh::default();
31ce2453c82fatagen2026-09-24 13:07 UTC83
    let mut waiting = HashMap::<Key, VecDeque<MutationJob>>::new();
31ce2453c82fatagen2026-09-24 13:07 UTC84
    let mut running = HashSet::<Key>::new();
1fe480c16a3catagen2026-09-26 08:44 UTC85
    let mut tasks = TaskSet::<Key>::new();
31ce2453c82fatagen2026-09-24 13:07 UTC86
    let mut queued = 0_usize;
31ce2453c82fatagen2026-09-24 13:07 UTC87
    let mut independent = 0_u64;
31ce2453c82fatagen2026-09-24 13:07 UTC88
    let mut receiving = true;
31ce2453c82fatagen2026-09-24 13:07 UTC89
    while receiving || !tasks.is_empty() {
31ce2453c82fatagen2026-09-24 13:07 UTC90
        tokio::select! {
31ce2453c82fatagen2026-09-24 13:07 UTC91
            biased;
31ce2453c82fatagen2026-09-24 13:07 UTC92
            Some(finished) = tasks.join_next(), if !tasks.is_empty() => {
31ce2453c82fatagen2026-09-24 13:07 UTC93
                // a job's own panic is caught inside it, so this is only ever a key
31ce2453c82fatagen2026-09-24 13:07 UTC94
                let Ok(key) = finished else { continue };
31ce2453c82fatagen2026-09-24 13:07 UTC95
                let next = waiting.get_mut(&key).and_then(VecDeque::pop_front);
31ce2453c82fatagen2026-09-24 13:07 UTC96
                if waiting.get(&key).is_some_and(VecDeque::is_empty) {
31ce2453c82fatagen2026-09-24 13:07 UTC97
                    waiting.remove(&key);
31ce2453c82fatagen2026-09-24 13:07 UTC98
                }
31ce2453c82fatagen2026-09-24 13:07 UTC99
                match next {
31ce2453c82fatagen2026-09-24 13:07 UTC100
                    Some(job) => {
31ce2453c82fatagen2026-09-24 13:07 UTC101
                        queued -= 1;
9bd34e25bb86atagen2026-10-24 07:23 UTC102
                        spawn(&mut tasks, &client, &fresh, key, job);
31ce2453c82fatagen2026-09-24 13:07 UTC103
                    }
31ce2453c82fatagen2026-09-24 13:07 UTC104
                    None => {
31ce2453c82fatagen2026-09-24 13:07 UTC105
                        running.remove(&key);
31ce2453c82fatagen2026-09-24 13:07 UTC106
                    }
31ce2453c82fatagen2026-09-24 13:07 UTC107
                }
31ce2453c82fatagen2026-09-24 13:07 UTC108
            }
31ce2453c82fatagen2026-09-24 13:07 UTC109
            job = jobs.recv(), if receiving => {
31ce2453c82fatagen2026-09-24 13:07 UTC110
                let Some(job) = job else {
31ce2453c82fatagen2026-09-24 13:07 UTC111
                    receiving = false;
31ce2453c82fatagen2026-09-24 13:07 UTC112
                    continue;
31ce2453c82fatagen2026-09-24 13:07 UTC113
                };
5a52012c9b1datagen2026-09-24 23:45 UTC114
                let key = key(&job.command, client.user_id().map(ruma::UserId::server_name)).unwrap_or_else(|| {
31ce2453c82fatagen2026-09-24 13:07 UTC115
                    independent += 1;
31ce2453c82fatagen2026-09-24 13:07 UTC116
                    Key::Independent(independent)
31ce2453c82fatagen2026-09-24 13:07 UTC117
                });
31ce2453c82fatagen2026-09-24 13:07 UTC118
                if running.insert(key.clone()) {
9bd34e25bb86atagen2026-10-24 07:23 UTC119
                    spawn(&mut tasks, &client, &fresh, key, job);
31ce2453c82fatagen2026-09-24 13:07 UTC120
                } else if queued < queue_limit {
31ce2453c82fatagen2026-09-24 13:07 UTC121
                    waiting.entry(key).or_default().push_back(job);
31ce2453c82fatagen2026-09-24 13:07 UTC122
                    queued += 1;
31ce2453c82fatagen2026-09-24 13:07 UTC123
                } else {
31ce2453c82fatagen2026-09-24 13:07 UTC124
                    job.reply.done(Err(Error::new(
31ce2453c82fatagen2026-09-24 13:07 UTC125
                        ErrorKind::QueueFull,
31ce2453c82fatagen2026-09-24 13:07 UTC126
                        "room command queue is full",
31ce2453c82fatagen2026-09-24 13:07 UTC127
                    )));
31ce2453c82fatagen2026-09-24 13:07 UTC128
                }
31ce2453c82fatagen2026-09-24 13:07 UTC129
            }
31ce2453c82fatagen2026-09-24 13:07 UTC130
        }
31ce2453c82fatagen2026-09-24 13:07 UTC131
    }
31ce2453c82fatagen2026-09-24 13:07 UTC132
}
31ce2453c82fatagen2026-09-24 13:07 UTC133
5a52012c9b1datagen2026-09-24 23:45 UTC134
/// `server` is ours, since a new alias lives there
5a52012c9b1datagen2026-09-24 23:45 UTC135
fn key(mutation: &Mutation, server: Option<&ruma::ServerName>) -> Option<Key> {
31ce2453c82fatagen2026-09-24 13:07 UTC136
    let room_id = match mutation {
31ce2453c82fatagen2026-09-24 13:07 UTC137
        Mutation::GlobalAccountData { event_type, .. } => {
31ce2453c82fatagen2026-09-24 13:07 UTC138
            return Some(Key::AccountData(event_type.to_string()));
31ce2453c82fatagen2026-09-24 13:07 UTC139
        }
9bd34e25bb86atagen2026-10-24 07:23 UTC140
        Mutation::Ignore { .. } => {
9bd34e25bb86atagen2026-10-24 07:23 UTC141
            return Some(Key::AccountData(
9bd34e25bb86atagen2026-10-24 07:23 UTC142
                GlobalAccountDataEventType::IgnoredUserList.to_string(),
9bd34e25bb86atagen2026-10-24 07:23 UTC143
            ));
9bd34e25bb86atagen2026-10-24 07:23 UTC144
        }
9bd34e25bb86atagen2026-10-24 07:23 UTC145
        Mutation::ReorderSpaces(_) => {
9bd34e25bb86atagen2026-10-24 07:23 UTC146
            return Some(Key::AccountData(space_order::EVENT_TYPE.to_owned()));
9bd34e25bb86atagen2026-10-24 07:23 UTC147
        }
fa51490b6ae9atagen2026-10-24 08:41 UTC148
        Mutation::Pack {
fa51490b6ae9atagen2026-10-24 08:41 UTC149
            pack: ImagePackSource::User,
fa51490b6ae9atagen2026-10-24 08:41 UTC150
            ..
fa51490b6ae9atagen2026-10-24 08:41 UTC151
        } => return Some(Key::AccountData(USER_PACK.to_owned())),
fa51490b6ae9atagen2026-10-24 08:41 UTC152
        Mutation::Pack {
fa51490b6ae9atagen2026-10-24 08:41 UTC153
            pack: ImagePackSource::Room { room_id, .. },
fa51490b6ae9atagen2026-10-24 08:41 UTC154
            ..
fa51490b6ae9atagen2026-10-24 08:41 UTC155
        } => room_id,
fa51490b6ae9atagen2026-10-24 08:41 UTC156
        Mutation::PackEnabledEverywhere { .. } => {
fa51490b6ae9atagen2026-10-24 08:41 UTC157
            return Some(Key::AccountData(ENABLED_ROOMS.to_owned()));
fa51490b6ae9atagen2026-10-24 08:41 UTC158
        }
31ce2453c82fatagen2026-09-24 13:07 UTC159
        Mutation::Room(RoomCommand::Join { room, .. }) => {
31ce2453c82fatagen2026-09-24 13:07 UTC160
            return Some(match OwnedRoomId::try_from(room.clone()) {
31ce2453c82fatagen2026-09-24 13:07 UTC161
                Ok(room_id) => Key::Room(room_id),
31ce2453c82fatagen2026-09-24 13:07 UTC162
                Err(alias) => Key::Alias(alias.to_string()),
31ce2453c82fatagen2026-09-24 13:07 UTC163
            });
31ce2453c82fatagen2026-09-24 13:07 UTC164
        }
31ce2453c82fatagen2026-09-24 13:07 UTC165
        Mutation::Room(RoomCommand::CreateDirect(user_id)) => {
31ce2453c82fatagen2026-09-24 13:07 UTC166
            return Some(Key::Direct(user_id.clone()));
31ce2453c82fatagen2026-09-24 13:07 UTC167
        }
5a52012c9b1datagen2026-09-24 23:45 UTC168
        Mutation::Room(RoomCommand::Create(options)) => {
5a52012c9b1datagen2026-09-24 23:45 UTC169
            return match (&options.alias_localpart, options.invite.as_slice()) {
5a52012c9b1datagen2026-09-24 23:45 UTC170
                (Some(localpart), _) => Some(Key::Alias(format!("#{localpart}:{}", server?))),
5a52012c9b1datagen2026-09-24 23:45 UTC171
                (None, [user_id]) if options.direct => Some(Key::Direct(user_id.clone())),
5a52012c9b1datagen2026-09-24 23:45 UTC172
                _ => None,
5a52012c9b1datagen2026-09-24 23:45 UTC173
            };
5a52012c9b1datagen2026-09-24 23:45 UTC174
        }
31ce2453c82fatagen2026-09-24 13:07 UTC175
        Mutation::Room(
31ce2453c82fatagen2026-09-24 13:07 UTC176
            RoomCommand::Leave(room_id)
31ce2453c82fatagen2026-09-24 13:07 UTC177
            | RoomCommand::Forget(room_id)
31ce2453c82fatagen2026-09-24 13:07 UTC178
            | RoomCommand::AcceptInvite(room_id)
31ce2453c82fatagen2026-09-24 13:07 UTC179
            | RoomCommand::RejectInvite(room_id)
31ce2453c82fatagen2026-09-24 13:07 UTC180
            | RoomCommand::InviteUser { room_id, .. }
31ce2453c82fatagen2026-09-24 13:07 UTC181
            | RoomCommand::KickUser { room_id, .. }
31ce2453c82fatagen2026-09-24 13:07 UTC182
            | RoomCommand::BanUser { room_id, .. }
31ce2453c82fatagen2026-09-24 13:07 UTC183
            | RoomCommand::UnbanUser { room_id, .. }
31ce2453c82fatagen2026-09-24 13:07 UTC184
            | RoomCommand::SetPowerLevels { room_id, .. }
31ce2453c82fatagen2026-09-24 13:07 UTC185
            | RoomCommand::SetUserPowerLevel { room_id, .. }
9bd34e25bb86atagen2026-10-24 07:23 UTC186
            | RoomCommand::MuteUser { room_id, .. }
9bd34e25bb86atagen2026-10-24 07:23 UTC187
            | RoomCommand::SetFavourite { room_id, .. }
fa5b4e09943aatagen2026-10-24 09:33 UTC188
            | RoomCommand::SetPinned { room_id, .. }
9bd34e25bb86atagen2026-10-24 07:23 UTC189
            | RoomCommand::SetOwnDisplayName { room_id, .. }
9bd34e25bb86atagen2026-10-24 07:23 UTC190
            | RoomCommand::SetOwnAvatar { room_id, .. }
31ce2453c82fatagen2026-09-24 13:07 UTC191
            | RoomCommand::SetAccountData { room_id, .. },
31ce2453c82fatagen2026-09-24 13:07 UTC192
        ) => room_id,
31ce2453c82fatagen2026-09-24 13:07 UTC193
        Mutation::Room(_) => return None,
31ce2453c82fatagen2026-09-24 13:07 UTC194
    };
31ce2453c82fatagen2026-09-24 13:07 UTC195
    Some(Key::Room(room_id.clone()))
31ce2453c82fatagen2026-09-24 13:07 UTC196
}
31ce2453c82fatagen2026-09-24 13:07 UTC197
5a52012c9b1datagen2026-09-24 23:45 UTC198
/// dms made here that the next sync hasn't told the sdk about yet, since `get_dm_room`
5a52012c9b1datagen2026-09-24 23:45 UTC199
/// only knows `m.direct` once it comes back round
5a52012c9b1datagen2026-09-24 23:45 UTC200
type FreshDms = Arc<std::sync::Mutex<HashMap<OwnedUserId, OwnedRoomId>>>;
5a52012c9b1datagen2026-09-24 23:45 UTC201
9bd34e25bb86atagen2026-10-24 07:23 UTC202
/// space orders written here that sync hasn't echoed into the store yet
9bd34e25bb86atagen2026-10-24 07:23 UTC203
type FreshOrders = Arc<std::sync::Mutex<HashMap<OwnedRoomId, String>>>;
9bd34e25bb86atagen2026-10-24 07:23 UTC204
9bd34e25bb86atagen2026-10-24 07:23 UTC205
#[derive(Clone, Default)]
9bd34e25bb86atagen2026-10-24 07:23 UTC206
struct Fresh {
9bd34e25bb86atagen2026-10-24 07:23 UTC207
    dms: FreshDms,
9bd34e25bb86atagen2026-10-24 07:23 UTC208
    orders: FreshOrders,
9bd34e25bb86atagen2026-10-24 07:23 UTC209
}
9bd34e25bb86atagen2026-10-24 07:23 UTC210
9bd34e25bb86atagen2026-10-24 07:23 UTC211
fn spawn(tasks: &mut TaskSet<Key>, client: &Client, fresh: &Fresh, key: Key, job: MutationJob) {
31ce2453c82fatagen2026-09-24 13:07 UTC212
    let client = client.clone();
9bd34e25bb86atagen2026-10-24 07:23 UTC213
    let fresh = fresh.clone();
31ce2453c82fatagen2026-09-24 13:07 UTC214
    tasks.spawn(async move {
31ce2453c82fatagen2026-09-24 13:07 UTC215
        let Job { command, reply } = job;
9bd34e25bb86atagen2026-10-24 07:23 UTC216
        reply.send(guarded("room command", execute(&client, &fresh, command)).await);
31ce2453c82fatagen2026-09-24 13:07 UTC217
        key
31ce2453c82fatagen2026-09-24 13:07 UTC218
    });
31ce2453c82fatagen2026-09-24 13:07 UTC219
}
31ce2453c82fatagen2026-09-24 13:07 UTC220
5a52012c9b1datagen2026-09-24 23:45 UTC221
async fn execute(
5a52012c9b1datagen2026-09-24 23:45 UTC222
    client: &Client,
9bd34e25bb86atagen2026-10-24 07:23 UTC223
    fresh: &Fresh,
5a52012c9b1datagen2026-09-24 23:45 UTC224
    mutation: Mutation,
5a52012c9b1datagen2026-09-24 23:45 UTC225
) -> Result<CommandOutput, Error> {
31ce2453c82fatagen2026-09-24 13:07 UTC226
    let command = match mutation {
31ce2453c82fatagen2026-09-24 13:07 UTC227
        Mutation::GlobalAccountData {
31ce2453c82fatagen2026-09-24 13:07 UTC228
            event_type,
31ce2453c82fatagen2026-09-24 13:07 UTC229
            content,
31ce2453c82fatagen2026-09-24 13:07 UTC230
        } => {
31ce2453c82fatagen2026-09-24 13:07 UTC231
            client
31ce2453c82fatagen2026-09-24 13:07 UTC232
                .account()
31ce2453c82fatagen2026-09-24 13:07 UTC233
                .set_account_data_raw(event_type, content)
31ce2453c82fatagen2026-09-24 13:07 UTC234
                .await
31ce2453c82fatagen2026-09-24 13:07 UTC235
                .map_err(|source| Error::from_sdk("writing account data failed", source))?;
31ce2453c82fatagen2026-09-24 13:07 UTC236
            return Ok(CommandOutput::Done);
31ce2453c82fatagen2026-09-24 13:07 UTC237
        }
9bd34e25bb86atagen2026-10-24 07:23 UTC238
        Mutation::Ignore { user_id, ignored } => {
9bd34e25bb86atagen2026-10-24 07:23 UTC239
            return set_ignored(client, user_id, ignored).await.map(done);
9bd34e25bb86atagen2026-10-24 07:23 UTC240
        }
9bd34e25bb86atagen2026-10-24 07:23 UTC241
        Mutation::ReorderSpaces(order) => {
9bd34e25bb86atagen2026-10-24 07:23 UTC242
            return reorder_spaces(client, &fresh.orders, order).await.map(done);
9bd34e25bb86atagen2026-10-24 07:23 UTC243
        }
fa51490b6ae9atagen2026-10-24 08:41 UTC244
        Mutation::Pack { pack, edit } => return packs::edit_pack(client, &pack, edit).await,
fa51490b6ae9atagen2026-10-24 08:41 UTC245
        Mutation::PackEnabledEverywhere {
fa51490b6ae9atagen2026-10-24 08:41 UTC246
            room_id,
fa51490b6ae9atagen2026-10-24 08:41 UTC247
            state_key,
fa51490b6ae9atagen2026-10-24 08:41 UTC248
            enabled,
fa51490b6ae9atagen2026-10-24 08:41 UTC249
        } => {
fa51490b6ae9atagen2026-10-24 08:41 UTC250
            return packs::set_enabled_everywhere(client, room_id, state_key, enabled)
fa51490b6ae9atagen2026-10-24 08:41 UTC251
                .await
fa51490b6ae9atagen2026-10-24 08:41 UTC252
                .map(done);
fa51490b6ae9atagen2026-10-24 08:41 UTC253
        }
31ce2453c82fatagen2026-09-24 13:07 UTC254
        Mutation::Room(command) => command,
31ce2453c82fatagen2026-09-24 13:07 UTC255
    };
9bd34e25bb86atagen2026-10-24 07:23 UTC256
    let fresh_dms = &fresh.dms;
31ce2453c82fatagen2026-09-24 13:07 UTC257
    let room = |room_id: &OwnedRoomId| client.get_room(room_id).ok_or_else(Error::room_unknown);
31ce2453c82fatagen2026-09-24 13:07 UTC258
    let joined = |room: Room| CommandOutput::Room(room.room_id().to_owned());
31ce2453c82fatagen2026-09-24 13:07 UTC259
    let sdk = |message: &'static str| move |source| Error::from_sdk(message, source);
31ce2453c82fatagen2026-09-24 13:07 UTC260
    match command {
31ce2453c82fatagen2026-09-24 13:07 UTC261
        RoomCommand::Join { room, via } => client
31ce2453c82fatagen2026-09-24 13:07 UTC262
            .join_room_by_id_or_alias(&room, &via)
31ce2453c82fatagen2026-09-24 13:07 UTC263
            .await
31ce2453c82fatagen2026-09-24 13:07 UTC264
            .map(joined)
31ce2453c82fatagen2026-09-24 13:07 UTC265
            .map_err(sdk("joining room failed")),
31ce2453c82fatagen2026-09-24 13:07 UTC266
        RoomCommand::Leave(room_id) => room(&room_id)?
31ce2453c82fatagen2026-09-24 13:07 UTC267
            .leave()
31ce2453c82fatagen2026-09-24 13:07 UTC268
            .await
31ce2453c82fatagen2026-09-24 13:07 UTC269
            .map(done)
31ce2453c82fatagen2026-09-24 13:07 UTC270
            .map_err(sdk("leaving room failed")),
31ce2453c82fatagen2026-09-24 13:07 UTC271
        RoomCommand::Forget(room_id) => room(&room_id)?
31ce2453c82fatagen2026-09-24 13:07 UTC272
            .forget()
31ce2453c82fatagen2026-09-24 13:07 UTC273
            .await
31ce2453c82fatagen2026-09-24 13:07 UTC274
            .map(done)
31ce2453c82fatagen2026-09-24 13:07 UTC275
            .map_err(sdk("forgetting room failed")),
31ce2453c82fatagen2026-09-24 13:07 UTC276
        RoomCommand::AcceptInvite(room_id) => room(&room_id)?
31ce2453c82fatagen2026-09-24 13:07 UTC277
            .join()
31ce2453c82fatagen2026-09-24 13:07 UTC278
            .await
31ce2453c82fatagen2026-09-24 13:07 UTC279
            .map(done)
31ce2453c82fatagen2026-09-24 13:07 UTC280
            .map_err(sdk("accepting invite failed")),
31ce2453c82fatagen2026-09-24 13:07 UTC281
        RoomCommand::RejectInvite(room_id) => room(&room_id)?
31ce2453c82fatagen2026-09-24 13:07 UTC282
            .leave()
31ce2453c82fatagen2026-09-24 13:07 UTC283
            .await
31ce2453c82fatagen2026-09-24 13:07 UTC284
            .map(done)
31ce2453c82fatagen2026-09-24 13:07 UTC285
            .map_err(sdk("rejecting invite failed")),
31ce2453c82fatagen2026-09-24 13:07 UTC286
        RoomCommand::InviteUser { room_id, user_id } => room(&room_id)?
31ce2453c82fatagen2026-09-24 13:07 UTC287
            .invite_user_by_id(&user_id)
31ce2453c82fatagen2026-09-24 13:07 UTC288
            .await
31ce2453c82fatagen2026-09-24 13:07 UTC289
            .map(done)
31ce2453c82fatagen2026-09-24 13:07 UTC290
            .map_err(sdk("inviting user failed")),
31ce2453c82fatagen2026-09-24 13:07 UTC291
        RoomCommand::KickUser {
31ce2453c82fatagen2026-09-24 13:07 UTC292
            room_id,
31ce2453c82fatagen2026-09-24 13:07 UTC293
            user_id,
31ce2453c82fatagen2026-09-24 13:07 UTC294
            reason,
31ce2453c82fatagen2026-09-24 13:07 UTC295
        } => room(&room_id)?
31ce2453c82fatagen2026-09-24 13:07 UTC296
            .kick_user(&user_id, reason.as_deref())
31ce2453c82fatagen2026-09-24 13:07 UTC297
            .await
31ce2453c82fatagen2026-09-24 13:07 UTC298
            .map(done)
31ce2453c82fatagen2026-09-24 13:07 UTC299
            .map_err(sdk("kicking user failed")),
31ce2453c82fatagen2026-09-24 13:07 UTC300
        RoomCommand::BanUser {
31ce2453c82fatagen2026-09-24 13:07 UTC301
            room_id,
31ce2453c82fatagen2026-09-24 13:07 UTC302
            user_id,
31ce2453c82fatagen2026-09-24 13:07 UTC303
            reason,
31ce2453c82fatagen2026-09-24 13:07 UTC304
        } => room(&room_id)?
31ce2453c82fatagen2026-09-24 13:07 UTC305
            .ban_user(&user_id, reason.as_deref())
31ce2453c82fatagen2026-09-24 13:07 UTC306
            .await
31ce2453c82fatagen2026-09-24 13:07 UTC307
            .map(done)
31ce2453c82fatagen2026-09-24 13:07 UTC308
            .map_err(sdk("banning user failed")),
31ce2453c82fatagen2026-09-24 13:07 UTC309
        RoomCommand::UnbanUser {
31ce2453c82fatagen2026-09-24 13:07 UTC310
            room_id,
31ce2453c82fatagen2026-09-24 13:07 UTC311
            user_id,
31ce2453c82fatagen2026-09-24 13:07 UTC312
            reason,
31ce2453c82fatagen2026-09-24 13:07 UTC313
        } => room(&room_id)?
31ce2453c82fatagen2026-09-24 13:07 UTC314
            .unban_user(&user_id, reason.as_deref())
31ce2453c82fatagen2026-09-24 13:07 UTC315
            .await
31ce2453c82fatagen2026-09-24 13:07 UTC316
            .map(done)
31ce2453c82fatagen2026-09-24 13:07 UTC317
            .map_err(sdk("unbanning user failed")),
31ce2453c82fatagen2026-09-24 13:07 UTC318
        RoomCommand::SetPowerLevels { room_id, changes } => {
31ce2453c82fatagen2026-09-24 13:07 UTC319
            apply_power_level_changes(&room(&room_id)?, changes)
31ce2453c82fatagen2026-09-24 13:07 UTC320
                .await
31ce2453c82fatagen2026-09-24 13:07 UTC321
                .map(done)
31ce2453c82fatagen2026-09-24 13:07 UTC322
        }
31ce2453c82fatagen2026-09-24 13:07 UTC323
        RoomCommand::SetUserPowerLevel {
31ce2453c82fatagen2026-09-24 13:07 UTC324
            room_id,
31ce2453c82fatagen2026-09-24 13:07 UTC325
            user_id,
31ce2453c82fatagen2026-09-24 13:07 UTC326
            level,
31ce2453c82fatagen2026-09-24 13:07 UTC327
        } => room(&room_id)?
31ce2453c82fatagen2026-09-24 13:07 UTC328
            .update_power_levels(vec![(&user_id, power_level(level)?)])
31ce2453c82fatagen2026-09-24 13:07 UTC329
            .await
31ce2453c82fatagen2026-09-24 13:07 UTC330
            .map(done)
31ce2453c82fatagen2026-09-24 13:07 UTC331
            .map_err(sdk("updating user power level failed")),
9bd34e25bb86atagen2026-10-24 07:23 UTC332
        RoomCommand::MuteUser {
9bd34e25bb86atagen2026-10-24 07:23 UTC333
            room_id,
9bd34e25bb86atagen2026-10-24 07:23 UTC334
            user_id,
9bd34e25bb86atagen2026-10-24 07:23 UTC335
            muted,
9bd34e25bb86atagen2026-10-24 07:23 UTC336
        } => mute_user(client, &room(&room_id)?, &user_id, muted)
9bd34e25bb86atagen2026-10-24 07:23 UTC337
            .await
9bd34e25bb86atagen2026-10-24 07:23 UTC338
            .map(done),
fa5b4e09943aatagen2026-10-24 09:33 UTC339
        RoomCommand::SetPinned {
fa5b4e09943aatagen2026-10-24 09:33 UTC340
            room_id,
fa5b4e09943aatagen2026-10-24 09:33 UTC341
            event_id,
fa5b4e09943aatagen2026-10-24 09:33 UTC342
            pinned,
fa5b4e09943aatagen2026-10-24 09:33 UTC343
        } => set_pinned(client, &room(&room_id)?, &event_id, pinned)
fa5b4e09943aatagen2026-10-24 09:33 UTC344
            .await
fa5b4e09943aatagen2026-10-24 09:33 UTC345
            .map(done),
9bd34e25bb86atagen2026-10-24 07:23 UTC346
        RoomCommand::SetFavourite { room_id, favourite } => room(&room_id)?
9bd34e25bb86atagen2026-10-24 07:23 UTC347
            .set_is_favourite(favourite, None)
9bd34e25bb86atagen2026-10-24 07:23 UTC348
            .await
9bd34e25bb86atagen2026-10-24 07:23 UTC349
            .map(done)
9bd34e25bb86atagen2026-10-24 07:23 UTC350
            .map_err(sdk("setting favourite failed")),
9bd34e25bb86atagen2026-10-24 07:23 UTC351
        RoomCommand::SetOwnDisplayName { room_id, name } => {
9bd34e25bb86atagen2026-10-24 07:23 UTC352
            set_own_member_field(client, &room(&room_id)?, ProfileField::DisplayName, name)
9bd34e25bb86atagen2026-10-24 07:23 UTC353
                .await
9bd34e25bb86atagen2026-10-24 07:23 UTC354
                .map(done)
9bd34e25bb86atagen2026-10-24 07:23 UTC355
        }
9bd34e25bb86atagen2026-10-24 07:23 UTC356
        RoomCommand::SetOwnAvatar { room_id, avatar } => set_own_member_field(
9bd34e25bb86atagen2026-10-24 07:23 UTC357
            client,
9bd34e25bb86atagen2026-10-24 07:23 UTC358
            &room(&room_id)?,
9bd34e25bb86atagen2026-10-24 07:23 UTC359
            ProfileField::Avatar,
9bd34e25bb86atagen2026-10-24 07:23 UTC360
            avatar.map(String::from),
9bd34e25bb86atagen2026-10-24 07:23 UTC361
        )
9bd34e25bb86atagen2026-10-24 07:23 UTC362
        .await
9bd34e25bb86atagen2026-10-24 07:23 UTC363
        .map(done),
31ce2453c82fatagen2026-09-24 13:07 UTC364
        RoomCommand::SetAccountData {
31ce2453c82fatagen2026-09-24 13:07 UTC365
            room_id,
31ce2453c82fatagen2026-09-24 13:07 UTC366
            event_type,
31ce2453c82fatagen2026-09-24 13:07 UTC367
            content,
31ce2453c82fatagen2026-09-24 13:07 UTC368
        } => room(&room_id)?
31ce2453c82fatagen2026-09-24 13:07 UTC369
            .set_account_data_raw(event_type, content)
31ce2453c82fatagen2026-09-24 13:07 UTC370
            .await
31ce2453c82fatagen2026-09-24 13:07 UTC371
            .map(done)
31ce2453c82fatagen2026-09-24 13:07 UTC372
            .map_err(sdk("writing room account data failed")),
31ce2453c82fatagen2026-09-24 13:07 UTC373
        RoomCommand::Create(options) => client
31ce2453c82fatagen2026-09-24 13:07 UTC374
            .create_room(create_room_request(options))
31ce2453c82fatagen2026-09-24 13:07 UTC375
            .await
31ce2453c82fatagen2026-09-24 13:07 UTC376
            .map(joined)
31ce2453c82fatagen2026-09-24 13:07 UTC377
            .map_err(sdk("creating room failed")),
5a52012c9b1datagen2026-09-24 23:45 UTC378
        RoomCommand::CreateDirect(user_id) => {
5a52012c9b1datagen2026-09-24 23:45 UTC379
            if let Some(existing) = client.get_dm_room(&user_id) {
5a52012c9b1datagen2026-09-24 23:45 UTC380
                return Ok(joined(existing));
5a52012c9b1datagen2026-09-24 23:45 UTC381
            }
5a52012c9b1datagen2026-09-24 23:45 UTC382
            let fresh = fresh_dms
5a52012c9b1datagen2026-09-24 23:45 UTC383
                .lock()
5a52012c9b1datagen2026-09-24 23:45 UTC384
                .expect("nothing panics holding this")
5a52012c9b1datagen2026-09-24 23:45 UTC385
                .get(&user_id)
5a52012c9b1datagen2026-09-24 23:45 UTC386
                .cloned();
5a52012c9b1datagen2026-09-24 23:45 UTC387
            if let Some(room_id) = fresh.filter(|room_id| client.get_room(room_id).is_some()) {
5a52012c9b1datagen2026-09-24 23:45 UTC388
                return Ok(CommandOutput::Room(room_id));
5a52012c9b1datagen2026-09-24 23:45 UTC389
            }
5a52012c9b1datagen2026-09-24 23:45 UTC390
            let room = client
31ce2453c82fatagen2026-09-24 13:07 UTC391
                .create_dm(&user_id)
31ce2453c82fatagen2026-09-24 13:07 UTC392
                .await
5a52012c9b1datagen2026-09-24 23:45 UTC393
                .map_err(sdk("creating direct room failed"))?;
5a52012c9b1datagen2026-09-24 23:45 UTC394
            fresh_dms
5a52012c9b1datagen2026-09-24 23:45 UTC395
                .lock()
5a52012c9b1datagen2026-09-24 23:45 UTC396
                .expect("nothing panics holding this")
5a52012c9b1datagen2026-09-24 23:45 UTC397
                .insert(user_id, room.room_id().to_owned());
5a52012c9b1datagen2026-09-24 23:45 UTC398
            Ok(joined(room))
5a52012c9b1datagen2026-09-24 23:45 UTC399
        }
31ce2453c82fatagen2026-09-24 13:07 UTC400
        _ => Err(Error::new(ErrorKind::Other, "room command was misrouted")),
31ce2453c82fatagen2026-09-24 13:07 UTC401
    }
31ce2453c82fatagen2026-09-24 13:07 UTC402
}
31ce2453c82fatagen2026-09-24 13:07 UTC403
31ce2453c82fatagen2026-09-24 13:07 UTC404
/// one state event carrying every requested change, since the sdk's own patcher is private
31ce2453c82fatagen2026-09-24 13:07 UTC405
async fn apply_power_level_changes(room: &Room, changes: PowerLevelChanges) -> Result<(), Error> {
31ce2453c82fatagen2026-09-24 13:07 UTC406
    let mut levels = room
31ce2453c82fatagen2026-09-24 13:07 UTC407
        .power_levels()
31ce2453c82fatagen2026-09-24 13:07 UTC408
        .await
31ce2453c82fatagen2026-09-24 13:07 UTC409
        .map_err(|source| Error::from_sdk("reading power levels failed", source.into()))?;
31ce2453c82fatagen2026-09-24 13:07 UTC410
    for (target, value) in [
31ce2453c82fatagen2026-09-24 13:07 UTC411
        (&mut levels.ban, changes.ban),
31ce2453c82fatagen2026-09-24 13:07 UTC412
        (&mut levels.invite, changes.invite),
31ce2453c82fatagen2026-09-24 13:07 UTC413
        (&mut levels.kick, changes.kick),
31ce2453c82fatagen2026-09-24 13:07 UTC414
        (&mut levels.redact, changes.redact),
31ce2453c82fatagen2026-09-24 13:07 UTC415
        (&mut levels.events_default, changes.events_default),
31ce2453c82fatagen2026-09-24 13:07 UTC416
        (&mut levels.state_default, changes.state_default),
31ce2453c82fatagen2026-09-24 13:07 UTC417
        (&mut levels.users_default, changes.users_default),
31ce2453c82fatagen2026-09-24 13:07 UTC418
        (&mut levels.notifications.room, changes.room_notification),
31ce2453c82fatagen2026-09-24 13:07 UTC419
    ] {
31ce2453c82fatagen2026-09-24 13:07 UTC420
        if let Some(value) = value {
31ce2453c82fatagen2026-09-24 13:07 UTC421
            *target = power_level(value)?;
31ce2453c82fatagen2026-09-24 13:07 UTC422
        }
31ce2453c82fatagen2026-09-24 13:07 UTC423
    }
31ce2453c82fatagen2026-09-24 13:07 UTC424
    for (event_type, value) in [
31ce2453c82fatagen2026-09-24 13:07 UTC425
        (StateEventType::RoomName.into(), changes.room_name),
31ce2453c82fatagen2026-09-24 13:07 UTC426
        (StateEventType::RoomAvatar.into(), changes.room_avatar),
31ce2453c82fatagen2026-09-24 13:07 UTC427
        (StateEventType::RoomTopic.into(), changes.room_topic),
31ce2453c82fatagen2026-09-24 13:07 UTC428
        (StateEventType::SpaceChild.into(), changes.space_child),
31ce2453c82fatagen2026-09-24 13:07 UTC429
        (MessageLikeEventType::Beacon.into(), changes.beacon),
31ce2453c82fatagen2026-09-24 13:07 UTC430
        (StateEventType::BeaconInfo.into(), changes.beacon_info),
31ce2453c82fatagen2026-09-24 13:07 UTC431
    ] {
31ce2453c82fatagen2026-09-24 13:07 UTC432
        if let Some(value) = value {
31ce2453c82fatagen2026-09-24 13:07 UTC433
            levels.events.insert(event_type, power_level(value)?);
31ce2453c82fatagen2026-09-24 13:07 UTC434
        }
31ce2453c82fatagen2026-09-24 13:07 UTC435
    }
31ce2453c82fatagen2026-09-24 13:07 UTC436
    let content = RoomPowerLevelsEventContent::try_from(levels).map_err(|source| {
31ce2453c82fatagen2026-09-24 13:07 UTC437
        Error::with_source(
31ce2453c82fatagen2026-09-24 13:07 UTC438
            ErrorKind::Invalid,
31ce2453c82fatagen2026-09-24 13:07 UTC439
            "normalising power levels failed",
31ce2453c82fatagen2026-09-24 13:07 UTC440
            source,
31ce2453c82fatagen2026-09-24 13:07 UTC441
        )
31ce2453c82fatagen2026-09-24 13:07 UTC442
    })?;
31ce2453c82fatagen2026-09-24 13:07 UTC443
    room.send_state_event(content)
31ce2453c82fatagen2026-09-24 13:07 UTC444
        .await
31ce2453c82fatagen2026-09-24 13:07 UTC445
        .map(drop)
31ce2453c82fatagen2026-09-24 13:07 UTC446
        .map_err(|source| Error::from_sdk("updating power levels failed", source))
31ce2453c82fatagen2026-09-24 13:07 UTC447
}
31ce2453c82fatagen2026-09-24 13:07 UTC448
9bd34e25bb86atagen2026-10-24 07:23 UTC449
/// reads the list from the server, since the store only catches up on the next sync
9bd34e25bb86atagen2026-10-24 07:23 UTC450
async fn set_ignored(client: &Client, user_id: OwnedUserId, ignored: bool) -> Result<(), Error> {
9bd34e25bb86atagen2026-10-24 07:23 UTC451
    if ignored && client.user_id() == Some(&*user_id) {
9bd34e25bb86atagen2026-10-24 07:23 UTC452
        return Err(Error::new(ErrorKind::Invalid, "you can't ignore yourself"));
9bd34e25bb86atagen2026-10-24 07:23 UTC453
    }
9bd34e25bb86atagen2026-10-24 07:23 UTC454
    let account = client.account();
9bd34e25bb86atagen2026-10-24 07:23 UTC455
    let mut list = account
9bd34e25bb86atagen2026-10-24 07:23 UTC456
        .fetch_account_data_static::<IgnoredUserListEventContent>()
9bd34e25bb86atagen2026-10-24 07:23 UTC457
        .await
9bd34e25bb86atagen2026-10-24 07:23 UTC458
        .map_err(|source| Error::from_sdk("reading the ignore list failed", source))?
9bd34e25bb86atagen2026-10-24 07:23 UTC459
        .map(|raw| raw.deserialize())
9bd34e25bb86atagen2026-10-24 07:23 UTC460
        .transpose()
9bd34e25bb86atagen2026-10-24 07:23 UTC461
        .map_err(|source| {
9bd34e25bb86atagen2026-10-24 07:23 UTC462
            Error::with_source(ErrorKind::Invalid, "the ignore list is malformed", source)
9bd34e25bb86atagen2026-10-24 07:23 UTC463
        })?
9bd34e25bb86atagen2026-10-24 07:23 UTC464
        .unwrap_or_default();
9bd34e25bb86atagen2026-10-24 07:23 UTC465
    let changed = if ignored {
9bd34e25bb86atagen2026-10-24 07:23 UTC466
        list.ignored_users
9bd34e25bb86atagen2026-10-24 07:23 UTC467
            .insert(user_id, IgnoredUser::new())
9bd34e25bb86atagen2026-10-24 07:23 UTC468
            .is_none()
9bd34e25bb86atagen2026-10-24 07:23 UTC469
    } else {
9bd34e25bb86atagen2026-10-24 07:23 UTC470
        list.ignored_users.remove(&user_id).is_some()
9bd34e25bb86atagen2026-10-24 07:23 UTC471
    };
9bd34e25bb86atagen2026-10-24 07:23 UTC472
    if !changed {
9bd34e25bb86atagen2026-10-24 07:23 UTC473
        return Ok(());
9bd34e25bb86atagen2026-10-24 07:23 UTC474
    }
9bd34e25bb86atagen2026-10-24 07:23 UTC475
    account
9bd34e25bb86atagen2026-10-24 07:23 UTC476
        .set_account_data(list)
9bd34e25bb86atagen2026-10-24 07:23 UTC477
        .await
9bd34e25bb86atagen2026-10-24 07:23 UTC478
        .map(drop)
9bd34e25bb86atagen2026-10-24 07:23 UTC479
        .map_err(|source| Error::from_sdk("writing the ignore list failed", source))
9bd34e25bb86atagen2026-10-24 07:23 UTC480
}
9bd34e25bb86atagen2026-10-24 07:23 UTC481
9bd34e25bb86atagen2026-10-24 07:23 UTC482
async fn reorder_spaces(
9bd34e25bb86atagen2026-10-24 07:23 UTC483
    client: &Client,
9bd34e25bb86atagen2026-10-24 07:23 UTC484
    fresh: &FreshOrders,
9bd34e25bb86atagen2026-10-24 07:23 UTC485
    order: Vec<OwnedRoomId>,
9bd34e25bb86atagen2026-10-24 07:23 UTC486
) -> Result<(), Error> {
9bd34e25bb86atagen2026-10-24 07:23 UTC487
    if order.iter().collect::<HashSet<_>>().len() != order.len() {
9bd34e25bb86atagen2026-10-24 07:23 UTC488
        return Err(Error::new(ErrorKind::Invalid, "a space is listed twice"));
9bd34e25bb86atagen2026-10-24 07:23 UTC489
    }
9bd34e25bb86atagen2026-10-24 07:23 UTC490
    let mut rooms = HashMap::new();
9bd34e25bb86atagen2026-10-24 07:23 UTC491
    let mut spaces = Vec::with_capacity(order.len());
9bd34e25bb86atagen2026-10-24 07:23 UTC492
    for room_id in order {
9bd34e25bb86atagen2026-10-24 07:23 UTC493
        let room = client.get_room(&room_id).ok_or_else(Error::room_unknown)?;
9bd34e25bb86atagen2026-10-24 07:23 UTC494
        let stored = stored_space_order(&room).await;
9bd34e25bb86atagen2026-10-24 07:23 UTC495
        let current = {
9bd34e25bb86atagen2026-10-24 07:23 UTC496
            let mut fresh = fresh.lock().expect("nothing panics holding this");
9bd34e25bb86atagen2026-10-24 07:23 UTC497
            match fresh.get(&room_id) {
9bd34e25bb86atagen2026-10-24 07:23 UTC498
                Some(written) if stored.as_ref() != Some(written) => Some(written.clone()),
9bd34e25bb86atagen2026-10-24 07:23 UTC499
                Some(_) => fresh.remove(&room_id),
9bd34e25bb86atagen2026-10-24 07:23 UTC500
                None => stored,
9bd34e25bb86atagen2026-10-24 07:23 UTC501
            }
9bd34e25bb86atagen2026-10-24 07:23 UTC502
        };
9bd34e25bb86atagen2026-10-24 07:23 UTC503
        rooms.insert(room_id.clone(), room);
9bd34e25bb86atagen2026-10-24 07:23 UTC504
        spaces.push((room_id, current));
9bd34e25bb86atagen2026-10-24 07:23 UTC505
    }
9bd34e25bb86atagen2026-10-24 07:23 UTC506
    for (room_id, order) in space_order::plan(&spaces) {
9bd34e25bb86atagen2026-10-24 07:23 UTC507
        let content = Raw::new(&serde_json::json!({ "order": order }))
9bd34e25bb86atagen2026-10-24 07:23 UTC508
            .expect("a string map always serialises")
9bd34e25bb86atagen2026-10-24 07:23 UTC509
            .cast_unchecked();
9bd34e25bb86atagen2026-10-24 07:23 UTC510
        rooms[&room_id]
9bd34e25bb86atagen2026-10-24 07:23 UTC511
            .set_account_data_raw(space_order::EVENT_TYPE.into(), content)
9bd34e25bb86atagen2026-10-24 07:23 UTC512
            .await
9bd34e25bb86atagen2026-10-24 07:23 UTC513
            .map_err(|source| Error::from_sdk("writing a space order failed", source))?;
9bd34e25bb86atagen2026-10-24 07:23 UTC514
        fresh
9bd34e25bb86atagen2026-10-24 07:23 UTC515
            .lock()
9bd34e25bb86atagen2026-10-24 07:23 UTC516
            .expect("nothing panics holding this")
9bd34e25bb86atagen2026-10-24 07:23 UTC517
            .insert(room_id, order);
9bd34e25bb86atagen2026-10-24 07:23 UTC518
    }
9bd34e25bb86atagen2026-10-24 07:23 UTC519
    Ok(())
9bd34e25bb86atagen2026-10-24 07:23 UTC520
}
9bd34e25bb86atagen2026-10-24 07:23 UTC521
9bd34e25bb86atagen2026-10-24 07:23 UTC522
/// below the cheapest of the levels a message could need, so neither plain nor encrypted
9bd34e25bb86atagen2026-10-24 07:23 UTC523
/// sends get through
9bd34e25bb86atagen2026-10-24 07:23 UTC524
async fn mute_user(
9bd34e25bb86atagen2026-10-24 07:23 UTC525
    client: &Client,
9bd34e25bb86atagen2026-10-24 07:23 UTC526
    room: &Room,
9bd34e25bb86atagen2026-10-24 07:23 UTC527
    user_id: &OwnedUserId,
9bd34e25bb86atagen2026-10-24 07:23 UTC528
    muted: bool,
9bd34e25bb86atagen2026-10-24 07:23 UTC529
) -> Result<(), Error> {
9bd34e25bb86atagen2026-10-24 07:23 UTC530
    let own_user_id = client
9bd34e25bb86atagen2026-10-24 07:23 UTC531
        .user_id()
9bd34e25bb86atagen2026-10-24 07:23 UTC532
        .ok_or_else(|| Error::new(ErrorKind::Unauthenticated, "the client is not logged in"))?;
9bd34e25bb86atagen2026-10-24 07:23 UTC533
    let mut levels = room
9bd34e25bb86atagen2026-10-24 07:23 UTC534
        .power_levels()
9bd34e25bb86atagen2026-10-24 07:23 UTC535
        .await
9bd34e25bb86atagen2026-10-24 07:23 UTC536
        .map_err(|source| Error::from_sdk("reading power levels failed", source.into()))?;
9bd34e25bb86atagen2026-10-24 07:23 UTC537
    if !levels.user_can_change_user_power_level(own_user_id, user_id) {
9bd34e25bb86atagen2026-10-24 07:23 UTC538
        return Err(Error::new(
9bd34e25bb86atagen2026-10-24 07:23 UTC539
            ErrorKind::Forbidden,
9bd34e25bb86atagen2026-10-24 07:23 UTC540
            "you can't change this user's power level",
9bd34e25bb86atagen2026-10-24 07:23 UTC541
        ));
9bd34e25bb86atagen2026-10-24 07:23 UTC542
    }
9bd34e25bb86atagen2026-10-24 07:23 UTC543
    let speaking = [
9bd34e25bb86atagen2026-10-24 07:23 UTC544
        levels.events_default,
9bd34e25bb86atagen2026-10-24 07:23 UTC545
        levels.for_message(MessageLikeEventType::RoomMessage),
9bd34e25bb86atagen2026-10-24 07:23 UTC546
        levels.for_message(MessageLikeEventType::RoomEncrypted),
9bd34e25bb86atagen2026-10-24 07:23 UTC547
    ]
9bd34e25bb86atagen2026-10-24 07:23 UTC548
    .into_iter()
9bd34e25bb86atagen2026-10-24 07:23 UTC549
    .min()
9bd34e25bb86atagen2026-10-24 07:23 UTC550
    .expect("the list isn't empty");
9bd34e25bb86atagen2026-10-24 07:23 UTC551
    let current = levels
9bd34e25bb86atagen2026-10-24 07:23 UTC552
        .users
9bd34e25bb86atagen2026-10-24 07:23 UTC553
        .get(user_id)
9bd34e25bb86atagen2026-10-24 07:23 UTC554
        .copied()
9bd34e25bb86atagen2026-10-24 07:23 UTC555
        .unwrap_or(levels.users_default);
9bd34e25bb86atagen2026-10-24 07:23 UTC556
    match (muted, current < speaking) {
9bd34e25bb86atagen2026-10-24 07:23 UTC557
        (true, true) | (false, false) => return Ok(()),
9bd34e25bb86atagen2026-10-24 07:23 UTC558
        (true, false) => {
9bd34e25bb86atagen2026-10-24 07:23 UTC559
            let level = speaking.checked_sub(Int::from(1)).ok_or_else(|| {
9bd34e25bb86atagen2026-10-24 07:23 UTC560
                Error::new(
9bd34e25bb86atagen2026-10-24 07:23 UTC561
                    ErrorKind::Invalid,
9bd34e25bb86atagen2026-10-24 07:23 UTC562
                    "there's no power level below this room's lowest",
9bd34e25bb86atagen2026-10-24 07:23 UTC563
                )
9bd34e25bb86atagen2026-10-24 07:23 UTC564
            })?;
9bd34e25bb86atagen2026-10-24 07:23 UTC565
            levels.users.insert(user_id.clone(), level);
9bd34e25bb86atagen2026-10-24 07:23 UTC566
        }
9bd34e25bb86atagen2026-10-24 07:23 UTC567
        (false, true) => {
9bd34e25bb86atagen2026-10-24 07:23 UTC568
            levels.users.remove(user_id);
9bd34e25bb86atagen2026-10-24 07:23 UTC569
        }
9bd34e25bb86atagen2026-10-24 07:23 UTC570
    }
9bd34e25bb86atagen2026-10-24 07:23 UTC571
    let content = RoomPowerLevelsEventContent::try_from(levels).map_err(|source| {
9bd34e25bb86atagen2026-10-24 07:23 UTC572
        Error::with_source(
9bd34e25bb86atagen2026-10-24 07:23 UTC573
            ErrorKind::Invalid,
9bd34e25bb86atagen2026-10-24 07:23 UTC574
            "normalising power levels failed",
9bd34e25bb86atagen2026-10-24 07:23 UTC575
            source,
9bd34e25bb86atagen2026-10-24 07:23 UTC576
        )
9bd34e25bb86atagen2026-10-24 07:23 UTC577
    })?;
9bd34e25bb86atagen2026-10-24 07:23 UTC578
    room.send_state_event(content)
9bd34e25bb86atagen2026-10-24 07:23 UTC579
        .await
9bd34e25bb86atagen2026-10-24 07:23 UTC580
        .map(drop)
9bd34e25bb86atagen2026-10-24 07:23 UTC581
        .map_err(|source| Error::from_sdk("updating power levels failed", source))
9bd34e25bb86atagen2026-10-24 07:23 UTC582
}
9bd34e25bb86atagen2026-10-24 07:23 UTC583
9bd34e25bb86atagen2026-10-24 07:23 UTC584
#[derive(Clone, Copy)]
9bd34e25bb86atagen2026-10-24 07:23 UTC585
enum ProfileField {
9bd34e25bb86atagen2026-10-24 07:23 UTC586
    DisplayName,
9bd34e25bb86atagen2026-10-24 07:23 UTC587
    Avatar,
9bd34e25bb86atagen2026-10-24 07:23 UTC588
}
9bd34e25bb86atagen2026-10-24 07:23 UTC589
9bd34e25bb86atagen2026-10-24 07:23 UTC590
/// rewrites your member event from the server's copy so membership and unknown keys survive,
9bd34e25bb86atagen2026-10-24 07:23 UTC591
/// and an earlier change this sync hasn't delivered yet isn't lost
9bd34e25bb86atagen2026-10-24 07:23 UTC592
async fn set_own_member_field(
9bd34e25bb86atagen2026-10-24 07:23 UTC593
    client: &Client,
9bd34e25bb86atagen2026-10-24 07:23 UTC594
    room: &Room,
9bd34e25bb86atagen2026-10-24 07:23 UTC595
    field: ProfileField,
9bd34e25bb86atagen2026-10-24 07:23 UTC596
    value: Option<String>,
9bd34e25bb86atagen2026-10-24 07:23 UTC597
) -> Result<(), Error> {
9bd34e25bb86atagen2026-10-24 07:23 UTC598
    let own_user_id = client
9bd34e25bb86atagen2026-10-24 07:23 UTC599
        .user_id()
9bd34e25bb86atagen2026-10-24 07:23 UTC600
        .ok_or_else(|| Error::new(ErrorKind::Unauthenticated, "the client is not logged in"))?
9bd34e25bb86atagen2026-10-24 07:23 UTC601
        .to_owned();
9bd34e25bb86atagen2026-10-24 07:23 UTC602
    let key = match field {
9bd34e25bb86atagen2026-10-24 07:23 UTC603
        ProfileField::DisplayName => "displayname",
9bd34e25bb86atagen2026-10-24 07:23 UTC604
        ProfileField::Avatar => "avatar_url",
9bd34e25bb86atagen2026-10-24 07:23 UTC605
    };
9bd34e25bb86atagen2026-10-24 07:23 UTC606
    let value = match value {
9bd34e25bb86atagen2026-10-24 07:23 UTC607
        Some(value) => Some(value),
9bd34e25bb86atagen2026-10-24 07:23 UTC608
        None => {
9bd34e25bb86atagen2026-10-24 07:23 UTC609
            let profile = client
9bd34e25bb86atagen2026-10-24 07:23 UTC610
                .account()
9bd34e25bb86atagen2026-10-24 07:23 UTC611
                .fetch_user_profile()
9bd34e25bb86atagen2026-10-24 07:23 UTC612
                .await
9bd34e25bb86atagen2026-10-24 07:23 UTC613
                .map_err(|source| Error::from_sdk("fetching the profile failed", source))?;
9bd34e25bb86atagen2026-10-24 07:23 UTC614
            let global = match field {
9bd34e25bb86atagen2026-10-24 07:23 UTC615
                ProfileField::DisplayName => profile.get_static::<DisplayName>(),
9bd34e25bb86atagen2026-10-24 07:23 UTC616
                ProfileField::Avatar => profile
9bd34e25bb86atagen2026-10-24 07:23 UTC617
                    .get_static::<AvatarUrl>()
9bd34e25bb86atagen2026-10-24 07:23 UTC618
                    .map(|avatar| avatar.map(String::from)),
9bd34e25bb86atagen2026-10-24 07:23 UTC619
            };
9bd34e25bb86atagen2026-10-24 07:23 UTC620
            global.map_err(|source| {
9bd34e25bb86atagen2026-10-24 07:23 UTC621
                Error::with_source(
9bd34e25bb86atagen2026-10-24 07:23 UTC622
                    ErrorKind::Server,
9bd34e25bb86atagen2026-10-24 07:23 UTC623
                    "the server sent a malformed profile",
9bd34e25bb86atagen2026-10-24 07:23 UTC624
                    source,
9bd34e25bb86atagen2026-10-24 07:23 UTC625
                )
9bd34e25bb86atagen2026-10-24 07:23 UTC626
            })?
9bd34e25bb86atagen2026-10-24 07:23 UTC627
        }
9bd34e25bb86atagen2026-10-24 07:23 UTC628
    };
9bd34e25bb86atagen2026-10-24 07:23 UTC629
    let request = get_state_event_for_key::v3::Request::new(
9bd34e25bb86atagen2026-10-24 07:23 UTC630
        room.room_id().to_owned(),
9bd34e25bb86atagen2026-10-24 07:23 UTC631
        StateEventType::RoomMember,
9bd34e25bb86atagen2026-10-24 07:23 UTC632
        own_user_id.to_string(),
9bd34e25bb86atagen2026-10-24 07:23 UTC633
    );
9bd34e25bb86atagen2026-10-24 07:23 UTC634
    let response = client
9bd34e25bb86atagen2026-10-24 07:23 UTC635
        .send(request)
9bd34e25bb86atagen2026-10-24 07:23 UTC636
        .await
9bd34e25bb86atagen2026-10-24 07:23 UTC637
        .map_err(|source| Error::from_http("reading your member event failed", source))?;
9bd34e25bb86atagen2026-10-24 07:23 UTC638
    let mut content: JsonObject =
9bd34e25bb86atagen2026-10-24 07:23 UTC639
        serde_json::from_str(response.event_or_content.get()).map_err(|source| {
9bd34e25bb86atagen2026-10-24 07:23 UTC640
            Error::with_source(ErrorKind::Invalid, "your member event is malformed", source)
9bd34e25bb86atagen2026-10-24 07:23 UTC641
        })?;
9bd34e25bb86atagen2026-10-24 07:23 UTC642
    match value {
9bd34e25bb86atagen2026-10-24 07:23 UTC643
        Some(value) => content.insert(key.to_owned(), JsonValue::String(value)),
9bd34e25bb86atagen2026-10-24 07:23 UTC644
        None => content.remove(key),
9bd34e25bb86atagen2026-10-24 07:23 UTC645
    };
9bd34e25bb86atagen2026-10-24 07:23 UTC646
    room.send_state_event_raw(
9bd34e25bb86atagen2026-10-24 07:23 UTC647
        "m.room.member",
9bd34e25bb86atagen2026-10-24 07:23 UTC648
        own_user_id.as_str(),
9bd34e25bb86atagen2026-10-24 07:23 UTC649
        JsonValue::Object(content),
9bd34e25bb86atagen2026-10-24 07:23 UTC650
    )
9bd34e25bb86atagen2026-10-24 07:23 UTC651
    .await
9bd34e25bb86atagen2026-10-24 07:23 UTC652
    .map(drop)
9bd34e25bb86atagen2026-10-24 07:23 UTC653
    .map_err(|source| Error::from_sdk("updating your member event failed", source))
9bd34e25bb86atagen2026-10-24 07:23 UTC654
}
9bd34e25bb86atagen2026-10-24 07:23 UTC655
fa5b4e09943aatagen2026-10-24 09:33 UTC656
/// against the server's list, since the store's lags a sync and another client may have just pinned
fa5b4e09943aatagen2026-10-24 09:33 UTC657
async fn set_pinned(
fa5b4e09943aatagen2026-10-24 09:33 UTC658
    client: &Client,
fa5b4e09943aatagen2026-10-24 09:33 UTC659
    room: &Room,
fa5b4e09943aatagen2026-10-24 09:33 UTC660
    event_id: &EventId,
fa5b4e09943aatagen2026-10-24 09:33 UTC661
    pinned: bool,
fa5b4e09943aatagen2026-10-24 09:33 UTC662
) -> Result<(), Error> {
fa5b4e09943aatagen2026-10-24 09:33 UTC663
    let request = get_state_event_for_key::v3::Request::new(
fa5b4e09943aatagen2026-10-24 09:33 UTC664
        room.room_id().to_owned(),
fa5b4e09943aatagen2026-10-24 09:33 UTC665
        StateEventType::RoomPinnedEvents,
fa5b4e09943aatagen2026-10-24 09:33 UTC666
        String::new(),
fa5b4e09943aatagen2026-10-24 09:33 UTC667
    );
fa5b4e09943aatagen2026-10-24 09:33 UTC668
    let mut content: JsonObject = match client.send(request).await {
fa5b4e09943aatagen2026-10-24 09:33 UTC669
        Ok(response) => {
fa5b4e09943aatagen2026-10-24 09:33 UTC670
            serde_json::from_str(response.event_or_content.get()).map_err(|source| {
fa5b4e09943aatagen2026-10-24 09:33 UTC671
                Error::with_source(ErrorKind::Server, "the pinned list is malformed", source)
fa5b4e09943aatagen2026-10-24 09:33 UTC672
            })?
fa5b4e09943aatagen2026-10-24 09:33 UTC673
        }
fa5b4e09943aatagen2026-10-24 09:33 UTC674
        Err(error) if error.client_api_error_kind() == Some(&ApiErrorKind::NotFound) => {
fa5b4e09943aatagen2026-10-24 09:33 UTC675
            JsonObject::new()
fa5b4e09943aatagen2026-10-24 09:33 UTC676
        }
fa5b4e09943aatagen2026-10-24 09:33 UTC677
        Err(source) => return Err(Error::from_http("reading the pinned list failed", source)),
fa5b4e09943aatagen2026-10-24 09:33 UTC678
    };
fa5b4e09943aatagen2026-10-24 09:33 UTC679
    let list = content
fa5b4e09943aatagen2026-10-24 09:33 UTC680
        .entry("pinned")
fa5b4e09943aatagen2026-10-24 09:33 UTC681
        .or_insert_with(|| JsonValue::Array(Vec::new()));
fa5b4e09943aatagen2026-10-24 09:33 UTC682
    if !list.is_array() {
fa5b4e09943aatagen2026-10-24 09:33 UTC683
        *list = JsonValue::Array(Vec::new());
fa5b4e09943aatagen2026-10-24 09:33 UTC684
    }
fa5b4e09943aatagen2026-10-24 09:33 UTC685
    let list = list.as_array_mut().expect("just made it an array");
fa5b4e09943aatagen2026-10-24 09:33 UTC686
    let present = list.iter().any(|id| id.as_str() == Some(event_id.as_str()));
fa5b4e09943aatagen2026-10-24 09:33 UTC687
    match (pinned, present) {
fa5b4e09943aatagen2026-10-24 09:33 UTC688
        (true, true) | (false, false) => return Ok(()),
fa5b4e09943aatagen2026-10-24 09:33 UTC689
        (true, false) => list.push(event_id.as_str().into()),
fa5b4e09943aatagen2026-10-24 09:33 UTC690
        (false, true) => list.retain(|id| id.as_str() != Some(event_id.as_str())),
fa5b4e09943aatagen2026-10-24 09:33 UTC691
    }
fa5b4e09943aatagen2026-10-24 09:33 UTC692
    room.send_state_event_raw("m.room.pinned_events", "", JsonValue::Object(content))
fa5b4e09943aatagen2026-10-24 09:33 UTC693
        .await
fa5b4e09943aatagen2026-10-24 09:33 UTC694
        .map(drop)
fa5b4e09943aatagen2026-10-24 09:33 UTC695
        .map_err(|source| Error::from_sdk("updating the pinned list failed", source))
fa5b4e09943aatagen2026-10-24 09:33 UTC696
}
fa5b4e09943aatagen2026-10-24 09:33 UTC697
31ce2453c82fatagen2026-09-24 13:07 UTC698
fn done<T>(_: T) -> CommandOutput {
31ce2453c82fatagen2026-09-24 13:07 UTC699
    CommandOutput::Done
31ce2453c82fatagen2026-09-24 13:07 UTC700
}
31ce2453c82fatagen2026-09-24 13:07 UTC701
31ce2453c82fatagen2026-09-24 13:07 UTC702
fn power_level(value: i64) -> Result<Int, Error> {
31ce2453c82fatagen2026-09-24 13:07 UTC703
    value.try_into().map_err(|source| {
31ce2453c82fatagen2026-09-24 13:07 UTC704
        Error::with_source(
31ce2453c82fatagen2026-09-24 13:07 UTC705
            ErrorKind::Invalid,
31ce2453c82fatagen2026-09-24 13:07 UTC706
            "power level is outside the Matrix integer range",
31ce2453c82fatagen2026-09-24 13:07 UTC707
            source,
31ce2453c82fatagen2026-09-24 13:07 UTC708
        )
31ce2453c82fatagen2026-09-24 13:07 UTC709
    })
31ce2453c82fatagen2026-09-24 13:07 UTC710
}
31ce2453c82fatagen2026-09-24 13:07 UTC711
31ce2453c82fatagen2026-09-24 13:07 UTC712
fn create_room_request(options: CreateRoomOptions) -> CreateRoomRequest {
31ce2453c82fatagen2026-09-24 13:07 UTC713
    let mut request = CreateRoomRequest::new();
31ce2453c82fatagen2026-09-24 13:07 UTC714
    request.name = options.name;
31ce2453c82fatagen2026-09-24 13:07 UTC715
    request.topic = options.topic;
31ce2453c82fatagen2026-09-24 13:07 UTC716
    request.room_alias_name = options.alias_localpart;
31ce2453c82fatagen2026-09-24 13:07 UTC717
    request.invite = options.invite;
31ce2453c82fatagen2026-09-24 13:07 UTC718
    request.is_direct = options.direct;
31ce2453c82fatagen2026-09-24 13:07 UTC719
    request.visibility = match options.visibility {
31ce2453c82fatagen2026-09-24 13:07 UTC720
        RoomVisibility::Private => SdkVisibility::Private,
31ce2453c82fatagen2026-09-24 13:07 UTC721
        RoomVisibility::Public => SdkVisibility::Public,
31ce2453c82fatagen2026-09-24 13:07 UTC722
    };
31ce2453c82fatagen2026-09-24 13:07 UTC723
    request.preset = options.preset.map(|preset| match preset {
31ce2453c82fatagen2026-09-24 13:07 UTC724
        RoomPreset::PrivateChat => SdkPreset::PrivateChat,
31ce2453c82fatagen2026-09-24 13:07 UTC725
        RoomPreset::TrustedPrivateChat => SdkPreset::TrustedPrivateChat,
31ce2453c82fatagen2026-09-24 13:07 UTC726
        RoomPreset::PublicChat => SdkPreset::PublicChat,
31ce2453c82fatagen2026-09-24 13:07 UTC727
    });
31ce2453c82fatagen2026-09-24 13:07 UTC728
    request
31ce2453c82fatagen2026-09-24 13:07 UTC729
}