diff --git a/src/activitypub/actors.js b/src/activitypub/actors.js index bd339a7f5d..90fadfe628 100644 --- a/src/activitypub/actors.js +++ b/src/activitypub/actors.js @@ -21,6 +21,11 @@ const failedWebfingerCache = TTLCache({ const activitypub = module.parent.exports; const Actors = module.exports; +Actors._followerCache = TTLCache({ + name: 'ap-follower-cache', + max: 5000, + ttl: 1000 * 60 * 60, // 1 hour +}); Actors.qualify = async (ids, options = {}) => { /** @@ -420,17 +425,22 @@ Actors.assertGroup = async (ids, options = {}) => { return categoryObjs; }; -Actors.getLocalFollowers = async (id) => { - // Returns local uids and cids that follow a remote actor (by id) - const response = { +Actors.getFollowers = async (id) => { + /** + * Returns followers by local or remote id. Pass in a... + * - Remote id: returns local uids/cids that follow + * - Local id: returns remote uids that follow + */ + let response = Actors._followerCache.get(id); + if (response) { + return response; + } + + response = { uids: new Set(), cids: new Set(), }; - if (!activitypub.helpers.isUri(id)) { - return response; - } - const [isUser, isCategory] = await Promise.all([ user.exists(id), categories.exists(id), @@ -440,10 +450,10 @@ Actors.getLocalFollowers = async (id) => { const members = await db.getSortedSetMembers(`followersRemote:${id}`); members.forEach((id) => { - if (utils.isNumber(id)) { - response.uids.add(parseInt(id, 10)); - } else if (id.startsWith('cid|') && utils.isNumber(id.slice(4))) { + if (id.startsWith('cid|') && utils.isNumber(id.slice(4))) { response.cids.add(parseInt(id.slice(4), 10)); + } else { + response.uids.add(utils.isNumber(id) ? parseInt(id, 10) : id); } }); } else if (isCategory) { @@ -462,6 +472,7 @@ Actors.getLocalFollowers = async (id) => { }); } + Actors._followerCache.set(id, response); return response; }; diff --git a/src/activitypub/inbox.js b/src/activitypub/inbox.js index f3413f3f51..ea0e99de35 100644 --- a/src/activitypub/inbox.js +++ b/src/activitypub/inbox.js @@ -47,7 +47,7 @@ inbox.create = async (req) => { } // Category sync, remove when cross-posting available - const { cids } = await activitypub.actors.getLocalFollowers(actor); + const { cids } = await activitypub.actors.getFollowers(actor); let cid = null; if (cids.size > 0) { cid = Array.from(cids)[0]; @@ -202,7 +202,7 @@ inbox.update = async (req) => { return await activitypub.notes.assertPrivate(object); } - const { cids } = await activitypub.actors.getLocalFollowers(actor); + const { cids } = await activitypub.actors.getFollowers(actor); let cid = null; if (cids.size > 0) { cid = Array.from(cids)[0]; @@ -387,7 +387,7 @@ inbox.announce = async (req) => { let pid; // Category sync, remove when cross-posting available - const { cids } = await activitypub.actors.getLocalFollowers(actor); + const { cids } = await activitypub.actors.getFollowers(actor); const syncedCids = Array.from(cids); // 1b12 announce @@ -522,6 +522,7 @@ inbox.follow = async (req) => { const followerRemoteCount = await db.sortedSetCard(`followersRemote:${id}`); await user.setUserField(id, 'followerRemoteCount', followerRemoteCount); + activitypub.actors._followerCache.del(id); await user.onFollow(actor, id); activitypub.send('uid', id, actor, { @@ -613,6 +614,8 @@ inbox.accept = async (req) => { db.sortedSetAdd(`followersRemote:${actor}`, timestamp, `cid|${id}`), // for notes assertion checking ]); } + + activitypub.actors._followerCache.del(actor); } }; @@ -650,6 +653,7 @@ inbox.undo = async (req) => { const followerRemoteCount = await db.sortedSetCard(`followerRemote:${id}`); await user.setUserField(id, 'followerRemoteCount', followerRemoteCount); notifications.rescind(`follow:${id}:uid:${actor}`); + activitypub.actors._followerCache.del(id); break; } diff --git a/src/activitypub/notes.js b/src/activitypub/notes.js index 3cef55670e..32d801f57e 100644 --- a/src/activitypub/notes.js +++ b/src/activitypub/notes.js @@ -506,7 +506,7 @@ Notes.updateLocalRecipients = async (id, { to, cc }) => { const followedUid = await db.getObjectField('followersUrl:uid', recipient); if (followedUid) { - const { uids: followers } = await activitypub.actors.getLocalFollowers(followedUid); + const { uids: followers } = await activitypub.actors.getFollowers(followedUid); if (followers.size > 0) { followers.forEach((uid) => { uids.add(uid); @@ -590,7 +590,7 @@ Notes.syncUserInboxes = async function (tid, uid) { }); // Category followers - const categoryFollowers = await activitypub.actors.getLocalFollowers(cid); + const categoryFollowers = await activitypub.actors.getFollowers(cid); categoryFollowers.uids.forEach((uid) => { uids.add(uid); }); diff --git a/src/activitypub/out.js b/src/activitypub/out.js index 701464ba8c..dbe3a3e8e4 100644 --- a/src/activitypub/out.js +++ b/src/activitypub/out.js @@ -513,6 +513,7 @@ Out.undo.follow = enabledCheck(async (type, id, actor) => { db.sortedSetRemove(`followersRemote:${actor}`, `cid|${id}`), ]); } + activitypub.actors._followerCache.del(actor); }); Out.undo.like = enabledCheck(async (uid, pid) => { diff --git a/src/categories/index.js b/src/categories/index.js index 047de142c8..ed0b60b08f 100644 --- a/src/categories/index.js +++ b/src/categories/index.js @@ -58,7 +58,7 @@ Categories.getCategoryById = async function (data) { Categories.getTopicCount(data), Categories.getWatchState([data.cid], data.uid), getChildrenTree(category, data.uid), - !utils.isNumber(data.cid) ? activitypub.actors.getLocalFollowers(data.cid) : null, + !utils.isNumber(data.cid) ? activitypub.actors.getFollowers(data.cid) : null, ]; if (category.parentCid) {