Files
SocialPub/PrivaPub/Federation/Actors/AccountCountsJob.cs
T

60 lines
2.4 KiB
C#
Raw Normal View History

using MongoDB.Entities;
using PrivaPub.Federation.Objects;
using PrivaPub.Infrastructure.Http;
using PrivaPub.Infrastructure.Jobs;
using PrivaPub.Models.Jobs;
using PrivaPub.Models.User;
using PrivaPub.StaticServices;
namespace PrivaPub.Federation.Actors
{
// A remote account's follower, following and post counts, as its own server publishes them (the totalItems of its
// followers, following and outbox collections). Read when the account is fetched, at most once a day, never when
// someone looks at it; a hidden or missing collection leaves its count unknown. Mastodon and GoToSocial do the same.
public class AccountCountsJob : IJobHandler
{
readonly IRemoteActorService _remoteActors;
readonly DbEntities _dbEntities;
public AccountCountsJob(IRemoteActorService remoteActors, DbEntities dbEntities)
{
_remoteActors = remoteActors;
_dbEntities = dbEntities;
}
public JobKind Kind => JobKind.CountAccount;
public int Concurrency => 2;
public int MaxAttempts => 2;
public int PerHostLimit => 1;
public static string DedupeKey(string actorUri, DateTime day) => $"count|{actorUri}|{day:yyyyMMdd}";
public async Task<JobOutcome> Handle(Job job, CancellationToken token)
{
var actor = await _dbEntities.ForeignAvatars.Match(a => a.ActorURI == job.Payload && !a.DeletionAt.HasValue).ExecuteFirstAsync(token);
if (actor == default)
return JobOutcome.Done;
await DB.Default.Update<ForeignAvatar>().MatchID(actor.ID)
.Modify(a => a.FollowersCount, await Total(actor, actor.FollowersURL, token))
.Modify(a => a.FollowingCount, await Total(actor, actor.FollowingURL, token))
.Modify(a => a.StatusesCount, await Total(actor, actor.OutboxURL, token))
.Modify(a => a.CountedAt, DateTime.UtcNow)
.ExecuteAsync(token);
return JobOutcome.Done;
}
// only from the actor's own server, as everything we believe about it
async Task<int?> Total(ForeignAvatar actor, string collection, CancellationToken token)
{
if (string.IsNullOrEmpty(collection) || !Origin.Same(collection, actor.ActorURI))
return default;
using var scope = HttpScope.For("collection");
using var fetched = await _remoteActors.FetchObject(collection, token);
return fetched != default && fetched.Root.ValueKind == System.Text.Json.JsonValueKind.Object
&& fetched.Root.TryGetProperty("totalItems", out var total) && total.TryGetInt32(out var count) && count >= 0
? count
: default;
}
}
}