feat: keep track of remote profiles for user directory and local requests

Signed-off-by: strawberry <strawberry@puppygock.gay>
This commit is contained in:
strawberry 2024-01-11 20:39:37 -05:00 committed by June
parent ef8dcdfe3c
commit b28a2fad97
6 changed files with 110 additions and 36 deletions

View file

@ -1426,7 +1426,7 @@ pub async fn leave_room(user_id: &UserId, room_id: &RoomId, reason: Option<Strin
.update_membership( .update_membership(
room_id, room_id,
user_id, user_id,
MembershipState::Leave, RoomMemberEventContent::new(MembershipState::Leave),
user_id, user_id,
last_state, last_state,
true, true,
@ -1461,7 +1461,7 @@ pub async fn leave_room(user_id: &UserId, room_id: &RoomId, reason: Option<Strin
.update_membership( .update_membership(
room_id, room_id,
user_id, user_id,
MembershipState::Leave, RoomMemberEventContent::new(MembershipState::Leave),
user_id, user_id,
None, None,
true, true,

View file

@ -103,15 +103,18 @@ pub async fn set_displayname_route(
Ok(set_display_name::v3::Response {}) Ok(set_display_name::v3::Response {})
} }
/// # `GET /_matrix/client/r0/profile/{userId}/displayname` /// # `GET /_matrix/client/v3/profile/{userId}/displayname`
/// ///
/// Returns the displayname of the user. /// Returns the displayname of the user.
/// ///
/// - If user is on another server: Fetches displayname over federation /// - If user is on another server and we do not have a local copy already
/// fetch displayname over federation
pub async fn get_displayname_route( pub async fn get_displayname_route(
body: Ruma<get_display_name::v3::Request>, body: Ruma<get_display_name::v3::Request>,
) -> Result<get_display_name::v3::Response> { ) -> Result<get_display_name::v3::Response> {
if body.user_id.server_name() != services().globals.server_name() { if (services().users.exists(&body.user_id)?)
&& (body.user_id.server_name() != services().globals.server_name())
{
let response = services() let response = services()
.sending .sending
.send_federation_request( .send_federation_request(
@ -123,6 +126,18 @@ pub async fn get_displayname_route(
) )
.await?; .await?;
// Create and update our local copy of the user
services().users.create(&body.user_id, None)?;
services()
.users
.set_displayname(&body.user_id, response.displayname.clone())?;
services()
.users
.set_avatar_url(&body.user_id, response.avatar_url)?;
services()
.users
.set_blurhash(&body.user_id, response.blurhash)?;
return Ok(get_display_name::v3::Response { return Ok(get_display_name::v3::Response {
displayname: response.displayname, displayname: response.displayname,
}); });
@ -225,15 +240,18 @@ pub async fn set_avatar_url_route(
Ok(set_avatar_url::v3::Response {}) Ok(set_avatar_url::v3::Response {})
} }
/// # `GET /_matrix/client/r0/profile/{userId}/avatar_url` /// # `GET /_matrix/client/v3/profile/{userId}/avatar_url`
/// ///
/// Returns the avatar_url and blurhash of the user. /// Returns the avatar_url and blurhash of the user.
/// ///
/// - If user is on another server: Fetches avatar_url and blurhash over federation /// - If user is on another server and we do not have a local copy already
/// fetch avatar_url and blurhash over federation
pub async fn get_avatar_url_route( pub async fn get_avatar_url_route(
body: Ruma<get_avatar_url::v3::Request>, body: Ruma<get_avatar_url::v3::Request>,
) -> Result<get_avatar_url::v3::Response> { ) -> Result<get_avatar_url::v3::Response> {
if body.user_id.server_name() != services().globals.server_name() { if (services().users.exists(&body.user_id)?)
&& (body.user_id.server_name() != services().globals.server_name())
{
let response = services() let response = services()
.sending .sending
.send_federation_request( .send_federation_request(
@ -245,6 +263,18 @@ pub async fn get_avatar_url_route(
) )
.await?; .await?;
// Create and update our local copy of the user
services().users.create(&body.user_id, None)?;
services()
.users
.set_displayname(&body.user_id, response.displayname)?;
services()
.users
.set_avatar_url(&body.user_id, response.avatar_url.clone())?;
services()
.users
.set_blurhash(&body.user_id, response.blurhash.clone())?;
return Ok(get_avatar_url::v3::Response { return Ok(get_avatar_url::v3::Response {
avatar_url: response.avatar_url, avatar_url: response.avatar_url,
blurhash: response.blurhash, blurhash: response.blurhash,
@ -257,15 +287,18 @@ pub async fn get_avatar_url_route(
}) })
} }
/// # `GET /_matrix/client/r0/profile/{userId}` /// # `GET /_matrix/client/v3/profile/{userId}`
/// ///
/// Returns the displayname, avatar_url and blurhash of the user. /// Returns the displayname, avatar_url and blurhash of the user.
/// ///
/// - If user is on another server: Fetches profile over federation /// - If user is on another server and we do not have a local copy already,
/// fetch profile over federation.
pub async fn get_profile_route( pub async fn get_profile_route(
body: Ruma<get_profile::v3::Request>, body: Ruma<get_profile::v3::Request>,
) -> Result<get_profile::v3::Response> { ) -> Result<get_profile::v3::Response> {
if body.user_id.server_name() != services().globals.server_name() { if (services().users.exists(&body.user_id)?)
&& (body.user_id.server_name() != services().globals.server_name())
{
let response = services() let response = services()
.sending .sending
.send_federation_request( .send_federation_request(
@ -277,6 +310,18 @@ pub async fn get_profile_route(
) )
.await?; .await?;
// Create and update our local copy of the user
services().users.create(&body.user_id, None)?;
services()
.users
.set_displayname(&body.user_id, response.displayname.clone())?;
services()
.users
.set_avatar_url(&body.user_id, response.avatar_url.clone())?;
services()
.users
.set_blurhash(&body.user_id, response.blurhash.clone())?;
return Ok(get_profile::v3::Response { return Ok(get_profile::v3::Response {
displayname: response.displayname, displayname: response.displayname,
avatar_url: response.avatar_url, avatar_url: response.avatar_url,
@ -285,7 +330,7 @@ pub async fn get_profile_route(
} }
if !services().users.exists(&body.user_id)? { if !services().users.exists(&body.user_id)? {
// Return 404 if this user doesn't exist // Return 404 if this user doesn't exist and we couldn't fetch it over federation
return Err(Error::BadRequest( return Err(Error::BadRequest(
ErrorKind::NotFound, ErrorKind::NotFound,
"Profile was not found.", "Profile was not found.",

View file

@ -1883,7 +1883,7 @@ pub async fn create_invite_route(
.update_membership( .update_membership(
&body.room_id, &body.room_id,
&invited_user, &invited_user,
MembershipState::Invite, RoomMemberEventContent::new(MembershipState::Invite),
&sender, &sender,
Some(invite_state), Some(invite_state),
true, true,

View file

@ -8,14 +8,13 @@ pub use data::Data;
use ruma::{ use ruma::{
api::client::error::ErrorKind, api::client::error::ErrorKind,
events::{ events::{
room::{create::RoomCreateEventContent, member::MembershipState}, room::{create::RoomCreateEventContent, member::RoomMemberEventContent},
AnyStrippedStateEvent, StateEventType, TimelineEventType, AnyStrippedStateEvent, StateEventType, TimelineEventType,
}, },
serde::Raw, serde::Raw,
state_res::{self, StateMap}, state_res::{self, StateMap},
EventId, OwnedEventId, RoomId, RoomVersionId, UserId, EventId, OwnedEventId, RoomId, RoomVersionId, UserId,
}; };
use serde::Deserialize;
use tokio::sync::MutexGuard; use tokio::sync::MutexGuard;
use tracing::warn; use tracing::warn;
@ -59,14 +58,9 @@ impl Service {
match pdu.kind { match pdu.kind {
TimelineEventType::RoomMember => { TimelineEventType::RoomMember => {
#[derive(Deserialize)] let membership_event =
struct ExtractMembership { match serde_json::from_str::<RoomMemberEventContent>(pdu.content.get()) {
membership: MembershipState, Ok(e) => e,
}
let membership =
match serde_json::from_str::<ExtractMembership>(pdu.content.get()) {
Ok(e) => e.membership,
Err(_) => continue, Err(_) => continue,
}; };
@ -83,7 +77,14 @@ impl Service {
services() services()
.rooms .rooms
.state_cache .state_cache
.update_membership(room_id, &user_id, membership, &pdu.sender, None, false) .update_membership(
room_id,
&user_id,
membership_event,
&pdu.sender,
None,
false,
)
.await?; .await?;
} }
TimelineEventType::SpaceChild => { TimelineEventType::SpaceChild => {

View file

@ -4,10 +4,14 @@ use std::{collections::HashSet, sync::Arc};
pub use data::Data; pub use data::Data;
use ruma::{ use ruma::{
api::federation::{self, query::get_profile_information::v1::ProfileField},
events::{ events::{
direct::DirectEvent, direct::DirectEvent,
ignored_user_list::IgnoredUserListEvent, ignored_user_list::IgnoredUserListEvent,
room::{create::RoomCreateEventContent, member::MembershipState}, room::{
create::RoomCreateEventContent,
member::{MembershipState, RoomMemberEventContent},
},
AnyStrippedStateEvent, AnySyncStateEvent, GlobalAccountDataEventType, AnyStrippedStateEvent, AnySyncStateEvent, GlobalAccountDataEventType,
RoomAccountDataEventType, StateEventType, RoomAccountDataEventType, StateEventType,
}, },
@ -29,15 +33,39 @@ impl Service {
&self, &self,
room_id: &RoomId, room_id: &RoomId,
user_id: &UserId, user_id: &UserId,
membership: MembershipState, membership_event: RoomMemberEventContent,
sender: &UserId, sender: &UserId,
last_state: Option<Vec<Raw<AnyStrippedStateEvent>>>, last_state: Option<Vec<Raw<AnyStrippedStateEvent>>>,
update_joined_count: bool, update_joined_count: bool,
) -> Result<()> { ) -> Result<()> {
let membership = membership_event.membership;
// Keep track what remote users exist by adding them as "deactivated" users // Keep track what remote users exist by adding them as "deactivated" users
if user_id.server_name() != services().globals.server_name() { if user_id.server_name() != services().globals.server_name() {
services().users.create(user_id, None)?; services().users.create(user_id, None)?;
// TODO: displayname, avatar url // Try to update our local copy of the user if ours does not match
if ((services().users.displayname(user_id)? != membership_event.displayname)
|| (services().users.avatar_url(user_id)? != membership_event.avatar_url)
|| (services().users.blurhash(user_id)? != membership_event.blurhash))
&& (membership != MembershipState::Leave)
{
let response = services()
.sending
.send_federation_request(
user_id.server_name(),
federation::query::get_profile_information::v1::Request {
user_id: user_id.into(),
field: Some(ProfileField::AvatarUrl),
},
)
.await?;
services()
.users
.set_displayname(user_id, response.displayname.clone())?;
services()
.users
.set_avatar_url(user_id, response.avatar_url)?;
services().users.set_blurhash(user_id, response.blurhash)?;
};
} }
match &membership { match &membership {

View file

@ -18,7 +18,9 @@ use ruma::{
events::{ events::{
push_rules::PushRulesEvent, push_rules::PushRulesEvent,
room::{ room::{
create::RoomCreateEventContent, encrypted::Relation, member::MembershipState, create::RoomCreateEventContent,
encrypted::Relation,
member::{MembershipState, RoomMemberEventContent},
power_levels::RoomPowerLevelsEventContent, power_levels::RoomPowerLevelsEventContent,
}, },
GlobalAccountDataEventType, StateEventType, TimelineEventType, GlobalAccountDataEventType, StateEventType, TimelineEventType,
@ -453,17 +455,15 @@ impl Service {
} }
TimelineEventType::RoomMember => { TimelineEventType::RoomMember => {
if let Some(state_key) = &pdu.state_key { if let Some(state_key) = &pdu.state_key {
#[derive(Deserialize)]
struct ExtractMembership {
membership: MembershipState,
}
// if the state_key fails // if the state_key fails
let target_user_id = UserId::parse(state_key.clone()) let target_user_id = UserId::parse(state_key.clone())
.expect("This state_key was previously validated"); .expect("This state_key was previously validated");
let content = serde_json::from_str::<ExtractMembership>(pdu.content.get()) let content = serde_json::from_str::<RoomMemberEventContent>(pdu.content.get())
.map_err(|_| Error::bad_database("Invalid content in pdu."))?; .map_err(|e| {
error!("Invalid room member event content in pdu: {e}");
Error::bad_database("Invalid room member event content in pdu.")
})?;
let invite_state = match content.membership { let invite_state = match content.membership {
MembershipState::Invite => { MembershipState::Invite => {
@ -481,7 +481,7 @@ impl Service {
.update_membership( .update_membership(
&pdu.room_id, &pdu.room_id,
&target_user_id, &target_user_id,
content.membership, content,
&pdu.sender, &pdu.sender,
invite_state, invite_state,
true, true,