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 apDb = require('../database').dbs.activitypub;
|
||||||
const { getISOTimestampString } = require('../database');
|
const { getISOTimestampString } = require('../database');
|
||||||
const { Errors } = require('../enig_error.js');
|
const { Errors } = require('../enig_error.js');
|
||||||
|
const { PublicCollectionId: APPublicCollectionId } = require('./const');
|
||||||
|
|
||||||
// deps
|
// deps
|
||||||
const { isString, get, isObject } = require('lodash');
|
const { isString, get, isObject } = require('lodash');
|
||||||
|
|
||||||
const APPublicCollectionId = 'https://www.w3.org/ns/activitystreams#Public';
|
|
||||||
const APPublicOwningUserId = 0;
|
const APPublicOwningUserId = 0;
|
||||||
|
|
||||||
module.exports = class Collection extends ActivityPubObject {
|
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) {
|
static addPublicInboxItem(inboxItem, cb) {
|
||||||
return Collection.addToCollection(
|
return Collection.addToCollection(
|
||||||
'publicInbox',
|
'publicInbox',
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
exports.ActivityStreamsContext = 'https://www.w3.org/ns/activitystreams';
|
exports.ActivityStreamsContext = 'https://www.w3.org/ns/activitystreams';
|
||||||
|
exports.PublicCollectionId = 'https://www.w3.org/ns/activitystreams#Public';
|
||||||
|
|
||||||
const WellKnownActivity = {
|
const WellKnownActivity = {
|
||||||
Create: 'Create',
|
Create: 'Create',
|
||||||
|
|
|
@ -3,7 +3,7 @@ const ActivityPubObject = require('./object');
|
||||||
const { Errors } = require('../enig_error');
|
const { Errors } = require('../enig_error');
|
||||||
const { getISOTimestampString } = require('../database');
|
const { getISOTimestampString } = require('../database');
|
||||||
const User = require('../user');
|
const User = require('../user');
|
||||||
const { messageBodyToHtml } = require('./util');
|
const { messageBodyToHtml, htmlToMessageBody } = require('./util');
|
||||||
|
|
||||||
// deps
|
// deps
|
||||||
const { v5: UUIDv5 } = require('uuid');
|
const { v5: UUIDv5 } = require('uuid');
|
||||||
|
@ -11,6 +11,7 @@ const Actor = require('./actor');
|
||||||
const moment = require('moment');
|
const moment = require('moment');
|
||||||
const Collection = require('./collection');
|
const Collection = require('./collection');
|
||||||
const async = require('async');
|
const async = require('async');
|
||||||
|
const { isString, isObject } = require('lodash');
|
||||||
|
|
||||||
const APMessageIdNamespace = '307bc7b3-3735-4573-9a20-e3f9eaac29c5';
|
const APMessageIdNamespace = '307bc7b3-3735-4573-9a20-e3f9eaac29c5';
|
||||||
|
|
||||||
|
@ -90,7 +91,7 @@ module.exports = class Note extends ActivityPubObject {
|
||||||
audience: [message.isPrivate() ? 'as:Private' : 'as:Public'],
|
audience: [message.isPrivate() ? 'as:Private' : 'as:Public'],
|
||||||
|
|
||||||
// :TODO: inReplyto if this is a reply; we need this store in message meta.
|
// :TODO: inReplyto if this is a reply; we need this store in message meta.
|
||||||
|
summary: message.subject,
|
||||||
content: messageBodyToHtml(message.message.trim()),
|
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
|
// stable ID based on Note ID
|
||||||
const message = new Message({
|
const message = new Message({
|
||||||
uuid: UUIDv5(this.id, APMessageIdNamespace),
|
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) => {
|
Actor.fromId(this.attributedTo, false, (err, attributedToActor) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
// :TODO: Log me
|
// :TODO: Log me
|
||||||
message.toUserName = this.attributedTo; // have some sort of value =/
|
message.fromUserName = this.attributedTo; // have some sort of value =/
|
||||||
} else {
|
} else {
|
||||||
message.toUserName =
|
message.fromUserName =
|
||||||
attributedToActor.preferredUsername || this.attributedTo;
|
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.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 {
|
try {
|
||||||
message.modTimestamp = moment(this.published);
|
message.modTimestamp = moment(this.published);
|
||||||
|
@ -130,21 +146,17 @@ module.exports = class Note extends ActivityPubObject {
|
||||||
message.modTimestamp = moment();
|
message.modTimestamp = moment();
|
||||||
}
|
}
|
||||||
|
|
||||||
// :TODO: areaTag
|
|
||||||
// :TODO: replyToMsgId from 'inReplyTo'
|
// :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.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);
|
return cb(null, message);
|
||||||
});
|
});
|
||||||
|
|
|
@ -11,6 +11,7 @@ const waterfall = require('async/waterfall');
|
||||||
const fs = require('graceful-fs');
|
const fs = require('graceful-fs');
|
||||||
const paths = require('path');
|
const paths = require('path');
|
||||||
const moment = require('moment');
|
const moment = require('moment');
|
||||||
|
const { striptags } = require('striptags');
|
||||||
|
|
||||||
exports.ActivityStreamsContext = 'https://www.w3.org/ns/activitystreams';
|
exports.ActivityStreamsContext = 'https://www.w3.org/ns/activitystreams';
|
||||||
exports.isValidLink = isValidLink;
|
exports.isValidLink = isValidLink;
|
||||||
|
@ -22,6 +23,7 @@ exports.userFromAccount = userFromAccount;
|
||||||
exports.accountFromSelfUrl = accountFromSelfUrl;
|
exports.accountFromSelfUrl = accountFromSelfUrl;
|
||||||
exports.getUserProfileTemplatedBody = getUserProfileTemplatedBody;
|
exports.getUserProfileTemplatedBody = getUserProfileTemplatedBody;
|
||||||
exports.messageBodyToHtml = messageBodyToHtml;
|
exports.messageBodyToHtml = messageBodyToHtml;
|
||||||
|
exports.htmlToMessageBody = htmlToMessageBody;
|
||||||
|
|
||||||
// :TODO: more info in default
|
// :TODO: more info in default
|
||||||
// this profile template is the *default* for both WebFinger
|
// this profile template is the *default* for both WebFinger
|
||||||
|
@ -175,3 +177,7 @@ function getUserProfileTemplatedBody(
|
||||||
function messageBodyToHtml(body) {
|
function messageBodyToHtml(body) {
|
||||||
return `<p>${body.replace(/\r?\n/g, '<br>')}</p>`;
|
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 Actor = require('../../../activitypub/actor');
|
||||||
const Collection = require('../../../activitypub/collection');
|
const Collection = require('../../../activitypub/collection');
|
||||||
const EnigAssert = require('../../../enigma_assert');
|
const EnigAssert = require('../../../enigma_assert');
|
||||||
|
const Message = require('../../../message');
|
||||||
|
|
||||||
// deps
|
// deps
|
||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
|
@ -18,6 +19,7 @@ const enigma_assert = require('../../../enigma_assert');
|
||||||
const httpSignature = require('http-signature');
|
const httpSignature = require('http-signature');
|
||||||
const async = require('async');
|
const async = require('async');
|
||||||
const Note = require('../../../activitypub/note');
|
const Note = require('../../../activitypub/note');
|
||||||
|
const User = require('../../../user');
|
||||||
|
|
||||||
exports.moduleInfo = {
|
exports.moduleInfo = {
|
||||||
name: 'ActivityPub',
|
name: 'ActivityPub',
|
||||||
|
@ -262,12 +264,6 @@ exports.getModule = class ActivityPubWebHandler extends WebHandlerModule {
|
||||||
}
|
}
|
||||||
|
|
||||||
_sharedInboxCreateActivity(req, resp, activity) {
|
_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;
|
let toActors = activity.to;
|
||||||
if (!Array.isArray(toActors)) {
|
if (!Array.isArray(toActors)) {
|
||||||
toActors = [toActors];
|
toActors = [toActors];
|
||||||
|
@ -288,22 +284,36 @@ exports.getModule = class ActivityPubWebHandler extends WebHandlerModule {
|
||||||
}
|
}
|
||||||
|
|
||||||
_deliverSharedInboxNote(req, resp, toActors, activity) {
|
_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(
|
async.forEach(
|
||||||
toActors,
|
toActors,
|
||||||
(actor, nextActor) => {
|
(actorId, nextActor) => {
|
||||||
if (Collection.PublicCollectionId === actor) {
|
if (Collection.PublicCollectionId === actorId) {
|
||||||
// Deliver to inbox for "everyone":
|
// Deliver to inbox for "everyone":
|
||||||
// - Add to 'sharedInbox' collection
|
// - Add to 'sharedInbox' collection
|
||||||
//
|
//
|
||||||
Collection.addPublicInboxItem(activity.object, err => {
|
Collection.addPublicInboxItem(note, err => {
|
||||||
if (err) {
|
|
||||||
return nextActor(err);
|
return nextActor(err);
|
||||||
}
|
|
||||||
|
|
||||||
return nextActor(null);
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
nextActor(null);
|
this._deliverInboxNoteToLocalActor(
|
||||||
|
req,
|
||||||
|
resp,
|
||||||
|
actorId,
|
||||||
|
note,
|
||||||
|
nextActor
|
||||||
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
err => {
|
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) {
|
_getCollectionHandler(name, req, resp, signature) {
|
||||||
EnigAssert(signature, 'Missing 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) {
|
static getUserIdAndNameByRealName(realName, cb) {
|
||||||
userDb.get(
|
userDb.get(
|
||||||
`SELECT id, user_name
|
`SELECT id, user_name
|
||||||
|
|
|
@ -63,6 +63,7 @@
|
||||||
"sqlite3": "5.0.11",
|
"sqlite3": "5.0.11",
|
||||||
"sqlite3-trans": "1.3.0",
|
"sqlite3-trans": "1.3.0",
|
||||||
"ssh2": "1.11.0",
|
"ssh2": "1.11.0",
|
||||||
|
"striptags": "^4.0.0-alpha.4",
|
||||||
"systeminformation": "5.12.3",
|
"systeminformation": "5.12.3",
|
||||||
"telnet-socket": "0.2.4",
|
"telnet-socket": "0.2.4",
|
||||||
"temptmp": "^1.1.0",
|
"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"
|
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
|
||||||
integrity sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==
|
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:
|
supports-color@^7.1.0:
|
||||||
version "7.1.0"
|
version "7.1.0"
|
||||||
resolved "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz"
|
resolved "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz"
|
||||||
|
|
Loading…
Reference in New Issue