diff --git a/src/activitypub/actors.js b/src/activitypub/actors.js index b13f35ed18..e581801136 100644 --- a/src/activitypub/actors.js +++ b/src/activitypub/actors.js @@ -410,6 +410,7 @@ async function _migratePersonToGroup(categoryObjs) { } Actors.getLocalFollowers = async (id) => { + // Returns local uids and cids that follow a remote actor (by id) const response = { uids: new Set(), cids: new Set(), @@ -419,15 +420,27 @@ Actors.getLocalFollowers = async (id) => { return response; } - const members = await db.getSortedSetMembers(`followersRemote:${id}`); + const [isUser, isCategory] = await Promise.all([ + user.exists(id), + categories.exists(id), + ]); - members.forEach((id) => { - if (utils.isNumber(id)) { - response.uids.add(parseInt(id, 10)); - } else if (id.startsWith('cid|') && utils.isNumber(id.slice(4))) { - response.cids.add(parseInt(id.slice(4), 10)); - } - }); + if (isUser) { + 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))) { + response.cids.add(parseInt(id.slice(4), 10)); + } + }); + } else if (isCategory) { + const members = await db.getSortedSetRangeByScore(`cid:${id}:uid:watch:state`, 0, -1, categories.watchStates.tracking, categories.watchStates.watching); + members.forEach((uid) => { + response.uids.add(uid); + }); + } return response; }; diff --git a/src/activitypub/notes.js b/src/activitypub/notes.js index eafb1f8cb6..be0b61f847 100644 --- a/src/activitypub/notes.js +++ b/src/activitypub/notes.js @@ -462,6 +462,12 @@ Notes.syncUserInboxes = async function (tid, uid) { uids.add(uid); }); + // Category followers + const categoryFollowers = await activitypub.actors.getLocalFollowers(cid); + categoryFollowers.uids.forEach((uid) => { + uids.add(uid); + }); + const keys = Array.from(uids).map(uid => `uid:${uid}:inbox`); const score = await db.sortedSetScore(`cid:${cid}:tids`, tid); diff --git a/src/user/index.js b/src/user/index.js index 35c3e67f3e..2059621f6d 100644 --- a/src/user/index.js +++ b/src/user/index.js @@ -46,8 +46,12 @@ User.exists = async function (uids) { const singular = !Array.isArray(uids); uids = singular ? [uids] : uids; - let results = await Promise.all(uids.map(async uid => await db.isMemberOfSortedSets(['users:joindate', 'usersRemote:lastCrawled'], uid))); - results = results.map(set => set.some(Boolean)); + const local = await db.isSortedSetMembers('users:joindate', uids); + const remote = await db.exists(uids.map(uid => `userRemote:${uid}`)); + const results = local.map((a, idx) => { + const b = remote[idx]; + return a || b; + }); return singular ? results.pop() : results; }; diff --git a/test/activitypub/notes.js b/test/activitypub/notes.js index c9c5e42e7b..5157bc5645 100644 --- a/test/activitypub/notes.js +++ b/test/activitypub/notes.js @@ -109,6 +109,22 @@ describe('Notes', () => { assert.strictEqual(category[prop], 1); }); }); + + it('should add a remote category topic to a user\'s inbox if they are following the category', async () => { + const { id: cid, actor } = helpers.mocks.group(); + await activitypub.actors.assertGroup([cid]); + + const uid = await user.create({ username: utils.generateUUID() }); + await api.categories.setWatchState({ uid }, { cid, state: categories.watchStates.tracking }); + + const { id } = helpers.mocks.note({ + cc: [cid], + }); + const { tid } = await activitypub.notes.assert(0, id); + + const inInbox = await db.isSortedSetMember(`uid:${uid}:inbox`, tid); + assert(inInbox); + }); }); describe('User-specific behaviours', () => {