Files
SocialPub/PrivaPub/Federation/Inbox/ConversationContainers.cs
T
thepraandClaude Opus 5.5 d06f684f0d A thread owner's Add of a reply to its context is the reply passed on
FEP-171b, received (Hubzilla, Streams, Forte): an Add whose target is a collection of the actor's own server, and whose
object is someone's Create, Update or Delete of their own object, is queued as that activity forwarded by the owner, so
it is believed on its FEP-8b32 proof or as its origin has it, and shares its job with the plain forward Hubzilla also
sends. Checked live against Hubzilla (unchanged, 20 checks).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LsXgEaXee4GCU1hwYgPJXw
2026-10-06 10:03:44 +02:00

46 lines
1.9 KiB
C#

using PrivaPub.Federation.Objects;
using PrivaPub.Infrastructure.Jobs;
using PrivaPub.Models.Jobs;
using PrivaPub.Models.User;
using System.Text.Json;
using System.Text.Json.Nodes;
using static PrivaPub.Federation.Objects.ActivityJson;
namespace PrivaPub.Federation.Inbox
{
// FEP-171b conversation containers (Hubzilla, Streams, Forte): a thread's owner adds each activity in it to the
// thread's context, a collection of its own, and tells the thread's audience with Add{activity}. The activity it adds
// is another's, so it is taken as forwarded by the owner: a Create, Update or Delete of the actor's own object,
// believed on its FEP-8b32 proof or as its origin has it (Forwarded). Hubzilla also passes the activity on by itself;
// the two are one job.
public static class ConversationContainers
{
public static async Task<bool> Unwrap(JsonNode add, ForeignAvatar owner, IJobQueue queue, CancellationToken token)
{
if (add["object"] is not JsonObject inner || Value(inner, "type") is not ("Create" or "Update" or "Delete") || Id(inner) is not { } innerId)
return false;
var target = add["target"];
var context = Id(target);
if (context == default || !Origin.Same(context, owner.ActorURI)
|| target is JsonObject collection && Id(collection["attributedTo"]) is { } attributedTo && attributedTo != owner.ActorURI)
return false;
var actorUri = Id(inner["actor"]);
if (actorUri == default || !Forwarded.Takeable(Value(inner, "type"), inner, actorUri))
{
Arrival.Drop("container-unusable");
return true;
}
var payload = new InboxPayload(actorUri, inner.ToJsonString(), "shared", ReceivedAt: DateTime.UtcNow, ForwardedBy: owner.ActorURI);
var queued = await queue.Enqueue(JobKind.ProcessInbox, JsonSerializer.Serialize(payload), new Uri(actorUri).Host.ToLowerInvariant(),
"inbox|forwarded|" + innerId, token);
if (queued)
Arrival.Accept("unwrapped");
else
Arrival.Drop("duplicate");
return true;
}
}
}