mirror of
https://github.com/NodeBB/NodeBB.git
synced 2026-01-05 15:20:39 +01:00
refactor: store local follow backreferences for remote users (both followers and following), update actor pruning to take local follow counts into consideration, fixes #12701
This commit is contained in:
@@ -207,12 +207,19 @@ Actors.getLocalFollowers = async (id) => {
|
||||
return response;
|
||||
};
|
||||
|
||||
Actors.getLocalFollowersCount = async (id) => {
|
||||
if (!activitypub.helpers.isUri(id)) {
|
||||
return false;
|
||||
Actors.getLocalFollowCounts = async (actor) => {
|
||||
let followers = 0; // x local followers
|
||||
let following = 0; // following x local users
|
||||
if (!activitypub.helpers.isUri(actor)) {
|
||||
return { followers, following };
|
||||
}
|
||||
|
||||
return await db.sortedSetCard(`followersRemote:${id}`);
|
||||
[followers, following] = await Promise.all([
|
||||
db.sortedSetCard(`followersRemote:${actor}`),
|
||||
db.sortedSetCard(`followingRemote:${actor}`),
|
||||
]);
|
||||
|
||||
return { followers, following };
|
||||
};
|
||||
|
||||
Actors.remove = async (id) => {
|
||||
@@ -270,7 +277,7 @@ Actors.prune = async () => {
|
||||
|
||||
await batch.processArray(uids, async (uids) => {
|
||||
const exists = await db.exists(uids.map(uid => `userRemote:${uid}`));
|
||||
const counts = await db.sortedSetsCard(uids.map(uid => `uid:${uid}:posts`));
|
||||
const postCounts = await db.sortedSetsCard(uids.map(uid => `uid:${uid}:posts`));
|
||||
await Promise.all(uids.map(async (uid, idx) => {
|
||||
if (!exists[idx]) {
|
||||
// id in zset but not asserted, handle and return early
|
||||
@@ -278,8 +285,9 @@ Actors.prune = async () => {
|
||||
return;
|
||||
}
|
||||
|
||||
const count = counts[idx];
|
||||
if (count < 1) {
|
||||
const { followers, following } = await Actors.getLocalFollowCounts(uid);
|
||||
const postCount = postCounts[idx];
|
||||
if ([postCount, followers, following].every(metric => metric < 1)) {
|
||||
try {
|
||||
await user.deleteAccount(uid);
|
||||
deletionCount += 1;
|
||||
|
||||
@@ -235,8 +235,8 @@ inbox.announce = async (req) => {
|
||||
} else { // Remote object
|
||||
// Follower check
|
||||
if (!cid) {
|
||||
const numFollowers = await activitypub.actors.getLocalFollowersCount(actor);
|
||||
if (!numFollowers) {
|
||||
const { followers } = await activitypub.actors.getLocalFollowCounts(actor);
|
||||
if (!followers) {
|
||||
winston.verbose(`[activitypub/inbox.announce] Rejecting ${object.id} via ${actor} due to no followers`);
|
||||
reject('Announce', object, actor);
|
||||
return;
|
||||
@@ -300,6 +300,7 @@ inbox.follow = async (req) => {
|
||||
|
||||
const now = Date.now();
|
||||
await db.sortedSetAdd(`followersRemote:${id}`, now, actor);
|
||||
await db.sortedSetAdd(`followingRemote:${actor}`, now, id); // for following backreference (actor pruning)
|
||||
|
||||
const followerRemoteCount = await db.sortedSetCard(`followersRemote:${id}`);
|
||||
await user.setUserField(id, 'followerRemoteCount', followerRemoteCount);
|
||||
@@ -422,7 +423,10 @@ inbox.undo = async (req) => {
|
||||
throw new Error('[[error:invalid-uid]]');
|
||||
}
|
||||
|
||||
await db.sortedSetRemove(`followersRemote:${id}`, actor);
|
||||
await Promise.all([
|
||||
db.sortedSetRemove(`followersRemote:${id}`, actor),
|
||||
db.sortedSetRemove(`followingRemote:${actor}`, id),
|
||||
]);
|
||||
const followerRemoteCount = await db.sortedSetCard(`followerRemote:${id}`);
|
||||
await user.setUserField(id, 'followerRemoteCount', followerRemoteCount);
|
||||
notifications.rescind(`follow:${id}:uid:${actor}`);
|
||||
|
||||
@@ -40,10 +40,10 @@ Mocks.profile = async (actors) => {
|
||||
let hostname;
|
||||
let {
|
||||
url, preferredUsername, published, icon, image,
|
||||
name, summary, followers, followerCount, followingCount,
|
||||
inbox, endpoints,
|
||||
name, summary, followers, inbox, endpoints,
|
||||
} = actor;
|
||||
preferredUsername = preferredUsername || slugify(name);
|
||||
const { followers: followerCount, following: followingCount } = await activitypub.actors.getLocalFollowCounts(uid);
|
||||
|
||||
try {
|
||||
({ hostname } = new URL(actor.id));
|
||||
|
||||
@@ -181,7 +181,7 @@ async function assertRelation(post) {
|
||||
*/
|
||||
|
||||
// Is followed by at least one local user
|
||||
const numFollowers = await activitypub.actors.getLocalFollowersCount(post.uid);
|
||||
const { followers } = await activitypub.actors.getLocalFollowCounts(post.uid);
|
||||
|
||||
// Local user is mentioned
|
||||
const { tag } = post._activitypub;
|
||||
@@ -201,7 +201,7 @@ async function assertRelation(post) {
|
||||
uids = uids.filter(Boolean);
|
||||
}
|
||||
|
||||
return numFollowers > 0 || uids.length;
|
||||
return followers > 0 || uids.length;
|
||||
}
|
||||
|
||||
Notes.updateLocalRecipients = async (id, { to, cc }) => {
|
||||
|
||||
41
src/upgrades/4.0.0/follow_backreferences.js
Normal file
41
src/upgrades/4.0.0/follow_backreferences.js
Normal file
@@ -0,0 +1,41 @@
|
||||
'use strict';
|
||||
|
||||
const db = require('../../database');
|
||||
const batch = require('../../batch');
|
||||
const activitypub = require('../../activitypub');
|
||||
|
||||
module.exports = {
|
||||
name: 'Establish follow backreference sorted sets for remote users',
|
||||
timestamp: Date.UTC(2024, 4, 1),
|
||||
method: async function () {
|
||||
const { progress } = this;
|
||||
const bulkOp = [];
|
||||
const now = Date.now();
|
||||
const reassert = [];
|
||||
|
||||
await batch.processSortedSet('users:joindate', async (uids) => {
|
||||
const [_followers, _following] = await Promise.all([
|
||||
db.getSortedSetsMembers(uids.map(uid => `followersRemote:${uid}`)),
|
||||
db.getSortedSetsMembers(uids.map(uid => `followingRemote:${uid}`)),
|
||||
]);
|
||||
|
||||
const toCheck = Array.from(new Set(_following.flat()));
|
||||
const asserted = await db.isSortedSetMembers('usersRemote:lastCrawled', toCheck);
|
||||
reassert.push(...toCheck.filter((actor, idx) => !asserted[idx]));
|
||||
|
||||
uids.forEach((uid, idx) => {
|
||||
const followers = _followers[idx];
|
||||
if (followers.length) {
|
||||
bulkOp.push(...followers.map(actor => [`followingRemote:${actor}`, now, uid]))
|
||||
}
|
||||
});
|
||||
|
||||
progress.incr(uids.length);
|
||||
}, { progress });
|
||||
|
||||
await Promise.all([
|
||||
db.sortedSetAddBulk(bulkOp),
|
||||
activitypub.actors.assert(Array.from(new Set(reassert))),
|
||||
]);
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user