Forte follows a persona by its profile page (/@name, WebFinger's alias) rather than its actor's id: Follow and
Undo{Follow} now find the persona by either (it was a 404). Forte also sends an edit only added to the thread's context
(FEP-171b), under the same activity id as the post's Create: the unwrapping now tells two activities that share an id
apart by what they carry, as the inbox does, so the Update is not taken for a copy.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LsXgEaXee4GCU1hwYgPJXw
50 lines
2.3 KiB
C#
50 lines
2.3 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 = JsonSerializer.Serialize(new InboxPayload(actorUri, inner.ToJsonString(), "shared", ReceivedAt: DateTime.UtcNow, ForwardedBy: owner.ActorURI));
|
|
var host = new Uri(actorUri).Host.ToLowerInvariant();
|
|
var dedupe = "inbox|forwarded|" + innerId;
|
|
var queued = await queue.Enqueue(JobKind.ProcessInbox, payload, host, dedupe, token);
|
|
// one id for two activities (Forte's Update of a post has its Create's id): what each carries tells them apart
|
|
if (!queued && InboxReceiver.CarriesOther(await queue.Payload(dedupe, token), inner))
|
|
queued = await queue.Enqueue(JobKind.ProcessInbox, payload, host, $"{dedupe}|{InboxReceiver.Digest(inner)}", token);
|
|
if (queued)
|
|
Arrival.Accept("unwrapped");
|
|
else
|
|
Arrival.Drop("duplicate");
|
|
return true;
|
|
}
|
|
}
|
|
}
|