Better ap object builder in post

This commit is contained in:
2026-01-01 11:25:19 +08:00
parent a72dbcfc0c
commit fb15930611
3 changed files with 98 additions and 71 deletions

View File

@@ -8,7 +8,7 @@ using Swashbuckle.AspNetCore.Annotations;
namespace DysonNetwork.Sphere.ActivityPub;
[ApiController]
[Route("activitypub")]
[Route("activitypub/actors/{username}")]
public class ActivityPubController(
AppDatabase db,
IConfiguration configuration,
@@ -20,7 +20,7 @@ public class ActivityPubController(
{
private string Domain => configuration["ActivityPub:Domain"] ?? "localhost";
[HttpGet("actors/{username}")]
[HttpGet("")]
[Produces("application/activity+json")]
[ProducesResponseType(typeof(ActivityPubActor), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
@@ -88,7 +88,7 @@ public class ActivityPubController(
return Ok(actor);
}
[HttpPost("actors/{username}/inbox")]
[HttpPost("inbox")]
[Consumes("application/activity+json")]
[ProducesResponseType(StatusCodes.Status202Accepted)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
@@ -120,7 +120,7 @@ public class ActivityPubController(
return Accepted();
}
[HttpGet("actors/{username}/outbox")]
[HttpGet("outbox")]
[Produces("application/activity+json")]
[ProducesResponseType(typeof(ActivityPubCollection), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ActivityPubCollectionPage), StatusCodes.Status200OK)]
@@ -157,32 +157,21 @@ public class ActivityPubController(
.Take(pageSize)
.ToListAsync();
var items = posts.Select(post => new
var items = posts.Select(post =>
{
id = $"https://{Domain}/activitypub/objects/{post.Id}/activity",
type = "Create",
actor = actorUrl,
published = (post.PublishedAt ?? post.CreatedAt).ToDateTimeOffset(),
to = new[] { "https://www.w3.org/ns/activitystreams#Public" },
cc = new[] { $"{actorUrl}/followers" },
@object = new
var postObject = ActivityPubObjectFactory.CreatePostObject(configuration, post, actorUrl);
postObject["url"] = $"https://{Domain}/posts/{post.Id}";
return new Dictionary<string, object>
{
id = $"https://{Domain}/activitypub/objects/{post.Id}",
type = post.Type == PostType.Article ? "Article" : "Note",
published = (post.PublishedAt ?? post.CreatedAt).ToDateTimeOffset(),
attributedTo = actorUrl,
content = post.Content ?? "",
url = $"https://{Domain}/posts/{post.Id}",
to = new[] { "https://www.w3.org/ns/activitystreams#Public" },
cc = new[] { $"{actorUrl}/followers" },
attachment = post.Attachments.Select(a => new
{
type = "Document",
mediaType = a.MimeType,
url = $"{configuration["ActivityPub:FileBaseUrl"] ?? $"https://{Domain}/files"}/{a.Id}"
})
}
}).ToList<object>();
["id"] = $"https://{Domain}/activitypub/objects/{post.Id}/activity",
["type"] = "Create",
["actor"] = actorUrl,
["published"] = (post.PublishedAt ?? post.CreatedAt).ToDateTimeOffset(),
["to"] = ActivityPubObjectFactory.PublicTo,
["cc"] = new[] { $"{actorUrl}/followers" },
["@object"] = postObject
};
}).Cast<object>().ToList();
var collectionPage = new ActivityPubCollectionPage
{
@@ -213,7 +202,7 @@ public class ActivityPubController(
}
}
[HttpGet("actors/{username}/followers")]
[HttpGet("followers")]
[Produces("application/activity+json")]
[ProducesResponseType(typeof(ActivityPubCollection), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ActivityPubCollectionPage), StatusCodes.Status200OK)]
@@ -279,7 +268,7 @@ public class ActivityPubController(
}
}
[HttpGet("actors/{username}/following")]
[HttpGet("following")]
[Produces("application/activity+json")]
[ProducesResponseType(typeof(ActivityPubCollection), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ActivityPubCollectionPage), StatusCodes.Status200OK)]
@@ -354,19 +343,19 @@ public class ActivityPubController(
logger.LogInformation("Using existing public key for publisher: {PublisherId}", publisher.Id);
return publicKeyPem;
}
logger.LogInformation("Generating new key pair for publisher: {PublisherId} ({Name})",
logger.LogInformation("Generating new key pair for publisher: {PublisherId} ({Name})",
publisher.Id, publisher.Name);
var (newPrivate, newPublic) = keyService.GenerateKeyPair();
SavePublisherKey(publisher, "private_key", newPrivate);
SavePublisherKey(publisher, "public_key", newPublic);
db.Update(publisher);
await db.SaveChangesAsync();
logger.LogInformation("Saved new key pair to database for publisher: {PublisherId}", publisher.Id);
return newPublic;
}

View File

@@ -166,24 +166,9 @@ public class ActivityPubDeliveryService(
["type"] = "Create",
["actor"] = actorUrl,
["published"] = (post.PublishedAt ?? post.CreatedAt).ToDateTimeOffset(),
["to"] = new[] { "https://www.w3.org/ns/activitystreams#Public" },
["to"] = ActivityPubObjectFactory.PublicTo,
["cc"] = new[] { $"{actorUrl}/followers" },
["object"] = new Dictionary<string, object>
{
["id"] = postUrl,
["type"] = post.Type == PostType.Article ? "Article" : "Note",
["published"] = (post.PublishedAt ?? post.CreatedAt).ToDateTimeOffset(),
["attributedTo"] = actorUrl,
["content"] = post.Content ?? "",
["to"] = new[] { "https://www.w3.org/ns/activitystreams#Public" },
["cc"] = new[] { $"{actorUrl}/followers" },
["attachment"] = post.Attachments.Select(a => new Dictionary<string, object>
{
["type"] = "Document",
["mediaType"] = "image/jpeg",
["url"] = $"{AssetsBaseUrl}/{a.Id}"
}).ToList<object>()
}
["object"] = ActivityPubObjectFactory.CreatePostObject(configuration, post, actorUrl)
};
var followers = await GetRemoteFollowersAsync();
@@ -217,25 +202,9 @@ public class ActivityPubDeliveryService(
["type"] = "Update",
["actor"] = actorUrl,
["published"] = (post.PublishedAt ?? post.CreatedAt).ToDateTimeOffset(),
["to"] = new[] { "https://www.w3.org/ns/activitystreams#Public" },
["to"] = ActivityPubObjectFactory.PublicTo,
["cc"] = new[] { $"{actorUrl}/followers" },
["object"] = new Dictionary<string, object>
{
["id"] = postUrl,
["type"] = post.Type == PostType.Article ? "Article" : "Note",
["published"] = (post.PublishedAt ?? post.CreatedAt).ToDateTimeOffset(),
["updated"] = post.EditedAt?.ToDateTimeOffset() ?? new DateTimeOffset(),
["attributedTo"] = actorUrl,
["content"] = post.Content ?? "",
["to"] = new[] { "https://www.w3.org/ns/activitystreams#Public" },
["cc"] = new[] { $"{actorUrl}/followers" },
["attachment"] = post.Attachments.Select(a => new Dictionary<string, object>
{
["type"] = "Document",
["mediaType"] = "image/jpeg",
["url"] = $"{AssetsBaseUrl}/{a.Id}"
}).ToList<object>()
}
["object"] = ActivityPubObjectFactory.CreatePostObject(configuration, post, actorUrl)
};
var followers = await GetRemoteFollowersAsync();

View File

@@ -0,0 +1,69 @@
using System.Text;
using DysonNetwork.Shared.Models;
using Markdig;
namespace DysonNetwork.Sphere.ActivityPub;
public static class ActivityPubObjectFactory
{
public static readonly string[] PublicTo = ["https://www.w3.org/ns/activitystreams#Public"];
public static Dictionary<string, object> CreatePostObject(
IConfiguration configuration,
SnPost post,
string actorUrl
)
{
var baseDomain = configuration["ActivityPub:Domain"] ?? "localhost";
var assetsBaseUrl = configuration["ActivityPub:FileBaseUrl"] ?? $"https://{baseDomain}/files";
var postUrl = $"https://{baseDomain}/posts/{post.Id}";
// Build content by combining title, description, and main content
var contentBuilder = new StringBuilder();
if (!string.IsNullOrWhiteSpace(post.Title))
contentBuilder.Append($"# {post.Title}\n\n");
if (!string.IsNullOrWhiteSpace(post.Description))
contentBuilder.Append($"{post.Description}\n\n");
if (!string.IsNullOrWhiteSpace(post.Content))
contentBuilder.Append(post.Content);
// Ensure content is not empty for ActivityPub compatibility
if (contentBuilder.Length == 0)
contentBuilder.Append("Shared media");
if (post.Tags.Count > 0)
{
contentBuilder.Append("\n\n");
contentBuilder.Append(
string.Join(' ', post.Tags.Select(x => $"#{x.Slug}"))
);
}
var finalContent = contentBuilder.ToString();
var postObject = new Dictionary<string, object>
{
["id"] = postUrl,
["type"] = post.Type == PostType.Article ? "Article" : "Note",
["published"] = (post.PublishedAt ?? post.CreatedAt).ToDateTimeOffset(),
["attributedTo"] = actorUrl,
["content"] = Markdown.ToHtml(finalContent),
["to"] = PublicTo,
["cc"] = new[] { $"{actorUrl}/followers" },
["attachment"] = post.Attachments.Select(a => new Dictionary<string, object>
{
["type"] = "Document",
["mediaType"] = a.MimeType ?? "media/jpeg",
["url"] = $"{assetsBaseUrl}/{a.Id}"
}).ToList<object>()
};
if (post.EditedAt.HasValue)
postObject["updated"] = post.EditedAt.Value.ToDateTimeOffset();
return postObject;
}
}