diff --git a/core/activitypub/activity.js b/core/activitypub/activity.js index 5f51c0bd..2d774d06 100644 --- a/core/activitypub/activity.js +++ b/core/activitypub/activity.js @@ -16,8 +16,8 @@ const { getISOTimestampString } = require('../database'); const _ = require('lodash'); module.exports = class Activity extends ActivityPubObject { - constructor(obj) { - super(obj); + constructor(obj, withContext = ActivityPubObject.DefaultContext) { + super(obj, withContext); } static get ActivityTypes() { @@ -48,14 +48,17 @@ module.exports = class Activity extends ActivityPubObject { }); } - static makeCreate(webServer, actor, obj) { - const activity = new Activity({ - id: Activity.activityObjectId(webServer), - to: obj.to, - type: WellKnownActivity.Create, - actor, - object: obj, - }); + static makeCreate(webServer, actor, obj, context) { + const activity = new Activity( + { + id: Activity.activityObjectId(webServer), + to: obj.to, + type: WellKnownActivity.Create, + actor, + object: obj, + }, + context + ); const copy = n => { if (obj[n]) { diff --git a/core/activitypub/actor.js b/core/activitypub/actor.js index 1352a8b0..db54a5d9 100644 --- a/core/activitypub/actor.js +++ b/core/activitypub/actor.js @@ -4,8 +4,8 @@ // ENiGMA½ const { Errors } = require('../enig_error.js'); const UserProps = require('../user_property'); -const { ActivityStreamsContext, isValidLink, userNameFromSubject } = require('./util'); const Endpoints = require('./endpoint'); +const { userNameFromSubject, isValidLink } = require('./util'); const Log = require('../logger').log; const { queryWebFinger } = require('../webfinger'); const EnigAssert = require('../enigma_assert'); @@ -24,10 +24,17 @@ const paths = require('path'); const ActorCacheTTL = moment.duration(120, 'days'); +// default context for Actor's +const DefaultContext = ActivityPubObject.makeContext(['https://w3id.org/security/v1'], { + toot: 'http://joinmastodon.org/ns#', + discoverable: 'toot:discoverable', + manuallyApprovesFollowers: 'as:manuallyApprovesFollowers', +}); + // https://www.w3.org/TR/activitypub/#actor-objects module.exports = class Actor extends ActivityPubObject { - constructor(obj) { - super(obj); + constructor(obj, withContext = DefaultContext) { + super(obj, withContext); } isValid() { @@ -90,16 +97,6 @@ module.exports = class Actor extends ActivityPubObject { }; const obj = { - '@context': [ - ActivityStreamsContext, - 'https://w3id.org/security/v1', // :TODO: add support - { - bbsInfo: { - '@id': 'bbs:bbsInfo', - '@type': '@id', - }, - }, - ], id: userActorId, type: 'Person', preferredUsername: user.username, diff --git a/core/activitypub/endpoint.js b/core/activitypub/endpoint.js index 8e2deff5..5e981694 100644 --- a/core/activitypub/endpoint.js +++ b/core/activitypub/endpoint.js @@ -1,5 +1,8 @@ const { WellKnownLocations } = require('../servers/content/web'); +// deps +const { v4: UUIDv4 } = require('uuid'); + exports.makeUserUrl = makeUserUrl; exports.inbox = inbox; exports.outbox = outbox; @@ -9,6 +12,7 @@ exports.actorId = actorId; exports.profile = profile; exports.avatar = avatar; exports.sharedInbox = sharedInbox; +exports.objectId = objectId; const ActivityPubUsersPrefix = '/ap/users/'; @@ -49,3 +53,9 @@ function avatar(webServer, user, filename) { function sharedInbox(webServer) { return webServer.buildUrl(WellKnownLocations.Internal + '/ap/shared-inbox'); } + +function objectId(webServer, objectType) { + return webServer.buildUrl( + WellKnownLocations.Internal + `/ap/${UUIDv4()}/${objectType}` + ); +} diff --git a/core/activitypub/note.js b/core/activitypub/note.js index 43af40ec..ec908d64 100644 --- a/core/activitypub/note.js +++ b/core/activitypub/note.js @@ -18,7 +18,7 @@ const APDefaultSummary = '[ActivityPub]'; module.exports = class Note extends ActivityPubObject { constructor(obj) { - super(obj); + super(obj, null); // Note are wrapped } isValid() { @@ -127,24 +127,29 @@ module.exports = class Note extends ActivityPubObject { published: getISOTimestampString(message.modTimestamp), to, attributedTo: fromActor.id, + summary: message.subject.trim(), content: messageToHtml(message), source: { content: message.message, mediaType: sourceMediaType, }, + sensitive: message.subject.startsWith('[NSFW]'), }; if (replyToNoteId) { obj.inReplyTo = replyToNoteId; } - // ignore the subject if it's our default summary value for replies - if (message.subject !== `RE: ${APDefaultSummary}`) { - obj.summary = message.subject; - } - const note = new Note(obj); - return callback(null, { note, fromUser, remoteActor }); + const context = ActivityPubObject.makeContext([], { + sensitive: 'as:sensitive', + }); + return callback(null, { + note, + fromUser, + remoteActor, + context, + }); }, ], (err, noteInfo) => { diff --git a/core/activitypub/object.js b/core/activitypub/object.js index d2286f45..50fa04f2 100644 --- a/core/activitypub/object.js +++ b/core/activitypub/object.js @@ -1,16 +1,32 @@ const { ActivityStreamsContext } = require('./util'); -const { WellKnownLocations } = require('../servers/content/web'); +const Endpoints = require('./endpoint'); // deps -const { isString } = require('lodash'); -const { v4: UUIDv4 } = require('uuid'); +const { isString, isObject } = require('lodash'); module.exports = class ActivityPubObject { - constructor(obj) { - this['@context'] = ActivityStreamsContext; + constructor(obj, withContext = [ActivityStreamsContext]) { + if (withContext) { + this.setContext(withContext); + } Object.assign(this, obj); } + static get DefaultContext() { + return [ActivityStreamsContext]; + } + + static makeContext(namespaceUrls, aliases = null) { + const context = [ActivityStreamsContext]; + if (Array.isArray(namespaceUrls)) { + context.push(...namespaceUrls); + } + if (isObject(aliases)) { + context.push(aliases); + } + return context; + } + static fromJsonString(s) { let obj; try { @@ -23,23 +39,27 @@ module.exports = class ActivityPubObject { } isValid() { - const nes = s => isString(s) && s.length > 1; + const nonEmpty = s => isString(s) && s.length > 1; // :TODO: Additional validation if ( (this['@context'] === ActivityStreamsContext || this['@context'][0] === ActivityStreamsContext) && - nes(this.id) && - nes(this.type) + nonEmpty(this.id) && + nonEmpty(this.type) ) { return true; } return false; } - static makeObjectId(webServer, suffix) { - // e.g. http://some.host/_enig/ap/bf81a22e-cb3e-41c8-b114-21f375b61124/activity - return webServer.buildUrl( - WellKnownLocations.Internal + `/ap/${UUIDv4()}/${suffix}` - ); + setContext(context) { + if (!Array.isArray(context)) { + context = [context]; + } + this['@context'] = context; + } + + static makeObjectId(webServer, objectType) { + return Endpoints.objectId(webServer, objectType); } }; diff --git a/core/scanner_tossers/activitypub.js b/core/scanner_tossers/activitypub.js index 795da959..9e0da50c 100644 --- a/core/scanner_tossers/activitypub.js +++ b/core/scanner_tossers/activitypub.js @@ -90,7 +90,7 @@ exports.getModule = class ActivityPubScannerTosser extends MessageScanTossModule ); }, (noteInfo, deliveryEndpoints, callback) => { - const { note, fromUser } = noteInfo; + const { note, fromUser, context } = noteInfo; // // Update the Note's addressing: @@ -127,7 +127,8 @@ exports.getModule = class ActivityPubScannerTosser extends MessageScanTossModule const activity = Activity.makeCreate( this._webServer(), note.attributedTo, - note + note, + context ); let allEndpoints = deliveryEndpoints.sharedInboxes;