using MongoDB.Entities; using PrivaPub.Federation.Actors; using PrivaPub.Federation.Outbox; using PrivaPub.Infrastructure.Statistics; using PrivaPub.Models.Federation; using PrivaPub.Models.Post; using PrivaPub.StaticServices; using System.Text.Json.Nodes; using PostEntity = PrivaPub.Models.Post.Post; namespace PrivaPub.Federation.Inbox { // ActivityPub's inbox forwarding of replies (owner decision 2026-10-05): a public or unlisted reply to one of our // personas' own posts reaches the persona's followers as its author sent it, and so do its edits and its deletion, as // Mastodon forwards the replies to its accounts' posts. The author's server sends a reply to its own followers and to // the persona, never to the persona's followers, who would otherwise see half of the thread. It goes as it came (an // LD signature in it stays good), signed by the persona; never to the replier's own server, never once more when it // reached us forwarded, and never for a followers-only reply or a DM, which were not for the persona's followers. public static class ReplyRelay { public static async Task Pass(PostEntity reply, DbEntities db, ILocalActorService localActors, IDeliveryService delivery, CancellationToken token) { var raw = Arrival.Current?.Raw; if (raw == default || delivery == default || localActors == default || reply is not { IsFederatedCopy: true } || reply.Visibility is not (PostVisibility.Public or PostVisibility.Unlisted) || string.IsNullOrEmpty(reply.AnsweringToPostId)) return; var parent = await db.Posts.MatchID(reply.AnsweringToPostId).ExecuteFirstAsync(token); if (parent is not { IsFederatedCopy: false, IsLocalOnly: false } || !string.IsNullOrEmpty(parent.GroupId) || parent.Visibility is not (PostVisibility.Public or PostVisibility.Unlisted or PostVisibility.FollowersOnly)) return; var owner = await localActors.FindById(LocalActorKind.Person, parent.GroupUserId, token); if (owner is not { IsFederated: true } || JsonNode.Parse(raw) is not JsonObject activity) return; var replier = Interactions.HostOf(reply.ActorURI); var inboxes = (await delivery.FollowerInboxes(owner, token)).Where(i => Interactions.HostOf(i) != replier).ToList(); if (inboxes.Count > 0) await delivery.Enqueue(owner, inboxes, activity, token); } } }