diff --git a/src/activitypub/inbox.js b/src/activitypub/inbox.js index 2f526c6477..58e9d101a2 100644 --- a/src/activitypub/inbox.js +++ b/src/activitypub/inbox.js @@ -5,6 +5,7 @@ const winston = require('winston'); const db = require('../database'); const user = require('../user'); const posts = require('../posts'); +const topics = require('../topics'); const categories = require('../categories'); const activitypub = require('.'); @@ -65,6 +66,46 @@ inbox.like = async (req) => { await posts.upvote(id, actor); }; +inbox.announce = async (req) => { + const { actor, object, published } = req.body; + let timestamp = Date.now(); + try { + timestamp = new Date(published).getTime(); + } catch (e) { + // ok to fail + } + + const { type, id } = await activitypub.helpers.resolveLocalId(object); + if (type !== 'post' || !(await posts.exists(id))) { + throw new Error('[[error:activitypub.invalid-id]]'); + } + + const assertion = await activitypub.actors.assert(actor); + if (!assertion) { + throw new Error('[[error:activitypub.invalid-id]]'); + } + + const tid = await posts.getPostField(id, 'tid'); + + // No double-announce allowed + const existing = await topics.events.find(tid, { + type: 'announce', + uid: actor, + pid: id, + }); + if (existing.length) { + await topics.events.purge(tid, existing); + } + + await topics.events.log(tid, { + type: 'announce', + uid: actor, + href: `/post/${id}`, + pid: id, + timestamp, + }); +}; + inbox.follow = async (req) => { // Sanity checks const { type, id } = await helpers.resolveLocalId(req.body.object); @@ -157,6 +198,10 @@ inbox.undo = async (req) => { const { actor, object } = req.body; const { type } = object; + if (actor !== object.actor) { + throw new Error('[[error:activitypub.actor-mismatch]]'); + } + const assertion = await activitypub.actors.assert(actor); if (!assertion) { throw new Error('[[error:activitypub.invalid-id]]'); @@ -202,5 +247,23 @@ inbox.undo = async (req) => { await posts.unvote(id, actor); break; } + + case 'Announce': { + const exists = await posts.exists(id); + if (localType !== 'post' || !exists) { + throw new Error('[[error:invalid-pid]]'); + } + + const tid = await posts.getPostField(id, 'tid'); + const existing = await topics.events.find(tid, { + type: 'announce', + uid: actor, + pid: id, + }); + + if (existing.length) { + await topics.events.purge(tid, existing); + } + } } }; diff --git a/src/activitypub/index.js b/src/activitypub/index.js index 0c7fb0b777..15a9e4c225 100644 --- a/src/activitypub/index.js +++ b/src/activitypub/index.js @@ -237,3 +237,15 @@ ActivityPub.send = async (type, id, targets, payload) => { } })); }; + +setTimeout(async () => { + await ActivityPub.send('uid', 1, 'https://localhost/uid/1', { + // type: 'Undo', + // object: { + type: 'Announce', + actor: `https://localhost/uid/1`, + object: 'https://localhost/post/1', + published: new Date().toISOString(), + // }, + }); +}, 2000); diff --git a/src/controllers/activitypub/index.js b/src/controllers/activitypub/index.js index d742af7d9a..e9b922df8a 100644 --- a/src/controllers/activitypub/index.js +++ b/src/controllers/activitypub/index.js @@ -112,42 +112,11 @@ Controller.getInbox = async (req, res) => { Controller.postInbox = async (req, res) => { // Note: underlying methods are internal use only, hence no exposure via src/api - switch (req.body.type) { - case 'Create': { - await activitypub.inbox.create(req); - break; - } - - case 'Update': { - await activitypub.inbox.update(req); - break; - } - - case 'Like': { - await activitypub.inbox.like(req); - break; - } - - case 'Follow': { - await activitypub.inbox.follow(req); - break; - } - - case 'Accept': { - await activitypub.inbox.accept(req); - break; - } - - case 'Undo': { - await activitypub.inbox.undo(req); - break; - } - - default: { - res.sendStatus(501); - break; - } + const method = String(req.body.type).toLowerCase(); + if (req.body.hasOwnProperty(method)) { + return res.sendStatus(501); } + await activitypub.inbox[method](req); res.sendStatus(200); }; diff --git a/src/topics/events.js b/src/topics/events.js index b67be1ea3c..ba74632b85 100644 --- a/src/topics/events.js +++ b/src/topics/events.js @@ -68,6 +68,10 @@ Events._types = { icon: 'fa-code-fork', translation: async (event, language) => translateEventArgs(event, language, 'topic:user-forked-topic', renderUser(event), `${relative_path}${event.href}`, renderTimeago(event)), }, + announce: { + icon: 'fa-share-alt', + translation: async (event, language) => translateEventArgs(event, language, 'activitypub:topic-event-announce', renderUser(event), `${relative_path}${event.href}`, renderTimeago(event)), + }, }; Events.init = async () => { @@ -199,8 +203,9 @@ async function modifyEvent({ tid, uid, eventIds, timestamps, events }) { event.id = parseInt(eventIds[idx], 10); event.timestamp = timestamps[idx]; event.timestampISO = new Date(timestamps[idx]).toISOString(); + event.uid = utils.isNumber(event.uid) ? parseInt(event.uid, 10) : event.uid; if (event.hasOwnProperty('uid')) { - event.user = users.get(event.uid === 'system' ? 'system' : parseInt(event.uid, 10)); + event.user = users.get(event.uid === 'system' ? 'system' : event.uid); } if (event.hasOwnProperty('fromCid')) { event.fromCategory = fromCategories[event.fromCid];