Blocks, mutes, bookmarks, pins and reports
- 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
This commit is contained in:
1 parent
525b5368a1
commit
8f75317050
20 files changed
+813
-21
No files matched your search
@@ -0,0 +1,75 @@
|
||||
using MongoDB.Entities;
|
||||
|
||||
using PrivaPub.Federation.Actors;
|
||||
using PrivaPub.Federation.Outbox;
|
||||
using PrivaPub.Federation.Rendering;
|
||||
using PrivaPub.Models.Social;
|
||||
using PrivaPub.Models.User;
|
||||
using PrivaPub.StaticServices;
|
||||
|
||||
using System.Text.Json.Nodes;
|
||||
|
||||
namespace PrivaPub.Domain.Relationships
|
||||
{
|
||||
public interface IReportService
|
||||
{
|
||||
Task<Report> File(LocalActor reporter, string targetAccountId, IReadOnlyList<string> postIds, string comment, string category, bool forward,
|
||||
CancellationToken token);
|
||||
}
|
||||
|
||||
public class ReportService : IReportService
|
||||
{
|
||||
readonly DbEntities _dbEntities;
|
||||
readonly ILocalActorService _localActors;
|
||||
readonly IDeliveryService _delivery;
|
||||
|
||||
public ReportService(DbEntities dbEntities, ILocalActorService localActors, IDeliveryService delivery)
|
||||
{
|
||||
_dbEntities = dbEntities;
|
||||
_localActors = localActors;
|
||||
_delivery = delivery;
|
||||
}
|
||||
|
||||
public async Task<Report> File(LocalActor reporter, string targetAccountId, IReadOnlyList<string> postIds, string comment, string category,
|
||||
bool forward, CancellationToken token)
|
||||
{
|
||||
var localTarget = await _dbEntities.Avatars.MatchID(targetAccountId).ExecuteFirstAsync(token);
|
||||
var remoteTarget = localTarget == default ? await _dbEntities.ForeignAvatars.MatchID(targetAccountId).ExecuteFirstAsync(token) : default;
|
||||
if (localTarget == default && remoteTarget == default)
|
||||
return default;
|
||||
var targetUri = localTarget != default ? _localActors.FromAvatar(localTarget).Uri : remoteTarget.ActorURI;
|
||||
var posts = postIds.Count == 0
|
||||
? new List<Models.Post.Post>()
|
||||
: await _dbEntities.Posts.Match(p => postIds.Contains(p.ID) && p.ActorURI == targetUri).ExecuteAsync(token);
|
||||
|
||||
var report = new Report
|
||||
{
|
||||
ReporterAvatarId = reporter.Id,
|
||||
TargetAccountId = targetAccountId,
|
||||
TargetActorURI = targetUri,
|
||||
PostIds = posts.Select(p => p.ID).ToList(),
|
||||
ObjectURIs = posts.Select(p => p.ObjectURI).ToList(),
|
||||
Comment = comment?.Trim(),
|
||||
Category = category is "spam" or "legal" or "violation" ? category : "other",
|
||||
Forwarded = forward && remoteTarget != default
|
||||
};
|
||||
await DB.Default.SaveAsync(report, token);
|
||||
|
||||
if (report.Forwarded)
|
||||
{
|
||||
var instance = await _localActors.GetInstanceActor(token);
|
||||
var flag = new JsonObject
|
||||
{
|
||||
["@context"] = ActivityPubRenderer.ActivityStreams,
|
||||
["id"] = instance.ActivityUri($"flag-{report.ID}"),
|
||||
["type"] = "Flag",
|
||||
["actor"] = instance.Uri,
|
||||
["content"] = report.Comment ?? string.Empty,
|
||||
["object"] = new JsonArray(report.ObjectURIs.Prepend(targetUri).Select(u => (JsonNode)u).ToArray())
|
||||
};
|
||||
await _delivery.Enqueue(instance, new[] { remoteTarget.SharedInboxURL ?? remoteTarget.InboxURL }, flag, token);
|
||||
}
|
||||
return report;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user