- Blocks are per persona and never federate (a Block activity would tell
the other server who blocked whom): the blocked account is removed as a
follower, with a Reject{Follow} if it is remote, unfollowed, cleared from
home and notifications, and refused with Reject if it follows again.
- Mutes (optionally timed, optionally sparing notifications) and per-
persona domain blocks keep authors out of home timelines, notifications
and every status list the API returns; the boosts of a hidden author's
posts are hidden too.
- Bookmarks and pins (at most five, public or unlisted, own posts) with
their Mastodon endpoints and flags; pinned posts are the actor's
`featured` collection at /trophies, and `featuredTags` points at
/tattoos.
- Reports: /api/v1/reports stores the report and, when forwarding to a
remote account, sends Flag from the instance actor, so the reporting
persona is never named to the other server. An inbound Flag about a local
persona or its posts becomes a report; moderators list and resolve them
under /clientapi/moderator/reports.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012CzABvBkbcFqoHdmi8b9WB
67 lines
2.0 KiB
C#
67 lines
2.0 KiB
C#
using MongoDB.Entities;
|
|
|
|
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.Objects.ActivityJson;
|
|
|
|
namespace PrivaPub.Federation.Inbox.Handlers
|
|
{
|
|
public class FlagHandler : IActivityHandler
|
|
{
|
|
readonly DbEntities _dbEntities;
|
|
readonly ILocalActorService _localActors;
|
|
|
|
public FlagHandler(DbEntities dbEntities, ILocalActorService localActors)
|
|
{
|
|
_dbEntities = dbEntities;
|
|
_localActors = localActors;
|
|
}
|
|
|
|
public string Type => "Flag";
|
|
|
|
public async Task Handle(JsonNode activity, ForeignAvatar actor, CancellationToken token)
|
|
{
|
|
var objects = activity["object"] switch
|
|
{
|
|
JsonArray array => array.Select(Id).Where(i => i != default).ToList(),
|
|
JsonNode single when Id(single) is { } id => new List<string> { id },
|
|
_ => new List<string>()
|
|
};
|
|
|
|
LocalActor target = default;
|
|
var postUris = new List<string>();
|
|
foreach (var uri in objects.Take(50))
|
|
{
|
|
var local = await _localActors.FindByUri(uri, token);
|
|
if (local is { Kind: LocalActorKind.Person } && uri.TrimEnd('/') == local.Uri)
|
|
target ??= local;
|
|
else
|
|
postUris.Add(uri);
|
|
}
|
|
var posts = postUris.Count == 0
|
|
? new List<Models.Post.Post>()
|
|
: await _dbEntities.Posts.Match(p => postUris.Contains(p.ObjectURI) && !p.IsFederatedCopy).ExecuteAsync(token);
|
|
if (target == default && posts.Count > 0)
|
|
target = await _localActors.FindById(LocalActorKind.Person, posts[0].GroupUserId, token);
|
|
if (target == default)
|
|
return;
|
|
|
|
await DB.Default.SaveAsync(new Report
|
|
{
|
|
ReporterActorURI = actor.ActorURI,
|
|
TargetAccountId = target.Id,
|
|
TargetActorURI = target.Uri,
|
|
PostIds = posts.Where(p => p.GroupUserId == target.Id).Select(p => p.ID).ToList(),
|
|
ObjectURIs = posts.Where(p => p.GroupUserId == target.Id).Select(p => p.ObjectURI).ToList(),
|
|
Comment = Value(activity, "content")
|
|
}, token);
|
|
}
|
|
}
|
|
}
|