diff --git a/src/activitypub/mocks.js b/src/activitypub/mocks.js index 2f7b157d2b..290ab13fc8 100644 --- a/src/activitypub/mocks.js +++ b/src/activitypub/mocks.js @@ -40,6 +40,95 @@ const sanitizeConfig = { }, }; +Mocks._normalize = async (object) => { + // Normalized incoming AP objects into expected types for easier mocking + let { attributedTo, url, image, content, source } = object; + + switch (true) { // non-string attributedTo handling + case Array.isArray(attributedTo): { + attributedTo = attributedTo.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; + }, []); + attributedTo = attributedTo.shift(); // take first valid uid + break; + } + + case typeof attributedTo === 'object' && attributedTo.hasOwnProperty('id'): { + attributedTo = attributedTo.id; + } + } + + let sourceContent = source && source.mediaType === 'text/markdown' ? source.content : undefined; + if (sourceContent) { + content = null; + sourceContent = await activitypub.helpers.remoteAnchorToLocalProfile(sourceContent, true); + } else if (content && content.length) { + content = sanitize(content, sanitizeConfig); + content = await activitypub.helpers.remoteAnchorToLocalProfile(content); + } else { + content = 'This post did not contain any content.'; + } + + switch (true) { + case image && image.hasOwnProperty('url') && !!image.url: { + image = image.url; + break; + } + + case image && typeof image === 'string': { + // no change + break; + } + + default: { + image = null; + } + } + if (image) { + const parsed = new URL(image); + if (!mime.getType(parsed.pathname).startsWith('image/')) { + activitypub.helpers.log(`[activitypub/mocks.post] Received image not identified as image due to MIME type: ${image}`); + image = null; + } + } + + 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 + } + } + + return { + ...object, + attributedTo, + content, + sourceContent, + image, + url, + }; +}; + Mocks.profile = async (actors, hostMap) => { // Should only ever be called by activitypub.actors.assert const profiles = await Promise.all(actors.map(async (actor) => { @@ -154,6 +243,8 @@ Mocks.post = async (objects) => { } const posts = await Promise.all(objects.map(async (object) => { + object = await Mocks._normalize(object); + if ( !activitypub._constants.acceptedPostTypes.includes(object.type) || !activitypub.helpers.isUri(object.id) // sanity-check the id @@ -166,31 +257,10 @@ Mocks.post = async (objects) => { url, attributedTo: uid, inReplyTo: toPid, - published, updated, name, content, source, + published, updated, name, content, sourceContent, type, to, cc, audience, attachment, tag, image, } = object; - switch (true) { // non-string attributedTo handling - case Array.isArray(uid): { - 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 - break; - } - - case typeof uid === 'object' && uid.hasOwnProperty('id'): { - uid = uid.id; - } - } await activitypub.actors.assert(uid); const resolved = await activitypub.helpers.resolveLocalId(toPid); @@ -202,59 +272,6 @@ Mocks.post = async (objects) => { let edited = new Date(updated); edited = Number.isNaN(edited.valueOf()) ? undefined : edited; - let sourceContent = source && source.mediaType === 'text/markdown' ? source.content : undefined; - if (sourceContent) { - content = null; - sourceContent = await activitypub.helpers.remoteAnchorToLocalProfile(sourceContent, true); - } else if (content && content.length) { - content = sanitize(content, sanitizeConfig); - content = await activitypub.helpers.remoteAnchorToLocalProfile(content); - } else { - content = 'This post did not contain any content.'; - } - - switch (true) { - case image && image.hasOwnProperty('url') && !!image.url: { - image = image.url; - break; - } - - case image && typeof image === 'string': { - // no change - break; - } - - default: { - image = null; - } - } - if (image) { - const parsed = new URL(image); - if (!mime.getType(parsed.pathname).startsWith('image/')) { - activitypub.helpers.log(`[activitypub/mocks.post] Received image not identified as image due to MIME type: ${image}`); - image = null; - } - } - - 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 }); @@ -281,6 +298,19 @@ Mocks.post = async (objects) => { return single ? posts.pop() : posts; }; +Mocks.message = async (object) => { + object = await Mocks._normalize(object); + + const message = { + mid: object.id, + uid: object.attributedTo, + content: object.content, + // ip: caller.ip, + }; + + return message; +}; + Mocks.actors = {}; Mocks.actors.user = async (uid) => { diff --git a/src/activitypub/notes.js b/src/activitypub/notes.js index 26dbbc0bd1..920fe69205 100644 --- a/src/activitypub/notes.js +++ b/src/activitypub/notes.js @@ -286,31 +286,30 @@ Notes.assertPrivate = async (object) => { timestamp = Date.now(); } + const payload = await activitypub.mocks.message(object); + if (!roomId) { - roomId = await messaging.newRoom(object.attributedTo, { uids: [...recipients] }); + roomId = await messaging.newRoom(payload.uid, { uids: [...recipients] }); } // Add any new members to the chat const added = Array.from(recipients).filter(uid => !participantUids.includes(uid)); const assertion = await activitypub.actors.assert(added); if (assertion) { - await messaging.addUsersToRoom(object.attributedTo, added, roomId); + await messaging.addUsersToRoom(payload.uid, added, roomId); } // Add message to room const message = await messaging.sendMessage({ - mid: object.id, - uid: object.attributedTo, - roomId: roomId, - content: object.content, - toMid: toMid, + ...payload, timestamp: Date.now(), - // ip: caller.ip, + roomId: roomId, + toMid: toMid, }); - messaging.notifyUsersInRoom(object.attributedTo, roomId, message); + messaging.notifyUsersInRoom(payload.uid, roomId, message); // Set real timestamp back so that the message shows even though it predates room joining - await messaging.setMessageField(object.id, 'timestamp', timestamp); + await messaging.setMessageField(payload.mid, 'timestamp', timestamp); return { roomId }; };