Files
SocialPub/PrivaPub/Federation/Inbox/Arrival.cs
T
thepraandClaude Opus 5.5 f6fc0c535c Relays: PrivaPub reads from the relays its configuration names
Federation:Relays names relays by their actor (or inbox) address; the instance actor follows Public at each, as Mastodon
subscribes, a minute after start and every six hours (asked again a day after no answer or a refusal, undone when a
relay is no longer named). What an accepted relay passes on comes to the federated timeline and nobody's home: a public
post it forwards (Activity-Relay), read again from its origin like any forwarded post, and a post it announces
(aode-relay), kept as its author's and never as the relay's boost. Nothing of a persona's is sent to a relay; sending
public posts there waits for the owner.

The pasture gains both relays (peers/relay.sh, peers/aoderelay.sh) and scenarios/relay.sh, 13 checks. The village
backlog is clean: 2574 checks pass, one known gap (Misskey's).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LsXgEaXee4GCU1hwYgPJXw
2026-10-05 23:11:59 +02:00

81 lines
2.7 KiB
C#

using PrivaPub.Federation.Actors;
using PrivaPub.Infrastructure.Statistics;
using PrivaPub.Models.Federation;
using PrivaPub.Models.Post;
namespace PrivaPub.Federation.Inbox
{
// Raw: the activity as it came, when its own author sent it (ReplyRelay passes it on as it is); ForwardedBy: the server
// that passed it on instead (a relay, a thread's server)
public sealed record Arrival(string ActivityId, string ActivityType, string ActorURI, string Inbox, string KeyId, string Algorithm,
IReadOnlyList<string> SignedHeaders, DateTime ReceivedAt, string Context = default, string Raw = default, string ForwardedBy = default)
{
static readonly AsyncLocal<Arrival> current = new();
public static Arrival Current
{
get => current.Value;
set => current.Value = value;
}
public ArrivalVerdict Verdict { get; } = new();
public static void Drop(string reason) => Current?.Verdict.Drop(reason);
public static void Reject(string reason) => Current?.Verdict.Reject(reason);
public static void Accept(string reason) => Current?.Verdict.Accept(reason);
public static void About(string objectType = default, PostVisibility? visibility = default, DateTime? created = default, LocalActor local = default) =>
Current?.Verdict.About(objectType, visibility, created, local);
}
public sealed class ArrivalVerdict
{
public string Outcome { get; private set; }
public string Reason { get; private set; }
public string ObjectType { get; private set; }
public string Audience { get; private set; }
public string LocalKind { get; private set; }
public long? AgeSeconds { get; private set; }
public void Drop(string reason) => Decide(Interactions.Dropped, reason);
public void Reject(string reason) => Decide(Interactions.Rejected, reason);
public void Accept(string reason) => Decide(Interactions.Accepted, reason);
void Decide(string outcome, string reason)
{
if (Outcome != default)
return;
Outcome = outcome;
Reason = reason;
}
public void About(string objectType, PostVisibility? visibility, DateTime? created, LocalActor local)
{
ObjectType = objectType ?? ObjectType;
if (visibility is { } known)
Audience = AudienceOf(known);
if (created is { } at)
AgeSeconds = Math.Max(0, (long)(DateTime.UtcNow - at).TotalSeconds);
if (local != default)
LocalKind = local.Kind switch
{
LocalActorKind.Person => "person",
LocalActorKind.Group when !local.IsCircle => "group",
LocalActorKind.Application => "application",
_ => default
};
}
public static string AudienceOf(PostVisibility visibility) => visibility switch
{
PostVisibility.Public => Interactions.Public,
PostVisibility.Unlisted => Interactions.Unlisted,
_ => Interactions.Private
};
}
}