From 2f3ca1e4778e542e9891f87f655acd3083ec05ca Mon Sep 17 00:00:00 2001 From: Laura Hausmann Date: Sun, 25 Feb 2024 00:55:35 +0100 Subject: [PATCH] [backend/federation] Handle user profile fields (ISH-34) --- .../Mastodon/Renderers/UserRenderer.cs | 12 +++++++++- .../Schemas/Entities/AccountEntity.cs | 2 +- .../Core/Configuration/Constants.cs | 3 ++- .../Federation/ActivityPub/NoteRenderer.cs | 1 - .../ActivityStreams/Types/ASActor.cs | 4 ++++ .../ActivityStreams/Types/ASAttachment.cs | 22 ++++++++++++++++--- .../Core/Services/NoteService.cs | 1 + .../Core/Services/UserService.cs | 15 +++++++++++-- 8 files changed, 51 insertions(+), 9 deletions(-) diff --git a/Iceshrimp.Backend/Controllers/Mastodon/Renderers/UserRenderer.cs b/Iceshrimp.Backend/Controllers/Mastodon/Renderers/UserRenderer.cs index 17f5771e..0e6457dd 100644 --- a/Iceshrimp.Backend/Controllers/Mastodon/Renderers/UserRenderer.cs +++ b/Iceshrimp.Backend/Controllers/Mastodon/Renderers/UserRenderer.cs @@ -22,6 +22,16 @@ public class UserRenderer(IOptions config, MfmConverter acct += $"@{user.Host}"; var profileEmoji = emoji?.Where(p => user.Emojis.Contains(p.Id)).ToList() ?? await GetEmoji([user]); + var fields = profile?.Fields + .Select(p => new Field + { + Name = p.Name, + Value = p.Value, + VerifiedAt = p.IsVerified.HasValue && p.IsVerified.Value + ? DateTime.Now.ToStringIso8601Like() + : null + }) + .ToList(); var res = new AccountEntity { @@ -44,7 +54,7 @@ public class UserRenderer(IOptions config, MfmConverter MovedToAccount = null, //TODO IsBot = user.IsBot, IsDiscoverable = user.IsExplorable, - Fields = [], //TODO + Fields = fields ?? [], Emoji = profileEmoji }; diff --git a/Iceshrimp.Backend/Controllers/Mastodon/Schemas/Entities/AccountEntity.cs b/Iceshrimp.Backend/Controllers/Mastodon/Schemas/Entities/AccountEntity.cs index ce813705..3c7299fc 100644 --- a/Iceshrimp.Backend/Controllers/Mastodon/Schemas/Entities/AccountEntity.cs +++ b/Iceshrimp.Backend/Controllers/Mastodon/Schemas/Entities/AccountEntity.cs @@ -23,7 +23,7 @@ public class AccountEntity : IEntity [J("moved")] public required AccountEntity? MovedToAccount { get; set; } [J("bot")] public required bool IsBot { get; set; } [J("discoverable")] public required bool IsDiscoverable { get; set; } - [J("fields")] public required Field[] Fields { get; set; } + [J("fields")] public required List Fields { get; set; } [J("source")] public AccountSource? Source { get; set; } [J("emojis")] public required List Emoji { get; set; } [J("id")] public required string Id { get; set; } diff --git a/Iceshrimp.Backend/Core/Configuration/Constants.cs b/Iceshrimp.Backend/Core/Configuration/Constants.cs index 5345153b..ffe53b1f 100644 --- a/Iceshrimp.Backend/Core/Configuration/Constants.cs +++ b/Iceshrimp.Backend/Core/Configuration/Constants.cs @@ -6,7 +6,8 @@ public static class Constants public const string W3IdSecurityNs = "https://w3id.org/security"; public const string PurlDcNs = "http://purl.org/dc/terms"; public const string XsdNs = "http://www.w3.org/2001/XMLSchema"; - public const string MastodonNs = "http://joinmastodon.org/ns"; + public const string SchemaNs = "http://schema.org"; + public const string MastodonNs = "http://joinmastodon.org/ns"; public static readonly string[] SystemUsers = ["instance.actor", "relay.actor"]; public static readonly string[] BrowserSafeMimeTypes = diff --git a/Iceshrimp.Backend/Core/Federation/ActivityPub/NoteRenderer.cs b/Iceshrimp.Backend/Core/Federation/ActivityPub/NoteRenderer.cs index 24d81712..3815e592 100644 --- a/Iceshrimp.Backend/Core/Federation/ActivityPub/NoteRenderer.cs +++ b/Iceshrimp.Backend/Core/Federation/ActivityPub/NoteRenderer.cs @@ -73,7 +73,6 @@ public class NoteRenderer(IOptions config, MfmConverter .Where(p => note.FileIds.Contains(p.Id) && p.UserHost == null) .Select(p => new ASDocument { - Type = $"{Constants.ActivityStreamsNs}#Document", Sensitive = p.IsSensitive, Url = new ASObjectBase(p.WebpublicUrl ?? p.Url), MediaType = p.Type, diff --git a/Iceshrimp.Backend/Core/Federation/ActivityStreams/Types/ASActor.cs b/Iceshrimp.Backend/Core/Federation/ActivityStreams/Types/ASActor.cs index 307440e7..7b886df4 100644 --- a/Iceshrimp.Backend/Core/Federation/ActivityStreams/Types/ASActor.cs +++ b/Iceshrimp.Backend/Core/Federation/ActivityStreams/Types/ASActor.cs @@ -120,6 +120,10 @@ public class ASActor : ASObject [J($"{Constants.ActivityStreamsNs}#tag")] [JC(typeof(ASTagConverter))] public List? Tags { get; set; } + + [J($"{Constants.ActivityStreamsNs}#attachment")] + [JC(typeof(ASAttachmentConverter))] + public List? Attachments { get; set; } public bool IsBot => Type == $"{Constants.ActivityStreamsNs}#Service"; diff --git a/Iceshrimp.Backend/Core/Federation/ActivityStreams/Types/ASAttachment.cs b/Iceshrimp.Backend/Core/Federation/ActivityStreams/Types/ASAttachment.cs index 3846ac12..d397b792 100644 --- a/Iceshrimp.Backend/Core/Federation/ActivityStreams/Types/ASAttachment.cs +++ b/Iceshrimp.Backend/Core/Federation/ActivityStreams/Types/ASAttachment.cs @@ -15,6 +15,8 @@ public class ASAttachment : ASObjectBase public class ASDocument : ASAttachment { + public ASDocument() => Type = $"{Constants.ActivityStreamsNs}#Document"; + [J($"{Constants.ActivityStreamsNs}#url")] [JC(typeof(ASObjectBaseConverter))] public ASObjectBase? Url { get; set; } @@ -32,6 +34,17 @@ public class ASDocument : ASAttachment public string? Description { get; set; } } +public class ASField : ASAttachment +{ + public ASField() => Type = $"{Constants.SchemaNs}#PropertyValue"; + + [J($"{Constants.ActivityStreamsNs}#name")] [JC(typeof(ValueObjectConverter))] + public string? Name; + + [J($"{Constants.SchemaNs}#value")] [JC(typeof(ValueObjectConverter))] + public string? Value; +} + public sealed class ASAttachmentConverter : JsonConverter { public override bool CanWrite => false; @@ -74,9 +87,12 @@ public sealed class ASAttachmentConverter : JsonConverter { var attachment = obj.ToObject(); - return attachment?.Type == $"{Constants.ActivityStreamsNs}#Document" - ? obj.ToObject() - : attachment; + return attachment?.Type switch + { + $"{Constants.ActivityStreamsNs}#Document" => obj.ToObject(), + $"{Constants.SchemaNs}#PropertyValue" => obj.ToObject(), + _ => attachment + }; } public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer) diff --git a/Iceshrimp.Backend/Core/Services/NoteService.cs b/Iceshrimp.Backend/Core/Services/NoteService.cs index 0650723d..65c20157 100644 --- a/Iceshrimp.Backend/Core/Services/NoteService.cs +++ b/Iceshrimp.Backend/Core/Services/NoteService.cs @@ -583,6 +583,7 @@ public class NoteService( if (attachments is not { Count: > 0 }) return []; var result = await attachments .OfType() + .Take(10) .Select(p => driveSvc.StoreFile(p.Url?.Id, user, p.Sensitive ?? sensitive, p.Description, p.MediaType)) .AwaitAllNoConcurrencyAsync(); diff --git a/Iceshrimp.Backend/Core/Services/UserService.cs b/Iceshrimp.Backend/Core/Services/UserService.cs index 74c6ba16..e277f929 100644 --- a/Iceshrimp.Backend/Core/Services/UserService.cs +++ b/Iceshrimp.Backend/Core/Services/UserService.cs @@ -101,6 +101,11 @@ public class UserService( .Where(p => p != null) .Cast() .ToList(); + var fields = actor.Attachments? + .OfType() + .Where(p => p is { Name: not null, Value: not null }) + .Select(p => new UserProfile.Field { Name = p.Name!, Value = p.Value! }) + .ToArray(); user = new User { @@ -134,7 +139,7 @@ public class UserService( Description = actor.MkSummary ?? await mfmConverter.FromHtmlAsync(actor.Summary), //Birthday = TODO, //Location = TODO, - //Fields = TODO, + Fields = fields ?? [], UserHost = user.Host, Url = actor.Url?.Link }; @@ -226,6 +231,12 @@ public class UserService( .Cast() .ToList(); + var fields = actor.Attachments? + .OfType() + .Where(p => p is { Name: not null, Value: not null }) + .Select(p => new UserProfile.Field { Name = p.Name!, Value = p.Value! }) + .ToArray(); + user.Emojis = emoji.Select(p => p.Id).ToList(); user.Tags = tags ?? []; //TODO: FollowersCount @@ -238,7 +249,7 @@ public class UserService( user.UserProfile.Description = actor.MkSummary ?? await mfmConverter.FromHtmlAsync(actor.Summary); //user.UserProfile.Birthday = TODO; //user.UserProfile.Location = TODO; - //user.UserProfile.Fields = TODO; + user.UserProfile.Fields = fields ?? []; user.UserProfile.UserHost = user.Host; user.UserProfile.Url = actor.Url?.Link;