From 9724953b5e7ce840bbbda9f4b0e16d6aa60a224c Mon Sep 17 00:00:00 2001 From: 31a05b9c Date: Wed, 6 May 2026 16:17:42 +0000 Subject: [PATCH] feat: admin commands for mass-rejecting invites --- src/admin/user/commands.rs | 76 ++++++++++++++++++++++++++++++++++++++ src/admin/user/mod.rs | 11 ++++++ 2 files changed, 87 insertions(+) diff --git a/src/admin/user/commands.rs b/src/admin/user/commands.rs index a6e7ca76f..8239c4841 100644 --- a/src/admin/user/commands.rs +++ b/src/admin/user/commands.rs @@ -427,6 +427,82 @@ pub(super) async fn deactivate_all(&self, no_leave_rooms: bool, force: bool) -> .await } +#[admin_command] +pub(super) async fn list_invited_rooms(&self, user_id: String) -> Result { + // Validate user id + let user_id = parse_local_user_id(self.services, &user_id)?; + + let mut rooms: Vec<((OwnedRoomId, u64, String), Result)> = self + .services + .rooms + .state_cache + .rooms_invited(&user_id) + .then(async |(room_id, _)| { + let sender = self + .services + .rooms + .state_cache + .invite_sender(&user_id, &room_id) + .await; + (get_room_info(self.services, &room_id).await, sender) + }) + .collect() + .await; + + if rooms.is_empty() { + return Err!("User is not invited to any rooms."); + } + + rooms.sort_by_key(|r| r.0.1); + rooms.reverse(); + + let body = rooms + .iter() + .map(|((id, members, name), sender)| match sender { + | Ok(user_id) => + format!("{id}\tInviter: {user_id}\tMembers: {members}\tName: {name}"), + | Err(_) => format!("{id}\tMembers: {members}\tName: {name}"), + }) + .collect::>() + .join("\n"); + + self.write_str(&format!("Rooms {user_id} is Invited to ({}):\n```\n{body}\n```", rooms.len())) + .await +} + +#[admin_command] +pub(super) async fn force_reject_invites(&self, user_id: String) -> Result { + let user_id = parse_local_user_id(self.services, &user_id)?; + + assert!( + self.services.globals.user_is_local(&user_id), + "Parsed user_id must be a local user" + ); + + let fails = self + .services + .rooms + .state_cache + .rooms_invited(&user_id) + .filter_map(async |(room_id, _)| { + match leave_room(self.services, &user_id, &room_id, None).await { + | Err(ref e) => { + warn!(%user_id, "Failed to leave {room_id} remotely: {e}"); + Some(()) + }, + | Ok(()) => None, + } + }) + .count() + .await; + + if fails > 0 { + return Err!("{fails} invites could not be rejected"); + } + + self.write_str("Successfully rejected all invites.").await +} + #[admin_command] pub(super) async fn list_joined_rooms(&self, user_id: String) -> Result { // Validate user id diff --git a/src/admin/user/mod.rs b/src/admin/user/mod.rs index 916fcd205..beed3f96c 100644 --- a/src/admin/user/mod.rs +++ b/src/admin/user/mod.rs @@ -160,12 +160,23 @@ pub enum UserCommand { #[clap(alias = "list")] ListUsers, + /// Lists all the rooms (local and remote) that the specified user is + /// invited to + ListInvitedRooms { + user_id: String, + }, + /// Lists all the rooms (local and remote) that the specified user is /// joined in ListJoinedRooms { user_id: String, }, + /// Manually make a user reject all current invites + ForceRejectInvites { + user_id: String, + }, + /// Manually join a local user to a room. ForceJoinRoom { user_id: String,