Skip to main content

tuwunel_api/server/
query.rs

1use axum::extract::State;
2use futures::{
3	StreamExt,
4	future::{join, join5},
5};
6use rand::seq::SliceRandom;
7use ruma::{
8	OwnedServerName,
9	api::federation::query::{
10		get_profile_information::{self, v1::Response as ProfileResponse},
11		get_room_information,
12	},
13	profile::{ProfileFieldName, ProfileFieldValue},
14};
15use serde_json::Value as JsonValue;
16use tuwunel_core::{Err, Result, err, utils::future::TryExtExt};
17
18use crate::Ruma;
19
20/// # `GET /_matrix/federation/v1/query/directory`
21///
22/// Resolve a room alias to a room id.
23pub(crate) async fn get_room_information_route(
24	State(services): State<crate::State>,
25	body: Ruma<get_room_information::v1::Request>,
26) -> Result<get_room_information::v1::Response> {
27	let room_id = services
28		.alias
29		.resolve_local_alias(&body.room_alias)
30		.await
31		.map_err(|_| err!(Request(NotFound("Room alias not found."))))?;
32
33	let mut servers: Vec<OwnedServerName> = services
34		.state_cache
35		.room_servers(&room_id)
36		.map(ToOwned::to_owned)
37		.collect()
38		.await;
39
40	servers.sort_unstable();
41	servers.dedup();
42
43	servers.shuffle(&mut rand::rng());
44
45	// insert our server as the very first choice if in list
46	if let Some(server_index) = servers
47		.iter()
48		.position(|server| server == services.globals.server_name())
49	{
50		servers.swap_remove(server_index);
51		servers.insert(0, services.globals.server_name().to_owned());
52	}
53
54	Ok(get_room_information::v1::Response { room_id, servers })
55}
56
57/// # `GET /_matrix/federation/v1/query/profile`
58///
59///
60/// Gets information on a profile.
61pub(crate) async fn get_profile_information_route(
62	State(services): State<crate::State>,
63	body: Ruma<get_profile_information::v1::Request>,
64) -> Result<ProfileResponse> {
65	if !services
66		.server
67		.config
68		.allow_inbound_profile_lookup_federation_requests
69	{
70		return Err!(Request(Forbidden(
71			"Profile lookup over federation is not allowed on this homeserver.",
72		)));
73	}
74
75	if !services.globals.user_is_local(&body.user_id) {
76		return Err!(Request(InvalidParam("User does not belong to this server.",)));
77	}
78
79	match &body.field {
80		| Some(ProfileFieldName::AvatarUrl | ProfileFieldName::DisplayName) => {
81			let avatar_url = services.users.avatar_url(&body.user_id).ok();
82			let displayname = services.users.displayname(&body.user_id).ok();
83			let (avatar_url, displayname) = join(avatar_url, displayname).await;
84
85			Ok([
86				avatar_url.map(ProfileFieldValue::AvatarUrl),
87				displayname.map(ProfileFieldValue::DisplayName),
88			]
89			.into_iter()
90			.flatten()
91			.collect())
92		},
93		| Some(custom_field) => {
94			let value = services
95				.users
96				.profile_key(&body.user_id, custom_field.as_str())
97				.await
98				.ok();
99
100			let entry = value
101				.as_ref()
102				.map(|raw| serde_json::to_value(raw.json()))
103				.transpose()?
104				.map(|json| (custom_field.as_str().to_owned(), json));
105
106			Ok(entry.into_iter().collect())
107		},
108		| None => {
109			let avatar_url = services.users.avatar_url(&body.user_id).ok();
110			let blurhash = services.users.blurhash(&body.user_id).ok();
111			let displayname = services.users.displayname(&body.user_id).ok();
112			let tz = services.users.timezone(&body.user_id).ok();
113			let custom = services
114				.users
115				.all_profile_keys(&body.user_id)
116				.collect::<Vec<_>>();
117
118			let (avatar_url, blurhash, custom, displayname, tz) =
119				join5(avatar_url, blurhash, custom, displayname, tz).await;
120
121			let mut response: ProfileResponse = [
122				avatar_url.map(ProfileFieldValue::AvatarUrl),
123				displayname.map(ProfileFieldValue::DisplayName),
124				tz.map(ProfileFieldValue::TimeZone),
125			]
126			.into_iter()
127			.flatten()
128			.collect();
129
130			if let Some(blurhash) = blurhash {
131				response.set("blurhash".to_owned(), JsonValue::String(blurhash));
132			}
133
134			response.extend(
135				custom
136					.into_iter()
137					.filter_map(|(k, v)| serde_json::to_value(v).ok().map(|v| (k, v))),
138			);
139
140			Ok(response)
141		},
142	}
143}