Files
SocialPub/PrivaPub/Domain/Moderation/ModerationFlags.cs
T

75 lines
4.5 KiB
C#
Raw Normal View History

using MongoDB.Bson;
using MongoDB.Driver;
namespace PrivaPub.Domain.Moderation
{
// Every post's AuthorSuspended and AuthorLimited worked out again from its author's and its server's moderation (R6),
// and every account's AccountState from when it was silenced or suspended: after a restore merged the moderation it keeps
// (ProtectiveMerge), and once by migration _020 for the servers silenced before posts carried the flags. Raw documents,
// since a restore runs before the entities are used. A banned login's personas are never flagged (Restrictions).
public static class ModerationFlags
{
public static async Task<long> Recompute(IMongoDatabase database, CancellationToken token)
{
var posts = database.GetCollection<BsonDocument>("Post");
var flagged = 0L;
await posts.UpdateManyAsync(new BsonDocument("AuthorSuspended", true), Builders<BsonDocument>.Update.Set("AuthorSuspended", false), cancellationToken: token);
await posts.UpdateManyAsync(new BsonDocument("AuthorLimited", true), Builders<BsonDocument>.Update.Set("AuthorLimited", false), cancellationToken: token);
var moderated = new BsonDocument("$or", new BsonArray
{
new BsonDocument("SuspendedAt", new BsonDocument("$ne", BsonNull.Value)),
new BsonDocument("SilencedAt", new BsonDocument("$ne", BsonNull.Value))
});
// personas here: their own posts and boosts
var avatars = database.GetCollection<BsonDocument>("Avatar");
await avatars.UpdateManyAsync(new BsonDocument("AccountState", new BsonDocument("$in", new BsonArray { 1, 2 })),
Builders<BsonDocument>.Update.Set("AccountState", 0), cancellationToken: token);
foreach (var avatar in await (await avatars.FindAsync(moderated, cancellationToken: token)).ToListAsync(token))
{
var (suspended, silenced) = (IsSet(avatar, "SuspendedAt"), IsSet(avatar, "SilencedAt"));
await avatars.UpdateOneAsync(new BsonDocument("_id", avatar["_id"]), Builders<BsonDocument>.Update.Set("AccountState", suspended ? 2 : 1),
cancellationToken: token);
flagged += (await posts.UpdateManyAsync(new BsonDocument { { "GroupUserId", avatar["_id"].ToString() }, { "IsFederatedCopy", false } },
Builders<BsonDocument>.Update.Set("AuthorSuspended", suspended).Set("AuthorLimited", silenced), cancellationToken: token)).ModifiedCount;
}
// accounts elsewhere: what they wrote and boosted (an account that deleted itself stays Deleted)
var foreign = database.GetCollection<BsonDocument>("ForeignAvatar");
await foreign.UpdateManyAsync(new BsonDocument("AccountState", new BsonDocument("$in", new BsonArray { 1, 2 })),
Builders<BsonDocument>.Update.Set("AccountState", 0), cancellationToken: token);
foreach (var account in await (await foreign.FindAsync(moderated, cancellationToken: token)).ToListAsync(token))
{
if (account.GetValue("ActorURI", BsonNull.Value) is not BsonString actor)
continue;
var (suspended, silenced) = (IsSet(account, "SuspendedAt"), IsSet(account, "SilencedAt"));
if (account.GetValue("AccountState", 0).ToInt32() != 4)
await foreign.UpdateOneAsync(new BsonDocument("_id", account["_id"]), Builders<BsonDocument>.Update.Set("AccountState", suspended ? 2 : 1),
cancellationToken: token);
var update = Builders<BsonDocument>.Update.Set("AuthorSuspended", suspended);
if (silenced)
update = update.Set("AuthorLimited", true);
flagged += (await posts.UpdateManyAsync(new BsonDocument { { "ActorURI", actor }, { "IsFederatedCopy", true } }, update, cancellationToken: token))
.ModifiedCount;
}
// servers silenced: every post of theirs, and of their subdomains
var blocks = database.GetCollection<BsonDocument>("DomainBlock");
foreach (var block in await (await blocks.FindAsync(new BsonDocument("Severity", 1), cancellationToken: token)).ToListAsync(token))
if (block.GetValue("Domain", BsonNull.Value) is BsonString domain && !string.IsNullOrWhiteSpace(domain.AsString))
flagged += (await posts.UpdateManyAsync(new BsonDocument
{
{ "ActorURI", OfDomain(domain.AsString) },
{ "IsFederatedCopy", true }
}, Builders<BsonDocument>.Update.Set("AuthorLimited", true), cancellationToken: token)).ModifiedCount;
return flagged;
}
// an actor's address on a server or one of its subdomains
public static BsonRegularExpression OfDomain(string domain) =>
new($"^https?://([^/]*\\.)?{System.Text.RegularExpressions.Regex.Escape(Federation.Moderation.DomainBlocks.Normalise(domain))}(:[0-9]+)?(/|$)", "i");
static bool IsSet(BsonDocument row, string field) => row.TryGetValue(field, out var value) && !value.IsBsonNull;
}
}