WIP: Import messages sent to local Actor inboxes to their private mail
This commit is contained in:
parent
5b69cdb516
commit
1aa56fbaa7
|
@ -3,11 +3,11 @@ const ActivityPubObject = require('./object');
|
|||
const apDb = require('../database').dbs.activitypub;
|
||||
const { getISOTimestampString } = require('../database');
|
||||
const { Errors } = require('../enig_error.js');
|
||||
const { PublicCollectionId: APPublicCollectionId } = require('./const');
|
||||
|
||||
// deps
|
||||
const { isString, get, isObject } = require('lodash');
|
||||
|
||||
const APPublicCollectionId = 'https://www.w3.org/ns/activitystreams#Public';
|
||||
const APPublicOwningUserId = 0;
|
||||
|
||||
module.exports = class Collection extends ActivityPubObject {
|
||||
|
@ -88,6 +88,17 @@ module.exports = class Collection extends ActivityPubObject {
|
|||
);
|
||||
}
|
||||
|
||||
static addInboxItem(inboxItem, owningUser, cb) {
|
||||
return Collection.addToCollection(
|
||||
'inbox',
|
||||
owningUser,
|
||||
inboxItem.id,
|
||||
inboxItem,
|
||||
true,
|
||||
cb
|
||||
);
|
||||
}
|
||||
|
||||
static addPublicInboxItem(inboxItem, cb) {
|
||||
return Collection.addToCollection(
|
||||
'publicInbox',
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
exports.ActivityStreamsContext = 'https://www.w3.org/ns/activitystreams';
|
||||
exports.PublicCollectionId = 'https://www.w3.org/ns/activitystreams#Public';
|
||||
|
||||
const WellKnownActivity = {
|
||||
Create: 'Create',
|
||||
|
|
|
@ -3,7 +3,7 @@ const ActivityPubObject = require('./object');
|
|||
const { Errors } = require('../enig_error');
|
||||
const { getISOTimestampString } = require('../database');
|
||||
const User = require('../user');
|
||||
const { messageBodyToHtml } = require('./util');
|
||||
const { messageBodyToHtml, htmlToMessageBody } = require('./util');
|
||||
|
||||
// deps
|
||||
const { v5: UUIDv5 } = require('uuid');
|
||||
|
@ -11,6 +11,7 @@ const Actor = require('./actor');
|
|||
const moment = require('moment');
|
||||
const Collection = require('./collection');
|
||||
const async = require('async');
|
||||
const { isString, isObject } = require('lodash');
|
||||
|
||||
const APMessageIdNamespace = '307bc7b3-3735-4573-9a20-e3f9eaac29c5';
|
||||
|
||||
|
@ -90,7 +91,7 @@ module.exports = class Note extends ActivityPubObject {
|
|||
audience: [message.isPrivate() ? 'as:Private' : 'as:Public'],
|
||||
|
||||
// :TODO: inReplyto if this is a reply; we need this store in message meta.
|
||||
|
||||
summary: message.subject,
|
||||
content: messageBodyToHtml(message.message.trim()),
|
||||
};
|
||||
|
||||
|
@ -104,24 +105,39 @@ module.exports = class Note extends ActivityPubObject {
|
|||
);
|
||||
}
|
||||
|
||||
toMessage(cb) {
|
||||
toMessage(options, cb) {
|
||||
if (!isObject(options.toUser) || !isString(options.areaTag)) {
|
||||
return cb(Errors.MissingParam('Missing one or more required options!'));
|
||||
}
|
||||
|
||||
// stable ID based on Note ID
|
||||
const message = new Message({
|
||||
uuid: UUIDv5(this.id, APMessageIdNamespace),
|
||||
});
|
||||
|
||||
// Fetch the remote actor
|
||||
// Fetch the remote actor info to get their user info
|
||||
Actor.fromId(this.attributedTo, false, (err, attributedToActor) => {
|
||||
if (err) {
|
||||
// :TODO: Log me
|
||||
message.toUserName = this.attributedTo; // have some sort of value =/
|
||||
message.fromUserName = this.attributedTo; // have some sort of value =/
|
||||
} else {
|
||||
message.toUserName =
|
||||
message.fromUserName =
|
||||
attributedToActor.preferredUsername || this.attributedTo;
|
||||
}
|
||||
|
||||
//
|
||||
// Note's can be addressed to 1:N users, but a Message is a 1:1
|
||||
// relationship. This method requires the mapping up front via options
|
||||
//
|
||||
(message.toUserName = options.toUser.username),
|
||||
(message.meta.System[Message.SystemMetaNames.LocalToUserID] =
|
||||
options.toUser.userId);
|
||||
message.areaTag = options.areaTag || Message.WellKnownAreaTags.Private;
|
||||
|
||||
message.subject = this.summary || '-ActivityPub-';
|
||||
message.message = this.content; // :TODO: HTML to suitable format, or even strip
|
||||
|
||||
// :TODO: it would be better to do some basic HTML to ANSI or pipe codes perhaps
|
||||
message.message = htmlToMessageBody(this.content);
|
||||
|
||||
try {
|
||||
message.modTimestamp = moment(this.published);
|
||||
|
@ -130,21 +146,17 @@ module.exports = class Note extends ActivityPubObject {
|
|||
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);
|
||||
message.setExternalFlavor(Message.AddressFlavor.ActivityPub);
|
||||
|
||||
message.meta.ActivityPub = message.meta.ActivityPub || {};
|
||||
message.meta.ActivityPub[Message.ActivityPubPropertyNames.ActivityId] =
|
||||
this.id;
|
||||
if (this.InReplyTo) {
|
||||
message.meta.ActivityPub[Message.ActivityPubPropertyNames.InReplyTo] =
|
||||
this.InReplyTo;
|
||||
}
|
||||
|
||||
return cb(null, message);
|
||||
});
|
||||
|
|
|
@ -11,6 +11,7 @@ const waterfall = require('async/waterfall');
|
|||
const fs = require('graceful-fs');
|
||||
const paths = require('path');
|
||||
const moment = require('moment');
|
||||
const { striptags } = require('striptags');
|
||||
|
||||
exports.ActivityStreamsContext = 'https://www.w3.org/ns/activitystreams';
|
||||
exports.isValidLink = isValidLink;
|
||||
|
@ -22,6 +23,7 @@ exports.userFromAccount = userFromAccount;
|
|||
exports.accountFromSelfUrl = accountFromSelfUrl;
|
||||
exports.getUserProfileTemplatedBody = getUserProfileTemplatedBody;
|
||||
exports.messageBodyToHtml = messageBodyToHtml;
|
||||
exports.htmlToMessageBody = htmlToMessageBody;
|
||||
|
||||
// :TODO: more info in default
|
||||
// this profile template is the *default* for both WebFinger
|
||||
|
@ -175,3 +177,7 @@ function getUserProfileTemplatedBody(
|
|||
function messageBodyToHtml(body) {
|
||||
return `<p>${body.replace(/\r?\n/g, '<br>')}</p>`;
|
||||
}
|
||||
|
||||
function htmlToMessageBody(html) {
|
||||
return striptags(html);
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ const ActivityPubSettings = require('../../../activitypub/settings');
|
|||
const Actor = require('../../../activitypub/actor');
|
||||
const Collection = require('../../../activitypub/collection');
|
||||
const EnigAssert = require('../../../enigma_assert');
|
||||
const Message = require('../../../message');
|
||||
|
||||
// deps
|
||||
const _ = require('lodash');
|
||||
|
@ -18,6 +19,7 @@ const enigma_assert = require('../../../enigma_assert');
|
|||
const httpSignature = require('http-signature');
|
||||
const async = require('async');
|
||||
const Note = require('../../../activitypub/note');
|
||||
const User = require('../../../user');
|
||||
|
||||
exports.moduleInfo = {
|
||||
name: 'ActivityPub',
|
||||
|
@ -262,12 +264,6 @@ exports.getModule = class ActivityPubWebHandler extends WebHandlerModule {
|
|||
}
|
||||
|
||||
_sharedInboxCreateActivity(req, resp, activity) {
|
||||
// When an object is being delivered to the originating actor's followers,
|
||||
// a server MAY reduce the number of receiving actors delivered to by
|
||||
// identifying all followers which share the same sharedInbox who would
|
||||
// otherwise be individual recipients and instead deliver objects to said
|
||||
// sharedInbox. Thus in this scenario, the remote/receiving server participates
|
||||
// in determining targeting and performing delivery to specific inboxes.
|
||||
let toActors = activity.to;
|
||||
if (!Array.isArray(toActors)) {
|
||||
toActors = [toActors];
|
||||
|
@ -288,22 +284,36 @@ exports.getModule = class ActivityPubWebHandler extends WebHandlerModule {
|
|||
}
|
||||
|
||||
_deliverSharedInboxNote(req, resp, toActors, activity) {
|
||||
// When an object is being delivered to the originating actor's followers,
|
||||
// a server MAY reduce the number of receiving actors delivered to by
|
||||
// identifying all followers which share the same sharedInbox who would
|
||||
// otherwise be individual recipients and instead deliver objects to said
|
||||
// sharedInbox. Thus in this scenario, the remote/receiving server participates
|
||||
// in determining targeting and performing delivery to specific inboxes.
|
||||
const note = new Note(activity.object);
|
||||
if (!note.isValid()) {
|
||||
// :TODO: Log me
|
||||
return this.webServer.notImplemented();
|
||||
}
|
||||
|
||||
async.forEach(
|
||||
toActors,
|
||||
(actor, nextActor) => {
|
||||
if (Collection.PublicCollectionId === actor) {
|
||||
(actorId, nextActor) => {
|
||||
if (Collection.PublicCollectionId === actorId) {
|
||||
// Deliver to inbox for "everyone":
|
||||
// - Add to 'sharedInbox' collection
|
||||
//
|
||||
Collection.addPublicInboxItem(activity.object, err => {
|
||||
if (err) {
|
||||
Collection.addPublicInboxItem(note, err => {
|
||||
return nextActor(err);
|
||||
}
|
||||
|
||||
return nextActor(null);
|
||||
});
|
||||
} else {
|
||||
nextActor(null);
|
||||
this._deliverInboxNoteToLocalActor(
|
||||
req,
|
||||
resp,
|
||||
actorId,
|
||||
note,
|
||||
nextActor
|
||||
);
|
||||
}
|
||||
},
|
||||
err => {
|
||||
|
@ -317,6 +327,49 @@ exports.getModule = class ActivityPubWebHandler extends WebHandlerModule {
|
|||
);
|
||||
}
|
||||
|
||||
_deliverInboxNoteToLocalActor(req, resp, actorId, note, cb) {
|
||||
const localUserName = accountFromSelfUrl(actorId);
|
||||
if (!localUserName) {
|
||||
this.log.debug({ url: req.url }, 'Could not get username from URL');
|
||||
return cb(null);
|
||||
}
|
||||
|
||||
User.getUserByUsername(localUserName, (err, localUser) => {
|
||||
if (err) {
|
||||
this.log.info(
|
||||
{ username: localUserName },
|
||||
`No local user account for "${localUserName}"`
|
||||
);
|
||||
return cb(null);
|
||||
}
|
||||
|
||||
Collection.addInboxItem(note, localUser, err => {
|
||||
if (err) {
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
//
|
||||
// Import the item to the user's private mailbox
|
||||
//
|
||||
const messageOpts = {
|
||||
// Notes can have 1:N 'to' relationships while a Message is 1:1;
|
||||
toUser: localUser,
|
||||
areaTag: Message.WellKnownAreaTags.Private,
|
||||
};
|
||||
|
||||
note.toMessage(messageOpts, (err, message) => {
|
||||
if (err) {
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
message.persist(err => {
|
||||
return cb(err);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
_getCollectionHandler(name, req, resp, signature) {
|
||||
EnigAssert(signature, 'Missing signature!');
|
||||
|
||||
|
|
|
@ -937,6 +937,15 @@ module.exports = class User {
|
|||
);
|
||||
}
|
||||
|
||||
static getUserByUsername(username, cb) {
|
||||
User.getUserIdAndName(username, (err, userId) => {
|
||||
if (err) {
|
||||
return cb(err);
|
||||
}
|
||||
return User.getUser(userId, cb);
|
||||
});
|
||||
}
|
||||
|
||||
static getUserIdAndNameByRealName(realName, cb) {
|
||||
userDb.get(
|
||||
`SELECT id, user_name
|
||||
|
|
|
@ -63,6 +63,7 @@
|
|||
"sqlite3": "5.0.11",
|
||||
"sqlite3-trans": "1.3.0",
|
||||
"ssh2": "1.11.0",
|
||||
"striptags": "^4.0.0-alpha.4",
|
||||
"systeminformation": "5.12.3",
|
||||
"telnet-socket": "0.2.4",
|
||||
"temptmp": "^1.1.0",
|
||||
|
|
|
@ -2675,6 +2675,11 @@ strip-json-comments@~2.0.1:
|
|||
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
|
||||
integrity sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==
|
||||
|
||||
striptags@^4.0.0-alpha.4:
|
||||
version "4.0.0-alpha.4"
|
||||
resolved "https://registry.yarnpkg.com/striptags/-/striptags-4.0.0-alpha.4.tgz#824f1ac040f824574316ce87a3663c0c4df9900d"
|
||||
integrity sha512-/0jWyVWhpg9ciRHfjKYBpMHXct/HrFRfsR2HU77nGPbc8SPcVSIHZlZR/0TG3MyPq2C+HiHuwx8BlbcdI/cNbw==
|
||||
|
||||
supports-color@^7.1.0:
|
||||
version "7.1.0"
|
||||
resolved "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz"
|
||||
|
|
Loading…
Reference in New Issue