One Post model for every visibility, read from notes the way peers write them
Post gains what federation and the Mastodon API need: Visibility (Public, Unlisted, FollowersOnly, Direct, Circle, LocalGeo), the author's account id, to/cc, the Create's id, url, context, quote, InReplyToURI and the parent's author, a separate SpoilerText next to the title, language, mentions, hashtags, remote attachments (alt text, blurhash, focus, size), reply/favourite/reblog counters, revisions, EditedAt and DeletedAt. Direct messages are Posts with Visibility Direct and a ConversationId; migration _005 copies DmPost rows across with their ids and fills the new fields of existing posts. DmPost is left in place so a rollback still sees the old messages. Inbound: - NoteParser reads Note, Article, Page, Question and media types: content, then contentMap, then _misskey_content; summary as the spoiler and name as the title; Mention and Hashtag tags; attachments; a PeerTube-style list attribution prefers the person over the channel; quote URIs. - Addressing classifies like Mastodon, finding followers-only by the author's own followers URL (now stored on ForeignAvatar), not by a "/followers" suffix. - Create keeps a post when a local persona is addressed or mentioned, when it replies to a local post (the parent's reply count goes up) or when a community it follows is addressed; an unsolicited public post is not stored. Update keeps the previous version as a revision. The outbox and object endpoints serve only Public and Unlisted posts. 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
796f06acf4
commit
e9529310fe
21 files changed
+777
-192
No files matched your search
@@ -2,69 +2,83 @@ using MongoDB.Entities;
|
||||
|
||||
using PrivaPub.Federation.Actors;
|
||||
using PrivaPub.Federation.Objects;
|
||||
using PrivaPub.Federation.Outbox;
|
||||
using PrivaPub.Federation.Rendering;
|
||||
using PrivaPub.Models.Federation;
|
||||
using PrivaPub.Models.Group;
|
||||
using PrivaPub.Models.Post;
|
||||
using PrivaPub.Models.User;
|
||||
using PrivaPub.StaticServices;
|
||||
|
||||
using System.Text.Json.Nodes;
|
||||
|
||||
using static PrivaPub.Federation.Inbox.ForeignMembers;
|
||||
using static PrivaPub.Federation.Objects.ActivityJson;
|
||||
|
||||
using DmPostEntity = PrivaPub.Models.Post.DmPost;
|
||||
using PostEntity = PrivaPub.Models.Post.Post;
|
||||
|
||||
namespace PrivaPub.Federation.Inbox.Handlers
|
||||
{
|
||||
public class UpdateHandler : IActivityHandler
|
||||
{
|
||||
const int MaxRevisions = 20;
|
||||
|
||||
readonly DbEntities _dbEntities;
|
||||
readonly ILocalActorService _localActors;
|
||||
readonly IRemoteActorService _remoteActors;
|
||||
readonly IDeliveryService _delivery;
|
||||
|
||||
public UpdateHandler(DbEntities dbEntities, ILocalActorService localActors, IRemoteActorService remoteActors, IDeliveryService delivery)
|
||||
public UpdateHandler(DbEntities dbEntities, ILocalActorService localActors, IRemoteActorService remoteActors)
|
||||
{
|
||||
_dbEntities = dbEntities;
|
||||
_localActors = localActors;
|
||||
_remoteActors = remoteActors;
|
||||
_delivery = delivery;
|
||||
}
|
||||
|
||||
public string Type => "Update";
|
||||
|
||||
public async Task Handle(JsonNode activity, ForeignAvatar actor, CancellationToken token)
|
||||
{
|
||||
var update = activity;
|
||||
|
||||
var inner = update["object"];
|
||||
var inner = activity["object"];
|
||||
if (Id(inner) == actor.ActorURI)
|
||||
{
|
||||
await _remoteActors.GetActor(actor.ActorURI, refresh: true, token);
|
||||
return;
|
||||
}
|
||||
if (inner is not JsonObject || Id(inner["attributedTo"]) != actor.ActorURI || !Origin.Same(Id(inner), actor.ActorURI))
|
||||
if (inner is not JsonObject || !Origin.Same(Id(inner), actor.ActorURI))
|
||||
return;
|
||||
var note = NoteParser.Parse(inner);
|
||||
if (note == default || note.AttributedTo != actor.ActorURI)
|
||||
return;
|
||||
|
||||
var objectUri = Id(inner);
|
||||
var text = ContentSanitizer.Html(Value(inner, "content"));
|
||||
await DB.Default.Update<PostEntity>()
|
||||
.Match(p => p.ObjectURI == objectUri && p.ActorURI == actor.ActorURI)
|
||||
.Modify(p => p.Text, text)
|
||||
.Modify(p => p.ContentHtml, text)
|
||||
.Modify(p => p.UpdateDate, DateTime.UtcNow)
|
||||
.ExecuteAsync(token);
|
||||
await DB.Default.Update<DmPostEntity>()
|
||||
.Match(p => p.ObjectURI == objectUri && p.ActorURI == actor.ActorURI)
|
||||
.Modify(p => p.Text, text)
|
||||
.Modify(p => p.ContentHtml, text)
|
||||
.Modify(p => p.UpdateDate, DateTime.UtcNow)
|
||||
.ExecuteAsync(token);
|
||||
return;
|
||||
}
|
||||
var post = await _dbEntities.Posts.Match(p => p.ObjectURI == note.Id && p.ActorURI == actor.ActorURI).ExecuteFirstAsync(token);
|
||||
if (post == default || post.DeletedAt.HasValue)
|
||||
return;
|
||||
|
||||
post.Revisions.Add(new PostRevision
|
||||
{
|
||||
Title = post.Title,
|
||||
SpoilerText = post.SpoilerText,
|
||||
ContentHtml = post.ContentHtml,
|
||||
HasContentWarning = post.HasContentWarning,
|
||||
EditedAt = post.EditedAt ?? post.CreationDate
|
||||
});
|
||||
if (post.Revisions.Count > MaxRevisions)
|
||||
post.Revisions.RemoveRange(0, post.Revisions.Count - MaxRevisions);
|
||||
|
||||
var mentions = new List<PostMention>();
|
||||
foreach (var mention in note.Mentions)
|
||||
{
|
||||
var local = await _localActors.FindByUri(mention.ActorURI, token);
|
||||
mentions.Add(new PostMention { ActorURI = mention.ActorURI, Handle = mention.Handle, IsLocal = local != default, AccountId = local?.Id });
|
||||
}
|
||||
|
||||
post.Title = note.Title;
|
||||
post.SpoilerText = note.SpoilerText;
|
||||
post.HasContentWarning = note.Sensitive;
|
||||
post.Text = note.ContentHtml;
|
||||
post.ContentHtml = note.ContentHtml;
|
||||
post.Language = note.Language;
|
||||
post.Tags = note.Tags.ToList();
|
||||
post.Mentions = mentions;
|
||||
post.Media = note.Attachments.ToList();
|
||||
post.EditedAt = note.Updated ?? DateTime.UtcNow;
|
||||
post.UpdateDate = DateTime.UtcNow;
|
||||
await DB.Default.SaveAsync(post, token);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user