diff --git a/DysonNetwork.Sphere/ActivityPub/ActivityPubDiscoveryService.cs b/DysonNetwork.Sphere/ActivityPub/ActivityPubDiscoveryService.cs index d3c4962..1925b1b 100644 --- a/DysonNetwork.Sphere/ActivityPub/ActivityPubDiscoveryService.cs +++ b/DysonNetwork.Sphere/ActivityPub/ActivityPubDiscoveryService.cs @@ -5,7 +5,7 @@ using System.Text.RegularExpressions; namespace DysonNetwork.Sphere.ActivityPub; -public class ActivityPubDiscoveryService( +public partial class ActivityPubDiscoveryService( AppDatabase db, IHttpClientFactory httpClientFactory, IConfiguration configuration, @@ -15,7 +15,7 @@ public class ActivityPubDiscoveryService( private string Domain => configuration["ActivityPub:Domain"] ?? "localhost"; private HttpClient HttpClient => httpClientFactory.CreateClient(); - private static readonly Regex HandlePattern = new(@"^@?(\w+)@([\w.-]+)$", RegexOptions.Compiled); + private static readonly Regex HandlePattern = HandleRegex(); public async Task DiscoverActorAsync(string query) { @@ -48,30 +48,21 @@ public class ActivityPubDiscoveryService( return localResults; var (username, domain) = ParseHandle(query); - if (username != null && domain != null) + if (username == null || domain == null) return localResults; { var actorUri = await GetActorUriFromWebfingerAsync(username, domain); - if (actorUri != null) - { - var remoteActor = await FetchAndStoreActorAsync(actorUri); - if (remoteActor != null && !localResults.Any(a => a.Uri == actorUri)) - { - var combined = new List(localResults) { remoteActor }; - return combined.Take(limit).ToList(); - } - } + if (actorUri == null) return localResults; + var remoteActor = await FetchAndStoreActorAsync(actorUri); + if (remoteActor == null || localResults.Any(a => a.Uri == actorUri)) return localResults; + var combined = new List(localResults) { remoteActor }; + return combined.Take(limit).ToList(); } - - return localResults; } private (string? username, string? domain) ParseHandle(string query) { var match = HandlePattern.Match(query.Trim()); - if (!match.Success) - return (null, null); - - return (match.Groups[1].Value, match.Groups[2].Value); + return !match.Success ? (null, null) : (match.Groups[1].Value, match.Groups[2].Value); } private async Task GetActorUriFromWebfingerAsync(string username, string domain) @@ -92,6 +83,8 @@ public class ActivityPubDiscoveryService( } var json = await response.Content.ReadAsStringAsync(); + logger.LogDebug("Webfinger response from {Url}: {Json}", webfingerUrl, json); + var webfingerData = JsonSerializer.Deserialize(json); if (webfingerData?.Links == null) @@ -100,9 +93,14 @@ public class ActivityPubDiscoveryService( return null; } + logger.LogDebug("Found {LinkCount} links in Webfinger response", webfingerData.Links.Count); + foreach (var link in webfingerData.Links) + { + logger.LogDebug("Link: rel={Rel}, type={Type}, href={Href}", link.Rel, link.Type, link.Href); + } + var selfLink = webfingerData.Links.FirstOrDefault(l => - l.Rel == "self" && - l.Type == "application/activity+json"); + l is { Rel: "self", Type: "application/activity+json" }); if (selfLink == null) { @@ -203,21 +201,15 @@ public class ActivityPubDiscoveryService( return actorUri.Split('/').Last(); } - private string? ExtractAvatarUrl(object? iconData) + private static string? ExtractAvatarUrl(object? iconData) { - if (iconData == null) - return null; - - if (iconData is JsonElement element) + return iconData switch { - if (element.ValueKind == JsonValueKind.String) - return element.GetString(); - - if (element.TryGetProperty("url", out var urlElement)) - return urlElement.GetString(); - } - - return iconData.ToString(); + null => null, + JsonElement { ValueKind: JsonValueKind.String } element => element.GetString(), + JsonElement element when element.TryGetProperty("url", out var urlElement) => urlElement.GetString(), + _ => iconData.ToString() + }; } private string? ExtractImageUrl(object? imageData) @@ -225,31 +217,30 @@ public class ActivityPubDiscoveryService( return ExtractAvatarUrl(imageData); } - private string? ExtractPublicKeyId(object? publicKeyData) + private static string? ExtractPublicKeyId(object? publicKeyData) { - if (publicKeyData == null) - return null; - - if (publicKeyData is JsonElement element && element.TryGetProperty("id", out var idElement)) - return idElement.GetString(); - - if (publicKeyData is Dictionary dict && dict.TryGetValue("id", out var idValue)) - return idValue?.ToString(); - - return null; + return publicKeyData switch + { + null => null, + JsonElement element when element.TryGetProperty("id", out var idElement) => idElement.GetString(), + Dictionary dict when dict.TryGetValue("id", out var idValue) => idValue.ToString(), + _ => null + }; } - private string? ExtractPublicKeyPem(object? publicKeyData) + private static string? ExtractPublicKeyPem(object? publicKeyData) { - if (publicKeyData == null) - return null; - - if (publicKeyData is JsonElement element && element.TryGetProperty("publicKeyPem", out var pemElement)) - return pemElement.GetString(); - - if (publicKeyData is Dictionary dict && dict.TryGetValue("publicKeyPem", out var pemValue)) - return pemValue?.ToString(); - - return null; + return publicKeyData switch + { + null => null, + JsonElement element when element.TryGetProperty("publicKeyPem", out var pemElement) => + pemElement.GetString(), + Dictionary dict when dict.TryGetValue("publicKeyPem", out var pemValue) => pemValue + .ToString(), + _ => null + }; } -} + + [GeneratedRegex(@"^@?(\w+)@([\w.-]+)$", RegexOptions.Compiled)] + private static partial Regex HandleRegex(); +} \ No newline at end of file diff --git a/DysonNetwork.Sphere/ActivityPub/ActivityPubFollowController.cs b/DysonNetwork.Sphere/ActivityPub/ActivityPubFollowController.cs index f409a11..f279f4f 100644 --- a/DysonNetwork.Sphere/ActivityPub/ActivityPubFollowController.cs +++ b/DysonNetwork.Sphere/ActivityPub/ActivityPubFollowController.cs @@ -12,7 +12,7 @@ namespace DysonNetwork.Sphere.ActivityPub; public class ActivityPubFollowController( AppDatabase db, ActivityPubDeliveryService deliveryService, - ActivityPubDiscoveryService discoveryService, + ActivityPubDiscoveryService discSrv, IConfiguration configuration, ILogger logger ) : ControllerBase @@ -34,7 +34,7 @@ public class ActivityPubFollowController( if (publisher == null) return BadRequest(new { error = "User doesn't have a publisher" }); - logger.LogInformation("User {UserId} wants to follow {TargetActor}", + logger.LogInformation("User {UserId} wants to follow {TargetActor}", currentUser, request.TargetActorUri); var success = await deliveryService.SendFollowActivityAsync( @@ -107,8 +107,8 @@ public class ActivityPubFollowController( var actors = await db.FediverseRelationships .Include(r => r.TargetActor) - .Where(r => - r.IsLocalActor && + .Where(r => + r.IsLocalActor && r.LocalPublisherId == publisher.Id && r.IsFollowing && r.State == RelationshipState.Accepted) @@ -139,8 +139,8 @@ public class ActivityPubFollowController( var actors = await db.FediverseRelationships .Include(r => r.Actor) - .Where(r => - !r.IsLocalActor && + .Where(r => + !r.IsLocalActor && r.LocalPublisherId == publisher.Id && r.IsFollowedBy && r.State == RelationshipState.Accepted) @@ -161,7 +161,7 @@ public class ActivityPubFollowController( if (string.IsNullOrWhiteSpace(query)) return BadRequest(new { error = "Query is required" }); - var actors = await discoveryService.SearchActorsAsync(query, limit, includeRemoteDiscovery: true); + var actors = await discSrv.SearchActorsAsync(query, limit, includeRemoteDiscovery: true); return Ok(actors); } @@ -184,22 +184,22 @@ public class ActivityPubFollowController( var actorUrl = $"https://{Domain}/activitypub/actors/{publisher.Name}"; var followingCount = await db.FediverseRelationships - .CountAsync(r => - r.IsLocalActor && + .CountAsync(r => + r.IsLocalActor && r.LocalPublisherId == publisher.Id && r.IsFollowing && r.State == RelationshipState.Accepted); var followersCount = await db.FediverseRelationships - .CountAsync(r => - !r.IsLocalActor && + .CountAsync(r => + !r.IsLocalActor && r.LocalPublisherId == publisher.Id && r.IsFollowedBy && r.State == RelationshipState.Accepted); var pendingCount = await db.FediverseRelationships - .CountAsync(r => - r.IsLocalActor && + .CountAsync(r => + r.IsLocalActor && r.LocalPublisherId == publisher.Id && r.State == RelationshipState.Pending); @@ -408,4 +408,4 @@ public class ActorCheckResult public bool IsDiscoverable { get; set; } public Instant? LastActivityAt { get; set; } public string? Error { get; set; } -} +} \ No newline at end of file diff --git a/DysonNetwork.Sphere/ActivityPub/WebFingerController.cs b/DysonNetwork.Sphere/ActivityPub/WebFingerController.cs index 4e7fe78..f233f4c 100644 --- a/DysonNetwork.Sphere/ActivityPub/WebFingerController.cs +++ b/DysonNetwork.Sphere/ActivityPub/WebFingerController.cs @@ -1,5 +1,6 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; +using System.Text.Json.Serialization; namespace DysonNetwork.Sphere.ActivityPub; @@ -68,13 +69,21 @@ public class WebFingerController( public class WebFingerResponse { + [JsonPropertyName("subject")] public string Subject { get; set; } = null!; + + [JsonPropertyName("links")] public List Links { get; set; } = []; } public class WebFingerLink { + [JsonPropertyName("rel")] public string Rel { get; set; } = null!; + + [JsonPropertyName("type")] public string Type { get; set; } = null!; + + [JsonPropertyName("href")] public string Href { get; set; } = null!; } diff --git a/DysonNetwork.sln.DotSettings.user b/DysonNetwork.sln.DotSettings.user index a75a9f5..16bf3b6 100644 --- a/DysonNetwork.sln.DotSettings.user +++ b/DysonNetwork.sln.DotSettings.user @@ -106,6 +106,7 @@ ForceIncluded ForceIncluded ForceIncluded + ForceIncluded ForceIncluded ForceIncluded ForceIncluded