diff --git a/src/admin/appservice/appservice_command.rs b/src/admin/appservice/appservice_command.rs
index d15cb7a6da3e0749a9db4a25b44cb3f4254abf18..6f36ebf73b327fad7b5b7f0c1755b4bae0b7f1f7 100644
--- a/src/admin/appservice/appservice_command.rs
+++ b/src/admin/appservice/appservice_command.rs
@@ -3,26 +3,26 @@
 use crate::{escape_html, services, Result};
 
 pub(crate) async fn register(body: Vec<&str>) -> Result<RoomMessageEventContent> {
-	if body.len() > 2 && body[0].trim().starts_with("```") && body.last().unwrap().trim() == "```" {
-		let appservice_config = body[1..body.len().checked_sub(1).unwrap()].join("\n");
-		let parsed_config = serde_yaml::from_str::<Registration>(&appservice_config);
-		match parsed_config {
-			Ok(yaml) => match services().appservice.register_appservice(yaml).await {
-				Ok(id) => Ok(RoomMessageEventContent::text_plain(format!(
-					"Appservice registered with ID: {id}."
-				))),
-				Err(e) => Ok(RoomMessageEventContent::text_plain(format!(
-					"Failed to register appservice: {e}"
-				))),
-			},
+	if body.len() < 2 || !body[0].trim().starts_with("```") || body.last().unwrap_or(&"").trim() != "```" {
+		return Ok(RoomMessageEventContent::text_plain(
+			"Expected code block in command body. Add --help for details.",
+		));
+	}
+
+	let appservice_config = body[1..body.len().checked_sub(1).unwrap()].join("\n");
+	let parsed_config = serde_yaml::from_str::<Registration>(&appservice_config);
+	match parsed_config {
+		Ok(yaml) => match services().appservice.register_appservice(yaml).await {
+			Ok(id) => Ok(RoomMessageEventContent::text_plain(format!(
+				"Appservice registered with ID: {id}."
+			))),
 			Err(e) => Ok(RoomMessageEventContent::text_plain(format!(
-				"Could not parse appservice config: {e}"
+				"Failed to register appservice: {e}"
 			))),
-		}
-	} else {
-		Ok(RoomMessageEventContent::text_plain(
-			"Expected code block in command body. Add --help for details.",
-		))
+		},
+		Err(e) => Ok(RoomMessageEventContent::text_plain(format!(
+			"Could not parse appservice config: {e}"
+		))),
 	}
 }
 
diff --git a/src/admin/debug/debug_commands.rs b/src/admin/debug/debug_commands.rs
index 4522b678be0038f420acb44192a55738e8590891..bd15ba50aab513c2ddf8be6c2aeab76b3ef92c05 100644
--- a/src/admin/debug/debug_commands.rs
+++ b/src/admin/debug/debug_commands.rs
@@ -1,9 +1,15 @@
-use std::{collections::BTreeMap, sync::Arc, time::Instant};
+use std::{
+	collections::{BTreeMap, HashMap},
+	sync::Arc,
+	time::Instant,
+};
 
+use api::client::validate_and_add_event_id;
 use conduit::{utils::HtmlEscape, Error, Result};
 use ruma::{
-	api::client::error::ErrorKind, events::room::message::RoomMessageEventContent, CanonicalJsonObject, EventId,
-	RoomId, RoomVersionId, ServerName,
+	api::{client::error::ErrorKind, federation::event::get_room_state},
+	events::room::message::RoomMessageEventContent,
+	CanonicalJsonObject, EventId, RoomId, RoomVersionId, ServerName,
 };
 use service::{rooms::event_handler::parse_incoming_pdu, sending::resolve::resolve_actual_dest, services, PduEvent};
 use tokio::sync::RwLock;
@@ -37,28 +43,30 @@ pub(crate) async fn get_auth_chain(_body: Vec<&str>, event_id: Box<EventId>) ->
 }
 
 pub(crate) async fn parse_pdu(body: Vec<&str>) -> Result<RoomMessageEventContent> {
-	if body.len() > 2 && body[0].trim().starts_with("```") && body.last().unwrap().trim() == "```" {
-		let string = body[1..body.len() - 1].join("\n");
-		match serde_json::from_str(&string) {
-			Ok(value) => match ruma::signatures::reference_hash(&value, &RoomVersionId::V6) {
-				Ok(hash) => {
-					let event_id = EventId::parse(format!("${hash}"));
-
-					match serde_json::from_value::<PduEvent>(serde_json::to_value(value).expect("value is json")) {
-						Ok(pdu) => Ok(RoomMessageEventContent::text_plain(format!("EventId: {event_id:?}\n{pdu:#?}"))),
-						Err(e) => Ok(RoomMessageEventContent::text_plain(format!(
-							"EventId: {event_id:?}\nCould not parse event: {e}"
-						))),
-					}
-				},
-				Err(e) => Ok(RoomMessageEventContent::text_plain(format!("Could not parse PDU JSON: {e:?}"))),
+	if body.len() < 2 || !body[0].trim().starts_with("```") || body.last().unwrap_or(&"").trim() != "```" {
+		return Ok(RoomMessageEventContent::text_plain(
+			"Expected code block in command body. Add --help for details.",
+		));
+	}
+
+	let string = body[1..body.len() - 1].join("\n");
+	match serde_json::from_str(&string) {
+		Ok(value) => match ruma::signatures::reference_hash(&value, &RoomVersionId::V6) {
+			Ok(hash) => {
+				let event_id = EventId::parse(format!("${hash}"));
+
+				match serde_json::from_value::<PduEvent>(serde_json::to_value(value).expect("value is json")) {
+					Ok(pdu) => Ok(RoomMessageEventContent::text_plain(format!("EventId: {event_id:?}\n{pdu:#?}"))),
+					Err(e) => Ok(RoomMessageEventContent::text_plain(format!(
+						"EventId: {event_id:?}\nCould not parse event: {e}"
+					))),
+				}
 			},
-			Err(e) => Ok(RoomMessageEventContent::text_plain(format!(
-				"Invalid json in command body: {e}"
-			))),
-		}
-	} else {
-		Ok(RoomMessageEventContent::text_plain("Expected code block in command body."))
+			Err(e) => Ok(RoomMessageEventContent::text_plain(format!("Could not parse PDU JSON: {e:?}"))),
+		},
+		Err(e) => Ok(RoomMessageEventContent::text_plain(format!(
+			"Invalid json in command body: {e}"
+		))),
 	}
 }
 
@@ -111,33 +119,40 @@ pub(crate) async fn get_remote_pdu_list(
 
 	if server == services().globals.server_name() {
 		return Ok(RoomMessageEventContent::text_plain(
-			"Not allowed to send federation requests to ourselves. Please use `get-pdu` for fetching local PDUs.",
+			"Not allowed to send federation requests to ourselves. Please use `get-pdu` for fetching local PDUs from \
+			 the database.",
 		));
 	}
 
-	if body.len() > 2 && body[0].trim().starts_with("```") && body.last().unwrap().trim() == "```" {
-		let list = body
-			.clone()
-			.drain(1..body.len().checked_sub(1).unwrap())
-			.filter_map(|pdu| EventId::parse(pdu).ok())
-			.collect::<Vec<_>>();
-
-		for pdu in list {
-			if force {
-				if let Err(e) = get_remote_pdu(Vec::new(), Box::from(pdu), server.clone()).await {
-					warn!(%e, "Failed to get remote PDU, ignoring error");
-				}
-			} else {
-				get_remote_pdu(Vec::new(), Box::from(pdu), server.clone()).await?;
+	if body.len() < 2 || !body[0].trim().starts_with("```") || body.last().unwrap_or(&"").trim() != "```" {
+		return Ok(RoomMessageEventContent::text_plain(
+			"Expected code block in command body. Add --help for details.",
+		));
+	}
+
+	let list = body
+		.clone()
+		.drain(1..body.len().checked_sub(1).unwrap())
+		.filter_map(|pdu| EventId::parse(pdu).ok())
+		.collect::<Vec<_>>();
+
+	for pdu in list {
+		if force {
+			if let Err(e) = get_remote_pdu(Vec::new(), Box::from(pdu), server.clone()).await {
+				services()
+					.admin
+					.send_message(RoomMessageEventContent::text_plain(format!(
+						"Failed to get remote PDU, ignoring error: {e}"
+					)))
+					.await;
+				warn!(%e, "Failed to get remote PDU, ignoring error");
 			}
+		} else {
+			get_remote_pdu(Vec::new(), Box::from(pdu), server.clone()).await?;
 		}
-
-		return Ok(RoomMessageEventContent::text_plain("Fetched list of remote PDUs."));
 	}
 
-	Ok(RoomMessageEventContent::text_plain(
-		"Expected code block in command body. Add --help for details.",
-	))
+	Ok(RoomMessageEventContent::text_plain("Fetched list of remote PDUs."))
 }
 
 pub(crate) async fn get_remote_pdu(
@@ -378,55 +393,55 @@ pub(crate) async fn change_log_level(
 }
 
 pub(crate) async fn sign_json(body: Vec<&str>) -> Result<RoomMessageEventContent> {
-	if body.len() > 2 && body[0].trim().starts_with("```") && body.last().unwrap().trim() == "```" {
-		let string = body[1..body.len().checked_sub(1).unwrap()].join("\n");
-		match serde_json::from_str(&string) {
-			Ok(mut value) => {
-				ruma::signatures::sign_json(
-					services().globals.server_name().as_str(),
-					services().globals.keypair(),
-					&mut value,
-				)
-				.expect("our request json is what ruma expects");
-				let json_text = serde_json::to_string_pretty(&value).expect("canonical json is valid json");
-				Ok(RoomMessageEventContent::text_plain(json_text))
-			},
-			Err(e) => Ok(RoomMessageEventContent::text_plain(format!("Invalid json: {e}"))),
-		}
-	} else {
-		Ok(RoomMessageEventContent::text_plain(
+	if body.len() < 2 || !body[0].trim().starts_with("```") || body.last().unwrap_or(&"").trim() != "```" {
+		return Ok(RoomMessageEventContent::text_plain(
 			"Expected code block in command body. Add --help for details.",
-		))
+		));
+	}
+
+	let string = body[1..body.len().checked_sub(1).unwrap()].join("\n");
+	match serde_json::from_str(&string) {
+		Ok(mut value) => {
+			ruma::signatures::sign_json(
+				services().globals.server_name().as_str(),
+				services().globals.keypair(),
+				&mut value,
+			)
+			.expect("our request json is what ruma expects");
+			let json_text = serde_json::to_string_pretty(&value).expect("canonical json is valid json");
+			Ok(RoomMessageEventContent::text_plain(json_text))
+		},
+		Err(e) => Ok(RoomMessageEventContent::text_plain(format!("Invalid json: {e}"))),
 	}
 }
 
 pub(crate) async fn verify_json(body: Vec<&str>) -> Result<RoomMessageEventContent> {
-	if body.len() > 2 && body[0].trim().starts_with("```") && body.last().unwrap().trim() == "```" {
-		let string = body[1..body.len().checked_sub(1).unwrap()].join("\n");
-		match serde_json::from_str(&string) {
-			Ok(value) => {
-				let pub_key_map = RwLock::new(BTreeMap::new());
-
-				services()
-					.rooms
-					.event_handler
-					.fetch_required_signing_keys([&value], &pub_key_map)
-					.await?;
-
-				let pub_key_map = pub_key_map.read().await;
-				match ruma::signatures::verify_json(&pub_key_map, &value) {
-					Ok(()) => Ok(RoomMessageEventContent::text_plain("Signature correct")),
-					Err(e) => Ok(RoomMessageEventContent::text_plain(format!(
-						"Signature verification failed: {e}"
-					))),
-				}
-			},
-			Err(e) => Ok(RoomMessageEventContent::text_plain(format!("Invalid json: {e}"))),
-		}
-	} else {
-		Ok(RoomMessageEventContent::text_plain(
+	if body.len() < 2 || !body[0].trim().starts_with("```") || body.last().unwrap_or(&"").trim() != "```" {
+		return Ok(RoomMessageEventContent::text_plain(
 			"Expected code block in command body. Add --help for details.",
-		))
+		));
+	}
+
+	let string = body[1..body.len().checked_sub(1).unwrap()].join("\n");
+	match serde_json::from_str(&string) {
+		Ok(value) => {
+			let pub_key_map = RwLock::new(BTreeMap::new());
+
+			services()
+				.rooms
+				.event_handler
+				.fetch_required_signing_keys([&value], &pub_key_map)
+				.await?;
+
+			let pub_key_map = pub_key_map.read().await;
+			match ruma::signatures::verify_json(&pub_key_map, &value) {
+				Ok(()) => Ok(RoomMessageEventContent::text_plain("Signature correct")),
+				Err(e) => Ok(RoomMessageEventContent::text_plain(format!(
+					"Signature verification failed: {e}"
+				))),
+			}
+		},
+		Err(e) => Ok(RoomMessageEventContent::text_plain(format!("Invalid json: {e}"))),
 	}
 }
 
@@ -472,6 +487,147 @@ pub(crate) async fn latest_pdu_in_room(_body: Vec<&str>, room_id: Box<RoomId>) -
 	Ok(RoomMessageEventContent::text_plain(format!("{latest_pdu:?}")))
 }
 
+#[tracing::instrument(skip(_body))]
+pub(crate) async fn force_set_room_state_from_server(
+	_body: Vec<&str>, server_name: Box<ServerName>, room_id: Box<RoomId>,
+) -> Result<RoomMessageEventContent> {
+	if !services()
+		.rooms
+		.state_cache
+		.server_in_room(&services().globals.config.server_name, &room_id)?
+	{
+		return Ok(RoomMessageEventContent::text_plain(
+			"We are not participating in the room / we don't know about the room ID.",
+		));
+	}
+
+	let first_pdu = services()
+		.rooms
+		.timeline
+		.latest_pdu_in_room(&room_id)?
+		.ok_or_else(|| Error::bad_database("Failed to find the latest PDU in database"))?;
+
+	let room_version = services().rooms.state.get_room_version(&room_id)?;
+
+	let mut state: HashMap<u64, Arc<EventId>> = HashMap::new();
+	let pub_key_map = RwLock::new(BTreeMap::new());
+
+	let remote_state_response = services()
+		.sending
+		.send_federation_request(
+			&server_name,
+			get_room_state::v1::Request {
+				room_id: room_id.clone().into(),
+				event_id: first_pdu.event_id.clone().into(),
+			},
+		)
+		.await?;
+
+	let mut events = Vec::with_capacity(remote_state_response.pdus.len());
+
+	for pdu in remote_state_response.pdus.clone() {
+		events.push(match parse_incoming_pdu(&pdu) {
+			Ok(t) => t,
+			Err(e) => {
+				warn!("Could not parse PDU, ignoring: {e}");
+				continue;
+			},
+		});
+	}
+
+	info!("Fetching required signing keys for all the state events we got");
+	services()
+		.rooms
+		.event_handler
+		.fetch_required_signing_keys(events.iter().map(|(_event_id, event, _room_id)| event), &pub_key_map)
+		.await?;
+
+	info!("Going through room_state response PDUs");
+	for result in remote_state_response
+		.pdus
+		.iter()
+		.map(|pdu| validate_and_add_event_id(pdu, &room_version, &pub_key_map))
+	{
+		let Ok((event_id, value)) = result.await else {
+			continue;
+		};
+
+		let pdu = PduEvent::from_id_val(&event_id, value.clone()).map_err(|e| {
+			warn!("Invalid PDU in fetching remote room state PDUs response: {} {:?}", e, value);
+			Error::BadServerResponse("Invalid PDU in send_join response.")
+		})?;
+
+		services()
+			.rooms
+			.outlier
+			.add_pdu_outlier(&event_id, &value)?;
+		if let Some(state_key) = &pdu.state_key {
+			let shortstatekey = services()
+				.rooms
+				.short
+				.get_or_create_shortstatekey(&pdu.kind.to_string().into(), state_key)?;
+			state.insert(shortstatekey, pdu.event_id.clone());
+		}
+	}
+
+	info!("Going through auth_chain response");
+	for result in remote_state_response
+		.auth_chain
+		.iter()
+		.map(|pdu| validate_and_add_event_id(pdu, &room_version, &pub_key_map))
+	{
+		let Ok((event_id, value)) = result.await else {
+			continue;
+		};
+
+		services()
+			.rooms
+			.outlier
+			.add_pdu_outlier(&event_id, &value)?;
+	}
+
+	let new_room_state = services()
+		.rooms
+		.event_handler
+		.resolve_state(room_id.clone().as_ref(), &room_version, state)
+		.await?;
+
+	info!("Forcing new room state");
+	let (short_state_hash, new, removed) = services()
+		.rooms
+		.state_compressor
+		.save_state(room_id.clone().as_ref(), new_room_state)?;
+
+	let mutex_state = Arc::clone(
+		services()
+			.globals
+			.roomid_mutex_state
+			.write()
+			.await
+			.entry(room_id.clone().into())
+			.or_default(),
+	);
+	let state_lock = mutex_state.lock().await;
+
+	services()
+		.rooms
+		.state
+		.force_state(room_id.clone().as_ref(), short_state_hash, new, removed, &state_lock)
+		.await?;
+
+	info!(
+		"Updating joined counts for room just in case (e.g. we may have found a difference in the room's \
+		 m.room.member state"
+	);
+	services().rooms.state_cache.update_joined_count(&room_id)?;
+
+	drop(state_lock);
+
+	Ok(RoomMessageEventContent::text_plain(
+		"Successfully forced the room state from the requested remote server.",
+	))
+}
+
 pub(crate) async fn resolve_true_destination(
 	_body: Vec<&str>, server_name: Box<ServerName>, no_cache: bool,
 ) -> Result<RoomMessageEventContent> {
diff --git a/src/admin/debug/mod.rs b/src/admin/debug/mod.rs
index 614a4ae5542006369876fb9ac6664db04a626cf0..afcc49b00bb2f52947972296037bc91666dac761 100644
--- a/src/admin/debug/mod.rs
+++ b/src/admin/debug/mod.rs
@@ -1,5 +1,5 @@
 use clap::Subcommand;
-use debug_commands::{first_pdu_in_room, latest_pdu_in_room};
+use debug_commands::{first_pdu_in_room, force_set_room_state_from_server, latest_pdu_in_room};
 use ruma::{events::room::message::RoomMessageEventContent, EventId, RoomId, ServerName};
 
 use self::debug_commands::{
@@ -123,6 +123,27 @@ pub(crate) enum DebugCommand {
 		room_id: Box<RoomId>,
 	},
 
+	/// - Forcefully replaces the room state of our local copy of the specified
+	///   room, with the copy (auth chain and room state events) the specified
+	///   remote server says.
+	///
+	/// A common desire for room deletion is to simply "reset" our copy of the
+	/// room. While this admin command is not a replacement for that, if you
+	/// know you have split/broken room state and you know another server in the
+	/// room that has the best/working room state, this command can let you use
+	/// their room state. Such example is your server saying users are in a
+	/// room, but other servers are saying they're not in the room in question.
+	///
+	/// This command will get the latest PDU in the room we know about, and
+	/// request the room state at that point in time via
+	/// `/_matrix/federation/v1/state/{roomId}`.
+	ForceSetRoomStateFromServer {
+		/// The impacted room ID
+		room_id: Box<RoomId>,
+		/// The server we will use to query the room state for
+		server_name: Box<ServerName>,
+	},
+
 	/// - Runs a server name through conduwuit's true destination resolution
 	///   process
 	///
@@ -174,6 +195,10 @@ pub(crate) async fn process(command: DebugCommand, body: Vec<&str>) -> Result<Ro
 			server,
 			force,
 		} => get_remote_pdu_list(body, server, force).await?,
+		DebugCommand::ForceSetRoomStateFromServer {
+			room_id,
+			server_name,
+		} => force_set_room_state_from_server(body, server_name, room_id).await?,
 		DebugCommand::ResolveTrueDestination {
 			server_name,
 			no_cache,
diff --git a/src/admin/federation/federation_commands.rs b/src/admin/federation/federation_commands.rs
index 3405b6fab539e809cd475aaf2fc41cb682dbadce..b52a62f29c3fe1707b5ba1e64abb52fdeef7f438 100644
--- a/src/admin/federation/federation_commands.rs
+++ b/src/admin/federation/federation_commands.rs
@@ -14,7 +14,7 @@ pub(crate) async fn enable_room(_body: Vec<&str>, room_id: Box<RoomId>) -> Resul
 	Ok(RoomMessageEventContent::text_plain("Room enabled."))
 }
 
-pub(crate) async fn incoming_federeation(_body: Vec<&str>) -> Result<RoomMessageEventContent> {
+pub(crate) async fn incoming_federation(_body: Vec<&str>) -> Result<RoomMessageEventContent> {
 	let map = services().globals.roomid_federationhandletime.read().await;
 	let mut msg = format!("Handling {} incoming pdus:\n", map.len());
 
@@ -101,7 +101,8 @@ pub(crate) async fn remote_user_in_rooms(_body: Vec<&str>, user_id: Box<UserId>)
 	rooms.reverse();
 
 	let output_plain = format!(
-		"Rooms {user_id} shares with us:\n{}",
+		"Rooms {user_id} shares with us ({}):\n{}",
+		rooms.len(),
 		rooms
 			.iter()
 			.map(|(id, members, name)| format!("{id}\tMembers: {members}\tName: {name}"))
@@ -109,15 +110,16 @@ pub(crate) async fn remote_user_in_rooms(_body: Vec<&str>, user_id: Box<UserId>)
 			.join("\n")
 	);
 	let output_html = format!(
-		"<table><caption>Rooms {user_id} shares with \
-		 us</caption>\n<tr><th>id</th>\t<th>members</th>\t<th>name</th></tr>\n{}</table>",
+		"<table><caption>Rooms {user_id} shares with us \
+		 ({})</caption>\n<tr><th>id</th>\t<th>members</th>\t<th>name</th></tr>\n{}</table>",
+		rooms.len(),
 		rooms
 			.iter()
 			.fold(String::new(), |mut output, (id, members, name)| {
 				writeln!(
 					output,
 					"<tr><td>{}</td>\t<td>{}</td>\t<td>{}</td></tr>",
-					escape_html(id.as_ref()),
+					id,
 					members,
 					escape_html(name)
 				)
diff --git a/src/admin/federation/mod.rs b/src/admin/federation/mod.rs
index b3da33f2a782954e944e730754a2992758ad502f..81469feb46bcb5c7a9cd22b014c0a113e901a9b2 100644
--- a/src/admin/federation/mod.rs
+++ b/src/admin/federation/mod.rs
@@ -2,7 +2,7 @@
 use ruma::{events::room::message::RoomMessageEventContent, RoomId, ServerName, UserId};
 
 use self::federation_commands::{
-	disable_room, enable_room, fetch_support_well_known, incoming_federeation, remote_user_in_rooms,
+	disable_room, enable_room, fetch_support_well_known, incoming_federation, remote_user_in_rooms,
 };
 use crate::Result;
 
@@ -51,7 +51,7 @@ pub(crate) async fn process(command: FederationCommand, body: Vec<&str>) -> Resu
 		FederationCommand::EnableRoom {
 			room_id,
 		} => enable_room(body, room_id).await?,
-		FederationCommand::IncomingFederation => incoming_federeation(body).await?,
+		FederationCommand::IncomingFederation => incoming_federation(body).await?,
 		FederationCommand::FetchSupportWellKnown {
 			server_name,
 		} => fetch_support_well_known(body, server_name).await?,
diff --git a/src/admin/media/media_commands.rs b/src/admin/media/media_commands.rs
index 956f5743d1b7a8bcf6daee1fe18f3a4bcc018eab..a78ee3873b2d17119a6c871818ad57ada316fa46 100644
--- a/src/admin/media/media_commands.rs
+++ b/src/admin/media/media_commands.rs
@@ -138,30 +138,30 @@ pub(crate) async fn delete(
 }
 
 pub(crate) async fn delete_list(body: Vec<&str>) -> Result<RoomMessageEventContent> {
-	if body.len() > 2 && body[0].trim().starts_with("```") && body.last().unwrap().trim() == "```" {
-		let mxc_list = body
-			.clone()
-			.drain(1..body.len().checked_sub(1).unwrap())
-			.collect::<Vec<_>>();
+	if body.len() < 2 || !body[0].trim().starts_with("```") || body.last().unwrap_or(&"").trim() != "```" {
+		return Ok(RoomMessageEventContent::text_plain(
+			"Expected code block in command body. Add --help for details.",
+		));
+	}
 
-		let mut mxc_deletion_count: usize = 0;
+	let mxc_list = body
+		.clone()
+		.drain(1..body.len().checked_sub(1).unwrap())
+		.collect::<Vec<_>>();
 
-		for mxc in mxc_list {
-			debug!("Deleting MXC {mxc} in bulk");
-			services().media.delete(mxc.to_owned()).await?;
-			mxc_deletion_count = mxc_deletion_count
-				.checked_add(1)
-				.expect("mxc_deletion_count should not get this high");
-		}
+	let mut mxc_deletion_count: usize = 0;
 
-		return Ok(RoomMessageEventContent::text_plain(format!(
-			"Finished bulk MXC deletion, deleted {mxc_deletion_count} total MXCs from our database and the filesystem.",
-		)));
+	for mxc in mxc_list {
+		debug!("Deleting MXC {mxc} in bulk");
+		services().media.delete(mxc.to_owned()).await?;
+		mxc_deletion_count = mxc_deletion_count
+			.checked_add(1)
+			.expect("mxc_deletion_count should not get this high");
 	}
 
-	Ok(RoomMessageEventContent::text_plain(
-		"Expected code block in command body. Add --help for details.",
-	))
+	Ok(RoomMessageEventContent::text_plain(format!(
+		"Finished bulk MXC deletion, deleted {mxc_deletion_count} total MXCs from our database and the filesystem.",
+	)))
 }
 
 pub(crate) async fn delete_past_remote_media(
diff --git a/src/admin/query/mod.rs b/src/admin/query/mod.rs
index bebbb7711e01d0e14efef02212d3f5840677e4f6..2e5b0bb805ca81fba176182111d426bccd705c21 100644
--- a/src/admin/query/mod.rs
+++ b/src/admin/query/mod.rs
@@ -3,10 +3,12 @@
 pub(crate) mod globals;
 pub(crate) mod presence;
 pub(crate) mod room_alias;
+pub(crate) mod room_state_cache;
 pub(crate) mod sending;
 pub(crate) mod users;
 
 use clap::Subcommand;
+use room_state_cache::room_state_cache;
 use ruma::{
 	events::{room::message::RoomMessageEventContent, RoomAccountDataEventType},
 	RoomAliasId, RoomId, ServerName, UserId,
@@ -38,6 +40,10 @@ pub(crate) enum QueryCommand {
 	#[command(subcommand)]
 	RoomAlias(RoomAlias),
 
+	/// - rooms/state_cache iterators and getters
+	#[command(subcommand)]
+	RoomStateCache(RoomStateCache),
+
 	/// - globals.rs iterators and getters
 	#[command(subcommand)]
 	Globals(Globals),
@@ -127,6 +133,78 @@ pub(crate) enum RoomAlias {
 	AllLocalAliases,
 }
 
+#[cfg_attr(test, derive(Debug))]
+#[derive(Subcommand)]
+pub(crate) enum RoomStateCache {
+	ServerInRoom {
+		server: Box<ServerName>,
+		room_id: Box<RoomId>,
+	},
+
+	RoomServers {
+		room_id: Box<RoomId>,
+	},
+
+	ServerRooms {
+		server: Box<ServerName>,
+	},
+
+	RoomMembers {
+		room_id: Box<RoomId>,
+	},
+
+	LocalUsersInRoom {
+		room_id: Box<RoomId>,
+	},
+
+	ActiveLocalUsersInRoom {
+		room_id: Box<RoomId>,
+	},
+
+	RoomJoinedCount {
+		room_id: Box<RoomId>,
+	},
+
+	RoomInvitedCount {
+		room_id: Box<RoomId>,
+	},
+
+	RoomUserOnceJoined {
+		room_id: Box<RoomId>,
+	},
+
+	RoomMembersInvited {
+		room_id: Box<RoomId>,
+	},
+
+	GetInviteCount {
+		room_id: Box<RoomId>,
+		user_id: Box<UserId>,
+	},
+
+	GetLeftCount {
+		room_id: Box<RoomId>,
+		user_id: Box<UserId>,
+	},
+
+	RoomsJoined {
+		user_id: Box<UserId>,
+	},
+
+	RoomsLeft {
+		user_id: Box<UserId>,
+	},
+
+	RoomsInvited {
+		user_id: Box<UserId>,
+	},
+
+	InviteState {
+		user_id: Box<UserId>,
+		room_id: Box<RoomId>,
+	},
+}
+
 #[cfg_attr(test, derive(Debug))]
 #[derive(Subcommand)]
 /// All the getters and iterators from src/database/key_value/globals.rs
@@ -216,6 +294,7 @@ pub(crate) async fn process(command: QueryCommand, _body: Vec<&str>) -> Result<R
 		QueryCommand::Appservice(command) => appservice(command).await?,
 		QueryCommand::Presence(command) => presence(command).await?,
 		QueryCommand::RoomAlias(command) => room_alias(command).await?,
+		QueryCommand::RoomStateCache(command) => room_state_cache(command).await?,
 		QueryCommand::Globals(command) => globals(command).await?,
 		QueryCommand::Sending(command) => sending(command).await?,
 		QueryCommand::Users(command) => users(command).await?,
diff --git a/src/admin/query/room_state_cache.rs b/src/admin/query/room_state_cache.rs
new file mode 100644
index 0000000000000000000000000000000000000000..c062497ed9546fb2090d9bb8438ea7df6bf93162
--- /dev/null
+++ b/src/admin/query/room_state_cache.rs
@@ -0,0 +1,249 @@
+use ruma::events::room::message::RoomMessageEventContent;
+
+use super::RoomStateCache;
+use crate::{services, Result};
+
+pub(crate) async fn room_state_cache(subcommand: RoomStateCache) -> Result<RoomMessageEventContent> {
+	match subcommand {
+		RoomStateCache::ServerInRoom {
+			server,
+			room_id,
+		} => {
+			let timer = tokio::time::Instant::now();
+			let result = services()
+				.rooms
+				.state_cache
+				.server_in_room(&server, &room_id);
+			let query_time = timer.elapsed();
+
+			Ok(RoomMessageEventContent::text_html(
+				format!("Query completed in {query_time:?}:\n\n```\n{result:?}```"),
+				format!("<p>Query completed in {query_time:?}:</p>\n<pre><code>{result:?}\n</code></pre>"),
+			))
+		},
+		RoomStateCache::RoomServers {
+			room_id,
+		} => {
+			let timer = tokio::time::Instant::now();
+			let results: Result<Vec<_>> = services()
+				.rooms
+				.state_cache
+				.room_servers(&room_id)
+				.collect();
+			let query_time = timer.elapsed();
+
+			Ok(RoomMessageEventContent::text_html(
+				format!("Query completed in {query_time:?}:\n\n```\n{results:?}```"),
+				format!("<p>Query completed in {query_time:?}:</p>\n<pre><code>{results:?}\n</code></pre>"),
+			))
+		},
+		RoomStateCache::ServerRooms {
+			server,
+		} => {
+			let timer = tokio::time::Instant::now();
+			let results: Result<Vec<_>> = services().rooms.state_cache.server_rooms(&server).collect();
+			let query_time = timer.elapsed();
+
+			Ok(RoomMessageEventContent::text_html(
+				format!("Query completed in {query_time:?}:\n\n```\n{results:?}```"),
+				format!("<p>Query completed in {query_time:?}:</p>\n<pre><code>{results:?}\n</code></pre>"),
+			))
+		},
+		RoomStateCache::RoomMembers {
+			room_id,
+		} => {
+			let timer = tokio::time::Instant::now();
+			let results: Result<Vec<_>> = services()
+				.rooms
+				.state_cache
+				.room_members(&room_id)
+				.collect();
+			let query_time = timer.elapsed();
+
+			Ok(RoomMessageEventContent::text_html(
+				format!("Query completed in {query_time:?}:\n\n```\n{results:?}```"),
+				format!("<p>Query completed in {query_time:?}:</p>\n<pre><code>{results:?}\n</code></pre>"),
+			))
+		},
+		RoomStateCache::LocalUsersInRoom {
+			room_id,
+		} => {
+			let timer = tokio::time::Instant::now();
+			let results: Vec<_> = services()
+				.rooms
+				.state_cache
+				.local_users_in_room(&room_id)
+				.collect();
+			let query_time = timer.elapsed();
+
+			Ok(RoomMessageEventContent::text_html(
+				format!("Query completed in {query_time:?}:\n\n```\n{results:?}```"),
+				format!("<p>Query completed in {query_time:?}:</p>\n<pre><code>{results:?}\n</code></pre>"),
+			))
+		},
+		RoomStateCache::ActiveLocalUsersInRoom {
+			room_id,
+		} => {
+			let timer = tokio::time::Instant::now();
+			let results: Vec<_> = services()
+				.rooms
+				.state_cache
+				.active_local_users_in_room(&room_id)
+				.collect();
+			let query_time = timer.elapsed();
+
+			Ok(RoomMessageEventContent::text_html(
+				format!("Query completed in {query_time:?}:\n\n```\n{results:?}```"),
+				format!("<p>Query completed in {query_time:?}:</p>\n<pre><code>{results:?}\n</code></pre>"),
+			))
+		},
+		RoomStateCache::RoomJoinedCount {
+			room_id,
+		} => {
+			let timer = tokio::time::Instant::now();
+			let results = services().rooms.state_cache.room_joined_count(&room_id);
+			let query_time = timer.elapsed();
+
+			Ok(RoomMessageEventContent::text_html(
+				format!("Query completed in {query_time:?}:\n\n```\n{results:?}```"),
+				format!("<p>Query completed in {query_time:?}:</p>\n<pre><code>{results:?}\n</code></pre>"),
+			))
+		},
+		RoomStateCache::RoomInvitedCount {
+			room_id,
+		} => {
+			let timer = tokio::time::Instant::now();
+			let results = services().rooms.state_cache.room_invited_count(&room_id);
+			let query_time = timer.elapsed();
+
+			Ok(RoomMessageEventContent::text_html(
+				format!("Query completed in {query_time:?}:\n\n```\n{results:?}```"),
+				format!("<p>Query completed in {query_time:?}:</p>\n<pre><code>{results:?}\n</code></pre>"),
+			))
+		},
+		RoomStateCache::RoomUserOnceJoined {
+			room_id,
+		} => {
+			let timer = tokio::time::Instant::now();
+			let results: Result<Vec<_>> = services()
+				.rooms
+				.state_cache
+				.room_useroncejoined(&room_id)
+				.collect();
+			let query_time = timer.elapsed();
+
+			Ok(RoomMessageEventContent::text_html(
+				format!("Query completed in {query_time:?}:\n\n```\n{results:?}```"),
+				format!("<p>Query completed in {query_time:?}:</p>\n<pre><code>{results:?}\n</code></pre>"),
+			))
+		},
+		RoomStateCache::RoomMembersInvited {
+			room_id,
+		} => {
+			let timer = tokio::time::Instant::now();
+			let results: Result<Vec<_>> = services()
+				.rooms
+				.state_cache
+				.room_members_invited(&room_id)
+				.collect();
+			let query_time = timer.elapsed();
+
+			Ok(RoomMessageEventContent::text_html(
+				format!("Query completed in {query_time:?}:\n\n```\n{results:?}```"),
+				format!("<p>Query completed in {query_time:?}:</p>\n<pre><code>{results:?}\n</code></pre>"),
+			))
+		},
+		RoomStateCache::GetInviteCount {
+			room_id,
+			user_id,
+		} => {
+			let timer = tokio::time::Instant::now();
+			let results = services()
+				.rooms
+				.state_cache
+				.get_invite_count(&room_id, &user_id);
+			let query_time = timer.elapsed();
+
+			Ok(RoomMessageEventContent::text_html(
+				format!("Query completed in {query_time:?}:\n\n```\n{results:?}```"),
+				format!("<p>Query completed in {query_time:?}:</p>\n<pre><code>{results:?}\n</code></pre>"),
+			))
+		},
+		RoomStateCache::GetLeftCount {
+			room_id,
+			user_id,
+		} => {
+			let timer = tokio::time::Instant::now();
+			let results = services()
+				.rooms
+				.state_cache
+				.get_left_count(&room_id, &user_id);
+			let query_time = timer.elapsed();
+
+			Ok(RoomMessageEventContent::text_html(
+				format!("Query completed in {query_time:?}:\n\n```\n{results:?}```"),
+				format!("<p>Query completed in {query_time:?}:</p>\n<pre><code>{results:?}\n</code></pre>"),
+			))
+		},
+		RoomStateCache::RoomsJoined {
+			user_id,
+		} => {
+			let timer = tokio::time::Instant::now();
+			let results: Result<Vec<_>> = services()
+				.rooms
+				.state_cache
+				.rooms_joined(&user_id)
+				.collect();
+			let query_time = timer.elapsed();
+
+			Ok(RoomMessageEventContent::text_html(
+				format!("Query completed in {query_time:?}:\n\n```\n{results:?}```"),
+				format!("<p>Query completed in {query_time:?}:</p>\n<pre><code>{results:?}\n</code></pre>"),
+			))
+		},
+		RoomStateCache::RoomsInvited {
+			user_id,
+		} => {
+			let timer = tokio::time::Instant::now();
+			let results: Result<Vec<_>> = services()
+				.rooms
+				.state_cache
+				.rooms_invited(&user_id)
+				.collect();
+			let query_time = timer.elapsed();
+
+			Ok(RoomMessageEventContent::text_html(
+				format!("Query completed in {query_time:?}:\n\n```\n{results:?}```"),
+				format!("<p>Query completed in {query_time:?}:</p>\n<pre><code>{results:?}\n</code></pre>"),
+			))
+		},
+		RoomStateCache::RoomsLeft {
+			user_id,
+		} => {
+			let timer = tokio::time::Instant::now();
+			let results: Result<Vec<_>> = services().rooms.state_cache.rooms_left(&user_id).collect();
+			let query_time = timer.elapsed();
+
+			Ok(RoomMessageEventContent::text_html(
+				format!("Query completed in {query_time:?}:\n\n```\n{results:?}```"),
+				format!("<p>Query completed in {query_time:?}:</p>\n<pre><code>{results:?}\n</code></pre>"),
+			))
+		},
+		RoomStateCache::InviteState {
+			user_id,
+			room_id,
+		} => {
+			let timer = tokio::time::Instant::now();
+			let results = services()
+				.rooms
+				.state_cache
+				.invite_state(&user_id, &room_id);
+			let query_time = timer.elapsed();
+
+			Ok(RoomMessageEventContent::text_html(
+				format!("Query completed in {query_time:?}:\n\n```\n{results:?}```"),
+				format!("<p>Query completed in {query_time:?}:</p>\n<pre><code>{results:?}\n</code></pre>"),
+			))
+		},
+	}
+}
diff --git a/src/admin/room/mod.rs b/src/admin/room/mod.rs
index ccc8c8bec9cecbbd443235881750513e0fd35bf2..ced96c95281b6529ec289d532e64e337cac9132a 100644
--- a/src/admin/room/mod.rs
+++ b/src/admin/room/mod.rs
@@ -7,6 +7,7 @@
 pub(crate) mod room_alias_commands;
 pub(crate) mod room_commands;
 pub(crate) mod room_directory_commands;
+pub(crate) mod room_info_commands;
 pub(crate) mod room_moderation_commands;
 
 #[cfg_attr(test, derive(Debug))]
@@ -17,6 +18,10 @@ pub(crate) enum RoomCommand {
 		page: Option<usize>,
 	},
 
+	#[command(subcommand)]
+	/// - View information about a room we know about
+	Info(RoomInfoCommand),
+
 	#[command(subcommand)]
 	/// - Manage moderation of remote or local rooms
 	Moderation(RoomModerationCommand),
@@ -30,6 +35,23 @@ pub(crate) enum RoomCommand {
 	Directory(RoomDirectoryCommand),
 }
 
+#[cfg_attr(test, derive(Debug))]
+#[derive(Subcommand)]
+pub(crate) enum RoomInfoCommand {
+	/// - List joined members in a room
+	ListJoinedMembers {
+		room_id: Box<RoomId>,
+	},
+
+	/// - Displays room topic
+	///
+	/// Room topics can be huge, so this is in its
+	/// own separate command
+	ViewRoomTopic {
+		room_id: Box<RoomId>,
+	},
+}
+
 #[cfg_attr(test, derive(Debug))]
 #[derive(Subcommand)]
 pub(crate) enum RoomAliasCommand {
@@ -147,6 +169,8 @@ pub(crate) enum RoomModerationCommand {
 
 pub(crate) async fn process(command: RoomCommand, body: Vec<&str>) -> Result<RoomMessageEventContent> {
 	Ok(match command {
+		RoomCommand::Info(command) => room_info_commands::process(command, body).await?,
+
 		RoomCommand::Alias(command) => room_alias_commands::process(command, body).await?,
 
 		RoomCommand::Directory(command) => room_directory_commands::process(command, body).await?,
diff --git a/src/admin/room/room_info_commands.rs b/src/admin/room/room_info_commands.rs
new file mode 100644
index 0000000000000000000000000000000000000000..0bcbc7aed15547d88ded9a356c69c791056d46cf
--- /dev/null
+++ b/src/admin/room/room_info_commands.rs
@@ -0,0 +1,93 @@
+use std::fmt::Write;
+
+use ruma::{events::room::message::RoomMessageEventContent, RoomId};
+use service::services;
+
+use super::RoomInfoCommand;
+use crate::{escape_html, Result};
+
+pub(crate) async fn process(command: RoomInfoCommand, body: Vec<&str>) -> Result<RoomMessageEventContent> {
+	match command {
+		RoomInfoCommand::ListJoinedMembers {
+			room_id,
+		} => list_joined_members(body, room_id).await,
+		RoomInfoCommand::ViewRoomTopic {
+			room_id,
+		} => view_room_topic(body, room_id).await,
+	}
+}
+
+async fn list_joined_members(_body: Vec<&str>, room_id: Box<RoomId>) -> Result<RoomMessageEventContent> {
+	let room_name = services()
+		.rooms
+		.state_accessor
+		.get_name(&room_id)
+		.ok()
+		.flatten()
+		.unwrap_or_else(|| room_id.to_string());
+
+	let members = services()
+		.rooms
+		.state_cache
+		.room_members(&room_id)
+		.filter_map(Result::ok);
+
+	let member_info = members
+		.into_iter()
+		.map(|user_id| {
+			(
+				user_id.clone(),
+				services()
+					.users
+					.displayname(&user_id)
+					.unwrap_or(None)
+					.unwrap_or_else(|| user_id.to_string()),
+			)
+		})
+		.collect::<Vec<_>>();
+
+	let output_plain = format!(
+		"{} Members in Room \"{}\":\n```\n{}\n```",
+		member_info.len(),
+		room_name,
+		member_info
+			.iter()
+			.map(|(mxid, displayname)| format!("{mxid} | {displayname}"))
+			.collect::<Vec<_>>()
+			.join("\n")
+	);
+
+	let output_html = format!(
+		"<table><caption>{} Members in Room \"{}\" </caption>\n<tr><th>MXID</th>\t<th>Display \
+		 Name</th></tr>\n{}</table>",
+		member_info.len(),
+		room_name,
+		member_info
+			.iter()
+			.fold(String::new(), |mut output, (mxid, displayname)| {
+				writeln!(
+					output,
+					"<tr><td>{}</td>\t<td>{}</td></tr>",
+					mxid,
+					escape_html(displayname.as_ref())
+				)
+				.expect("should be able to write to string buffer");
+				output
+			})
+	);
+
+	Ok(RoomMessageEventContent::text_html(output_plain, output_html))
+}
+
+async fn view_room_topic(_body: Vec<&str>, room_id: Box<RoomId>) -> Result<RoomMessageEventContent> {
+	let Some(room_topic) = services().rooms.state_accessor.get_room_topic(&room_id)? else {
+		return Ok(RoomMessageEventContent::text_plain("Room does not have a room topic set."));
+	};
+
+	let output_html = format!("<p>Room topic:</p>\n<hr>\n{}<hr>", escape_html(&room_topic));
+
+	Ok(RoomMessageEventContent::text_html(
+		format!("Room topic:\n\n```{room_topic}\n```"),
+		output_html,
+	))
+}
diff --git a/src/admin/room/room_moderation_commands.rs b/src/admin/room/room_moderation_commands.rs
index 6b759c9bb7ee5a43369ffcbb6d7420cfbf636008..9886756b6106bea940e745f22fbb898f67dd3151 100644
--- a/src/admin/room/room_moderation_commands.rs
+++ b/src/admin/room/room_moderation_commands.rs
@@ -6,11 +6,8 @@
 };
 use tracing::{debug, error, info, warn};
 
-use super::{
-	super::{escape_html, Service},
-	RoomModerationCommand,
-};
-use crate::{services, user_is_local, Result};
+use super::{super::Service, RoomModerationCommand};
+use crate::{escape_html, get_room_info, services, user_is_local, Result};
 
 pub(crate) async fn process(command: RoomModerationCommand, body: Vec<&str>) -> Result<RoomMessageEventContent> {
 	match command {
@@ -187,135 +184,137 @@ async fn ban_room(
 }
 
 async fn ban_list_of_rooms(body: Vec<&str>, force: bool, disable_federation: bool) -> Result<RoomMessageEventContent> {
-	if body.len() > 2 && body[0].trim().starts_with("```") && body.last().unwrap().trim() == "```" {
-		let rooms_s = body.clone().drain(1..body.len() - 1).collect::<Vec<_>>();
-
-		let admin_room_alias: Box<RoomAliasId> = format!("#admins:{}", services().globals.server_name())
-			.try_into()
-			.expect("#admins:server_name is a valid alias name");
-
-		let mut room_ban_count: usize = 0;
-		let mut room_ids: Vec<OwnedRoomId> = Vec::new();
-
-		for &room in &rooms_s {
-			match <&RoomOrAliasId>::try_from(room) {
-				Ok(room_alias_or_id) => {
-					if let Some(admin_room_id) = Service::get_admin_room().await? {
-						if room.to_owned().eq(&admin_room_id) || room.to_owned().eq(&admin_room_alias) {
-							info!("User specified admin room in bulk ban list, ignoring");
-							continue;
-						}
-					}
+	if body.len() < 2 || !body[0].trim().starts_with("```") || body.last().unwrap_or(&"").trim() != "```" {
+		return Ok(RoomMessageEventContent::text_plain(
+			"Expected code block in command body. Add --help for details.",
+		));
+	}
 
-					if room_alias_or_id.is_room_id() {
-						let room_id = match RoomId::parse(room_alias_or_id) {
-							Ok(room_id) => room_id,
-							Err(e) => {
-								if force {
-									// ignore rooms we failed to parse if we're force banning
-									warn!(
-										"Error parsing room \"{room}\" during bulk room banning, ignoring error and \
-										 logging here: {e}"
-									);
-									continue;
-								}
+	let rooms_s = body.clone().drain(1..body.len() - 1).collect::<Vec<_>>();
+
+	let admin_room_alias: Box<RoomAliasId> = format!("#admins:{}", services().globals.server_name())
+		.try_into()
+		.expect("#admins:server_name is a valid alias name");
 
-								return Ok(RoomMessageEventContent::text_plain(format!(
-									"{room} is not a valid room ID or room alias, please fix the list and try again: \
-									 {e}"
-								)));
-							},
-						};
+	let mut room_ban_count: usize = 0;
+	let mut room_ids: Vec<OwnedRoomId> = Vec::new();
 
-						room_ids.push(room_id);
+	for &room in &rooms_s {
+		match <&RoomOrAliasId>::try_from(room) {
+			Ok(room_alias_or_id) => {
+				if let Some(admin_room_id) = Service::get_admin_room().await? {
+					if room.to_owned().eq(&admin_room_id) || room.to_owned().eq(&admin_room_alias) {
+						info!("User specified admin room in bulk ban list, ignoring");
+						continue;
 					}
+				}
 
-					if room_alias_or_id.is_room_alias_id() {
-						match RoomAliasId::parse(room_alias_or_id) {
-							Ok(room_alias) => {
-								let room_id =
-									if let Some(room_id) = services().rooms.alias.resolve_local_alias(&room_alias)? {
-										room_id
-									} else {
-										debug!(
-											"We don't have this room alias to a room ID locally, attempting to fetch \
-											 room ID over federation"
-										);
-
-										match get_alias_helper(room_alias, None).await {
-											Ok(response) => {
-												debug!(
-													"Got federation response fetching room ID for room {room}: {:?}",
-													response
-												);
-												response.room_id
-											},
-											Err(e) => {
-												// don't fail if force blocking
-												if force {
-													warn!("Failed to resolve room alias {room} to a room ID: {e}");
-													continue;
-												}
-
-												return Ok(RoomMessageEventContent::text_plain(format!(
-													"Failed to resolve room alias {room} to a room ID: {e}"
-												)));
-											},
-										}
-									};
-
-								room_ids.push(room_id);
-							},
-							Err(e) => {
-								if force {
-									// ignore rooms we failed to parse if we're force deleting
-									error!(
-										"Error parsing room \"{room}\" during bulk room banning, ignoring error and \
-										 logging here: {e}"
+				if room_alias_or_id.is_room_id() {
+					let room_id = match RoomId::parse(room_alias_or_id) {
+						Ok(room_id) => room_id,
+						Err(e) => {
+							if force {
+								// ignore rooms we failed to parse if we're force banning
+								warn!(
+									"Error parsing room \"{room}\" during bulk room banning, ignoring error and \
+									 logging here: {e}"
+								);
+								continue;
+							}
+
+							return Ok(RoomMessageEventContent::text_plain(format!(
+								"{room} is not a valid room ID or room alias, please fix the list and try again: {e}"
+							)));
+						},
+					};
+
+					room_ids.push(room_id);
+				}
+
+				if room_alias_or_id.is_room_alias_id() {
+					match RoomAliasId::parse(room_alias_or_id) {
+						Ok(room_alias) => {
+							let room_id =
+								if let Some(room_id) = services().rooms.alias.resolve_local_alias(&room_alias)? {
+									room_id
+								} else {
+									debug!(
+										"We don't have this room alias to a room ID locally, attempting to fetch room \
+										 ID over federation"
 									);
-									continue;
-								}
-
-								return Ok(RoomMessageEventContent::text_plain(format!(
-									"{room} is not a valid room ID or room alias, please fix the list and try again: \
-									 {e}"
-								)));
-							},
-						}
-					}
-				},
-				Err(e) => {
-					if force {
-						// ignore rooms we failed to parse if we're force deleting
-						error!(
-							"Error parsing room \"{room}\" during bulk room banning, ignoring error and logging here: \
-							 {e}"
-						);
-						continue;
+
+									match get_alias_helper(room_alias, None).await {
+										Ok(response) => {
+											debug!(
+												"Got federation response fetching room ID for room {room}: {:?}",
+												response
+											);
+											response.room_id
+										},
+										Err(e) => {
+											// don't fail if force blocking
+											if force {
+												warn!("Failed to resolve room alias {room} to a room ID: {e}");
+												continue;
+											}
+
+											return Ok(RoomMessageEventContent::text_plain(format!(
+												"Failed to resolve room alias {room} to a room ID: {e}"
+											)));
+										},
+									}
+								};
+
+							room_ids.push(room_id);
+						},
+						Err(e) => {
+							if force {
+								// ignore rooms we failed to parse if we're force deleting
+								error!(
+									"Error parsing room \"{room}\" during bulk room banning, ignoring error and \
+									 logging here: {e}"
+								);
+								continue;
+							}
+
+							return Ok(RoomMessageEventContent::text_plain(format!(
+								"{room} is not a valid room ID or room alias, please fix the list and try again: {e}"
+							)));
+						},
 					}
+				}
+			},
+			Err(e) => {
+				if force {
+					// ignore rooms we failed to parse if we're force deleting
+					error!(
+						"Error parsing room \"{room}\" during bulk room banning, ignoring error and logging here: {e}"
+					);
+					continue;
+				}
 
-					return Ok(RoomMessageEventContent::text_plain(format!(
-						"{room} is not a valid room ID or room alias, please fix the list and try again: {e}"
-					)));
-				},
-			}
+				return Ok(RoomMessageEventContent::text_plain(format!(
+					"{room} is not a valid room ID or room alias, please fix the list and try again: {e}"
+				)));
+			},
 		}
+	}
 
-		for room_id in room_ids {
-			if services().rooms.metadata.ban_room(&room_id, true).is_ok() {
-				debug!("Banned {room_id} successfully");
-				room_ban_count = room_ban_count.saturating_add(1);
-			}
+	for room_id in room_ids {
+		if services().rooms.metadata.ban_room(&room_id, true).is_ok() {
+			debug!("Banned {room_id} successfully");
+			room_ban_count = room_ban_count.saturating_add(1);
+		}
 
-			debug!("Making all users leave the room {}", &room_id);
-			if force {
-				for local_user in services()
-					.rooms
-					.state_cache
-					.room_members(&room_id)
-					.filter_map(|user| {
-						user.ok().filter(|local_user| {
-							local_user.server_name() == services().globals.server_name()
+		debug!("Making all users leave the room {}", &room_id);
+		if force {
+			for local_user in services()
+				.rooms
+				.state_cache
+				.room_members(&room_id)
+				.filter_map(|user| {
+					user.ok().filter(|local_user| {
+						local_user.server_name() == services().globals.server_name()
 										// additional wrapped check here is to avoid adding remote users
 										// who are in the admin room to the list of local users (would fail auth check)
 										&& (local_user.server_name()
@@ -324,31 +323,31 @@ async fn ban_list_of_rooms(body: Vec<&str>, force: bool, disable_federation: boo
 												.users
 												.is_admin(local_user)
 												.unwrap_or(true)) // since this is a
-							 // force operation,
-							 // assume user is
-							 // an admin if
-							 // somehow this
-							 // fails
-						})
+						 // force operation,
+						 // assume user is
+						 // an admin if
+						 // somehow this
+						 // fails
 					})
-					.collect::<Vec<OwnedUserId>>()
-				{
-					debug!(
-						"Attempting leave for user {} in room {} (forced, ignoring all errors, evicting admins too)",
-						&local_user, room_id
-					);
-					if let Err(e) = leave_room(&local_user, &room_id, None).await {
-						warn!(%e, "Failed to leave room");
-					}
+				})
+				.collect::<Vec<OwnedUserId>>()
+			{
+				debug!(
+					"Attempting leave for user {} in room {} (forced, ignoring all errors, evicting admins too)",
+					&local_user, room_id
+				);
+				if let Err(e) = leave_room(&local_user, &room_id, None).await {
+					warn!(%e, "Failed to leave room");
 				}
-			} else {
-				for local_user in services()
-					.rooms
-					.state_cache
-					.room_members(&room_id)
-					.filter_map(|user| {
-						user.ok().filter(|local_user| {
-							local_user.server_name() == services().globals.server_name()
+			}
+		} else {
+			for local_user in services()
+				.rooms
+				.state_cache
+				.room_members(&room_id)
+				.filter_map(|user| {
+					user.ok().filter(|local_user| {
+						local_user.server_name() == services().globals.server_name()
 										// additional wrapped check here is to avoid adding remote users
 										// who are in the admin room to the list of local users (would fail auth check)
 										&& (local_user.server_name()
@@ -357,45 +356,41 @@ async fn ban_list_of_rooms(body: Vec<&str>, force: bool, disable_federation: boo
 												.users
 												.is_admin(local_user)
 												.unwrap_or(false))
-						})
 					})
-					.collect::<Vec<OwnedUserId>>()
-				{
-					debug!("Attempting leave for user {} in room {}", &local_user, &room_id);
-					if let Err(e) = leave_room(&local_user, &room_id, None).await {
-						error!(
-							"Error attempting to make local user {} leave room {} during bulk room banning: {}",
-							&local_user, &room_id, e
-						);
-						return Ok(RoomMessageEventContent::text_plain(format!(
-							"Error attempting to make local user {} leave room {} during room banning (room is still \
-							 banned but not removing any more users and not banning any more rooms): {}\nIf you would \
-							 like to ignore errors, use --force",
-							&local_user, &room_id, e
-						)));
-					}
+				})
+				.collect::<Vec<OwnedUserId>>()
+			{
+				debug!("Attempting leave for user {} in room {}", &local_user, &room_id);
+				if let Err(e) = leave_room(&local_user, &room_id, None).await {
+					error!(
+						"Error attempting to make local user {} leave room {} during bulk room banning: {}",
+						&local_user, &room_id, e
+					);
+					return Ok(RoomMessageEventContent::text_plain(format!(
+						"Error attempting to make local user {} leave room {} during room banning (room is still \
+						 banned but not removing any more users and not banning any more rooms): {}\nIf you would \
+						 like to ignore errors, use --force",
+						&local_user, &room_id, e
+					)));
 				}
 			}
-
-			if disable_federation {
-				services().rooms.metadata.disable_room(&room_id, true)?;
-			}
 		}
 
 		if disable_federation {
-			return Ok(RoomMessageEventContent::text_plain(format!(
-				"Finished bulk room ban, banned {room_ban_count} total rooms, evicted all users, and disabled \
-				 incoming federation with the room."
-			)));
+			services().rooms.metadata.disable_room(&room_id, true)?;
 		}
-		return Ok(RoomMessageEventContent::text_plain(format!(
-			"Finished bulk room ban, banned {room_ban_count} total rooms and evicted all users."
-		)));
 	}
 
-	Ok(RoomMessageEventContent::text_plain(
-		"Expected code block in command body. Add --help for details.",
-	))
+	if disable_federation {
+		Ok(RoomMessageEventContent::text_plain(format!(
+			"Finished bulk room ban, banned {room_ban_count} total rooms, evicted all users, and disabled incoming \
+			 federation with the room."
+		)))
+	} else {
+		Ok(RoomMessageEventContent::text_plain(format!(
+			"Finished bulk room ban, banned {room_ban_count} total rooms and evicted all users."
+		)))
+	}
 }
 
 async fn unban_room(
@@ -481,26 +476,51 @@ async fn list_banned_rooms(_body: Vec<&str>) -> Result<RoomMessageEventContent>
 
 	match rooms {
 		Ok(room_ids) => {
-			// TODO: add room name from our state cache if available, default to the room ID
-			// as the room name if we dont have it TODO: do same if we have a room alias for
-			// this
-			let plain_list = room_ids.iter().fold(String::new(), |mut output, room_id| {
-				writeln!(output, "- `{room_id}`").unwrap();
-				output
-			});
-
-			let html_list = room_ids.iter().fold(String::new(), |mut output, room_id| {
-				writeln!(output, "<li><code>{}</code></li>", escape_html(room_id.as_ref())).unwrap();
-				output
-			});
-
-			let plain = format!("Rooms:\n{plain_list}");
-			let html = format!("Rooms:\n<ul>{html_list}</ul>");
-			Ok(RoomMessageEventContent::text_html(plain, html))
+			if room_ids.is_empty() {
+				return Ok(RoomMessageEventContent::text_plain("No rooms are banned."));
+			}
+
+			let mut rooms = room_ids
+				.into_iter()
+				.map(|room_id| get_room_info(&room_id))
+				.collect::<Vec<_>>();
+			rooms.sort_by_key(|r| r.1);
+			rooms.reverse();
+
+			let output_plain = format!(
+				"Rooms Banned ({}):\n```\n{}```",
+				rooms.len(),
+				rooms
+					.iter()
+					.map(|(id, members, name)| format!("{id}\tMembers: {members}\tName: {name}"))
+					.collect::<Vec<_>>()
+					.join("\n")
+			);
+
+			let output_html = format!(
+				"<table><caption>Rooms Banned ({}) \
+				 </caption>\n<tr><th>id</th>\t<th>members</th>\t<th>name</th></tr>\n{}</table>",
+				rooms.len(),
+				rooms
+					.iter()
+					.fold(String::new(), |mut output, (id, members, name)| {
+						writeln!(
+							output,
+							"<tr><td>{}</td>\t<td>{}</td>\t<td>{}</td></tr>",
+							id,
+							members,
+							escape_html(name.as_ref())
+						)
+						.expect("should be able to write to string buffer");
+						output
+					})
+			);
+
+			Ok(RoomMessageEventContent::text_html(output_plain, output_html))
 		},
 		Err(e) => {
 			error!("Failed to list banned rooms: {}", e);
-			Ok(RoomMessageEventContent::text_plain(format!("Unable to list room aliases: {e}")))
+			Ok(RoomMessageEventContent::text_plain(format!("Unable to list banned rooms: {e}")))
 		},
 	}
 }
diff --git a/src/admin/user/mod.rs b/src/admin/user/mod.rs
index 031dd97a862bdd3e7ab814b44459fbbbdea7d36c..e11429deb2c0edf7f46445d39dbe8ffa265c9755 100644
--- a/src/admin/user/mod.rs
+++ b/src/admin/user/mod.rs
@@ -1,7 +1,8 @@
 pub(crate) mod user_commands;
 
 use clap::Subcommand;
-use ruma::events::room::message::RoomMessageEventContent;
+use ruma::{events::room::message::RoomMessageEventContent, RoomId};
+use user_commands::{delete_room_tag, get_room_tags, put_room_tag};
 
 use self::user_commands::{create, deactivate, deactivate_all, list, list_joined_rooms, reset_password};
 use crate::Result;
@@ -25,11 +26,11 @@ pub(crate) enum UserCommand {
 
 	/// - Deactivate a user
 	///
-	/// User will not be removed from all rooms by default.
-	/// Use --leave-rooms to force the user to leave all rooms
+	/// User will be removed from all rooms by default.
+	/// Use --no-leave-rooms to not leave all rooms by default.
 	Deactivate {
 		#[arg(short, long)]
-		leave_rooms: bool,
+		no_leave_rooms: bool,
 		user_id: String,
 	},
 
@@ -37,8 +38,10 @@ pub(crate) enum UserCommand {
 	///
 	/// Recommended to use in conjunction with list-local-users.
 	///
-	/// Users will not be removed from joined rooms by default.
-	/// Can be overridden with --leave-rooms OR the --force flag.
+	/// Users will be removed from joined rooms by default.
+	///
+	/// Can be overridden with --no-leave-rooms.
+	///
 	/// Removing a mass amount of users from a room may cause a significant
 	/// amount of leave events. The time to leave rooms may depend significantly
 	/// on joined rooms and servers.
@@ -48,7 +51,7 @@ pub(crate) enum UserCommand {
 	DeactivateAll {
 		#[arg(short, long)]
 		/// Remove users from their joined rooms
-		leave_rooms: bool,
+		no_leave_rooms: bool,
 		#[arg(short, long)]
 		/// Also deactivate admin accounts and will assume leave all rooms too
 		force: bool,
@@ -62,6 +65,32 @@ pub(crate) enum UserCommand {
 	ListJoinedRooms {
 		user_id: String,
 	},
+
+	/// - Puts a room tag for the specified user and room ID.
+	///
+	/// This is primarily useful if you'd like to set your admin room
+	/// to the special "System Alerts" section in Element as a way to
+	/// permanently see your admin room without it being buried away in your
+	/// favourites or rooms. To do this, you would pass your user, your admin
+	/// room's internal ID, and the tag name `m.server_notice`.
+	PutRoomTag {
+		user_id: String,
+		room_id: Box<RoomId>,
+		tag: String,
+	},
+
+	/// - Deletes the room tag for the specified user and room ID
+	DeleteRoomTag {
+		user_id: String,
+		room_id: Box<RoomId>,
+		tag: String,
+	},
+
+	/// - Gets all the room tags for the specified user and room ID
+	GetRoomTags {
+		user_id: String,
+		room_id: Box<RoomId>,
+	},
 }
 
 pub(crate) async fn process(command: UserCommand, body: Vec<&str>) -> Result<RoomMessageEventContent> {
@@ -72,18 +101,32 @@ pub(crate) async fn process(command: UserCommand, body: Vec<&str>) -> Result<Roo
 			password,
 		} => create(body, username, password).await?,
 		UserCommand::Deactivate {
-			leave_rooms,
+			no_leave_rooms,
 			user_id,
-		} => deactivate(body, leave_rooms, user_id).await?,
+		} => deactivate(body, no_leave_rooms, user_id).await?,
 		UserCommand::ResetPassword {
 			username,
 		} => reset_password(body, username).await?,
 		UserCommand::DeactivateAll {
-			leave_rooms,
+			no_leave_rooms,
 			force,
-		} => deactivate_all(body, leave_rooms, force).await?,
+		} => deactivate_all(body, no_leave_rooms, force).await?,
 		UserCommand::ListJoinedRooms {
 			user_id,
 		} => list_joined_rooms(body, user_id).await?,
+		UserCommand::PutRoomTag {
+			user_id,
+			room_id,
+			tag,
+		} => put_room_tag(body, user_id, room_id, tag).await?,
+		UserCommand::DeleteRoomTag {
+			user_id,
+			room_id,
+			tag,
+		} => delete_room_tag(body, user_id, room_id, tag).await?,
+		UserCommand::GetRoomTags {
+			user_id,
+			room_id,
+		} => get_room_tags(body, user_id, room_id).await?,
 	})
 }
diff --git a/src/admin/user/user_commands.rs b/src/admin/user/user_commands.rs
index 24f30931384428805b198484a382f9ae150fbd47..8b90894e940f2881a7f930b9247ae3a92c975647 100644
--- a/src/admin/user/user_commands.rs
+++ b/src/admin/user/user_commands.rs
@@ -1,20 +1,36 @@
-use std::{fmt::Write as _, sync::Arc};
+use std::{collections::BTreeMap, fmt::Write as _};
 
 use api::client::{join_room_by_id_helper, leave_all_rooms};
 use conduit::utils;
-use ruma::{events::room::message::RoomMessageEventContent, OwnedRoomId, UserId};
+use ruma::{
+	events::{
+		room::message::RoomMessageEventContent,
+		tag::{TagEvent, TagEventContent, TagInfo},
+		RoomAccountDataEventType,
+	},
+	OwnedRoomId, OwnedUserId, RoomId, UserId,
+};
 use tracing::{error, info, warn};
 
-use crate::{escape_html, get_room_info, services, user_is_local, Result};
+use crate::{
+	escape_html, get_room_info, services,
+	utils::{parse_active_local_user_id, parse_local_user_id},
+	Result,
+};
 
 const AUTO_GEN_PASSWORD_LENGTH: usize = 25;
 
 pub(crate) async fn list(_body: Vec<&str>) -> Result<RoomMessageEventContent> {
 	match services().users.list_local_users() {
 		Ok(users) => {
-			let mut msg = format!("Found {} local user account(s):\n", users.len());
-			msg += &users.join("\n");
-			Ok(RoomMessageEventContent::text_plain(&msg))
+			let mut plain_msg = format!("Found {} local user account(s):\n```\n", users.len());
+			plain_msg += &users.join("\n");
+			plain_msg += "\n```";
+
+			let mut html_msg = format!("<p>Found {} local user account(s):</p><pre><code>", users.len());
+			html_msg += &users.join("\n");
+			html_msg += "\n</code></pre>";
+			Ok(RoomMessageEventContent::text_html(&plain_msg, &html_msg))
 		},
 		Err(e) => Ok(RoomMessageEventContent::text_plain(e.to_string())),
 	}
@@ -23,34 +39,15 @@ pub(crate) async fn list(_body: Vec<&str>) -> Result<RoomMessageEventContent> {
 pub(crate) async fn create(
 	_body: Vec<&str>, username: String, password: Option<String>,
 ) -> Result<RoomMessageEventContent> {
-	let password = password.unwrap_or_else(|| utils::random_string(AUTO_GEN_PASSWORD_LENGTH));
-
 	// Validate user id
-	let user_id =
-		match UserId::parse_with_server_name(username.as_str().to_lowercase(), services().globals.server_name()) {
-			Ok(id) => id,
-			Err(e) => {
-				return Ok(RoomMessageEventContent::text_plain(format!(
-					"The supplied username is not a valid username: {e}"
-				)))
-			},
-		};
-
-	if !user_is_local(&user_id) {
-		return Ok(RoomMessageEventContent::text_plain(format!(
-			"User {user_id} does not belong to our server."
-		)));
-	}
-
-	if user_id.is_historical() {
-		return Ok(RoomMessageEventContent::text_plain(format!(
-			"Userid {user_id} is not allowed due to historical"
-		)));
-	}
+	let user_id = parse_local_user_id(&username)?;
 
 	if services().users.exists(&user_id)? {
 		return Ok(RoomMessageEventContent::text_plain(format!("Userid {user_id} already exists")));
 	}
+
+	let password = password.unwrap_or_else(|| utils::random_string(AUTO_GEN_PASSWORD_LENGTH));
+
 	// Create user
 	services().users.create(&user_id, Some(password.as_str()))?;
 
@@ -131,79 +128,46 @@ pub(crate) async fn create(
 }
 
 pub(crate) async fn deactivate(
-	_body: Vec<&str>, leave_rooms: bool, user_id: String,
+	_body: Vec<&str>, no_leave_rooms: bool, user_id: String,
 ) -> Result<RoomMessageEventContent> {
 	// Validate user id
-	let user_id =
-		match UserId::parse_with_server_name(user_id.as_str().to_lowercase(), services().globals.server_name()) {
-			Ok(id) => Arc::<UserId>::from(id),
-			Err(e) => {
-				return Ok(RoomMessageEventContent::text_plain(format!(
-					"The supplied username is not a valid username: {e}"
-				)))
-			},
-		};
-
-	// check if user belongs to our server
-	if user_id.server_name() != services().globals.server_name() {
-		return Ok(RoomMessageEventContent::text_plain(format!(
-			"User {user_id} does not belong to our server."
-		)));
-	}
+	let user_id = parse_local_user_id(&user_id)?;
 
-	// don't deactivate the conduit service account
+	// don't deactivate the server service account
 	if user_id
 		== UserId::parse_with_server_name("conduit", services().globals.server_name()).expect("conduit user exists")
 	{
 		return Ok(RoomMessageEventContent::text_plain(
-			"Not allowed to deactivate the Conduit service account.",
+			"Not allowed to deactivate the server service account.",
 		));
 	}
 
-	if services().users.exists(&user_id)? {
-		RoomMessageEventContent::text_plain(format!("Making {user_id} leave all rooms before deactivation..."));
-
-		services().users.deactivate_account(&user_id)?;
-
-		if leave_rooms {
-			leave_all_rooms(&user_id).await;
-		}
+	services().users.deactivate_account(&user_id)?;
 
-		Ok(RoomMessageEventContent::text_plain(format!(
-			"User {user_id} has been deactivated"
-		)))
-	} else {
-		Ok(RoomMessageEventContent::text_plain(format!(
-			"User {user_id} doesn't exist on this server"
-		)))
+	if !no_leave_rooms {
+		services()
+			.admin
+			.send_message(RoomMessageEventContent::text_plain(format!(
+				"Making {user_id} leave all rooms after deactivation..."
+			)))
+			.await;
+		leave_all_rooms(&user_id).await;
 	}
+
+	Ok(RoomMessageEventContent::text_plain(format!(
+		"User {user_id} has been deactivated"
+	)))
 }
 
 pub(crate) async fn reset_password(_body: Vec<&str>, username: String) -> Result<RoomMessageEventContent> {
-	// Validate user id
-	let user_id =
-		match UserId::parse_with_server_name(username.as_str().to_lowercase(), services().globals.server_name()) {
-			Ok(id) => Arc::<UserId>::from(id),
-			Err(e) => {
-				return Ok(RoomMessageEventContent::text_plain(format!(
-					"The supplied username is not a valid username: {e}"
-				)))
-			},
-		};
+	let user_id = parse_local_user_id(&username)?;
 
-	// check if user belongs to our server
-	if user_id.server_name() != services().globals.server_name() {
-		return Ok(RoomMessageEventContent::text_plain(format!(
-			"User {user_id} does not belong to our server."
-		)));
-	}
-
-	// Check if the specified user is valid
-	if !services().users.exists(&user_id)?
-		|| user_id
-			== UserId::parse_with_server_name("conduit", services().globals.server_name()).expect("conduit user exists")
+	if user_id
+		== UserId::parse_with_server_name("conduit", services().globals.server_name()).expect("conduit user exists")
 	{
-		return Ok(RoomMessageEventContent::text_plain("The specified user does not exist!"));
+		return Ok(RoomMessageEventContent::text_plain(
+			"Not allowed to set the password for the server account. Please use the emergency password config option.",
+		));
 	}
 
 	let new_password = utils::random_string(AUTO_GEN_PASSWORD_LENGTH);
@@ -221,107 +185,98 @@ pub(crate) async fn reset_password(_body: Vec<&str>, username: String) -> Result
 	}
 }
 
-pub(crate) async fn deactivate_all(body: Vec<&str>, leave_rooms: bool, force: bool) -> Result<RoomMessageEventContent> {
-	if body.len() > 2 && body[0].trim().starts_with("```") && body.last().unwrap().trim() == "```" {
-		let usernames = body.clone().drain(1..body.len() - 1).collect::<Vec<_>>();
-
-		let mut user_ids: Vec<&UserId> = Vec::new();
+pub(crate) async fn deactivate_all(
+	body: Vec<&str>, no_leave_rooms: bool, force: bool,
+) -> Result<RoomMessageEventContent> {
+	if body.len() < 2 || !body[0].trim().starts_with("```") || body.last().unwrap_or(&"").trim() != "```" {
+		return Ok(RoomMessageEventContent::text_plain(
+			"Expected code block in command body. Add --help for details.",
+		));
+	}
 
-		for &username in &usernames {
-			match <&UserId>::try_from(username) {
-				Ok(user_id) => user_ids.push(user_id),
-				Err(e) => {
-					return Ok(RoomMessageEventContent::text_plain(format!(
-						"{username} is not a valid username: {e}"
+	let usernames = body.clone().drain(1..body.len() - 1).collect::<Vec<_>>();
+
+	let mut user_ids: Vec<OwnedUserId> = Vec::with_capacity(usernames.len());
+	let mut admins = Vec::new();
+
+	for username in usernames {
+		match parse_active_local_user_id(username) {
+			Ok(user_id) => {
+				if services().users.is_admin(&user_id)? && !force {
+					services()
+						.admin
+						.send_message(RoomMessageEventContent::text_plain(format!(
+							"{username} is an admin and --force is not set, skipping over"
+						)))
+						.await;
+					admins.push(username);
+					continue;
+				}
+
+				// don't deactivate the server service account
+				if user_id
+					== UserId::parse_with_server_name("conduit", services().globals.server_name())
+						.expect("server user exists")
+				{
+					services()
+						.admin
+						.send_message(RoomMessageEventContent::text_plain(format!(
+							"{username} is the server service account, skipping over"
+						)))
+						.await;
+					continue;
+				}
+
+				user_ids.push(user_id);
+			},
+			Err(e) => {
+				services()
+					.admin
+					.send_message(RoomMessageEventContent::text_plain(format!(
+						"{username} is not a valid username, skipping over: {e}"
 					)))
-				},
-			}
-		}
-
-		let mut deactivation_count: usize = 0;
-		let mut admins = Vec::new();
-
-		if !force {
-			user_ids.retain(|&user_id| match services().users.is_admin(user_id) {
-				Ok(is_admin) => {
-					if is_admin {
-						admins.push(user_id.localpart());
-						false
-					} else {
-						true
-					}
-				},
-				Err(_) => false,
-			});
-		}
-
-		for &user_id in &user_ids {
-			// check if user belongs to our server and skips over non-local users
-			if user_id.server_name() != services().globals.server_name() {
-				continue;
-			}
-
-			// don't deactivate the conduit service account
-			if user_id
-				== UserId::parse_with_server_name("conduit", services().globals.server_name())
-					.expect("conduit user exists")
-			{
+					.await;
 				continue;
-			}
+			},
+		}
+	}
 
-			// user does not exist on our server
-			if !services().users.exists(user_id)? {
-				continue;
-			}
+	let mut deactivation_count: usize = 0;
 
-			if services().users.deactivate_account(user_id).is_ok() {
+	for user_id in user_ids {
+		match services().users.deactivate_account(&user_id) {
+			Ok(()) => {
 				deactivation_count = deactivation_count.saturating_add(1);
-			}
-		}
-
-		if leave_rooms || force {
-			for &user_id in &user_ids {
-				leave_all_rooms(user_id).await;
-			}
+				if !no_leave_rooms {
+					info!("Forcing user {user_id} to leave all rooms apart of deactivate-all");
+					leave_all_rooms(&user_id).await;
+				}
+			},
+			Err(e) => {
+				services()
+					.admin
+					.send_message(RoomMessageEventContent::text_plain(format!("Failed deactivating user: {e}")))
+					.await;
+			},
 		}
+	}
 
-		if admins.is_empty() {
-			Ok(RoomMessageEventContent::text_plain(format!(
-				"Deactivated {deactivation_count} accounts."
-			)))
-		} else {
-			Ok(RoomMessageEventContent::text_plain(format!(
-				"Deactivated {} accounts.\nSkipped admin accounts: {}. Use --force to deactivate admin accounts",
-				deactivation_count,
-				admins.join(", ")
-			)))
-		}
+	if admins.is_empty() {
+		Ok(RoomMessageEventContent::text_plain(format!(
+			"Deactivated {deactivation_count} accounts."
+		)))
 	} else {
-		Ok(RoomMessageEventContent::text_plain(
-			"Expected code block in command body. Add --help for details.",
-		))
+		Ok(RoomMessageEventContent::text_plain(format!(
+			"Deactivated {deactivation_count} accounts.\nSkipped admin accounts: {}. Use --force to deactivate admin \
+			 accounts",
+			admins.join(", ")
+		)))
 	}
 }
 
 pub(crate) async fn list_joined_rooms(_body: Vec<&str>, user_id: String) -> Result<RoomMessageEventContent> {
 	// Validate user id
-	let user_id =
-		match UserId::parse_with_server_name(user_id.as_str().to_lowercase(), services().globals.server_name()) {
-			Ok(id) => Arc::<UserId>::from(id),
-			Err(e) => {
-				return Ok(RoomMessageEventContent::text_plain(format!(
-					"The supplied username is not a valid username: {e}"
-				)))
-			},
-		};
-
-	if !user_is_local(&user_id) {
-		return Ok(RoomMessageEventContent::text_plain("User does not belong to our server."));
-	}
-
-	if !services().users.exists(&user_id)? {
-		return Ok(RoomMessageEventContent::text_plain("User does not exist on this server."));
-	}
+	let user_id = parse_local_user_id(&user_id)?;
 
 	let mut rooms: Vec<(OwnedRoomId, u64, String)> = services()
 		.rooms
@@ -347,6 +302,7 @@ pub(crate) async fn list_joined_rooms(_body: Vec<&str>, user_id: String) -> Resu
 			.collect::<Vec<_>>()
 			.join("\n")
 	);
+
 	let output_html = format!(
 		"<table><caption>Rooms {user_id} Joined \
 		 ({})</caption>\n<tr><th>id</th>\t<th>members</th>\t<th>name</th></tr>\n{}</table>",
@@ -365,5 +321,97 @@ pub(crate) async fn list_joined_rooms(_body: Vec<&str>, user_id: String) -> Resu
 				output
 			})
 	);
+
 	Ok(RoomMessageEventContent::text_html(output_plain, output_html))
 }
+
+pub(crate) async fn put_room_tag(
+	_body: Vec<&str>, user_id: String, room_id: Box<RoomId>, tag: String,
+) -> Result<RoomMessageEventContent> {
+	let user_id = parse_active_local_user_id(&user_id)?;
+
+	let event = services()
+		.account_data
+		.get(Some(&room_id), &user_id, RoomAccountDataEventType::Tag)?;
+
+	let mut tags_event = event.map_or_else(
+		|| TagEvent {
+			content: TagEventContent {
+				tags: BTreeMap::new(),
+			},
+		},
+		|e| serde_json::from_str(e.get()).expect("Bad account data in database for user {user_id}"),
+	);
+
+	tags_event
+		.content
+		.tags
+		.insert(tag.clone().into(), TagInfo::new());
+
+	services().account_data.update(
+		Some(&room_id),
+		&user_id,
+		RoomAccountDataEventType::Tag,
+		&serde_json::to_value(tags_event).expect("to json value always works"),
+	)?;
+
+	Ok(RoomMessageEventContent::text_plain(format!(
+		"Successfully updated room account data for {user_id} and room {room_id} with tag {tag}"
+	)))
+}
+
+pub(crate) async fn delete_room_tag(
+	_body: Vec<&str>, user_id: String, room_id: Box<RoomId>, tag: String,
+) -> Result<RoomMessageEventContent> {
+	let user_id = parse_active_local_user_id(&user_id)?;
+
+	let event = services()
+		.account_data
+		.get(Some(&room_id), &user_id, RoomAccountDataEventType::Tag)?;
+
+	let mut tags_event = event.map_or_else(
+		|| TagEvent {
+			content: TagEventContent {
+				tags: BTreeMap::new(),
+			},
+		},
+		|e| serde_json::from_str(e.get()).expect("Bad account data in database for user {user_id}"),
+	);
+
+	tags_event.content.tags.remove(&tag.clone().into());
+
+	services().account_data.update(
+		Some(&room_id),
+		&user_id,
+		RoomAccountDataEventType::Tag,
+		&serde_json::to_value(tags_event).expect("to json value always works"),
+	)?;
+
+	Ok(RoomMessageEventContent::text_plain(format!(
+		"Successfully updated room account data for {user_id} and room {room_id}, deleting room tag {tag}"
+	)))
+}
+
+pub(crate) async fn get_room_tags(
+	_body: Vec<&str>, user_id: String, room_id: Box<RoomId>,
+) -> Result<RoomMessageEventContent> {
+	let user_id = parse_active_local_user_id(&user_id)?;
+
+	let event = services()
+		.account_data
+		.get(Some(&room_id), &user_id, RoomAccountDataEventType::Tag)?;
+
+	let tags_event = event.map_or_else(
+		|| TagEvent {
+			content: TagEventContent {
+				tags: BTreeMap::new(),
+			},
+		},
+		|e| serde_json::from_str(e.get()).expect("Bad account data in database for user {user_id}"),
+	);
+
+	Ok(RoomMessageEventContent::text_html(
+		format!("<pre><code>\n{:?}\n</code></pre>", tags_event.content.tags),
+		format!("```\n{:?}\n```", tags_event.content.tags),
+	))
+}
diff --git a/src/admin/utils.rs b/src/admin/utils.rs
index 6e65f2b21e9641ba2c0784cb7a3c2a25862420fc..4c30e5a8b646308d69d1c76998f2a6ed12d1cf05 100644
--- a/src/admin/utils.rs
+++ b/src/admin/utils.rs
@@ -1,7 +1,9 @@
 pub(crate) use conduit::utils::HtmlEscape;
-use ruma::{OwnedRoomId, RoomId};
+use conduit_core::Error;
+use ruma::{OwnedRoomId, OwnedUserId, RoomId, UserId};
+use service::user_is_local;
 
-use crate::services;
+use crate::{services, Result};
 
 pub(crate) fn escape_html(s: &str) -> String {
 	s.replace('&', "&amp;")
@@ -28,3 +30,35 @@ pub(crate) fn get_room_info(id: &RoomId) -> (OwnedRoomId, u64, String) {
 			.unwrap_or_else(|| id.to_string()),
 	)
 }
+
+/// Parses user ID
+pub(crate) fn parse_user_id(user_id: &str) -> Result<OwnedUserId> {
+	UserId::parse_with_server_name(user_id.to_lowercase(), services().globals.server_name())
+		.map_err(|e| Error::Err(format!("The supplied username is not a valid username: {e}")))
+}
+
+/// Parses user ID as our local user
+pub(crate) fn parse_local_user_id(user_id: &str) -> Result<OwnedUserId> {
+	let user_id = parse_user_id(user_id)?;
+
+	if !user_is_local(&user_id) {
+		return Err(Error::Err(String::from("User does not belong to our server.")));
+	}
+
+	Ok(user_id)
+}
+
+/// Parses user ID that is an active (not guest or deactivated) local user
+pub(crate) fn parse_active_local_user_id(user_id: &str) -> Result<OwnedUserId> {
+	let user_id = parse_local_user_id(user_id)?;
+
+	if !services().users.exists(&user_id)? {
+		return Err(Error::Err(String::from("User does not exist on this server.")));
+	}
+
+	if services().users.is_deactivated(&user_id)? {
+		return Err(Error::Err(String::from("User is deactivated.")));
+	}
+
+	Ok(user_id)
+}
diff --git a/src/api/client/account.rs b/src/api/client/account.rs
index 72830f9d4f510bce34c71f1e4dae7a8ad01c9c25..dc384c4cfeaf2c40858e51748da255d66ae20ac8 100644
--- a/src/api/client/account.rs
+++ b/src/api/client/account.rs
@@ -1,5 +1,6 @@
 use std::fmt::Write;
 
+use axum_client_ip::InsecureClientIp;
 use conduit::debug_info;
 use register::RegistrationKind;
 use ruma::{
@@ -39,8 +40,9 @@
 ///
 /// Note: This will not reserve the username, so the username might become
 /// invalid when trying to register
+#[tracing::instrument(skip_all, fields(%client_ip))]
 pub(crate) async fn get_register_available_route(
-	body: Ruma<get_username_availability::v3::Request>,
+	InsecureClientIp(client_ip): InsecureClientIp, body: Ruma<get_username_availability::v3::Request>,
 ) -> Result<get_username_availability::v3::Response> {
 	// Validate user id
 	let user_id = UserId::parse_with_server_name(body.username.to_lowercase(), services().globals.server_name())
@@ -87,7 +89,10 @@ pub(crate) async fn get_register_available_route(
 /// - If `inhibit_login` is false: Creates a device and returns device id and
 ///   access_token
 #[allow(clippy::doc_markdown)]
-pub(crate) async fn register_route(body: Ruma<register::v3::Request>) -> Result<register::v3::Response> {
+#[tracing::instrument(skip_all, fields(%client_ip))]
+pub(crate) async fn register_route(
+	InsecureClientIp(client_ip): InsecureClientIp, body: Ruma<register::v3::Request>,
+) -> Result<register::v3::Response> {
 	if !services().globals.allow_registration() && body.appservice_info.is_none() {
 		info!(
 			"Registration disabled and request not from known appservice, rejecting registration attempt for username \
@@ -104,8 +109,8 @@ pub(crate) async fn register_route(body: Ruma<register::v3::Request>) -> Result<
 			|| (services().globals.allow_registration() && services().globals.config.registration_token.is_some()))
 	{
 		info!(
-			"Guest registration disabled / registration enabled with token configured, rejecting guest registration, \
-			 initial device name: {:?}",
+			"Guest registration disabled / registration enabled with token configured, rejecting guest registration \
+			 attempt, initial device name: {:?}",
 			body.initial_device_display_name
 		);
 		return Err(Error::BadRequest(
@@ -297,14 +302,14 @@ pub(crate) async fn register_route(body: Ruma<register::v3::Request>) -> Result<
 		services()
 			.admin
 			.send_message(RoomMessageEventContent::notice_plain(format!(
-				"New user \"{user_id}\" registered on this server."
+				"New user \"{user_id}\" registered on this server from IP {client_ip}."
 			)))
 			.await;
 	}
 
 	// log in conduit admin channel if a guest registered
 	if body.appservice_info.is_none() && is_guest && services().globals.log_guest_registrations() {
-		info!("New guest user \"{user_id}\" registered on this server.");
+		info!("New guest user \"{user_id}\" registered on this server from IP.");
 
 		if let Some(device_display_name) = &body.initial_device_display_name {
 			if body
@@ -316,14 +321,15 @@ pub(crate) async fn register_route(body: Ruma<register::v3::Request>) -> Result<
 					.admin
 					.send_message(RoomMessageEventContent::notice_plain(format!(
 						"Guest user \"{user_id}\" with device display name `{device_display_name}` registered on this \
-						 server."
+						 server from IP {client_ip}."
 					)))
 					.await;
 			} else {
 				services()
 					.admin
 					.send_message(RoomMessageEventContent::notice_plain(format!(
-						"Guest user \"{user_id}\" with no device display name registered on this server.",
+						"Guest user \"{user_id}\" with no device display name registered on this server from IP \
+						 {client_ip}.",
 					)))
 					.await;
 			}
@@ -331,7 +337,8 @@ pub(crate) async fn register_route(body: Ruma<register::v3::Request>) -> Result<
 			services()
 				.admin
 				.send_message(RoomMessageEventContent::notice_plain(format!(
-					"Guest user \"{user_id}\" with no device display name registered on this server.",
+					"Guest user \"{user_id}\" with no device display name registered on this server from IP \
+					 {client_ip}.",
 				)))
 				.await;
 		}
@@ -352,7 +359,7 @@ pub(crate) async fn register_route(body: Ruma<register::v3::Request>) -> Result<
 					.make_user_admin(&user_id, displayname)
 					.await?;
 
-				warn!("Granting {} admin privileges as the first user", user_id);
+				warn!("Granting {user_id} admin privileges as the first user");
 			}
 		}
 	}
@@ -416,8 +423,9 @@ pub(crate) async fn register_route(body: Ruma<register::v3::Request>) -> Result<
 ///   last seen ts)
 /// - Forgets to-device events
 /// - Triggers device list updates
+#[tracing::instrument(skip_all, fields(%client_ip))]
 pub(crate) async fn change_password_route(
-	body: Ruma<change_password::v3::Request>,
+	InsecureClientIp(client_ip): InsecureClientIp, body: Ruma<change_password::v3::Request>,
 ) -> Result<change_password::v3::Response> {
 	let sender_user = body.sender_user.as_ref().expect("user is authenticated");
 	let sender_device = body.sender_device.as_ref().expect("user is authenticated");
@@ -466,7 +474,7 @@ pub(crate) async fn change_password_route(
 		}
 	}
 
-	info!("User {} changed their password.", sender_user);
+	info!("User {sender_user} changed their password.");
 	services()
 		.admin
 		.send_message(RoomMessageEventContent::notice_plain(format!(
@@ -504,7 +512,10 @@ pub(crate) async fn whoami_route(body: Ruma<whoami::v3::Request>) -> Result<whoa
 /// - Forgets all to-device events
 /// - Triggers device list updates
 /// - Removes ability to log in again
-pub(crate) async fn deactivate_route(body: Ruma<deactivate::v3::Request>) -> Result<deactivate::v3::Response> {
+#[tracing::instrument(skip_all, fields(%client_ip))]
+pub(crate) async fn deactivate_route(
+	InsecureClientIp(client_ip): InsecureClientIp, body: Ruma<deactivate::v3::Request>,
+) -> Result<deactivate::v3::Response> {
 	let sender_user = body.sender_user.as_ref().expect("user is authenticated");
 	let sender_device = body.sender_device.as_ref().expect("user is authenticated");
 
@@ -542,7 +553,7 @@ pub(crate) async fn deactivate_route(body: Ruma<deactivate::v3::Request>) -> Res
 	// Remove devices and mark account as deactivated
 	services().users.deactivate_account(sender_user)?;
 
-	info!("User {} deactivated their account.", sender_user);
+	info!("User {sender_user} deactivated their account.");
 	services()
 		.admin
 		.send_message(RoomMessageEventContent::notice_plain(format!(
diff --git a/src/api/client/directory.rs b/src/api/client/directory.rs
index 0e5b8afa9f94fbfc4de7baaf7c8e247cfb41c110..20f0ccca998d265b2221b52b277b05f6f3b8bbb0 100644
--- a/src/api/client/directory.rs
+++ b/src/api/client/directory.rs
@@ -1,3 +1,4 @@
+use axum_client_ip::InsecureClientIp;
 use ruma::{
 	api::{
 		client::{
@@ -11,11 +12,8 @@
 	events::{
 		room::{
 			avatar::RoomAvatarEventContent,
-			canonical_alias::RoomCanonicalAliasEventContent,
 			create::RoomCreateEventContent,
-			guest_access::{GuestAccess, RoomGuestAccessEventContent},
 			join_rules::{JoinRule, RoomJoinRulesEventContent},
-			topic::RoomTopicEventContent,
 		},
 		StateEventType,
 	},
@@ -30,8 +28,9 @@
 /// Lists the public rooms on this server.
 ///
 /// - Rooms are ordered by the number of joined members
+#[tracing::instrument(skip_all, fields(%client_ip))]
 pub(crate) async fn get_public_rooms_filtered_route(
-	body: Ruma<get_public_rooms_filtered::v3::Request>,
+	InsecureClientIp(client_ip): InsecureClientIp, body: Ruma<get_public_rooms_filtered::v3::Request>,
 ) -> Result<get_public_rooms_filtered::v3::Response> {
 	if let Some(server) = &body.server {
 		if services()
@@ -55,8 +54,8 @@ pub(crate) async fn get_public_rooms_filtered_route(
 	)
 	.await
 	.map_err(|e| {
-		warn!("Failed to return our /publicRooms: {e}");
-		Error::BadRequest(ErrorKind::Unknown, "Failed to return this server's public room list.")
+		warn!(?body.server, "Failed to return /publicRooms: {e}");
+		Error::BadRequest(ErrorKind::Unknown, "Failed to return the requested server's public room list.")
 	})?;
 
 	Ok(response)
@@ -67,8 +66,9 @@ pub(crate) async fn get_public_rooms_filtered_route(
 /// Lists the public rooms on this server.
 ///
 /// - Rooms are ordered by the number of joined members
+#[tracing::instrument(skip_all, fields(%client_ip))]
 pub(crate) async fn get_public_rooms_route(
-	body: Ruma<get_public_rooms::v3::Request>,
+	InsecureClientIp(client_ip): InsecureClientIp, body: Ruma<get_public_rooms::v3::Request>,
 ) -> Result<get_public_rooms::v3::Response> {
 	if let Some(server) = &body.server {
 		if services()
@@ -92,8 +92,8 @@ pub(crate) async fn get_public_rooms_route(
 	)
 	.await
 	.map_err(|e| {
-		warn!("Failed to return our /publicRooms: {e}");
-		Error::BadRequest(ErrorKind::Unknown, "Failed to return this server's public room list.")
+		warn!(?body.server, "Failed to return /publicRooms: {e}");
+		Error::BadRequest(ErrorKind::Unknown, "Failed to return the requested server's public room list.")
 	})?;
 
 	Ok(get_public_rooms::v3::Response {
@@ -109,8 +109,9 @@ pub(crate) async fn get_public_rooms_route(
 /// Sets the visibility of a given room in the room directory.
 ///
 /// - TODO: Access control checks
+#[tracing::instrument(skip_all, fields(%client_ip))]
 pub(crate) async fn set_room_visibility_route(
-	body: Ruma<set_room_visibility::v3::Request>,
+	InsecureClientIp(client_ip): InsecureClientIp, body: Ruma<set_room_visibility::v3::Request>,
 ) -> Result<set_room_visibility::v3::Response> {
 	let sender_user = body.sender_user.as_ref().expect("user is authenticated");
 
@@ -230,12 +231,7 @@ pub(crate) async fn get_public_rooms_filtered_helper(
 				canonical_alias: services()
 					.rooms
 					.state_accessor
-					.room_state_get(&room_id, &StateEventType::RoomCanonicalAlias, "")?
-					.map_or(Ok(None), |s| {
-						serde_json::from_str(s.content.get())
-							.map(|c: RoomCanonicalAliasEventContent| c.alias)
-							.map_err(|_| Error::bad_database("Invalid canonical alias event in database."))
-					})?,
+					.get_canonical_alias(&room_id)?,
 				name: services().rooms.state_accessor.get_name(&room_id)?,
 				num_joined_members: services()
 					.rooms
@@ -250,26 +246,13 @@ pub(crate) async fn get_public_rooms_filtered_helper(
 				topic: services()
 					.rooms
 					.state_accessor
-					.room_state_get(&room_id, &StateEventType::RoomTopic, "")?
-					.map_or(Ok(None), |s| {
-						serde_json::from_str(s.content.get())
-							.map(|c: RoomTopicEventContent| Some(c.topic))
-							.map_err(|e| {
-								error!("Invalid room topic event in database for room {room_id}: {e}");
-								Error::bad_database("Invalid room topic event in database.")
-							})
-					})
+					.get_room_topic(&room_id)
 					.unwrap_or(None),
 				world_readable: services().rooms.state_accessor.is_world_readable(&room_id)?,
 				guest_can_join: services()
 					.rooms
 					.state_accessor
-					.room_state_get(&room_id, &StateEventType::RoomGuestAccess, "")?
-					.map_or(Ok(false), |s| {
-						serde_json::from_str(s.content.get())
-							.map(|c: RoomGuestAccessEventContent| c.guest_access == GuestAccess::CanJoin)
-							.map_err(|_| Error::bad_database("Invalid room guest access event in database."))
-					})?,
+					.guest_can_join(&room_id)?,
 				avatar_url: services()
 					.rooms
 					.state_accessor
diff --git a/src/api/client/membership.rs b/src/api/client/membership.rs
index 2d04be2f2262f638420ed044d5a8f45276de4f41..79ea50bd10bbe9027defa55b0604b588cf312c88 100644
--- a/src/api/client/membership.rs
+++ b/src/api/client/membership.rs
@@ -5,6 +5,7 @@
 	time::{Duration, Instant},
 };
 
+use axum_client_ip::InsecureClientIp;
 use ruma::{
 	api::{
 		client::{
@@ -141,8 +142,9 @@ async fn banned_room_check(user_id: &UserId, room_id: Option<&RoomId>, server_na
 ///   rules locally
 /// - If the server does not know about the room: asks other servers over
 ///   federation
+#[tracing::instrument(skip_all, fields(%client_ip))]
 pub(crate) async fn join_room_by_id_route(
-	body: Ruma<join_room_by_id::v3::Request>,
+	InsecureClientIp(client_ip): InsecureClientIp, body: Ruma<join_room_by_id::v3::Request>,
 ) -> Result<join_room_by_id::v3::Response> {
 	let sender_user = body.sender_user.as_ref().expect("user is authenticated");
 
@@ -193,8 +195,9 @@ pub(crate) async fn join_room_by_id_route(
 /// - If the server does not know about the room: use the server name query
 ///   param if specified. if not specified, asks other servers over federation
 ///   via room alias server name and room ID server name
+#[tracing::instrument(skip_all, fields(%client_ip))]
 pub(crate) async fn join_room_by_id_or_alias_route(
-	body: Ruma<join_room_by_id_or_alias::v3::Request>,
+	InsecureClientIp(client_ip): InsecureClientIp, body: Ruma<join_room_by_id_or_alias::v3::Request>,
 ) -> Result<join_room_by_id_or_alias::v3::Response> {
 	let sender_user = body.sender_user.as_deref().expect("user is authenticated");
 	let body = body.body;
@@ -295,7 +298,10 @@ pub(crate) async fn leave_room_route(body: Ruma<leave_room::v3::Request>) -> Res
 /// # `POST /_matrix/client/r0/rooms/{roomId}/invite`
 ///
 /// Tries to send an invite event into the room.
-pub(crate) async fn invite_user_route(body: Ruma<invite_user::v3::Request>) -> Result<invite_user::v3::Response> {
+#[tracing::instrument(skip_all, fields(%client_ip))]
+pub(crate) async fn invite_user_route(
+	InsecureClientIp(client_ip): InsecureClientIp, body: Ruma<invite_user::v3::Request>,
+) -> Result<invite_user::v3::Response> {
 	let sender_user = body.sender_user.as_ref().expect("user is authenticated");
 
 	if !services().users.is_admin(sender_user)? && services().globals.block_non_admin_invites() {
@@ -1314,7 +1320,7 @@ async fn make_join_request(
 	make_join_response_and_server
 }
 
-async fn validate_and_add_event_id(
+pub async fn validate_and_add_event_id(
 	pdu: &RawJsonValue, room_version: &RoomVersionId, pub_key_map: &RwLock<BTreeMap<String, BTreeMap<String, Base64>>>,
 ) -> Result<(OwnedEventId, CanonicalJsonObject)> {
 	let mut value: CanonicalJsonObject = serde_json::from_str(pdu.get()).map_err(|e| {
diff --git a/src/api/client/mod.rs b/src/api/client/mod.rs
index ced2bde528c8f8575d0dd1ff186db0a7b0eab4a3..6a6f16a75226039a76706250d6b46b59545e34d5 100644
--- a/src/api/client/mod.rs
+++ b/src/api/client/mod.rs
@@ -47,7 +47,7 @@
 pub(super) use keys::*;
 pub(super) use media::*;
 pub(super) use membership::*;
-pub use membership::{join_room_by_id_helper, leave_all_rooms, leave_room};
+pub use membership::{join_room_by_id_helper, leave_all_rooms, leave_room, validate_and_add_event_id};
 pub(super) use message::*;
 pub(super) use presence::*;
 pub(super) use profile::*;
diff --git a/src/api/server/invite.rs b/src/api/server/invite.rs
index 9ebc60ee41aaae84a5109ccde74f18e59e1d39cd..1b256b4a21bf8b4c233eef1f727497697c61ccb6 100644
--- a/src/api/server/invite.rs
+++ b/src/api/server/invite.rs
@@ -1,3 +1,4 @@
+use axum_client_ip::InsecureClientIp;
 use ruma::{
 	api::{client::error::ErrorKind, federation::membership::create_invite},
 	events::room::member::{MembershipState, RoomMemberEventContent},
@@ -16,7 +17,10 @@
 /// # `PUT /_matrix/federation/v2/invite/{roomId}/{eventId}`
 ///
 /// Invites a remote user to a room.
-pub(crate) async fn create_invite_route(body: Ruma<create_invite::v2::Request>) -> Result<create_invite::v2::Response> {
+#[tracing::instrument(skip_all, fields(%client_ip))]
+pub(crate) async fn create_invite_route(
+	InsecureClientIp(client_ip): InsecureClientIp, body: Ruma<create_invite::v2::Request>,
+) -> Result<create_invite::v2::Response> {
 	let origin = body.origin.as_ref().expect("server is authenticated");
 
 	// ACL check origin
diff --git a/src/api/server/publicrooms.rs b/src/api/server/publicrooms.rs
index 5a91f018e1291d04fdcac50409ded46d6ec33a03..6b5010e5206c6d07eae2694bbc7fe1cc917331a0 100644
--- a/src/api/server/publicrooms.rs
+++ b/src/api/server/publicrooms.rs
@@ -1,3 +1,4 @@
+use axum_client_ip::InsecureClientIp;
 use ruma::{
 	api::{
 		client::error::ErrorKind,
@@ -11,8 +12,9 @@
 /// # `POST /_matrix/federation/v1/publicRooms`
 ///
 /// Lists the public rooms on this server.
+#[tracing::instrument(skip_all, fields(%client_ip))]
 pub(crate) async fn get_public_rooms_filtered_route(
-	body: Ruma<get_public_rooms_filtered::v1::Request>,
+	InsecureClientIp(client_ip): InsecureClientIp, body: Ruma<get_public_rooms_filtered::v1::Request>,
 ) -> Result<get_public_rooms_filtered::v1::Response> {
 	if !services()
 		.globals
@@ -42,8 +44,9 @@ pub(crate) async fn get_public_rooms_filtered_route(
 /// # `GET /_matrix/federation/v1/publicRooms`
 ///
 /// Lists the public rooms on this server.
+#[tracing::instrument(skip_all, fields(%client_ip))]
 pub(crate) async fn get_public_rooms_route(
-	body: Ruma<get_public_rooms::v1::Request>,
+	InsecureClientIp(client_ip): InsecureClientIp, body: Ruma<get_public_rooms::v1::Request>,
 ) -> Result<get_public_rooms::v1::Response> {
 	if !services()
 		.globals
diff --git a/src/api/server/send.rs b/src/api/server/send.rs
index 592eb4f94f8d900b9beed4b2195956e6d9eff387..e2413a4abb6f949c5a8989a5b68c2a09a05adc10 100644
--- a/src/api/server/send.rs
+++ b/src/api/server/send.rs
@@ -1,5 +1,6 @@
 use std::{collections::BTreeMap, sync::Arc, time::Instant};
 
+use axum_client_ip::InsecureClientIp;
 use conduit::debug_warn;
 use ruma::{
 	api::{
@@ -25,8 +26,9 @@
 /// # `PUT /_matrix/federation/v1/send/{txnId}`
 ///
 /// Push EDUs and PDUs to this server.
+#[tracing::instrument(skip_all, fields(%client_ip))]
 pub(crate) async fn send_transaction_message_route(
-	body: Ruma<send_transaction_message::v1::Request>,
+	InsecureClientIp(client_ip): InsecureClientIp, body: Ruma<send_transaction_message::v1::Request>,
 ) -> Result<send_transaction_message::v1::Response> {
 	let origin = body.origin.as_ref().expect("server is authenticated");
 
diff --git a/src/core/utils/mod.rs b/src/core/utils/mod.rs
index f3a45e47c8dd08e751125a0cb62fa11dda8bf36c..9ffbbbd03dce3a767f3748993e6413e1d86e5062 100644
--- a/src/core/utils/mod.rs
+++ b/src/core/utils/mod.rs
@@ -7,8 +7,7 @@
 pub mod sys;
 
 use std::{
-	cmp,
-	cmp::Ordering,
+	cmp::{self, Ordering},
 	time::{SystemTime, UNIX_EPOCH},
 };
 
diff --git a/src/router/layers.rs b/src/router/layers.rs
index b358a3e63462daf45be8c2044e29fedf0c4515d2..3c7a5387bacb9adc6d7594b4ef3b80d85460d8a0 100644
--- a/src/router/layers.rs
+++ b/src/router/layers.rs
@@ -37,7 +37,6 @@ pub(crate) fn build(server: &Arc<Server>) -> io::Result<Router> {
 
 	let layers = layers
 		.sensitive_headers([header::AUTHORIZATION])
-		.sensitive_request_headers([HeaderName::from_static("x-forwarded-for")].into())
 		.layer(axum::middleware::from_fn_with_state(Arc::clone(server), request::spawn))
 		.layer(
 			TraceLayer::new_for_http()
diff --git a/src/service/rooms/spaces/mod.rs b/src/service/rooms/spaces/mod.rs
index 6fcaccb9c61c055fc21707ed5bf4be7b24b1b3a0..ffe1935dc467850860da5514bf723f9dae0e0609 100644
--- a/src/service/rooms/spaces/mod.rs
+++ b/src/service/rooms/spaces/mod.rs
@@ -15,11 +15,8 @@
 	events::{
 		room::{
 			avatar::RoomAvatarEventContent,
-			canonical_alias::RoomCanonicalAliasEventContent,
 			create::RoomCreateEventContent,
-			guest_access::{GuestAccess, RoomGuestAccessEventContent},
 			join_rules::{AllowRule, JoinRule, RoomJoinRulesEventContent, RoomMembership},
-			topic::RoomTopicEventContent,
 		},
 		space::child::{HierarchySpaceChildEvent, SpaceChildEventContent},
 		StateEventType,
@@ -559,12 +556,7 @@ fn get_room_summary(
 			canonical_alias: services()
 				.rooms
 				.state_accessor
-				.room_state_get(room_id, &StateEventType::RoomCanonicalAlias, "")?
-				.map_or(Ok(None), |s| {
-					serde_json::from_str(s.content.get())
-						.map(|c: RoomCanonicalAliasEventContent| c.alias)
-						.map_err(|_| Error::bad_database("Invalid canonical alias event in database."))
-				})?,
+				.get_canonical_alias(room_id)?,
 			name: services().rooms.state_accessor.get_name(room_id)?,
 			num_joined_members: services()
 				.rooms
@@ -580,18 +572,10 @@ fn get_room_summary(
 			topic: services()
 				.rooms
 				.state_accessor
-				.room_state_get(room_id, &StateEventType::RoomTopic, "")?
-				.map_or(Ok(None), |s| {
-					serde_json::from_str(s.content.get())
-						.map(|c: RoomTopicEventContent| Some(c.topic))
-						.map_err(|_| {
-							error!("Invalid room topic event in database for room {}", room_id);
-							Error::bad_database("Invalid room topic event in database.")
-						})
-				})
+				.get_room_topic(room_id)
 				.unwrap_or(None),
 			world_readable: services().rooms.state_accessor.is_world_readable(room_id)?,
-			guest_can_join: guest_can_join(room_id)?,
+			guest_can_join: services().rooms.state_accessor.guest_can_join(room_id)?,
 			avatar_url: services()
                 .rooms
                 .state_accessor
@@ -847,19 +831,6 @@ fn is_accessable_child_recurse(
 	}
 }
 
-/// Checks if guests are able to join a given room
-fn guest_can_join(room_id: &RoomId) -> Result<bool, Error> {
-	services()
-		.rooms
-		.state_accessor
-		.room_state_get(room_id, &StateEventType::RoomGuestAccess, "")?
-		.map_or(Ok(false), |s| {
-			serde_json::from_str(s.content.get())
-				.map(|c: RoomGuestAccessEventContent| c.guest_access == GuestAccess::CanJoin)
-				.map_err(|_| Error::bad_database("Invalid room guest access event in database."))
-		})
-}
-
 /// Returns the join rule for a given room
 fn get_join_rule(current_room: &RoomId) -> Result<(SpaceRoomJoinRule, Vec<OwnedRoomId>), Error> {
 	Ok(services()
diff --git a/src/service/rooms/state_accessor/mod.rs b/src/service/rooms/state_accessor/mod.rs
index 0681c298fc58ae34c4eb93c3cdc950346673edd9..d3dc92eff23f251b884404b9021c02e4d46a0539 100644
--- a/src/service/rooms/state_accessor/mod.rs
+++ b/src/service/rooms/state_accessor/mod.rs
@@ -10,13 +10,16 @@
 	events::{
 		room::{
 			avatar::RoomAvatarEventContent,
+			canonical_alias::RoomCanonicalAliasEventContent,
+			guest_access::{GuestAccess, RoomGuestAccessEventContent},
 			history_visibility::{HistoryVisibility, RoomHistoryVisibilityEventContent},
 			member::{MembershipState, RoomMemberEventContent},
 			name::RoomNameEventContent,
+			topic::RoomTopicEventContent,
 		},
 		StateEventType,
 	},
-	EventId, OwnedServerName, OwnedUserId, RoomId, ServerName, UserId,
+	EventId, OwnedRoomAliasId, OwnedServerName, OwnedUserId, RoomId, ServerName, UserId,
 };
 use serde_json::value::to_raw_value;
 use tokio::sync::MutexGuard;
@@ -304,8 +307,7 @@ pub async fn user_can_invite(
 
 	/// Checks if guests are able to view room content without joining
 	pub fn is_world_readable(&self, room_id: &RoomId) -> Result<bool, Error> {
-		Ok(self
-			.room_state_get(room_id, &StateEventType::RoomHistoryVisibility, "")?
+		self.room_state_get(room_id, &StateEventType::RoomHistoryVisibility, "")?
 			.map_or(Ok(false), |s| {
 				serde_json::from_str(s.content.get())
 					.map(|c: RoomHistoryVisibilityEventContent| {
@@ -319,6 +321,38 @@ pub fn is_world_readable(&self, room_id: &RoomId) -> Result<bool, Error> {
 						Error::bad_database("Invalid room history visibility event in database.")
 					})
 			})
-			.unwrap_or(false))
+	}
+
+	/// Checks if guests are able to join a given room
+	pub fn guest_can_join(&self, room_id: &RoomId) -> Result<bool, Error> {
+		self.room_state_get(room_id, &StateEventType::RoomGuestAccess, "")?
+			.map_or(Ok(false), |s| {
+				serde_json::from_str(s.content.get())
+					.map(|c: RoomGuestAccessEventContent| c.guest_access == GuestAccess::CanJoin)
+					.map_err(|_| Error::bad_database("Invalid room guest access event in database."))
+			})
+	}
+
+	/// Gets the primary alias from canonical alias event
+	pub fn get_canonical_alias(&self, room_id: &RoomId) -> Result<Option<OwnedRoomAliasId>, Error> {
+		self.room_state_get(room_id, &StateEventType::RoomCanonicalAlias, "")?
+			.map_or(Ok(None), |s| {
+				serde_json::from_str(s.content.get())
+					.map(|c: RoomCanonicalAliasEventContent| c.alias)
+					.map_err(|_| Error::bad_database("Invalid canonical alias event in database."))
+			})
+	}
+
+	/// Gets the room topic
+	pub fn get_room_topic(&self, room_id: &RoomId) -> Result<Option<String>, Error> {
+		self.room_state_get(room_id, &StateEventType::RoomTopic, "")?
+			.map_or(Ok(None), |s| {
+				serde_json::from_str(s.content.get())
+					.map(|c: RoomTopicEventContent| Some(c.topic))
+					.map_err(|e| {
+						error!("Invalid room topic event in database for room {room_id}: {e}");
+						Error::bad_database("Invalid room topic event in database.")
+					})
+			})
 	}
 }