Skip to main content

tuwunel_admin/room/
info.rs

1use futures::{FutureExt, StreamExt, TryFutureExt, join};
2use ruma::{
3	OwnedRoomAliasId, OwnedRoomOrAliasId, RoomAliasId,
4	events::{
5		StateEventType,
6		room::{canonical_alias::RoomCanonicalAliasEventContent, power_levels::UserPowerLevel},
7	},
8};
9use tuwunel_core::{Err, Result, utils::TryFutureExtExt};
10
11use crate::admin_command;
12
13#[admin_command]
14pub(super) async fn room_info(&self, room: OwnedRoomOrAliasId) -> Result {
15	let room_id = self.services.alias.maybe_resolve(&room).await?;
16
17	if !self.services.metadata.exists(&room_id).await {
18		return Err!("Room {room_id} is not known to this server.");
19	}
20
21	let create_event = self
22		.services
23		.state_accessor
24		.get_create(&room_id)
25		.boxed();
26
27	let name = self
28		.services
29		.state_accessor
30		.get_name(&room_id)
31		.unwrap_or_else(|_| "(none)".to_owned())
32		.boxed();
33
34	let topic = self
35		.services
36		.state_accessor
37		.get_room_topic(&room_id)
38		.unwrap_or_else(|_| "(none)".to_owned())
39		.boxed();
40
41	let canonical_alias = self
42		.services
43		.state_accessor
44		.room_state_get_content(&room_id, &StateEventType::RoomCanonicalAlias, "")
45		.map_ok(|content: RoomCanonicalAliasEventContent| (content.alias, content.alt_aliases))
46		.unwrap_or_default()
47		.boxed();
48
49	let aliases = self
50		.services
51		.alias
52		.local_aliases_for_room(&room_id)
53		.map(Into::into)
54		.collect::<Vec<OwnedRoomAliasId>>()
55		.boxed();
56
57	let power_levels = self
58		.services
59		.state_accessor
60		.get_power_levels(&room_id)
61		.boxed();
62
63	let (create_event, name, topic, (canonical_alias, alt_aliases), mut aliases, power_levels) =
64		join!(create_event, name, topic, canonical_alias, aliases, power_levels);
65
66	let create_event = create_event?;
67	let power_levels = power_levels?;
68
69	let room_version = create_event.room_version()?;
70
71	aliases.sort();
72
73	// Local aliases not published in the canonical alias event.
74	let unlisted: Vec<_> = aliases
75		.iter()
76		.filter(|&alias| canonical_alias.as_ref() != Some(alias) && !alt_aliases.contains(alias))
77		.collect();
78
79	let state_default = power_levels.state_default;
80
81	let mut admins: Vec<_> = power_levels
82		.users
83		.keys()
84		.chain(
85			power_levels
86				.rules
87				.privileged_creators
88				.iter()
89				.flatten(),
90		)
91		.map(|user_id| (user_id, power_levels.for_user(user_id)))
92		.filter(|&(_, pl)| pl >= state_default)
93		.collect();
94
95	admins.sort_by(|(user_a, pl_a), (user_b, pl_b)| {
96		pl_b.cmp(pl_a).then_with(|| user_a.cmp(user_b))
97	});
98
99	writeln!(self, "```\nRoom information for {room_id}\n").await?;
100
101	writeln!(self, "Room version: {room_version}\n").await?;
102
103	writeln!(self, "Name: {name}").await?;
104	writeln!(self, "Topic: \n{topic}\n").await?;
105
106	writeln!(self, "Aliases:").await?;
107	let canonical_alias = canonical_alias
108		.as_deref()
109		.map(RoomAliasId::as_str)
110		.unwrap_or("none");
111	writeln!(self, "  Canonical: {canonical_alias}").await?;
112
113	writeln!(self, "  Alternative:").await?;
114	if alt_aliases.is_empty() {
115		writeln!(self, "    none").await?;
116	} else {
117		for alias in &alt_aliases {
118			writeln!(self, "    - {alias}").await?;
119		}
120	}
121
122	writeln!(self, "  Local unlisted:").await?;
123	if unlisted.is_empty() {
124		writeln!(self, "    none").await?;
125	} else {
126		for alias in unlisted {
127			writeln!(self, "    - {alias}").await?;
128		}
129	}
130
131	writeln!(self, "\nAdmins (power level >= {state_default}):").await?;
132	for (user_id, pl) in &admins {
133		let pl = match pl {
134			| UserPowerLevel::Int(pl) => pl.to_string(),
135			| UserPowerLevel::Infinite => "creator".to_owned(),
136		};
137
138		writeln!(self, "  - {user_id} ({pl})").await?;
139	}
140
141	writeln!(self, "```").await?;
142
143	Ok(())
144}