A Matrix homeserver written in Rust https://conduit.rs
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

306 lines
10 KiB

  1. use super::State;
  2. use crate::{ConduitResult, Database, Error, Result, Ruma};
  3. use ruma::{
  4. api::client::{
  5. error::ErrorKind,
  6. r0::{
  7. directory::{
  8. self, get_public_rooms, get_public_rooms_filtered, get_room_visibility,
  9. set_room_visibility,
  10. },
  11. room,
  12. },
  13. },
  14. events::{
  15. room::{avatar, canonical_alias, guest_access, history_visibility, name, topic},
  16. EventType,
  17. },
  18. Raw,
  19. };
  20. #[cfg(feature = "conduit_bin")]
  21. use rocket::{get, post, put};
  22. #[cfg_attr(
  23. feature = "conduit_bin",
  24. post("/_matrix/client/r0/publicRooms", data = "<body>")
  25. )]
  26. pub async fn get_public_rooms_filtered_route(
  27. db: State<'_, Database>,
  28. body: Ruma<get_public_rooms_filtered::Request>,
  29. ) -> ConduitResult<get_public_rooms_filtered::Response> {
  30. let limit = body.limit.map_or(10, u64::from);
  31. let mut since = 0_u64;
  32. if let Some(s) = &body.since {
  33. let mut characters = s.chars();
  34. let backwards = match characters.next() {
  35. Some('n') => false,
  36. Some('p') => true,
  37. _ => {
  38. return Err(Error::BadRequest(
  39. ErrorKind::InvalidParam,
  40. "Invalid `since` token",
  41. ))
  42. }
  43. };
  44. since = characters
  45. .collect::<String>()
  46. .parse()
  47. .map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Invalid `since` token."))?;
  48. if backwards {
  49. since = since.saturating_sub(limit);
  50. }
  51. }
  52. let mut all_rooms =
  53. db.rooms
  54. .public_rooms()
  55. .map(|room_id| {
  56. let room_id = room_id?;
  57. // TODO: Do not load full state?
  58. let state = db.rooms.room_state_full(&room_id)?;
  59. let chunk = directory::PublicRoomsChunk {
  60. aliases: Vec::new(),
  61. canonical_alias: state
  62. .get(&(EventType::RoomCanonicalAlias, "".to_owned()))
  63. .map_or(Ok::<_, Error>(None), |s| {
  64. Ok(serde_json::from_value::<
  65. Raw<canonical_alias::CanonicalAliasEventContent>,
  66. >(s.content.clone())
  67. .expect("from_value::<Raw<..>> can never fail")
  68. .deserialize()
  69. .map_err(|_| {
  70. Error::bad_database("Invalid canonical alias event in database.")
  71. })?
  72. .alias)
  73. })?,
  74. name: state.get(&(EventType::RoomName, "".to_owned())).map_or(
  75. Ok::<_, Error>(None),
  76. |s| {
  77. Ok(serde_json::from_value::<Raw<name::NameEventContent>>(
  78. s.content.clone(),
  79. )
  80. .expect("from_value::<Raw<..>> can never fail")
  81. .deserialize()
  82. .map_err(|_| {
  83. Error::bad_database("Invalid room name event in database.")
  84. })?
  85. .name()
  86. .map(|n| n.to_owned()))
  87. },
  88. )?,
  89. num_joined_members: (db.rooms.room_members(&room_id).count() as u32).into(),
  90. room_id,
  91. topic: state.get(&(EventType::RoomTopic, "".to_owned())).map_or(
  92. Ok::<_, Error>(None),
  93. |s| {
  94. Ok(Some(
  95. serde_json::from_value::<Raw<topic::TopicEventContent>>(
  96. s.content.clone(),
  97. )
  98. .expect("from_value::<Raw<..>> can never fail")
  99. .deserialize()
  100. .map_err(|_| {
  101. Error::bad_database("Invalid room topic event in database.")
  102. })?
  103. .topic,
  104. ))
  105. },
  106. )?,
  107. world_readable: state
  108. .get(&(EventType::RoomHistoryVisibility, "".to_owned()))
  109. .map_or(Ok::<_, Error>(false), |s| {
  110. Ok(serde_json::from_value::<
  111. Raw<history_visibility::HistoryVisibilityEventContent>,
  112. >(s.content.clone())
  113. .expect("from_value::<Raw<..>> can never fail")
  114. .deserialize()
  115. .map_err(|_| {
  116. Error::bad_database(
  117. "Invalid room history visibility event in database.",
  118. )
  119. })?
  120. .history_visibility
  121. == history_visibility::HistoryVisibility::WorldReadable)
  122. })?,
  123. guest_can_join: state
  124. .get(&(EventType::RoomGuestAccess, "".to_owned()))
  125. .map_or(Ok::<_, Error>(false), |s| {
  126. Ok(
  127. serde_json::from_value::<Raw<guest_access::GuestAccessEventContent>>(
  128. s.content.clone(),
  129. )
  130. .expect("from_value::<Raw<..>> can never fail")
  131. .deserialize()
  132. .map_err(|_| {
  133. Error::bad_database("Invalid room guest access event in database.")
  134. })?
  135. .guest_access
  136. == guest_access::GuestAccess::CanJoin,
  137. )
  138. })?,
  139. avatar_url: state
  140. .get(&(EventType::RoomAvatar, "".to_owned()))
  141. .map(|s| {
  142. Ok::<_, Error>(
  143. serde_json::from_value::<Raw<avatar::AvatarEventContent>>(
  144. s.content.clone(),
  145. )
  146. .expect("from_value::<Raw<..>> can never fail")
  147. .deserialize()
  148. .map_err(|_| {
  149. Error::bad_database("Invalid room avatar event in database.")
  150. })?
  151. .url,
  152. )
  153. })
  154. .transpose()?,
  155. };
  156. Ok(chunk)
  157. })
  158. .filter_map(|r: Result<_>| r.ok()) // Filter out buggy rooms
  159. // We need to collect all, so we can sort by member count
  160. .collect::<Vec<_>>();
  161. all_rooms.sort_by(|l, r| r.num_joined_members.cmp(&l.num_joined_members));
  162. /*
  163. all_rooms.extend_from_slice(
  164. &server_server::send_request(
  165. &db,
  166. "privacytools.io".to_owned(),
  167. ruma::api::federation::v1::get_public_rooms::Request {
  168. limit: Some(20_u32.into()),
  169. since: None,
  170. room_network: ruma::api::federation::v1::get_public_rooms::RoomNetwork::Matrix,
  171. },
  172. )
  173. .await
  174. ?
  175. .chunk
  176. .into_iter()
  177. .map(|c| serde_json::from_str(&serde_json::to_string(&c)?)?)
  178. .collect::<Vec<_>>(),
  179. );
  180. */
  181. let total_room_count_estimate = (all_rooms.len() as u32).into();
  182. let chunk = all_rooms
  183. .into_iter()
  184. .skip(since as usize)
  185. .take(limit as usize)
  186. .collect::<Vec<_>>();
  187. let prev_batch = if since == 0 {
  188. None
  189. } else {
  190. Some(format!("p{}", since))
  191. };
  192. let next_batch = if chunk.len() < limit as usize {
  193. None
  194. } else {
  195. Some(format!("n{}", since + limit))
  196. };
  197. Ok(get_public_rooms_filtered::Response {
  198. chunk,
  199. prev_batch,
  200. next_batch,
  201. total_room_count_estimate: Some(total_room_count_estimate),
  202. }
  203. .into())
  204. }
  205. #[cfg_attr(
  206. feature = "conduit_bin",
  207. get("/_matrix/client/r0/publicRooms", data = "<body>")
  208. )]
  209. pub async fn get_public_rooms_route(
  210. db: State<'_, Database>,
  211. body: Ruma<get_public_rooms::Request>,
  212. ) -> ConduitResult<get_public_rooms::Response> {
  213. let Ruma {
  214. body:
  215. get_public_rooms::Request {
  216. limit,
  217. server,
  218. since,
  219. },
  220. sender_id,
  221. device_id,
  222. json_body,
  223. } = body;
  224. let get_public_rooms_filtered::Response {
  225. chunk,
  226. prev_batch,
  227. next_batch,
  228. total_room_count_estimate,
  229. } = get_public_rooms_filtered_route(
  230. db,
  231. Ruma {
  232. body: get_public_rooms_filtered::Request {
  233. filter: None,
  234. limit,
  235. room_network: get_public_rooms_filtered::RoomNetwork::Matrix,
  236. server,
  237. since,
  238. },
  239. sender_id,
  240. device_id,
  241. json_body,
  242. },
  243. )
  244. .await?
  245. .0;
  246. Ok(get_public_rooms::Response {
  247. chunk,
  248. prev_batch,
  249. next_batch,
  250. total_room_count_estimate,
  251. }
  252. .into())
  253. }
  254. #[cfg_attr(
  255. feature = "conduit_bin",
  256. put("/_matrix/client/r0/directory/list/room/<_>", data = "<body>")
  257. )]
  258. pub async fn set_room_visibility_route(
  259. db: State<'_, Database>,
  260. body: Ruma<set_room_visibility::Request>,
  261. ) -> ConduitResult<set_room_visibility::Response> {
  262. match body.visibility {
  263. room::Visibility::Public => db.rooms.set_public(&body.room_id, true)?,
  264. room::Visibility::Private => db.rooms.set_public(&body.room_id, false)?,
  265. }
  266. Ok(set_room_visibility::Response.into())
  267. }
  268. #[cfg_attr(
  269. feature = "conduit_bin",
  270. get("/_matrix/client/r0/directory/list/room/<_>", data = "<body>")
  271. )]
  272. pub async fn get_room_visibility_route(
  273. db: State<'_, Database>,
  274. body: Ruma<get_room_visibility::Request>,
  275. ) -> ConduitResult<get_room_visibility::Response> {
  276. Ok(get_room_visibility::Response {
  277. visibility: if db.rooms.is_public_room(&body.room_id)? {
  278. room::Visibility::Public
  279. } else {
  280. room::Visibility::Private
  281. },
  282. }
  283. .into())
  284. }