Edit history from formerRepresentations

Pleroma and Akkoma send a post's earlier versions with it. A post PrivaPub first meets after its edits now has its
history, and an edit that carries them brings the versions missed in between; each is read as the post itself is. The
Akkoma scenario checks a post fetched after two edits by an account no persona follows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LsXgEaXee4GCU1hwYgPJXw
This commit is contained in:
thepraandClaude Opus 5.5 committed 2026-10-06 12:07:49 +02:00
1 parent 02928caac1
commit 679463eb3f
12 files changed
+288 -24

No files matched your search

+40 -4
View File
@@ -52,6 +52,7 @@ namespace PrivaPub.Federation.Objects
public EventDetails Event { get; init; }
public PostPlace Place { get; init; }
public bool? CommentsEnabled { get; init; }//Lemmy, PieFed and Mbin: false once its moderators lock it; null when not said
public IReadOnlyList<PostRevision> FormerRepresentations { get; init; } = Array.Empty<PostRevision>();//its earlier versions, oldest first
}
public static class NoteParser
@@ -59,6 +60,7 @@ namespace PrivaPub.Federation.Objects
static readonly string[] NoteTypes = { "Note", "Article", "Page", "Question", "Event", "Video", "Audio", "Image", "ChatMessage", "PodcastEpisode" };
const int MaxAttachments = 16;
const int MaxTags = 64;
public const int MaxRevisions = 20;
public static NoteDocument Parse(JsonNode node)
{
@@ -75,8 +77,7 @@ namespace PrivaPub.Federation.Objects
var summary = Value(note, "summary");
var excerpt = ObjectShapes.Text(summary, 1000)
?? (note["preview"] is JsonObject preview && Value(preview, "type") == "Note" ? ObjectShapes.Text(Value(preview, "content"), 1000) : default);
var sensitive = note["sensitive"] is JsonValue flag && flag.TryGetValue<bool>(out var marked) && marked;
var summaryWarns = type is "Note" or "Question" || sensitive;
var (sensitive, summaryWarns) = Warns(note, type, summary);
return new NoteDocument
{
Id = id,
@@ -87,7 +88,7 @@ namespace PrivaPub.Federation.Objects
Title = ObjectShapes.Text(Value(note, "name"), 500),
SpoilerText = summaryWarns ? Plain(summary, 500) : default,
Excerpt = summaryWarns ? default : excerpt,
Sensitive = sensitive || summaryWarns && !string.IsNullOrWhiteSpace(summary),
Sensitive = sensitive,
Language = language,
Url = UrlOf(note["url"]) ?? id,
InReplyTo = Id(note["inReplyTo"]),
@@ -128,10 +129,45 @@ namespace PrivaPub.Federation.Objects
Audio = ObjectShapes.Audio(type == "PodcastEpisode" ? note["audio"] as JsonObject : note),
Event = ObjectShapes.Event(note),
Place = ObjectShapes.NotePlace(note),
CommentsEnabled = note["commentsEnabled"] is JsonValue comments && comments.TryGetValue<bool>(out var enabled) ? enabled : null
CommentsEnabled = note["commentsEnabled"] is JsonValue comments && comments.TryGetValue<bool>(out var enabled) ? enabled : null,
FormerRepresentations = Former(note, type)
};
}
// a summary is a content warning only on a Note or a Question, or a post marked sensitive; elsewhere it is an excerpt
static (bool Sensitive, bool SummaryWarns) Warns(JsonObject note, string type, string summary)
{
var marked = note["sensitive"] is JsonValue flag && flag.TryGetValue<bool>(out var value) && value;
var warns = type is "Note" or "Question" || marked;
return (marked || warns && !string.IsNullOrWhiteSpace(summary), warns);
}
// Pleroma's and Akkoma's earlier versions of an edited post (`formerRepresentations`, newest first there), each read as
// the post itself is: the newest twenty, oldest first, each dated when it was written
static List<PostRevision> Former(JsonObject note, string type)
{
if (note["formerRepresentations"] is not JsonObject former || former["orderedItems"] is not JsonArray items)
return [];
return items.OfType<JsonObject>().Take(MaxRevisions * 2)
.Select(version =>
{
var summary = Value(version, "summary");
var (sensitive, warns) = Warns(version, type, summary);
return new PostRevision
{
Title = ObjectShapes.Text(Value(version, "name"), 500),
SpoilerText = warns ? Plain(summary, 500) : default,
ContentHtml = ContentSanitizer.Html(Content(version).Html),
HasContentWarning = sensitive,
EditedAt = Time(Value(version, "updated")) ?? Time(Value(version, "published")) ?? default
};
})
.Reverse()
.OrderBy(r => r.EditedAt)
.TakeLast(MaxRevisions)
.ToList();
}
// Castopod's PodcastEpisode: its words in `description` (a Note of its own), its sound in `audio`, its title the
// sound's name; read as a post with those
static JsonObject Episode(JsonObject episode)