Compare commits

...

4 Commits

Author SHA1 Message Date
Ginger b0bb6e9fa5 fix: Add unstable feature flag 2026-05-13 13:43:15 -04:00
Ginger 03a178a794 chore: News fragment 2026-05-13 13:41:10 -04:00
Ginger 0eeb4243be feat: Add support for MSC4466 2026-05-13 13:40:16 -04:00
Ginger 6f83925a4f fix: Use correct service name in membership service 2026-05-13 08:53:15 -04:00
6 changed files with 139 additions and 60 deletions
Generated
+11 -11
View File
@@ -4528,7 +4528,7 @@ dependencies = [
[[package]]
name = "ruma"
version = "0.15.1"
source = "git+https://github.com/ruma/ruma.git?rev=9c9dccc93f054bbd28f23f630223fffa6289ecbc#9c9dccc93f054bbd28f23f630223fffa6289ecbc"
source = "git+https://github.com/ruma/ruma.git?rev=3ecd80b92794d2d93f657a7b3db62d4be237526b#3ecd80b92794d2d93f657a7b3db62d4be237526b"
dependencies = [
"assign",
"js_int",
@@ -4547,7 +4547,7 @@ dependencies = [
[[package]]
name = "ruma-appservice-api"
version = "0.15.0"
source = "git+https://github.com/ruma/ruma.git?rev=9c9dccc93f054bbd28f23f630223fffa6289ecbc#9c9dccc93f054bbd28f23f630223fffa6289ecbc"
source = "git+https://github.com/ruma/ruma.git?rev=3ecd80b92794d2d93f657a7b3db62d4be237526b#3ecd80b92794d2d93f657a7b3db62d4be237526b"
dependencies = [
"js_int",
"ruma-common",
@@ -4559,7 +4559,7 @@ dependencies = [
[[package]]
name = "ruma-client-api"
version = "0.23.1"
source = "git+https://github.com/ruma/ruma.git?rev=9c9dccc93f054bbd28f23f630223fffa6289ecbc#9c9dccc93f054bbd28f23f630223fffa6289ecbc"
source = "git+https://github.com/ruma/ruma.git?rev=3ecd80b92794d2d93f657a7b3db62d4be237526b#3ecd80b92794d2d93f657a7b3db62d4be237526b"
dependencies = [
"as_variant",
"assign",
@@ -4581,7 +4581,7 @@ dependencies = [
[[package]]
name = "ruma-common"
version = "0.18.0"
source = "git+https://github.com/ruma/ruma.git?rev=9c9dccc93f054bbd28f23f630223fffa6289ecbc#9c9dccc93f054bbd28f23f630223fffa6289ecbc"
source = "git+https://github.com/ruma/ruma.git?rev=3ecd80b92794d2d93f657a7b3db62d4be237526b#3ecd80b92794d2d93f657a7b3db62d4be237526b"
dependencies = [
"as_variant",
"base64 0.22.1",
@@ -4614,7 +4614,7 @@ dependencies = [
[[package]]
name = "ruma-events"
version = "0.33.0"
source = "git+https://github.com/ruma/ruma.git?rev=9c9dccc93f054bbd28f23f630223fffa6289ecbc#9c9dccc93f054bbd28f23f630223fffa6289ecbc"
source = "git+https://github.com/ruma/ruma.git?rev=3ecd80b92794d2d93f657a7b3db62d4be237526b#3ecd80b92794d2d93f657a7b3db62d4be237526b"
dependencies = [
"as_variant",
"indexmap",
@@ -4635,7 +4635,7 @@ dependencies = [
[[package]]
name = "ruma-federation-api"
version = "0.14.0"
source = "git+https://github.com/ruma/ruma.git?rev=9c9dccc93f054bbd28f23f630223fffa6289ecbc#9c9dccc93f054bbd28f23f630223fffa6289ecbc"
source = "git+https://github.com/ruma/ruma.git?rev=3ecd80b92794d2d93f657a7b3db62d4be237526b#3ecd80b92794d2d93f657a7b3db62d4be237526b"
dependencies = [
"bytes",
"headers",
@@ -4658,7 +4658,7 @@ dependencies = [
[[package]]
name = "ruma-identifiers-validation"
version = "0.12.1"
source = "git+https://github.com/ruma/ruma.git?rev=9c9dccc93f054bbd28f23f630223fffa6289ecbc#9c9dccc93f054bbd28f23f630223fffa6289ecbc"
source = "git+https://github.com/ruma/ruma.git?rev=3ecd80b92794d2d93f657a7b3db62d4be237526b#3ecd80b92794d2d93f657a7b3db62d4be237526b"
dependencies = [
"js_int",
"thiserror",
@@ -4667,7 +4667,7 @@ dependencies = [
[[package]]
name = "ruma-macros"
version = "0.18.0"
source = "git+https://github.com/ruma/ruma.git?rev=9c9dccc93f054bbd28f23f630223fffa6289ecbc#9c9dccc93f054bbd28f23f630223fffa6289ecbc"
source = "git+https://github.com/ruma/ruma.git?rev=3ecd80b92794d2d93f657a7b3db62d4be237526b#3ecd80b92794d2d93f657a7b3db62d4be237526b"
dependencies = [
"as_variant",
"cfg-if",
@@ -4683,7 +4683,7 @@ dependencies = [
[[package]]
name = "ruma-push-gateway-api"
version = "0.14.0"
source = "git+https://github.com/ruma/ruma.git?rev=9c9dccc93f054bbd28f23f630223fffa6289ecbc#9c9dccc93f054bbd28f23f630223fffa6289ecbc"
source = "git+https://github.com/ruma/ruma.git?rev=3ecd80b92794d2d93f657a7b3db62d4be237526b#3ecd80b92794d2d93f657a7b3db62d4be237526b"
dependencies = [
"js_int",
"ruma-common",
@@ -4695,7 +4695,7 @@ dependencies = [
[[package]]
name = "ruma-signatures"
version = "0.20.0"
source = "git+https://github.com/ruma/ruma.git?rev=9c9dccc93f054bbd28f23f630223fffa6289ecbc#9c9dccc93f054bbd28f23f630223fffa6289ecbc"
source = "git+https://github.com/ruma/ruma.git?rev=3ecd80b92794d2d93f657a7b3db62d4be237526b#3ecd80b92794d2d93f657a7b3db62d4be237526b"
dependencies = [
"base64 0.22.1",
"ed25519-dalek",
@@ -4711,7 +4711,7 @@ dependencies = [
[[package]]
name = "ruma-state-res"
version = "0.16.0"
source = "git+https://github.com/ruma/ruma.git?rev=9c9dccc93f054bbd28f23f630223fffa6289ecbc#9c9dccc93f054bbd28f23f630223fffa6289ecbc"
source = "git+https://github.com/ruma/ruma.git?rev=3ecd80b92794d2d93f657a7b3db62d4be237526b#3ecd80b92794d2d93f657a7b3db62d4be237526b"
dependencies = [
"js_int",
"ruma-common",
+2 -1
View File
@@ -352,7 +352,7 @@ version = "1.1.1"
[workspace.dependencies.ruma]
# version = "0.14.1"
git = "https://github.com/ruma/ruma.git"
rev = "9c9dccc93f054bbd28f23f630223fffa6289ecbc"
rev = "3ecd80b92794d2d93f657a7b3db62d4be237526b"
features = [
"appservice-api-c",
"client-api",
@@ -388,6 +388,7 @@ features = [
"unstable-msc4293",
"unstable-msc4406",
"unstable-msc4439",
"unstable-msc4466",
"unstable-extensible-events",
]
+1
View File
@@ -0,0 +1 @@
Added support for MSC4466, which allows clients to customize how changes to a user's global profile are propagated. Contributed by @ginger.
+123 -47
View File
@@ -8,12 +8,12 @@
UserId,
api::{
client::profile::{
delete_profile_field, get_profile, get_profile_field, set_profile_field,
PropagateTo, delete_profile_field, get_profile, get_profile_field, set_profile_field,
},
federation,
},
assign,
events::room::member::{MembershipState, RoomMemberEventContent},
events::room::member::MembershipState,
presence::PresenceState,
profile::{ProfileFieldName, ProfileFieldValue},
};
@@ -63,8 +63,13 @@ pub(crate) async fn set_profile_field_route(
return Err!(Request(InvalidParam("You may not change a remote user's profile data.")));
}
set_profile_field(&services, &body.user_id, ProfileFieldChange::Set(body.value.clone()))
.await?;
set_profile_field(
&services,
&body.user_id,
ProfileFieldChange::Set(body.value.clone()),
body.propagate_to.clone(),
)
.await?;
Ok(set_profile_field::v3::Response::new())
}
@@ -84,8 +89,13 @@ pub(crate) async fn delete_profile_field_route(
return Err!(Request(InvalidParam("You may not change a remote user's profile data.")));
}
set_profile_field(&services, &body.user_id, ProfileFieldChange::Delete(body.field.clone()))
.await?;
set_profile_field(
&services,
&body.user_id,
ProfileFieldChange::Delete(body.field.clone()),
body.propagate_to.clone(),
)
.await?;
Ok(delete_profile_field::v3::Response::new())
}
@@ -120,7 +130,13 @@ async fn fetch_full_profile(
continue;
};
let _ = set_profile_field(services, user_id, ProfileFieldChange::Set(value)).await;
let _ = set_profile_field(
services,
user_id,
ProfileFieldChange::Set(value),
PropagateTo::None,
)
.await;
}
Some(BTreeMap::from_iter(response))
@@ -154,8 +170,13 @@ async fn fetch_profile_field(
if let Some(value) = response.get(field.as_str()).map(ToOwned::to_owned) {
if let Ok(value) = ProfileFieldValue::new(field.as_str(), value) {
let _ = set_profile_field(services, user_id, ProfileFieldChange::Set(value.clone()))
.await;
let _ = set_profile_field(
services,
user_id,
ProfileFieldChange::Set(value.clone()),
PropagateTo::None,
)
.await;
Ok(Some(value))
} else {
@@ -164,7 +185,13 @@ async fn fetch_profile_field(
)))
}
} else {
let _ = set_profile_field(services, user_id, ProfileFieldChange::Delete(field)).await;
let _ = set_profile_field(
services,
user_id,
ProfileFieldChange::Delete(field),
PropagateTo::None,
)
.await;
Ok(None)
}
@@ -257,6 +284,7 @@ async fn set_profile_field(
services: &Services,
user_id: &UserId,
change: ProfileFieldChange,
propagate_to: PropagateTo,
) -> Result<()> {
const MAX_KEY_LENGTH_BYTES: usize = 255;
const MAX_PROFILE_LENGTH_BYTES: usize = 65536;
@@ -304,6 +332,91 @@ async fn set_profile_field(
}
}
// If the user is local and changed their displayname or avatar_url, update it
// in all their joined rooms. This is done before updating their profile data
// so we can check the old value of the field if `propagate_to` is `unchanged`.
if matches!(field_name, ProfileFieldName::AvatarUrl | ProfileFieldName::DisplayName)
&& matches!(propagate_to, PropagateTo::All | PropagateTo::Unchanged)
&& services.users.is_active_local(user_id).await
{
let current_displayname = services.users.displayname(user_id).await.ok();
let current_avatar_url = services.users.avatar_url(user_id).await.ok();
let mut all_joined_rooms = services.rooms.state_cache.rooms_joined(user_id);
while let Some(room_id) = all_joined_rooms.next().await {
// TODO: this clobbers any custom fields on the event content
let mut current_membership = services
.rooms
.state_accessor
.get_member(&room_id, user_id)
.await
.expect("should be able to fetch membership event for joined room");
assert_eq!(
current_membership.membership,
MembershipState::Join,
"user should be joined"
);
// If `propagate_to` is `unchanged`, and the current value of the field we're
// updating was changed from its global value in this room, skip it.
if matches!(propagate_to, PropagateTo::Unchanged) {
let field_changed_from_global = match field_name {
| ProfileFieldName::AvatarUrl =>
current_membership.avatar_url.as_ref() != current_avatar_url.as_ref(),
| ProfileFieldName::DisplayName =>
current_membership.displayname.as_ref() != current_displayname.as_ref(),
| _ => unreachable!(),
};
if field_changed_from_global {
continue;
}
}
let state_lock = services.rooms.state.mutex.lock(room_id.as_str()).await;
// Preserve keys in accordance with the key copying rules
current_membership.reason = None;
current_membership.join_authorized_via_users_server = None;
match &change {
| ProfileFieldChange::Set(ProfileFieldValue::AvatarUrl(avatar_url)) => {
current_membership.avatar_url = Some(avatar_url.clone());
},
| ProfileFieldChange::Set(ProfileFieldValue::DisplayName(displayname)) => {
current_membership.displayname = Some(displayname.clone());
},
| ProfileFieldChange::Delete(ProfileFieldName::AvatarUrl) => {
current_membership.avatar_url = None;
},
| ProfileFieldChange::Delete(ProfileFieldName::DisplayName) => {
current_membership.displayname = None;
},
| _ => unreachable!(),
}
let _ = services
.rooms
.timeline
.build_and_append_pdu(
PartialPdu::state(user_id.to_string(), &current_membership),
user_id,
Some(&room_id),
&state_lock,
)
.await;
}
if services.config.allow_local_presence {
// Send a presence EDU to indicate the profile changed
let _ = services
.presence
.ping_presence(user_id, &PresenceState::Online)
.await;
}
}
match change {
| ProfileFieldChange::Set(ProfileFieldValue::DisplayName(displayname)) => {
services
@@ -337,42 +450,5 @@ async fn set_profile_field(
},
}
// If the user is local and changed their displayname or avatar_url, update it
// in all their joined rooms
if matches!(field_name, ProfileFieldName::AvatarUrl | ProfileFieldName::DisplayName)
&& services.users.is_active_local(user_id).await
{
let displayname = services.users.displayname(user_id).await.ok();
let avatar_url = services.users.avatar_url(user_id).await.ok();
let membership_content = assign!(
RoomMemberEventContent::new(MembershipState::Join), { displayname, avatar_url }
);
let mut all_joined_rooms = services.rooms.state_cache.rooms_joined(user_id);
while let Some(room_id) = all_joined_rooms.next().await {
let state_lock = services.rooms.state.mutex.lock(room_id.as_str()).await;
let _ = services
.rooms
.timeline
.build_and_append_pdu(
PartialPdu::state(user_id.to_string(), &membership_content),
user_id,
Some(&room_id),
&state_lock,
)
.await;
}
if services.config.allow_local_presence {
// Send a presence EDU to indicate the profile changed
let _ = services
.presence
.ping_presence(user_id, &PresenceState::Online)
.await;
}
}
Ok(())
}
+1
View File
@@ -42,5 +42,6 @@ pub fn unstable_features() -> BTreeMap<String, bool> {
("org.matrix.simplified_msc3575".to_owned(), true), /* Simplified Sliding sync (https://github.com/matrix-org/matrix-spec-proposals/pull/4186) */
("uk.timedout.msc4323".to_owned(), true), /* agnostic suspend (https://github.com/matrix-org/matrix-spec-proposals/pull/4323) */
("org.matrix.msc4155".to_owned(), true), /* invite filtering (https://github.com/matrix-org/matrix-spec-proposals/pull/4155) */
("computer.gingershaped.msc4466".to_owned(), true), /* profile change propagation (https://github.com/matrix-org/matrix-spec-proposals/pull/4466) */
])
}
+1 -1
View File
@@ -73,7 +73,7 @@ fn build(args: crate::Args<'_>) -> Result<Arc<Self>> {
db: args.db.clone(),
antispam: args.depend::<antispam::Service>("antispam"),
globals: args.depend::<globals::Service>("globals"),
metadata: args.depend::<metadata::Service>("metadata"),
metadata: args.depend::<metadata::Service>("rooms::metadata"),
outlier: args.depend::<outlier::Service>("rooms::outlier"),
sending: args.depend::<sending::Service>("sending"),
server_keys: args.depend::<server_keys::Service>("server_keys"),