using System.Text.Json; using PrivaPub.Federation.Objects; namespace PrivaPub.Federation.Actors { public sealed record ActorKey(string Id, string Pem); public sealed class ActorDocument { static readonly string[] ActorTypes = { "Person", "Service", "Application", "Group", "Organization" }; public string Id { get; init; } public string Type { get; init; } public string PreferredUsername { get; init; } public string Name { get; init; } public string Summary { get; init; } public string Url { get; init; } public string Inbox { get; init; } public string Outbox { get; init; } public string Followers { get; init; } public string Following { get; init; } public string SharedInbox { get; init; } public string Icon { get; init; } public bool Discoverable { get; init; } = true; public IReadOnlyList Keys { get; init; } = Array.Empty(); public ActorKey Key(string keyId) => Keys.FirstOrDefault(k => k.Id == keyId); public static ActorDocument Parse(JsonElement root) { if (root.ValueKind != JsonValueKind.Object) return default; var id = RemoteActorService.Text(root, "id"); var type = RemoteActorService.Text(root, "type"); if (Origin.Of(id) == default || !ActorTypes.Contains(type)) return default; var inbox = RemoteActorService.Text(root, "inbox"); return new ActorDocument { Id = id, Type = type, PreferredUsername = RemoteActorService.Text(root, "preferredUsername"), Name = RemoteActorService.Text(root, "name"), Summary = RemoteActorService.Text(root, "summary"), Url = RemoteActorService.Text(root, "url") ?? id, Inbox = Origin.Same(inbox, id) ? inbox : default, Outbox = RemoteActorService.Text(root, "outbox"), Followers = RemoteActorService.Text(root, "followers"), Following = RemoteActorService.Text(root, "following"), SharedInbox = root.TryGetProperty("endpoints", out var endpoints) ? RemoteActorService.Text(endpoints, "sharedInbox") : default, Icon = root.TryGetProperty("icon", out var icon) ? RemoteActorService.Text(icon, "url") : default, Discoverable = !root.TryGetProperty("discoverable", out var discoverable) || discoverable.ValueKind != JsonValueKind.False, Keys = ParseKeys(root, id) }; } static List ParseKeys(JsonElement root, string actorId) { var keys = new List(); if (!root.TryGetProperty("publicKey", out var publicKey)) return keys; var candidates = publicKey.ValueKind switch { JsonValueKind.Object => new[] { publicKey }, JsonValueKind.Array => publicKey.EnumerateArray().Where(k => k.ValueKind == JsonValueKind.Object).ToArray(), _ => Array.Empty() }; foreach (var key in candidates) { var keyId = RemoteActorService.Text(key, "id"); var owner = RemoteActorService.Text(key, "owner"); var pem = RemoteActorService.Text(key, "publicKeyPem"); if (string.IsNullOrEmpty(pem) || owner != actorId || !Origin.Same(keyId, actorId)) continue; keys.Add(new ActorKey(keyId, pem)); } return keys; } } }