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
81 lines
2.7 KiB
C#
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
|
|
};
|
|
}
|
|
}
|