From 2aa6eb608b8c981f35a0bf77c617fd0856355545 Mon Sep 17 00:00:00 2001 From: pancakes Date: Sun, 26 Jan 2025 17:22:06 +1000 Subject: [PATCH] [backend/federation] Federate user pronouns --- .../Core/Configuration/Constants.cs | 1 + .../Federation/ActivityPub/UserRenderer.cs | 9 +++++++- .../ActivityStreams/Contexts/iceshrimp.json | 3 ++- .../ActivityStreams/Types/ASAttachment.cs | 19 ++++++++++++++++ .../Core/Services/UserService.cs | 22 ++++++++++++++++++- 5 files changed, 51 insertions(+), 3 deletions(-) diff --git a/Iceshrimp.Backend/Core/Configuration/Constants.cs b/Iceshrimp.Backend/Core/Configuration/Constants.cs index 5cbac8d0..496405fc 100644 --- a/Iceshrimp.Backend/Core/Configuration/Constants.cs +++ b/Iceshrimp.Backend/Core/Configuration/Constants.cs @@ -21,6 +21,7 @@ public static class Constants public const string MastodonNs = "http://joinmastodon.org/ns"; public const string MisskeyNs = "https://misskey-hub.net/ns"; public const string FedibirdNs = "http://fedibird.com/ns"; + public const string PancakesNs = "https://ns.pancakes.gay/as/"; public static readonly string[] SystemUsers = ["instance.actor", "relay.actor"]; public const string APMime = "application/activity+json"; diff --git a/Iceshrimp.Backend/Core/Federation/ActivityPub/UserRenderer.cs b/Iceshrimp.Backend/Core/Federation/ActivityPub/UserRenderer.cs index 14210683..4ba50b11 100644 --- a/Iceshrimp.Backend/Core/Federation/ActivityPub/UserRenderer.cs +++ b/Iceshrimp.Backend/Core/Federation/ActivityPub/UserRenderer.cs @@ -80,7 +80,14 @@ public class UserRenderer( var attachments = profile?.Fields .Select(p => new ASField { Name = p.Name, Value = RenderFieldValue(p.Value) }) - .Cast() + .Concat(profile.Pronouns != null && profile.Pronouns.Count != 0 + ? + [ + profile.Pronouns.TryGetValue("", out var pronouns) + ? new ASPronouns { Name = pronouns } + : new ASPronouns { NameMap = profile.Pronouns } + ] + : []) .ToList(); var summary = profile?.Description != null diff --git a/Iceshrimp.Backend/Core/Federation/ActivityStreams/Contexts/iceshrimp.json b/Iceshrimp.Backend/Core/Federation/ActivityStreams/Contexts/iceshrimp.json index 6650681d..71daf9ae 100644 --- a/Iceshrimp.Backend/Core/Federation/ActivityStreams/Contexts/iceshrimp.json +++ b/Iceshrimp.Backend/Core/Federation/ActivityStreams/Contexts/iceshrimp.json @@ -43,7 +43,8 @@ "Bite": "https://ns.mia.jetzt/as#Bite", "quoteUri": "http://fedibird.com/ns#quoteUri", - "EmojiReact": "http://litepub.social/ns#EmojiReact" + "EmojiReact": "http://litepub.social/ns#EmojiReact", + "Pronouns": "https://ns.pancakes.gay/as/#Pronouns" } ] } \ No newline at end of file diff --git a/Iceshrimp.Backend/Core/Federation/ActivityStreams/Types/ASAttachment.cs b/Iceshrimp.Backend/Core/Federation/ActivityStreams/Types/ASAttachment.cs index 5cd6334a..4b4ef524 100644 --- a/Iceshrimp.Backend/Core/Federation/ActivityStreams/Types/ASAttachment.cs +++ b/Iceshrimp.Backend/Core/Federation/ActivityStreams/Types/ASAttachment.cs @@ -1,8 +1,11 @@ +using System.Text.Json.Serialization; using Iceshrimp.Backend.Core.Configuration; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using J = Newtonsoft.Json.JsonPropertyAttribute; using JC = Newtonsoft.Json.JsonConverterAttribute; +using JI = System.Text.Json.Serialization.JsonIgnoreAttribute; +using JsonConverter = Newtonsoft.Json.JsonConverter; namespace Iceshrimp.Backend.Core.Federation.ActivityStreams.Types; @@ -50,6 +53,21 @@ public class ASField : ASAttachment public ASField() => Type = $"{Constants.SchemaNs}#PropertyValue"; } +public class ASPronouns : ASAttachment +{ + public ASPronouns() => Type = $"{Constants.PancakesNs}#Pronouns"; + + [J($"{Constants.ActivityStreamsNs}#name")] + [JC(typeof(ValueObjectConverter))] + [JI(Condition = JsonIgnoreCondition.WhenWritingNull)] + public string? Name { get; set; } + + [J($"{Constants.ActivityStreamsNs}#nameMap")] + [JC(typeof(ValueObjectConverter))] + [JI(Condition = JsonIgnoreCondition.WhenWritingNull)] + public Dictionary? NameMap { get; set; } +} + public class ASImageConverter : ASSerializer.ListSingleObjectConverter; public sealed class ASAttachmentConverter : JsonConverter @@ -99,6 +117,7 @@ public sealed class ASAttachmentConverter : JsonConverter $"{Constants.ActivityStreamsNs}#Document" => obj.ToObject(), $"{Constants.ActivityStreamsNs}#Image" => obj.ToObject(), $"{Constants.SchemaNs}#PropertyValue" => obj.ToObject(), + $"{Constants.PancakesNs}#Pronouns" => obj.ToObject(), _ => attachment }; } diff --git a/Iceshrimp.Backend/Core/Services/UserService.cs b/Iceshrimp.Backend/Core/Services/UserService.cs index 67d9fa48..25507c89 100644 --- a/Iceshrimp.Backend/Core/Services/UserService.cs +++ b/Iceshrimp.Backend/Core/Services/UserService.cs @@ -158,6 +158,15 @@ public class UserService( }) .AwaitAllAsync() : null; + + var pronounsAttachment = actor.Attachments?.OfType() + .FirstOrDefault(p => p is { Name: not null } or { NameMap: not null }); + var pronouns = pronounsAttachment switch + { + { Name: not null } => new Dictionary { { "", pronounsAttachment.Name } }, + { NameMap: not null } => pronounsAttachment.NameMap, + _ => null + }; var bio = actor.MkSummary?.ReplaceLineEndings("\n").Trim(); if (bio == null) @@ -209,7 +218,8 @@ public class UserService( //Location = TODO, Fields = fields?.ToArray() ?? [], UserHost = user.Host, - Url = actor.Url?.Link + Url = actor.Url?.Link, + Pronouns = pronouns }; var publicKey = new UserPublickey @@ -326,6 +336,15 @@ public class UserService( .AwaitAllAsync() : null; + var pronounsAttachment = actor.Attachments?.OfType() + .FirstOrDefault(p => p is { Name: not null } or { NameMap: not null }); + var pronouns = pronounsAttachment switch + { + { Name: not null } => new Dictionary { { "", pronounsAttachment.Name } }, + { NameMap: not null } => pronounsAttachment.NameMap, + _ => null + }; + user.Emojis = emoji.Select(p => p.Id).ToList(); //TODO: FollowersCount //TODO: FollowingCount @@ -351,6 +370,7 @@ public class UserService( user.UserProfile.Fields = fields?.ToArray() ?? []; user.UserProfile.UserHost = user.Host; user.UserProfile.Url = actor.Url?.Link; + user.UserProfile.Pronouns = pronouns; user.UserProfile.MentionsResolved = false;