using MongoDB.Entities; using PrivaPub.Domain.Social; using PrivaPub.Federation.Actors; using PrivaPub.Models.Social; using PrivaPub.Federation.Objects; using PrivaPub.Federation.Outbox; using PrivaPub.Federation.Rendering; using PrivaPub.Models.Federation; using PrivaPub.Models.Group; using PrivaPub.Models.Post; using PrivaPub.Models.User; using PrivaPub.StaticServices; using System.Text.Json.Nodes; using static PrivaPub.Federation.Inbox.ForeignMembers; using static PrivaPub.Federation.Objects.ActivityJson; using PostEntity = PrivaPub.Models.Post.Post; namespace PrivaPub.Federation.Inbox.Handlers { public class FollowHandler : IActivityHandler { readonly DbEntities _dbEntities; readonly ILocalActorService _localActors; readonly IRemoteActorService _remoteActors; readonly IDeliveryService _delivery; public FollowHandler(DbEntities dbEntities, ILocalActorService localActors, IRemoteActorService remoteActors, IDeliveryService delivery) { _dbEntities = dbEntities; _localActors = localActors; _remoteActors = remoteActors; _delivery = delivery; } public string Type => "Follow"; public async Task Handle(JsonNode activity, ForeignAvatar actor, CancellationToken token) { var follow = activity; var follower = actor; var target = await _localActors.FindByUri(Id(follow["object"]), token); if (target is not { IsFederated: true } || target.Kind == LocalActorKind.Application) { Arrival.Drop("unknown-recipient"); return; } if (target.Kind == LocalActorKind.Person && await DB.Default.Find().Match(b => b.AvatarId == target.Id && b.TargetActorURI == follower.ActorURI).ExecuteAnyAsync(token)) { var reject = new JsonObject { ["@context"] = ActivityPubRenderer.ActivityStreams, ["id"] = target.ActivityUri($"reject-{Guid.NewGuid():N}"), ["type"] = "Reject", ["actor"] = target.Uri, ["object"] = follow.DeepClone() }; await _delivery.Enqueue(target, new[] { follower.InboxURL }, reject, token); Arrival.Reject("blocked"); return; } var existing = await _dbEntities.Followers .Match(f => f.LocalActorId == target.Id && f.LocalActorKind == target.Kind && f.ActorURI == follower.ActorURI) .ExecuteFirstAsync(token); var record = existing ?? new Follower { LocalActorId = target.Id, LocalActorKind = target.Kind, ActorURI = follower.ActorURI }; record.InboxURL = follower.InboxURL; record.SharedInboxURL = follower.SharedInboxURL; record.FollowActivityURI = Id(follow); record.IsAccepted = existing?.IsAccepted == true || !target.ManuallyApprovesFollowers; await DB.Default.SaveAsync(record, token); Arrival.Accept(record.IsAccepted ? "auto-accepted" : "pending"); if (target.Kind == LocalActorKind.Person) await Notifications.Add(target.Id, record.IsAccepted ? NotificationType.Follow : NotificationType.FollowRequest, follower.ID, follower.ActorURI, default, token); if (!record.IsAccepted) return; if (target.Kind == LocalActorKind.Group) await AddForeignMember(target.Id, follower.ActorURI, token); var accept = ActivityPubRenderer.Accept(target, follow, $"accept-{record.ID}-{DateTime.UtcNow.Ticks}"); await _delivery.Enqueue(target, new[] { follower.InboxURL }, accept, token); return; } } }