From 7871ce91902b73aff4d356236a3f9e7126a68561 Mon Sep 17 00:00:00 2001 From: timedout Date: Sat, 31 Oct 2026 20:55:08 +0000 Subject: [PATCH] feat: Move room state commands to the room group --- src/admin/debug/commands.rs | 95 ++----------------------------------- src/admin/debug/mod.rs | 25 +--------- src/admin/room/commands.rs | 59 ++++++++++++++++++++++- src/admin/room/mod.rs | 13 ++++- 4 files changed, 74 insertions(+), 118 deletions(-) diff --git a/src/admin/debug/commands.rs b/src/admin/debug/commands.rs index 7b5e82dc6..aa122a08b 100644 --- a/src/admin/debug/commands.rs +++ b/src/admin/debug/commands.rs @@ -6,17 +6,15 @@ use std::{ }; use conduwuit::{ - Err, Result, at, debug_error, err, info, + Err, Result, debug_error, err, info, matrix::{ - Event, StateKey, + Event, pdu::{PduEvent, PduId, RawPduId}, }, - state_res::EventTypeExt, trace, utils, utils::{ stream::{BroadbandExt, IterStream, ReadyExt}, string::EMPTY, - to_canonical_object, }, warn, }; @@ -27,11 +25,8 @@ use resolvematrix::{ server::{MatrixResolver, MatrixResolverBuilder}, }; use ruma::{ - CanonicalJsonObject, CanonicalJsonValue, EventId, OwnedEventId, OwnedRoomId, - OwnedRoomOrAliasId, OwnedServerName, RoomId, RoomVersionId, UInt, - api::federation::event::get_room_state, - events::{AnyStateEvent, StateEventType}, - serde::Raw, + CanonicalJsonObject, CanonicalJsonValue, EventId, OwnedEventId, OwnedRoomId, OwnedServerName, + RoomId, RoomVersionId, UInt, api::federation::event::get_room_state, }; use service::rooms::{ short::{ShortEventId, ShortRoomId}, @@ -518,88 +513,6 @@ impl crate::Context<'_> { .await } - pub(super) async fn get_state_at(&self, event_id: OwnedEventId) -> Result { - self.bail_restricted()?; - - let shortstatehash = self - .services - .rooms - .state_accessor - .pdu_shortstatehash(&event_id) - .await?; - - let state_ids: Vec = self - .services - .rooms - .state_accessor - .state_full_ids(shortstatehash) - .map(at!(1)) - .collect() - .await; - - let mut pdus: HashMap<(StateEventType, StateKey), CanonicalJsonObject> = state_ids - .iter() - .try_stream::() - .and_then(|id| async { - let pdu_json = self.services.rooms.timeline.get_pdu_json(id).await?; - let event_type = pdu_json - .get("type") - .and_then(CanonicalJsonValue::as_str) - .expect("event type must be present in events"); - let state_key = pdu_json - .get("state_key") - .and_then(CanonicalJsonValue::as_str) - .expect("state keys must be present for events in the room state"); - Ok(((event_type.into(), state_key.into()), pdu_json)) - }) - .try_collect() - .await?; - - let pdu = self.services.rooms.timeline.get_pdu(&event_id).await?; - if let Some(state_key) = pdu.state_key() { - pdus.insert(pdu.kind().with_state_key(state_key), to_canonical_object(pdu)?); - } - - let json = - serde_json::to_string_pretty(&pdus.values().collect::>()).map_err(|e| { - err!(Database( - "Failed to convert room state events to pretty JSON, possible invalid room \ - state events in our database {e}", - )) - })?; - - let out = format!("```json\n{json}\n```"); - self.write_str(&out).await - } - - pub(super) async fn get_room_state(&self, room: OwnedRoomOrAliasId) -> Result { - self.bail_restricted()?; - - let room_id = self.services.rooms.alias.resolve(&room).await?; - let room_state: Vec> = self - .services - .rooms - .state_accessor - .room_state_full_pdus(&room_id) - .map_ok(Event::into_format) - .try_collect() - .await?; - - if room_state.is_empty() { - return Err!("Unable to find room state in our database (vector is empty)",); - } - - let json = serde_json::to_string_pretty(&room_state).map_err(|e| { - err!(Database( - "Failed to convert room state events to pretty JSON, possible invalid room \ - state events in our database {e}", - )) - })?; - - let out = format!("```json\n{json}\n```"); - self.write_str(&out).await - } - pub(super) async fn ping(&self, server: OwnedServerName) -> Result { if server == self.services.globals.server_name() { return Err!("Not allowed to send federation requests to ourselves."); diff --git a/src/admin/debug/mod.rs b/src/admin/debug/mod.rs index 9bf3647b3..396e98616 100644 --- a/src/admin/debug/mod.rs +++ b/src/admin/debug/mod.rs @@ -3,7 +3,7 @@ pub(crate) mod tester; use clap::Subcommand; use conduwuit::Result; -use ruma::{OwnedEventId, OwnedRoomId, OwnedRoomOrAliasId, OwnedServerName}; +use ruma::{OwnedEventId, OwnedRoomId, OwnedServerName}; use service::rooms::short::{ShortEventId, ShortRoomId}; use self::tester::TesterCommand; @@ -80,29 +80,6 @@ pub enum DebugCommand { force: bool, }, - /// Gets all the room state events for the specified room. - /// - /// This is functionally equivalent to `GET - /// /_matrix/client/v3/rooms/{roomid}/state`, except the admin command does - /// *not* check if the sender user is allowed to see state events. This is - /// done because it's implied that server admins here have database access - /// and can see/get room info themselves anyways if they were malicious - /// admins. - /// - /// Of course the check is still done on the actual client API. - GetRoomState { - /// Room ID - room_id: OwnedRoomOrAliasId, - }, - - /// Gets all the room state events at the specified event. - /// - /// State at event might not be available for some PDUs, such as rejected - /// ones. - GetStateAt { - event_id: OwnedEventId, - }, - /// Sends a federation request to the remote server's /// `/_matrix/federation/v1/version` endpoint and measures the latency it /// took for the server to respond diff --git a/src/admin/room/commands.rs b/src/admin/room/commands.rs index 50849186b..1b6442e08 100644 --- a/src/admin/room/commands.rs +++ b/src/admin/room/commands.rs @@ -1,6 +1,8 @@ -use conduwuit::{Err, Result}; +use conduwuit::{Err, Event, Result, err}; use futures::StreamExt; -use ruma::OwnedRoomId; +#[allow(unused_imports)] +use ruma::state_res::Event as _; +use ruma::{OwnedEventId, OwnedRoomId, OwnedRoomOrAliasId}; use crate::{PAGE_SIZE, get_room_info}; @@ -82,4 +84,57 @@ impl crate::Context<'_> { self.write_str(&format!("{result}")).await } + + pub(super) async fn export_state( + &self, + room: OwnedRoomOrAliasId, + event_id: Option, + ) -> Result { + self.bail_restricted()?; + + let room_id = self.services.rooms.alias.resolve(&room).await?; + let shortstatehash = match &event_id { + | Some(event_id) => + self.services + .rooms + .state_accessor + .pdu_shortstatehash(event_id) + .await?, + | None => + self.services + .rooms + .state + .get_room_shortstatehash(&room_id) + .await?, + }; + let mut state_at: Vec<_> = self + .services + .rooms + .state_accessor + .state_full_pdus(shortstatehash) + .map(Event::into_pdu) + .collect() + .await; + + if let Some(event_id) = event_id { + let pdu = self.services.rooms.timeline.get_pdu(&event_id).await?; + if let Some(state_key) = pdu.state_key() { + state_at.retain(|p| p.kind() != pdu.kind() || p.state_key() != Some(state_key)); + state_at.push(pdu); + } + } + + if state_at.is_empty() { + return Err!(Request(NotFound("No state found."))); + } + + let json = serde_json::to_string_pretty(&state_at).map_err(|e| { + err!(Database( + "Failed to convert room state events to pretty JSON, possible invalid room \ + state events in the database: {e:?}", + )) + })?; + + self.write_str(&format!("```json\n{json}\n```")).await + } } diff --git a/src/admin/room/mod.rs b/src/admin/room/mod.rs index 5878cc852..82ac3145d 100644 --- a/src/admin/room/mod.rs +++ b/src/admin/room/mod.rs @@ -6,7 +6,7 @@ mod moderation; use clap::Subcommand; use conduwuit::Result; -use ruma::OwnedRoomId; +use ruma::{OwnedEventId, OwnedRoomId, OwnedRoomOrAliasId}; use self::{ alias::RoomAliasCommand, directory::RoomDirectoryCommand, info::RoomInfoCommand, @@ -60,4 +60,15 @@ pub enum RoomCommand { Exists { room_id: OwnedRoomId, }, + + /// Exports the room's current state as a JSON file. + /// If an event ID is supplied, the state at that event (the state preceding + /// it, plus any change made by the named event) is returned instead, if it + /// is known. + ExportState { + /// The room ID or alias to get the state for. + room: OwnedRoomOrAliasId, + /// The event ID to get the state at, if any. + event_id: Option, + }, }