Files

107 lines
4.4 KiB
C#
Raw Permalink Normal View History

using MongoDB.Driver;
using MongoDB.Entities;
using PrivaPub.Federation.Actors;
using PrivaPub.Federation.Objects;
using PrivaPub.Models.Federation;
using PrivaPub.Models.Social;
using PrivaPub.Models.User;
using PrivaPub.StaticServices;
using System.Text.Json.Nodes;
using static PrivaPub.Federation.Objects.ActivityJson;
namespace PrivaPub.Federation.Inbox.Handlers
{
// A remote account blocks one of our personas, as Mastodon does it: the follows between them end here too (the blocker
// already ended its side, so nothing is sent back), the persona stops seeing the blocker's posts and notifications, the
// relationship says blocked_by, and nothing of the persona's is addressed to the blocker any more. Undo{Block} lifts it.
public class BlockHandler : IActivityHandler
{
readonly DbEntities _dbEntities;
readonly ILocalActorService _localActors;
public BlockHandler(DbEntities dbEntities, ILocalActorService localActors)
{
_dbEntities = dbEntities;
_localActors = localActors;
}
public string Type => "Block";
public async Task Handle(JsonNode activity, ForeignAvatar actor, CancellationToken token)
{
var target = Id(activity["object"]) is { } uri ? await _localActors.FindByUri(uri, token) : default;
if (target is not { Kind: LocalActorKind.Person })
{
Arrival.Drop("unknown-object");
return;
}
if (Community(activity, actor) is { } community)
{
await Ban(activity, target, community, token);
return;
}
try
{
await DB.Default.SaveAsync(new BlockedBy { AvatarId = target.Id, ActorURI = actor.ActorURI, AccountId = actor.ID, ActivityURI = Id(activity) }, token);
}
catch (MongoWriteException ex) when (ex.WriteError?.Category == ServerErrorCategory.DuplicateKey)
{
Arrival.Drop("duplicate");
return;
}
Arrival.Accept("stored");
await DB.Default.DeleteAsync<Following>(f => f.AvatarId == target.Id && f.TargetActorURI == actor.ActorURI);
await DB.Default.DeleteAsync<Follower>(f => f.LocalActorId == target.Id && f.ActorURI == actor.ActorURI);
await DB.Default.DeleteAsync<TimelineEntry>(e => e.AvatarId == target.Id && e.AuthorAccountId == actor.ID);
await DB.Default.DeleteAsync<Notification>(n => n.AvatarId == target.Id && n.FromAccountId == actor.ID);
}
// Lemmy also sends a community's ban to the banned account's own server, as its moderator's Block with the community
// as `target`: a ban from that community, as the community's Announce brings it, never the moderator's own block. The
// moderator must be on the community's server.
static string Community(JsonNode block, ForeignAvatar actor) =>
block is JsonObject && Id(block["target"]) is { } community && community != actor.ActorURI && Origin.Same(community, actor.ActorURI)
? community
: default;
async Task Ban(JsonNode activity, LocalActor persona, string communityUri, CancellationToken token)
{
var community = await _dbEntities.ForeignAvatars.Match(f => f.ActorURI == communityUri && f.AvatarType == AvatarType.Group).ExecuteFirstAsync(token);
if (community == default)
{
Arrival.Drop("unknown-community");
return;
}
try
{
await DB.Default.SaveAsync(new BlockedBy { AvatarId = persona.Id, ActorURI = community.ActorURI, AccountId = community.ID, ActivityURI = Id(activity) }, token);
Arrival.Accept("banned");
}
catch (MongoWriteException ex) when (ex.WriteError?.Category == ServerErrorCategory.DuplicateKey)
{
Arrival.Drop("duplicate");
}
}
// Undo{Block}: the block it names by id, or else the blocker's block of the persona the inner Block names; a
// community's ban sent by its moderator is the community's
public static async Task<bool> Undo(JsonNode inner, string innerId, ForeignAvatar actor, ILocalActorService localActors, CancellationToken token)
{
var by = Community(inner, actor) ?? actor.ActorURI;
var block = innerId == default
? default
: await DB.Default.Find<BlockedBy>().Match(b => b.ActivityURI == innerId && b.ActorURI == by).ExecuteFirstAsync(token);
if (block == default && inner is JsonObject && Id(inner["object"]) is { } objectUri
&& await localActors.FindByUri(objectUri, token) is { Kind: LocalActorKind.Person } persona)
block = await DB.Default.Find<BlockedBy>().Match(b => b.AvatarId == persona.Id && b.ActorURI == by).ExecuteFirstAsync(token);
if (block == default)
return false;
await DB.Default.DeleteAsync<BlockedBy>(block.ID);
return true;
}
}
}