diff --git a/src/activitypub/mocks.js b/src/activitypub/mocks.js index 4467227bb6..fd6ac41fe2 100644 --- a/src/activitypub/mocks.js +++ b/src/activitypub/mocks.js @@ -211,7 +211,7 @@ Mocks.note = async (post) => { const raw = await posts.getPostField(post.pid, 'content'); - // todo: post visibility, category privileges integration + // todo: post visibility const to = [activitypub._constants.publicAddress]; const cc = [`${nconf.get('url')}/uid/${post.user.uid}/followers`]; diff --git a/src/api/activitypub.js b/src/api/activitypub.js index 857e0a0184..c816aa0f12 100644 --- a/src/api/activitypub.js +++ b/src/api/activitypub.js @@ -9,14 +9,29 @@ */ const nconf = require('nconf'); +const winston = require('winston'); const db = require('../database'); +const meta = require('../meta'); +const privileges = require('../privileges'); const activitypub = require('../activitypub'); const posts = require('../posts'); const activitypubApi = module.exports; -activitypubApi.follow = async (caller, { uid } = {}) => { +function noop() {} + +function enabledCheck(next) { + return async function (caller, params) { + if (!meta.config.activitypubEnabled) { + return noop; + } + + next(caller, params); + }; +} + +activitypubApi.follow = enabledCheck(async (caller, { uid } = {}) => { const result = await activitypub.helpers.query(uid); if (!result) { throw new Error('[[error:activitypub.invalid-id]]'); @@ -26,10 +41,10 @@ activitypubApi.follow = async (caller, { uid } = {}) => { type: 'Follow', object: result.actorUri, }); -}; +}); // should be .undo.follow -activitypubApi.unfollow = async (caller, { uid }) => { +activitypubApi.unfollow = enabledCheck(async (caller, { uid }) => { const result = await activitypub.helpers.query(uid); if (!result) { throw new Error('[[error:activitypub.invalid-id]]'); @@ -48,7 +63,7 @@ activitypubApi.unfollow = async (caller, { uid }) => { db.sortedSetRemove(`followingRemote:${caller.uid}`, result.actorUri), db.decrObjectField(`user:${caller.uid}`, 'followingRemoteCount'), ]); -}; +}); activitypubApi.create = {}; @@ -65,12 +80,18 @@ async function buildRecipients(object, uid) { return { targets }; } -activitypubApi.create.post = async (caller, { pid }) => { +activitypubApi.create.post = enabledCheck(async (caller, { pid }) => { const post = (await posts.getPostSummaryByPids([pid], caller.uid, { stripTags: false })).pop(); if (!post) { return; } + const allowed = await privileges.posts.can('topics:read', pid, activitypub._constants.uid); + if (!allowed) { + winston.verbose(`[activitypub/api] Not federating creation of pid ${pid} to the fediverse due to privileges.`); + return; + } + const object = await activitypub.mocks.note(post); const { targets } = await buildRecipients(object, post.user.uid); @@ -82,11 +103,11 @@ activitypubApi.create.post = async (caller, { pid }) => { }; await activitypub.send('uid', caller.uid, Array.from(targets), payload); -}; +}); activitypubApi.update = {}; -activitypubApi.update.profile = async (caller, { uid }) => { +activitypubApi.update.profile = enabledCheck(async (caller, { uid }) => { const [object, followers] = await Promise.all([ activitypub.mocks.actors.user(uid), db.getSortedSetMembers(`followersRemote:${caller.uid}`), @@ -98,12 +119,18 @@ activitypubApi.update.profile = async (caller, { uid }) => { cc: [], object, }); -}; +}); -activitypubApi.update.note = async (caller, { post }) => { +activitypubApi.update.note = enabledCheck(async (caller, { post }) => { const object = await activitypub.mocks.note(post); const { targets } = await buildRecipients(object, post.user.uid); + const allowed = await privileges.posts.can('topics:read', post.pid, activitypub._constants.uid); + if (!allowed) { + winston.verbose(`[activitypub/api] Not federating update of pid ${post.pid} to the fediverse due to privileges.`); + return; + } + const payload = { type: 'Update', to: object.to, @@ -112,12 +139,12 @@ activitypubApi.update.note = async (caller, { post }) => { }; await activitypub.send('uid', caller.uid, Array.from(targets), payload); -}; +}); activitypubApi.like = {}; -activitypubApi.like.note = async (caller, { pid }) => { - if (!activitypub.helpers.isUri(pid)) { +activitypubApi.like.note = enabledCheck(async (caller, { pid }) => { + if (!activitypub.helpers.isUri(pid)) { // remote only return; } @@ -130,13 +157,13 @@ activitypubApi.like.note = async (caller, { pid }) => { type: 'Like', object: pid, }); -}; +}); activitypubApi.undo = {}; // activitypubApi.undo.follow = -activitypubApi.undo.like = async (caller, { pid }) => { +activitypubApi.undo.like = enabledCheck(async (caller, { pid }) => { if (!activitypub.helpers.isUri(pid)) { return; } @@ -154,4 +181,4 @@ activitypubApi.undo.like = async (caller, { pid }) => { object: pid, }, }); -}; +}); diff --git a/src/api/topics.js b/src/api/topics.js index 26f00c8e5f..1a693c28af 100644 --- a/src/api/topics.js +++ b/src/api/topics.js @@ -80,7 +80,10 @@ topicsAPI.create = async function (caller, data) { socketHelpers.emitToUids('event:new_post', { posts: [result.postData] }, [caller.uid]); socketHelpers.emitToUids('event:new_topic', result.topicData, [caller.uid]); socketHelpers.notifyNew(caller.uid, 'newTopic', { posts: [result.postData], topic: result.topicData }); - activitypubApi.create.post(caller, { pid: result.postData.pid }); + + if (!isScheduling) { + activitypubApi.create.post(caller, { pid: result.postData.pid }); + } return result.topicData; }; diff --git a/src/topics/scheduled.js b/src/topics/scheduled.js index 52d70366dc..240a7f89e4 100644 --- a/src/topics/scheduled.js +++ b/src/topics/scheduled.js @@ -10,6 +10,7 @@ const socketHelpers = require('../socket.io/helpers'); const topics = require('./index'); const groups = require('../groups'); const user = require('../user'); +const api = require('../api'); const Scheduled = module.exports; @@ -47,6 +48,7 @@ async function postTids(tids) { sendNotifications(uids, topicsData), updateUserLastposttimes(uids, topicsData), updateGroupPosts(uids, topicsData), + federatePosts(uids, topicsData), ...topicsData.map(topicData => unpin(topicData.tid, topicData)), )); } @@ -149,6 +151,14 @@ async function updateGroupPosts(uids, topicsData) { })); } +function federatePosts(uids, topicData) { + topicData.forEach(({ mainPid: pid }, idx) => { + const uid = uids[idx]; + + api.activitypub.create.post({ uid }, { pid }); + }); +} + async function shiftPostTimes(tid, timestamp) { const pids = (await posts.getPidsFromSet(`tid:${tid}:posts`, 0, -1, false)); // Leaving other related score values intact, since they reflect post order correctly, and it seems that's good enough