Conduit is a simple, fast and reliable chat server powered by Matrix 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.
305 lines
10 KiB
305 lines
10 KiB
use super::State; |
|
use crate::{ConduitResult, Database, Error, Result, Ruma}; |
|
use ruma::{ |
|
api::client::{ |
|
error::ErrorKind, |
|
r0::{ |
|
directory::{ |
|
self, get_public_rooms, get_public_rooms_filtered, get_room_visibility, |
|
set_room_visibility, |
|
}, |
|
room, |
|
}, |
|
}, |
|
events::{ |
|
room::{avatar, canonical_alias, guest_access, history_visibility, name, topic}, |
|
EventType, |
|
}, |
|
Raw, |
|
}; |
|
|
|
#[cfg(feature = "conduit_bin")] |
|
use rocket::{get, post, put}; |
|
|
|
#[cfg_attr( |
|
feature = "conduit_bin", |
|
post("/_matrix/client/r0/publicRooms", data = "<body>") |
|
)] |
|
pub async fn get_public_rooms_filtered_route( |
|
db: State<'_, Database>, |
|
body: Ruma<get_public_rooms_filtered::Request>, |
|
) -> ConduitResult<get_public_rooms_filtered::Response> { |
|
let limit = body.limit.map_or(10, u64::from); |
|
let mut since = 0_u64; |
|
|
|
if let Some(s) = &body.since { |
|
let mut characters = s.chars(); |
|
let backwards = match characters.next() { |
|
Some('n') => false, |
|
Some('p') => true, |
|
_ => { |
|
return Err(Error::BadRequest( |
|
ErrorKind::InvalidParam, |
|
"Invalid `since` token", |
|
)) |
|
} |
|
}; |
|
|
|
since = characters |
|
.collect::<String>() |
|
.parse() |
|
.map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Invalid `since` token."))?; |
|
|
|
if backwards { |
|
since = since.saturating_sub(limit); |
|
} |
|
} |
|
|
|
let mut all_rooms = |
|
db.rooms |
|
.public_rooms() |
|
.map(|room_id| { |
|
let room_id = room_id?; |
|
|
|
// TODO: Do not load full state? |
|
let state = db.rooms.room_state_full(&room_id)?; |
|
|
|
let chunk = directory::PublicRoomsChunk { |
|
aliases: Vec::new(), |
|
canonical_alias: state |
|
.get(&(EventType::RoomCanonicalAlias, "".to_owned())) |
|
.map_or(Ok::<_, Error>(None), |s| { |
|
Ok(serde_json::from_value::< |
|
Raw<canonical_alias::CanonicalAliasEventContent>, |
|
>(s.content.clone()) |
|
.expect("from_value::<Raw<..>> can never fail") |
|
.deserialize() |
|
.map_err(|_| { |
|
Error::bad_database("Invalid canonical alias event in database.") |
|
})? |
|
.alias) |
|
})?, |
|
name: state.get(&(EventType::RoomName, "".to_owned())).map_or( |
|
Ok::<_, Error>(None), |
|
|s| { |
|
Ok(serde_json::from_value::<Raw<name::NameEventContent>>( |
|
s.content.clone(), |
|
) |
|
.expect("from_value::<Raw<..>> can never fail") |
|
.deserialize() |
|
.map_err(|_| { |
|
Error::bad_database("Invalid room name event in database.") |
|
})? |
|
.name() |
|
.map(|n| n.to_owned())) |
|
}, |
|
)?, |
|
num_joined_members: (db.rooms.room_members(&room_id).count() as u32).into(), |
|
room_id, |
|
topic: state.get(&(EventType::RoomTopic, "".to_owned())).map_or( |
|
Ok::<_, Error>(None), |
|
|s| { |
|
Ok(Some( |
|
serde_json::from_value::<Raw<topic::TopicEventContent>>( |
|
s.content.clone(), |
|
) |
|
.expect("from_value::<Raw<..>> can never fail") |
|
.deserialize() |
|
.map_err(|_| { |
|
Error::bad_database("Invalid room topic event in database.") |
|
})? |
|
.topic, |
|
)) |
|
}, |
|
)?, |
|
world_readable: state |
|
.get(&(EventType::RoomHistoryVisibility, "".to_owned())) |
|
.map_or(Ok::<_, Error>(false), |s| { |
|
Ok(serde_json::from_value::< |
|
Raw<history_visibility::HistoryVisibilityEventContent>, |
|
>(s.content.clone()) |
|
.expect("from_value::<Raw<..>> can never fail") |
|
.deserialize() |
|
.map_err(|_| { |
|
Error::bad_database( |
|
"Invalid room history visibility event in database.", |
|
) |
|
})? |
|
.history_visibility |
|
== history_visibility::HistoryVisibility::WorldReadable) |
|
})?, |
|
guest_can_join: state |
|
.get(&(EventType::RoomGuestAccess, "".to_owned())) |
|
.map_or(Ok::<_, Error>(false), |s| { |
|
Ok( |
|
serde_json::from_value::<Raw<guest_access::GuestAccessEventContent>>( |
|
s.content.clone(), |
|
) |
|
.expect("from_value::<Raw<..>> can never fail") |
|
.deserialize() |
|
.map_err(|_| { |
|
Error::bad_database("Invalid room guest access event in database.") |
|
})? |
|
.guest_access |
|
== guest_access::GuestAccess::CanJoin, |
|
) |
|
})?, |
|
avatar_url: state |
|
.get(&(EventType::RoomAvatar, "".to_owned())) |
|
.map(|s| { |
|
Ok::<_, Error>( |
|
serde_json::from_value::<Raw<avatar::AvatarEventContent>>( |
|
s.content.clone(), |
|
) |
|
.expect("from_value::<Raw<..>> can never fail") |
|
.deserialize() |
|
.map_err(|_| { |
|
Error::bad_database("Invalid room avatar event in database.") |
|
})? |
|
.url, |
|
) |
|
}) |
|
.transpose()?, |
|
}; |
|
Ok(chunk) |
|
}) |
|
.filter_map(|r: Result<_>| r.ok()) // Filter out buggy rooms |
|
// We need to collect all, so we can sort by member count |
|
.collect::<Vec<_>>(); |
|
|
|
all_rooms.sort_by(|l, r| r.num_joined_members.cmp(&l.num_joined_members)); |
|
|
|
/* |
|
all_rooms.extend_from_slice( |
|
&server_server::send_request( |
|
&db, |
|
"privacytools.io".to_owned(), |
|
ruma::api::federation::v1::get_public_rooms::Request { |
|
limit: Some(20_u32.into()), |
|
since: None, |
|
room_network: ruma::api::federation::v1::get_public_rooms::RoomNetwork::Matrix, |
|
}, |
|
) |
|
.await |
|
? |
|
.chunk |
|
.into_iter() |
|
.map(|c| serde_json::from_str(&serde_json::to_string(&c)?)?) |
|
.collect::<Vec<_>>(), |
|
); |
|
*/ |
|
|
|
let total_room_count_estimate = (all_rooms.len() as u32).into(); |
|
|
|
let chunk = all_rooms |
|
.into_iter() |
|
.skip(since as usize) |
|
.take(limit as usize) |
|
.collect::<Vec<_>>(); |
|
|
|
let prev_batch = if since == 0 { |
|
None |
|
} else { |
|
Some(format!("p{}", since)) |
|
}; |
|
|
|
let next_batch = if chunk.len() < limit as usize { |
|
None |
|
} else { |
|
Some(format!("n{}", since + limit)) |
|
}; |
|
|
|
Ok(get_public_rooms_filtered::Response { |
|
chunk, |
|
prev_batch, |
|
next_batch, |
|
total_room_count_estimate: Some(total_room_count_estimate), |
|
} |
|
.into()) |
|
} |
|
|
|
#[cfg_attr( |
|
feature = "conduit_bin", |
|
get("/_matrix/client/r0/publicRooms", data = "<body>") |
|
)] |
|
pub async fn get_public_rooms_route( |
|
db: State<'_, Database>, |
|
body: Ruma<get_public_rooms::Request>, |
|
) -> ConduitResult<get_public_rooms::Response> { |
|
let Ruma { |
|
body: |
|
get_public_rooms::Request { |
|
limit, |
|
server, |
|
since, |
|
}, |
|
sender_id, |
|
device_id, |
|
json_body, |
|
} = body; |
|
|
|
let get_public_rooms_filtered::Response { |
|
chunk, |
|
prev_batch, |
|
next_batch, |
|
total_room_count_estimate, |
|
} = get_public_rooms_filtered_route( |
|
db, |
|
Ruma { |
|
body: get_public_rooms_filtered::Request { |
|
filter: None, |
|
limit, |
|
room_network: get_public_rooms_filtered::RoomNetwork::Matrix, |
|
server, |
|
since, |
|
}, |
|
sender_id, |
|
device_id, |
|
json_body, |
|
}, |
|
) |
|
.await? |
|
.0; |
|
|
|
Ok(get_public_rooms::Response { |
|
chunk, |
|
prev_batch, |
|
next_batch, |
|
total_room_count_estimate, |
|
} |
|
.into()) |
|
} |
|
|
|
#[cfg_attr( |
|
feature = "conduit_bin", |
|
put("/_matrix/client/r0/directory/list/room/<_>", data = "<body>") |
|
)] |
|
pub async fn set_room_visibility_route( |
|
db: State<'_, Database>, |
|
body: Ruma<set_room_visibility::Request>, |
|
) -> ConduitResult<set_room_visibility::Response> { |
|
match body.visibility { |
|
room::Visibility::Public => db.rooms.set_public(&body.room_id, true)?, |
|
room::Visibility::Private => db.rooms.set_public(&body.room_id, false)?, |
|
} |
|
|
|
Ok(set_room_visibility::Response.into()) |
|
} |
|
|
|
#[cfg_attr( |
|
feature = "conduit_bin", |
|
get("/_matrix/client/r0/directory/list/room/<_>", data = "<body>") |
|
)] |
|
pub async fn get_room_visibility_route( |
|
db: State<'_, Database>, |
|
body: Ruma<get_room_visibility::Request>, |
|
) -> ConduitResult<get_room_visibility::Response> { |
|
Ok(get_room_visibility::Response { |
|
visibility: if db.rooms.is_public_room(&body.room_id)? { |
|
room::Visibility::Public |
|
} else { |
|
room::Visibility::Private |
|
}, |
|
} |
|
.into()) |
|
}
|
|
|