enigma-bbs/core/activitypub/note.js

153 lines
5.2 KiB
JavaScript

const Message = require('../message');
const ActivityPubObject = require('./object');
const { Errors } = require('../enig_error');
const { getISOTimestampString } = require('../database');
const User = require('../user');
const { messageBodyToHtml } = require('./util');
// deps
const { v5: UUIDv5 } = require('uuid');
const Actor = require('./actor');
const moment = require('moment');
const Collection = require('./collection');
const async = require('async');
const APMessageIdNamespace = '307bc7b3-3735-4573-9a20-e3f9eaac29c5';
module.exports = class Note extends ActivityPubObject {
constructor(obj) {
super(obj);
}
isValid() {
if (!super.isValid()) {
return false;
}
// :TODO: validate required properties
return true;
}
static fromPublicNoteId(noteId, cb) {
Collection.embeddedObjById('outbox', false, noteId, (err, obj) => {
if (err) {
return cb(err);
}
return cb(null, new Note(obj.object));
});
}
// A local Message bound for ActivityPub
static fromLocalOutgoingMessage(message, webServer, cb) {
const localUserId = message.getLocalFromUserId();
if (!localUserId) {
return cb(Errors.UnexpectedState('Invalid user ID for local user!'));
}
if (Message.AddressFlavor.ActivityPub !== message.getAddressFlavor()) {
return cb(
Errors.Invalid('Cannot build note for non-ActivityPub addressed message')
);
}
const remoteActorAccount = message.getRemoteToUser();
if (!remoteActorAccount) {
return cb(
Errors.UnexpectedState('Message does not contain a remote address')
);
}
async.waterfall(
[
callback => {
return User.getUser(localUserId, callback);
},
(fromUser, callback) => {
Actor.fromLocalUser(fromUser, webServer, (err, fromActor) => {
return callback(err, fromUser, fromActor);
});
},
(fromUser, fromActor, callback) => {
Actor.fromAccountName(remoteActorAccount, (err, remoteActor) => {
return callback(err, fromUser, fromActor, remoteActor);
});
},
(fromUser, fromActor, remoteActor, callback) => {
const to = message.isPrivate()
? remoteActor.id
: Collection.PublicCollectionId;
// Refs
// - https://docs.joinmastodon.org/spec/activitypub/#properties-used
const obj = {
id: ActivityPubObject.makeObjectId(webServer, 'note'),
type: 'Note',
published: getISOTimestampString(message.modTimestamp),
to,
attributedTo: fromActor.id,
audience: [message.isPrivate() ? 'as:Private' : 'as:Public'],
// :TODO: inReplyto if this is a reply; we need this store in message meta.
content: messageBodyToHtml(message.message.trim()),
};
const note = new Note(obj);
return callback(null, { note, fromUser, remoteActor });
},
],
(err, noteInfo) => {
return cb(err, noteInfo);
}
);
}
toMessage(cb) {
// stable ID based on Note ID
const message = new Message({
uuid: UUIDv5(this.id, APMessageIdNamespace),
});
// Fetch the remote actor
Actor.fromId(this.attributedTo, false, (err, attributedToActor) => {
if (err) {
// :TODO: Log me
message.toUserName = this.attributedTo; // have some sort of value =/
} else {
message.toUserName =
attributedToActor.preferredUsername || this.attributedTo;
}
message.subject = this.summary || '-ActivityPub-';
message.message = this.content; // :TODO: HTML to suitable format, or even strip
try {
message.modTimestamp = moment(this.published);
} catch (e) {
// :TODO: Log warning
message.modTimestamp = moment();
}
// :TODO: areaTag
// :TODO: replyToMsgId from 'inReplyTo'
// :TODO: RemoteFromUser
message.meta[Message.WellKnownMetaCategories.ActivityPub] =
message.meta[Message.WellKnownMetaCategories.ActivityPub] || {};
const apMeta = message.meta[Message.WellKnownAreaTags.ActivityPub];
apMeta[Message.ActivityPubPropertyNames.ActivityId] = this.id;
if (this.InReplyTo) {
apMeta[Message.ActivityPubPropertyNames.InReplyTo] = this.InReplyTo;
}
message.setRemoteFromUser(this.attributedTo);
message.setExternalFlavor(Message.ExternalFlavor.ActivityPub);
return cb(null, message);
});
}
};