A post named by its page is found as by its id

Pixelfed names one of our posts by the address of its page
(/@name/<post id>, the post's url) in its Like, Announce and their Undo,
so its likes were dropped as unknown objects. Before an activity is
handled, such a reference to a post of ours, as its object or the object
of the activity it undoes, is replaced by the post's id. Found by the
pasture's new Pixelfed peer.

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-05 06:40:46 +02:00
1 parent 34c0f696af
commit 26dac40720
4 files changed
+72 -2

No files matched your search

@@ -0,0 +1,42 @@
using System.Text.Json.Nodes;
using System.Text.RegularExpressions;
using MongoDB.Entities;
using PostEntity = PrivaPub.Models.Post.Post;
namespace PrivaPub.Federation.Inbox
{
// Some servers name one of our posts by the address of its page instead of its id (Pixelfed likes /@name/<post id>, the
// post's `url`). Before an activity is handled, such a reference to a post of ours, as the activity's object or as the
// object of the activity it undoes, is replaced by the post's id, so that every handler finds the post the same way.
public static partial class LocalReferences
{
[GeneratedRegex("^/@[^/?#]+/([0-9a-f]{24})$")]
private static partial Regex PostPage();
public static async Task Canonicalise(JsonNode activity, string baseAddress, CancellationToken token)
{
if (activity is not JsonObject outer || string.IsNullOrEmpty(baseAddress))
return;
await Replace(outer, baseAddress, token);
if (outer["object"] is JsonObject inner)
await Replace(inner, baseAddress, token);
}
static async Task Replace(JsonObject node, string baseAddress, CancellationToken token)
{
if (node["object"] is not JsonValue value || !value.TryGetValue<string>(out var uri) || PostId(uri, baseAddress) is not { } id)
return;
var post = await DB.Default.Find<PostEntity>().Match(p => p.ID == id && !p.IsFederatedCopy).ExecuteFirstAsync(token);
if (post?.ObjectURI is { } canonical)
node["object"] = canonical;
}
// the id of the post whose page the address is, on our own site
public static string PostId(string uri, string baseAddress) =>
uri.StartsWith(baseAddress + "/@", StringComparison.OrdinalIgnoreCase) && PostPage().Match(uri[baseAddress.Length..]) is { Success: true } page
? page.Groups[1].Value
: default;
}
}