using MongoDB.Entities; using PrivaPub.Federation.Actors; using PrivaPub.Domain.Statuses; using PrivaPub.Federation.Objects; using PrivaPub.Federation.Outbox; using PrivaPub.Models.Post; using PrivaPub.Models.User; using PrivaPub.StaticServices; using System.Text.Json.Nodes; using static PrivaPub.Federation.Objects.ActivityJson; using PostEntity = PrivaPub.Models.Post.Post; namespace PrivaPub.Federation.Inbox.Handlers { public class UpdateHandler : IActivityHandler { readonly DbEntities _dbEntities; readonly ILocalActorService _localActors; readonly IRemoteActorService _remoteActors; readonly IGroupDistributor _groups; readonly IObjectRecords _records; readonly IQuoteService _quotes; readonly IDeliveryService _delivery; public UpdateHandler(DbEntities dbEntities, ILocalActorService localActors, IRemoteActorService remoteActors, IGroupDistributor groups, IObjectRecords records, IQuoteService quotes, IDeliveryService delivery = default) { _delivery = delivery; _quotes = quotes; _records = records; _groups = groups; _dbEntities = dbEntities; _localActors = localActors; _remoteActors = remoteActors; } public string Type => "Update"; public async Task Handle(JsonNode activity, ForeignAvatar actor, CancellationToken token) { var inner = activity["object"]; if (Id(inner) == actor.ActorURI) { await _remoteActors.GetActor(actor.ActorURI, refresh: true, token); Arrival.Accept("actor-refresh"); Arrival.About(actor.AvatarType.ToString()); return; } if (inner is not JsonObject || !Origin.Same(Id(inner), actor.ActorURI)) { Arrival.Drop("cross-origin"); return; } var note = NoteParser.Parse(inner); // edited by another account of the author's own server (Mobilizon's organiser edits the group's event): applied // as that server has it now if (note != default && note.AttributedTo != actor.ActorURI && Origin.Same(note.AttributedTo, actor.ActorURI)) { using var fetched = await _remoteActors.FetchObject(note.Id, token); var current = fetched == default ? default : NoteParser.Parse(JsonNode.Parse(fetched.Root.GetRawText())); if (current == default || current.Id != note.Id || current.AttributedTo != note.AttributedTo) { Arrival.Drop("fetch-failed"); return; } note = current; actor = await _remoteActors.GetActor(note.AttributedTo, refresh: false, token); if (actor == default) { Arrival.Drop("author-unavailable"); return; } } if (note == default || note.AttributedTo != actor.ActorURI) { Arrival.Drop(note == default ? "unparseable" : "misattributed"); return; } var post = await _dbEntities.Posts.Match(p => p.ObjectURI == note.Id && p.ActorURI == actor.ActorURI).ExecuteFirstAsync(token); if (post == default || post.DeletedAt.HasValue) { Arrival.Drop("unknown-object"); return; } Arrival.About(note.Type, post.Visibility, post.CreationDate); var edited = await RemoteEdits.Apply(post, note, Id(activity), _localActors, _records, _quotes, token); Arrival.Accept(edited ? "edit" : "refresh"); if (edited) await ReplyRelay.Pass(post, _dbEntities, _localActors, _delivery, token); if (edited && !string.IsNullOrEmpty(post.GroupId) && await _localActors.FindById(Models.Federation.LocalActorKind.Group, post.GroupId, token) is { IsCircle: false } community) await _groups.Announce(community, activity.AsObject(), post.ObjectURI, isNewPost: false, token); } } }