add MSC3266 room summary API support

Signed-off-by: strawberry <strawberry@puppygock.gay>
This commit is contained in:
strawberry 2024-07-03 12:40:08 -04:00
parent 82acc7c0b0
commit b0eec60724
5 changed files with 174 additions and 21 deletions

View file

@ -1,5 +1,8 @@
use axum_client_ip::InsecureClientIp;
use conduit::{warn, RumaResponse};
use ruma::{
api::client::{error::ErrorKind, membership::mutual_rooms},
api::client::{error::ErrorKind, membership::mutual_rooms, room::get_summary},
events::room::member::MembershipState,
OwnedRoomId,
};
@ -12,8 +15,9 @@ use crate::{services, Error, Result, Ruma};
/// TODO: Implement pagination, currently this just returns everything
///
/// An implementation of [MSC2666](https://github.com/matrix-org/matrix-spec-proposals/pull/2666)
#[tracing::instrument(skip_all, fields(%client), name = "mutual_rooms")]
pub(crate) async fn get_mutual_rooms_route(
body: Ruma<mutual_rooms::unstable::Request>,
InsecureClientIp(client): InsecureClientIp, body: Ruma<mutual_rooms::unstable::Request>,
) -> Result<mutual_rooms::unstable::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
@ -43,3 +47,116 @@ pub(crate) async fn get_mutual_rooms_route(
next_batch_token: None,
})
}
/// # `GET /_matrix/client/unstable/im.nheko.summary/rooms/{roomIdOrAlias}/summary`
///
/// Returns a short description of the state of a room.
///
/// This is the "wrong" endpoint that some implementations/clients may use
/// according to the MSC. Request and response bodies are the same as
/// `get_room_summary`.
///
/// An implementation of [MSC3266](https://github.com/matrix-org/matrix-spec-proposals/pull/3266)
pub(crate) async fn get_room_summary_legacy(
InsecureClientIp(client): InsecureClientIp, body: Ruma<get_summary::msc3266::Request>,
) -> Result<RumaResponse<get_summary::msc3266::Response>> {
get_room_summary(InsecureClientIp(client), body)
.await
.map(RumaResponse)
}
/// # `GET /_matrix/client/unstable/im.nheko.summary/summary/{roomIdOrAlias}`
///
/// Returns a short description of the state of a room.
///
/// TODO: support fetching remote room info if we don't know the room
///
/// An implementation of [MSC3266](https://github.com/matrix-org/matrix-spec-proposals/pull/3266)
#[tracing::instrument(skip_all, fields(%client), name = "room_summary")]
pub(crate) async fn get_room_summary(
InsecureClientIp(client): InsecureClientIp, body: Ruma<get_summary::msc3266::Request>,
) -> Result<get_summary::msc3266::Response> {
let sender_user = body.sender_user.as_ref();
let room_id = services()
.rooms
.alias
.resolve(&body.room_id_or_alias)
.await?;
if !services().rooms.metadata.exists(&room_id)? {
return Err(Error::BadRequest(ErrorKind::NotFound, "Room is unknown to this server"));
}
if sender_user.is_none()
&& !services()
.rooms
.state_accessor
.is_world_readable(&room_id)
.unwrap_or(false)
{
return Err(Error::BadRequest(
ErrorKind::forbidden(),
"Room is not world readable, authentication is required",
));
}
Ok(get_summary::msc3266::Response {
room_id: room_id.clone(),
canonical_alias: services()
.rooms
.state_accessor
.get_canonical_alias(&room_id)?,
avatar_url: services()
.rooms
.state_accessor
.get_avatar(&room_id)?
.into_option()
.unwrap_or_default()
.url,
guest_can_join: services().rooms.state_accessor.guest_can_join(&room_id)?,
name: services()
.rooms
.state_accessor
.get_name(&room_id)
.unwrap_or(None),
num_joined_members: services()
.rooms
.state_cache
.room_joined_count(&room_id)
.unwrap_or_default()
.unwrap_or_else(|| {
warn!("Room {room_id} has no member count");
0
})
.try_into()
.expect("user count should not be that big"),
topic: services()
.rooms
.state_accessor
.get_room_topic(&room_id)
.unwrap_or(None),
world_readable: services()
.rooms
.state_accessor
.is_world_readable(&room_id)
.unwrap_or(false),
join_rule: services().rooms.state_accessor.get_join_rule(&room_id)?.0,
room_type: services().rooms.state_accessor.get_room_type(&room_id)?,
room_version: Some(services().rooms.state.get_room_version(&room_id)?),
membership: if let Some(sender_user) = sender_user {
services()
.rooms
.state_accessor
.get_member(&room_id, sender_user)?
.map_or_else(|| Some(MembershipState::Leave), |content| Some(content.membership))
} else {
None
},
encryption: services()
.rooms
.state_accessor
.get_room_encryption(&room_id)
.unwrap_or_else(|_e| None),
})
}

View file

@ -172,7 +172,6 @@ pub fn build(router: Router, server: &Server) -> Router {
.ruma_route(client::get_key_changes_route)
.ruma_route(client::get_pushers_route)
.ruma_route(client::set_pushers_route)
// .ruma_route(client::third_party_route)
.ruma_route(client::upgrade_room_route)
.ruma_route(client::get_threads_route)
.ruma_route(client::get_relating_events_with_rel_type_and_event_type_route)
@ -180,6 +179,11 @@ pub fn build(router: Router, server: &Server) -> Router {
.ruma_route(client::get_relating_events_route)
.ruma_route(client::get_hierarchy_route)
.ruma_route(client::get_mutual_rooms_route)
.ruma_route(client::get_room_summary)
.route(
"/_matrix/client/unstable/im.nheko.summary/rooms/:room_id_or_alias/summary",
get(client::get_room_summary_legacy)
)
.ruma_route(client::well_known_support)
.ruma_route(client::well_known_client)
.route("/_conduwuit/server_version", get(client::conduwuit_server_version))

View file

@ -14,6 +14,8 @@ use ruma::{
room::{
avatar::RoomAvatarEventContent,
canonical_alias::RoomCanonicalAliasEventContent,
create::RoomCreateEventContent,
encryption::RoomEncryptionEventContent,
guest_access::{GuestAccess, RoomGuestAccessEventContent},
history_visibility::{HistoryVisibility, RoomHistoryVisibilityEventContent},
join_rules::{AllowRule, JoinRule, RoomJoinRulesEventContent, RoomMembership},
@ -24,8 +26,10 @@ use ruma::{
},
StateEventType,
},
room::RoomType,
space::SpaceRoomJoinRule,
EventId, OwnedRoomAliasId, OwnedRoomId, OwnedServerName, OwnedUserId, RoomId, ServerName, UserId,
EventEncryptionAlgorithm, EventId, OwnedRoomAliasId, OwnedRoomId, OwnedServerName, OwnedUserId, RoomId, ServerName,
UserId,
};
use serde_json::value::to_raw_value;
@ -451,4 +455,31 @@ impl Service {
}
room_ids
}
pub fn get_room_type(&self, room_id: &RoomId) -> Result<Option<RoomType>> {
Ok(self
.room_state_get(room_id, &StateEventType::RoomCreate, "")?
.map(|s| {
serde_json::from_str::<RoomCreateEventContent>(s.content.get()).map_err(|e| {
error!("Invalid room create event in database: {e}");
Error::BadDatabase("Invalid room create event in database.")
})
})
.transpose()?
.and_then(|e| e.room_type))
}
/// Gets the room's encryption algorithm if `m.room.encryption` state event
/// is found
pub fn get_room_encryption(&self, room_id: &RoomId) -> Result<Option<EventEncryptionAlgorithm>> {
self.room_state_get(room_id, &StateEventType::RoomEncryption, "")?
.map_or(Ok(None), |s| {
serde_json::from_str::<RoomEncryptionEventContent>(s.content.get())
.map(|content| Some(content.algorithm))
.map_err(|e| {
error!("Invalid room encryption event in database: {e}");
Error::BadDatabase("Invalid room encryption event in database.")
})
})
}
}