using MongoDB.Entities; using PrivaPub.Domain.Social; using PrivaPub.Federation.Actors; using PrivaPub.Models.Federation; using PrivaPub.Models.Social; 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 UndoHandler : IActivityHandler { readonly IReactions _reactions; readonly DbEntities _dbEntities; readonly ILocalActorService _localActors; public UndoHandler(DbEntities dbEntities, ILocalActorService localActors, IReactions reactions) { _reactions = reactions; _dbEntities = dbEntities; _localActors = localActors; } public string Type => "Undo"; public async Task Handle(JsonNode activity, ForeignAvatar actor, CancellationToken token) { var inner = activity["object"]; var innerType = inner is JsonObject ? Value(inner, "type") : default; var innerId = Id(inner); if (innerType is null or "Follow") await UndoFollow(inner, innerId, actor, token); if (innerType is null or "Like") await UndoLike(inner, innerId, actor, token); if (innerType is null or "Announce") await UndoAnnounce(innerId, actor, token); if (innerType is null or "Dislike") await UndoDislike(inner, innerId, actor, token); if (innerType is null or "Like" or "EmojiReact") await _reactions.Withdraw(actor, innerId, inner is JsonObject ? Id(inner["object"]) : default, inner is JsonObject ? Value(inner, "_misskey_reaction") ?? Value(inner, "content") : default, token); } async Task UndoDislike(JsonNode inner, string innerId, ForeignAvatar actor, CancellationToken token) { var downvote = innerId == default ? default : await DB.Default.Find().Match(d => d.ActivityURI == innerId && d.ActorURI == actor.ActorURI).ExecuteFirstAsync(token); if (downvote == default && inner is JsonObject && Id(inner["object"]) is { } objectUri) { var post = await _dbEntities.Posts.Match(p => p.ObjectURI == objectUri).ExecuteFirstAsync(token); downvote = post == default ? default : await DB.Default.Find().Match(d => d.PostId == post.ID && d.ActorURI == actor.ActorURI).ExecuteFirstAsync(token); } if (downvote == default) return; await DB.Default.DeleteAsync(downvote.ID); await DB.Default.Update().MatchID(downvote.PostId).Modify(b => b.Inc(p => p.DownvotesCount, -1)).ExecuteAsync(token); } async Task UndoFollow(JsonNode inner, string innerId, ForeignAvatar actor, CancellationToken token) { var followers = await _dbEntities.Followers.Match(f => f.ActorURI == actor.ActorURI).ExecuteAsync(token); var targetUri = inner is JsonObject ? Id(inner["object"]) : default; var target = targetUri == default ? default : await _localActors.FindByUri(targetUri, token); foreach (var follower in followers) { var matchesActivity = innerId != default && follower.FollowActivityURI == innerId; var matchesTarget = target != default && target.Id == follower.LocalActorId && target.Kind == follower.LocalActorKind; if (!matchesActivity && !matchesTarget) continue; await DB.Default.DeleteAsync(follower.ID); if (follower.LocalActorKind == LocalActorKind.Group) await RemoveForeignMember(follower.LocalActorId, actor.ActorURI, token); } } async Task UndoLike(JsonNode inner, string innerId, ForeignAvatar actor, CancellationToken token) { var favourite = innerId == default ? default : await _dbEntities.Favourites.Match(f => f.ActivityURI == innerId && f.ActorURI == actor.ActorURI).ExecuteFirstAsync(token); if (favourite == default && inner is JsonObject && Id(inner["object"]) is { } objectUri) { var post = await _dbEntities.Posts.Match(p => p.ObjectURI == objectUri).ExecuteFirstAsync(token); favourite = post == default ? default : await _dbEntities.Favourites.Match(f => f.PostId == post.ID && f.ActorURI == actor.ActorURI).ExecuteFirstAsync(token); } if (favourite == default) return; await DB.Default.DeleteAsync(favourite.ID); await DB.Default.Update().MatchID(favourite.PostId).Modify(b => b.Inc(p => p.FavouritesCount, -1)).ExecuteAsync(token); } async Task UndoAnnounce(string innerId, ForeignAvatar actor, CancellationToken token) { if (innerId == default) return; var reblog = await _dbEntities.Posts .Match(p => p.ObjectURI == innerId && p.ActorURI == actor.ActorURI && p.ReblogOfPostId != null) .ExecuteFirstAsync(token); if (reblog == default) return; await DB.Default.DeleteAsync(reblog.ID); await DB.Default.DeleteAsync(e => e.PostId == reblog.ID); await DB.Default.Update().MatchID(reblog.ReblogOfPostId).Modify(b => b.Inc(p => p.ReblogsCount, -1)).ExecuteAsync(token); } } }