diff --git a/src/admin/room/mod.rs b/src/admin/room/mod.rs index ccc8c8be..ced96c95 100644 --- a/src/admin/room/mod.rs +++ b/src/admin/room/mod.rs @@ -7,6 +7,7 @@ use crate::Result; 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, }, + #[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, + }, + + /// - Displays room topic + /// + /// Room topics can be huge, so this is in its + /// own separate command + ViewRoomTopic { + room_id: Box, + }, +} + #[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 { 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 00000000..0bcbc7ae --- /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 { + 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) -> Result { + 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::>(); + + 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::>() + .join("\n") + ); + + let output_html = format!( + "\n\t\n{}
{} Members in Room \"{}\"
MXIDDisplay \ + Name
", + member_info.len(), + room_name, + member_info + .iter() + .fold(String::new(), |mut output, (mxid, displayname)| { + writeln!( + output, + "{}\t{}", + 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) -> Result { + 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!("

Room topic:

\n
\n{}
", escape_html(&room_topic)); + + Ok(RoomMessageEventContent::text_html( + format!("Room topic:\n\n```{room_topic}\n```"), + output_html, + )) +}