75 lines
4.5 KiB
C#
75 lines
4.5 KiB
C#
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;
|
||
|
|
}
|
||
|
|
}
|