Outbound documents speak Mastodon's dialect under PrivaPub's route names
Local text (Domain/Content/ContentRenderer): Markdig with raw HTML off and
autolinks, or plain text for API clients; then @user and @user@host are
resolved (locally or through WebFinger) into Mastodon's h-card markup and
#tags into hashtag links, skipping code, links and e-mail addresses. A
post stores the result, its mentions and its tags, and is delivered to
the mentioned actors and the parent's author as well as to followers.
Renderer:
- Mastodon's @context (toot, schema PropertyValue, discoverable,
indexable, blurhash, focalPoint, featured, alsoKnownAs, movedTo) and
FEP-2c59's webfinger;
- an actor's url is its HTML page, its fields are PropertyValue
attachments, published is cut to the day and indexable is false;
- a note's content is the stored rendering; a title becomes name and a
bold first line, a content warning without its own text is summarised
by the title or "Content warning"; Mention and Hashtag tags, contentMap,
updated, and to/cc by visibility.
Routes take the agreed names: /groupies, /stalking, /scribbles/{id},
/grunts/{id} (a Create resolves), /whispers/{id} for a DM's context. The
outbox is a collection with a first page, paged by max_id. A browser
asking for an actor or a note is redirected to /@user, and WebFinger's
profile-page points there.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012CzABvBkbcFqoHdmi8b9WB
This commit is contained in:
1 parent
e9529310fe
commit
e54e17708f
9 files changed
+590
-74
No files matched your search
@@ -13,6 +13,7 @@ using PrivaPub.StaticServices;
|
||||
using System.Text.Json.Nodes;
|
||||
|
||||
using PostEntity = PrivaPub.Models.Post.Post;
|
||||
using PrivaPub.Domain.Content;
|
||||
using PrivaPub.Federation.Actors;
|
||||
using PrivaPub.Federation.Rendering;
|
||||
using PrivaPub.Federation.Outbox;
|
||||
@@ -36,6 +37,7 @@ namespace PrivaPub.Services
|
||||
readonly ILocalActorService _localActors;
|
||||
readonly IRemoteActorService _remoteActors;
|
||||
readonly IDeliveryService _delivery;
|
||||
readonly IContentRenderer _content;
|
||||
readonly IStringLocalizer<GenericRes> _localizer;
|
||||
readonly ILogger<PostsService> _logger;
|
||||
|
||||
@@ -43,6 +45,7 @@ namespace PrivaPub.Services
|
||||
ILocalActorService localActors,
|
||||
IRemoteActorService remoteActors,
|
||||
IDeliveryService delivery,
|
||||
IContentRenderer content,
|
||||
IStringLocalizer<GenericRes> localizer,
|
||||
ILogger<PostsService> logger)
|
||||
{
|
||||
@@ -50,6 +53,7 @@ namespace PrivaPub.Services
|
||||
_localActors = localActors;
|
||||
_remoteActors = remoteActors;
|
||||
_delivery = delivery;
|
||||
_content = content;
|
||||
_localizer = localizer;
|
||||
_logger = logger;
|
||||
}
|
||||
@@ -76,6 +80,7 @@ namespace PrivaPub.Services
|
||||
}
|
||||
|
||||
var parent = await Parent(form.AnsweringToPostId, token);
|
||||
var rendered = await _content.Markdown(form.Text, token);
|
||||
var post = new PostEntity
|
||||
{
|
||||
GroupUserId = author.Id,
|
||||
@@ -84,8 +89,10 @@ namespace PrivaPub.Services
|
||||
Visibility = isLocalOnly ? PostVisibility.Circle : PostVisibility.Public,
|
||||
Title = form.Title,
|
||||
Text = form.Text,
|
||||
ContentHtml = ActivityPubRenderer.Html(form.Text),
|
||||
ContentHtml = rendered.Html,
|
||||
ContentFormat = ContentFormat.Markdown,
|
||||
Mentions = rendered.Mentions.Select(ToMention).ToList(),
|
||||
Tags = rendered.Tags.ToList(),
|
||||
HasContentWarning = form.HasContentWarning,
|
||||
AnsweringToPostId = parent?.ID,
|
||||
InReplyToURI = parent?.ObjectURI ?? RemoteUri(form.AnsweringToPostId),
|
||||
@@ -95,7 +102,7 @@ namespace PrivaPub.Services
|
||||
};
|
||||
post.ID = (string)post.GenerateNewID();
|
||||
post.ObjectURI = author.PostUri(post.ID);
|
||||
post.Url = post.ObjectURI;
|
||||
post.Url = author.PostHtmlUrl(post.ID);
|
||||
|
||||
if (isLocalOnly)
|
||||
{
|
||||
@@ -113,7 +120,10 @@ namespace PrivaPub.Services
|
||||
if (parent != default)
|
||||
await DB.Default.Update<PostEntity>().MatchID(parent.ID).Modify(b => b.Inc(p => p.RepliesCount, 1)).ExecuteAsync(token);
|
||||
|
||||
await _delivery.EnqueueToFollowers(author, create, token);
|
||||
var addressedInboxes = rendered.Mentions.Where(m => !m.IsLocal).Select(m => m.Inbox).ToList();
|
||||
if (parent is { IsFederatedCopy: true } && !string.IsNullOrEmpty(parent.AuthorAccountId))
|
||||
addressedInboxes.Add((await _dbEntities.ForeignAvatars.MatchID(parent.AuthorAccountId).ExecuteFirstAsync(token))?.InboxURL);
|
||||
await _delivery.EnqueueToFollowers(author, create, token, addressedInboxes);
|
||||
if (group != default)
|
||||
await _delivery.EnqueueToFollowers(group, ActivityPubRenderer.Announce(group, post.ObjectURI, $"announce-{post.ID}"), token);
|
||||
|
||||
@@ -248,7 +258,7 @@ namespace PrivaPub.Services
|
||||
{
|
||||
dmGroup = new DmGroup { Members = members, ParticipantsKey = key };
|
||||
dmGroup.ID = (string)dmGroup.GenerateNewID();
|
||||
dmGroup.ConversationURI = $"{author.Uri}/conversations/{dmGroup.ID}";
|
||||
dmGroup.ConversationURI = author.ConversationUri(dmGroup.ID);
|
||||
await DB.Default.SaveAsync(dmGroup, token);
|
||||
}
|
||||
}
|
||||
@@ -261,14 +271,14 @@ namespace PrivaPub.Services
|
||||
Visibility = PostVisibility.Direct,
|
||||
ContextURI = dmGroup.ConversationURI,
|
||||
Text = form.Text,
|
||||
ContentHtml = ActivityPubRenderer.Html(form.Text),
|
||||
ContentHtml = (await _content.Markdown(form.Text, token)).Html,
|
||||
ContentFormat = ContentFormat.Markdown,
|
||||
HasContentWarning = form.HasContentWarning,
|
||||
ActorURI = author.Uri
|
||||
};
|
||||
dm.ID = (string)dm.GenerateNewID();
|
||||
dm.ObjectURI = author.PostUri(dm.ID);
|
||||
dm.Url = dm.ObjectURI;
|
||||
dm.Url = author.PostHtmlUrl(dm.ID);
|
||||
|
||||
var remote = new List<(string Uri, string Handle)>();
|
||||
var inboxes = new List<string>();
|
||||
@@ -377,6 +387,14 @@ namespace PrivaPub.Services
|
||||
: await _dbEntities.Posts.MatchID(answeringTo).ExecuteFirstAsync(token);
|
||||
}
|
||||
|
||||
static PostMention ToMention(ResolvedMention mention) => new()
|
||||
{
|
||||
ActorURI = mention.ActorUri,
|
||||
Handle = "@" + mention.Handle,
|
||||
IsLocal = mention.IsLocal,
|
||||
AccountId = mention.AccountId
|
||||
};
|
||||
|
||||
static string RemoteUri(string answeringTo) =>
|
||||
answeringTo != default && answeringTo.StartsWith("https://", StringComparison.OrdinalIgnoreCase) ? answeringTo : default;
|
||||
|
||||
|
||||
Reference in new issue
Block a user