From 95f2c4edb568b828dde5987d4502c59db5e8246e Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Wed, 26 Feb 2025 13:55:39 -0500 Subject: [PATCH] feat: support remote "Video" type objects in note assertion, #13120 - handle array attributedTo (plus per-object actor assertion instead of batched) - explicit "Video" type handling to automatically save URL as post attachment - handle array url property --- src/activitypub/index.js | 2 +- src/activitypub/mocks.js | 46 ++++++++++++++++++++++++++++++++--- src/middleware/activitypub.js | 1 + 3 files changed, 44 insertions(+), 5 deletions(-) diff --git a/src/activitypub/index.js b/src/activitypub/index.js index b5a70752d1..8472a552ac 100644 --- a/src/activitypub/index.js +++ b/src/activitypub/index.js @@ -37,7 +37,7 @@ ActivityPub._constants = Object.freeze({ 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"', ], acceptedPostTypes: [ - 'Note', 'Page', 'Article', 'Question', + 'Note', 'Page', 'Article', 'Question', 'Video', ], acceptableActorTypes: new Set(['Application', 'Group', 'Organization', 'Person', 'Service']), requiredActorProps: ['inbox', 'outbox'], diff --git a/src/activitypub/mocks.js b/src/activitypub/mocks.js index d45e585f81..3164fd85e1 100644 --- a/src/activitypub/mocks.js +++ b/src/activitypub/mocks.js @@ -153,9 +153,6 @@ Mocks.post = async (objects) => { objects = [objects]; } - const actorIds = new Set(objects.map(object => object.attributedTo).filter(Boolean)); - await activitypub.actors.assert(Array.from(actorIds)); - const posts = await Promise.all(objects.map(async (object) => { if ( !activitypub._constants.acceptedPostTypes.includes(object.type) || @@ -170,13 +167,30 @@ Mocks.post = async (objects) => { attributedTo: uid, inReplyTo: toPid, published, updated, name, content, source, - to, cc, audience, attachment, tag, image, + type, to, cc, audience, attachment, tag, image, } = object; + if (Array.isArray(uid)) { // Handle array attributedTo + uid = uid.reduce((valid, cur) => { + if (typeof cur === 'string') { + valid.push(cur); + } else if (typeof cur === 'object') { + if (cur.type === 'Person' && cur.id) { + valid.push(cur.id); + } + } + + return valid; + }, []); + uid = uid.shift(); // take first valid uid + await activitypub.actors.assert(uid); + } + const resolved = await activitypub.helpers.resolveLocalId(toPid); if (resolved.type === 'post') { toPid = resolved.id; } + const timestamp = new Date(published).getTime(); let edited = new Date(updated); edited = Number.isNaN(edited.valueOf()) ? undefined : edited; @@ -215,6 +229,30 @@ Mocks.post = async (objects) => { } } + if (url) { // Handle url array + if (Array.isArray(url)) { + url = url.reduce((valid, cur) => { + if (typeof cur === 'string') { + valid.push(cur); + } else if (typeof cur === 'object') { + if (cur.type === 'Link' && cur.href) { + if (!cur.mediaType || (cur.mediaType && cur.mediaType === 'text/html')) { + valid.push(cur.href); + } + } + } + + return valid; + }, []); + url = url.shift(); // take first valid url + } + } + + if (type === 'Video') { + attachment = attachment || []; + attachment.push({ url }); + } + const payload = { uid, pid, diff --git a/src/middleware/activitypub.js b/src/middleware/activitypub.js index 05f67d3338..4d3edb016c 100644 --- a/src/middleware/activitypub.js +++ b/src/middleware/activitypub.js @@ -137,6 +137,7 @@ middleware.resolveObjects = async function (req, res, next) { next(); }; +// todo: deprecate... this should be handled in actor and note assertion methods instead, or perhaps via helper fn middleware.normalize = async function (req, res, next) { // Normalizes the received data structure const { body } = req;