Skip to main content

tuwunel_admin/room/
alias.rs

1use std::fmt::Write;
2
3use clap::Subcommand;
4use futures::StreamExt;
5use ruma::{OwnedRoomAliasId, OwnedRoomId};
6use tuwunel_core::{Err, Result, err};
7use tuwunel_macros::{admin_command, admin_command_dispatch};
8use tuwunel_service::Services;
9
10use crate::Context;
11
12#[admin_command_dispatch(handler_prefix = "alias")]
13#[derive(Debug, Subcommand)]
14pub(crate) enum RoomAliasCommand {
15	/// - Make an alias point to a room.
16	Set {
17		#[arg(short, long)]
18		/// Set the alias even if a room is already using it
19		force: bool,
20
21		/// The room id to set the alias on
22		room_id: OwnedRoomId,
23
24		/// The alias localpart to use (`alias`, not `#alias:servername.tld`)
25		room_alias_localpart: String,
26	},
27
28	/// - Remove a local alias
29	Remove {
30		/// The alias localpart to remove (`alias`, not `#alias:servername.tld`)
31		room_alias_localpart: String,
32	},
33
34	/// - Show which room is using an alias
35	Which {
36		/// The alias localpart to look up (`alias`, not
37		/// `#alias:servername.tld`)
38		room_alias_localpart: String,
39	},
40
41	/// - List aliases currently being used
42	List {
43		/// If set, only list the aliases for this room
44		room_id: Option<OwnedRoomId>,
45	},
46}
47
48fn parse_alias_from_localpart(
49	services: &Services,
50	room_alias_localpart: &String,
51) -> Result<OwnedRoomAliasId> {
52	let room_alias_str = format!("#{}:{}", room_alias_localpart, services.globals.server_name());
53
54	Ok(OwnedRoomAliasId::try_from(room_alias_str)?)
55}
56
57#[admin_command]
58pub(super) async fn alias_set(
59	&self,
60	force: bool,
61	room_id: OwnedRoomId,
62	room_alias_localpart: String,
63) -> Result {
64	let room_alias = parse_alias_from_localpart(self.services, &room_alias_localpart)?;
65
66	match self
67		.services
68		.alias
69		.resolve_local_alias(&room_alias)
70		.await
71	{
72		| Ok(id) => {
73			if !force {
74				return Err!(
75					"Refusing to overwrite in use alias for {id}, use -f or --force to overwrite"
76				);
77			}
78
79			self.services
80				.alias
81				.set_alias(&room_alias, &room_id)
82				.map_err(|err| err!("Failed to remove alias: {err}"))?;
83
84			self.write_str(&format!("Successfully overwrote alias (formerly {id})"))
85				.await
86		},
87		| _ => {
88			self.services
89				.alias
90				.set_alias(&room_alias, &room_id)
91				.map_err(|err| err!("Failed to remove alias: {err}"))?;
92
93			self.write_str("Successfully set alias").await
94		},
95	}
96}
97
98#[admin_command]
99pub(super) async fn alias_remove(&self, room_alias_localpart: String) -> Result {
100	let room_alias = parse_alias_from_localpart(self.services, &room_alias_localpart)?;
101
102	let id = self
103		.services
104		.alias
105		.resolve_local_alias(&room_alias)
106		.await
107		.map_err(|_| err!("Alias isn't in use."))?;
108
109	self.services
110		.alias
111		.remove_alias(&room_alias)
112		.await
113		.map_err(|err| err!("Failed to remove alias: {err}"))?;
114
115	self.write_str(&format!("Removed alias from {id}"))
116		.await
117}
118
119#[admin_command]
120pub(super) async fn alias_which(&self, room_alias_localpart: String) -> Result {
121	let room_alias = parse_alias_from_localpart(self.services, &room_alias_localpart)?;
122
123	let id = self
124		.services
125		.alias
126		.resolve_local_alias(&room_alias)
127		.await
128		.map_err(|_| err!("Alias isn't in use."))?;
129
130	self.write_str(&format!("Alias resolves to {id}"))
131		.await
132}
133
134#[admin_command]
135pub(super) async fn alias_list(&self, room_id: Option<OwnedRoomId>) -> Result {
136	match room_id {
137		| Some(room_id) => list_aliases_for_room(self, room_id).await,
138		| None => list_all_aliases(self).await,
139	}
140}
141
142async fn list_aliases_for_room(context: &Context<'_>, room_id: OwnedRoomId) -> Result {
143	let aliases: Vec<OwnedRoomAliasId> = context
144		.services
145		.alias
146		.local_aliases_for_room(&room_id)
147		.map(Into::into)
148		.collect()
149		.await;
150
151	let mut plain_list = String::new();
152
153	for alias in aliases {
154		writeln!(plain_list, "- {alias}")?;
155	}
156
157	let plain = format!("Aliases for {room_id}:\n{plain_list}");
158	context.write_str(&plain).await
159}
160
161async fn list_all_aliases(context: &Context<'_>) -> Result {
162	let aliases = context
163		.services
164		.alias
165		.all_local_aliases()
166		.map(|(room_id, localpart)| (room_id.to_owned(), localpart.to_owned()))
167		.collect::<Vec<_>>()
168		.await;
169
170	let server_name = context.services.globals.server_name();
171
172	let mut plain_list = String::new();
173	for (room_id, alias_id) in aliases {
174		writeln!(plain_list, "- `{room_id}` -> #{alias_id}:{server_name}")?;
175	}
176
177	let plain = format!("Aliases:\n{plain_list}");
178	context.write_str(&plain).await
179}