2023-01-13 01:49:13 +00:00
|
|
|
const Activity = require('../activitypub/activity');
|
2023-01-12 05:37:09 +00:00
|
|
|
const Message = require('../message');
|
|
|
|
const { MessageScanTossModule } = require('../msg_scan_toss_module');
|
|
|
|
const { getServer } = require('../listening_server');
|
2023-01-13 01:26:44 +00:00
|
|
|
const Log = require('../logger').log;
|
|
|
|
|
|
|
|
// deps
|
|
|
|
const async = require('async');
|
|
|
|
const _ = require('lodash');
|
2023-01-22 01:51:54 +00:00
|
|
|
const Collection = require('../activitypub/collection');
|
2023-01-23 21:45:56 +00:00
|
|
|
const Note = require('../activitypub/note');
|
2023-01-12 05:37:09 +00:00
|
|
|
|
|
|
|
exports.moduleInfo = {
|
|
|
|
name: 'ActivityPub',
|
|
|
|
desc: 'Provides ActivityPub scanner/tosser integration',
|
|
|
|
author: 'NuSkooler',
|
|
|
|
};
|
|
|
|
|
|
|
|
exports.getModule = class ActivityPubScannerTosser extends MessageScanTossModule {
|
|
|
|
constructor() {
|
|
|
|
super();
|
2023-01-13 01:26:44 +00:00
|
|
|
|
|
|
|
this.log = Log.child({ module: 'ActivityPubScannerTosser' });
|
2023-01-12 05:37:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
startup(cb) {
|
|
|
|
return cb(null);
|
|
|
|
}
|
|
|
|
|
|
|
|
shutdown(cb) {
|
|
|
|
return cb(null);
|
|
|
|
}
|
|
|
|
|
|
|
|
record(message) {
|
2023-01-13 01:38:42 +00:00
|
|
|
if (!this._shouldExportMessage(message)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2023-01-12 05:37:09 +00:00
|
|
|
if (!this._isEnabled()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2023-01-13 01:26:44 +00:00
|
|
|
async.waterfall(
|
|
|
|
[
|
|
|
|
callback => {
|
2023-01-23 21:45:56 +00:00
|
|
|
Note.fromLocalOutgoingMessage(
|
2023-01-13 01:26:44 +00:00
|
|
|
message,
|
2023-01-23 21:45:56 +00:00
|
|
|
this._webServer(),
|
|
|
|
(err, noteInfo) => {
|
|
|
|
return callback(err, noteInfo);
|
|
|
|
}
|
2023-01-13 01:26:44 +00:00
|
|
|
);
|
|
|
|
},
|
|
|
|
(noteInfo, callback) => {
|
2023-01-23 21:45:56 +00:00
|
|
|
const { note, fromUser, remoteActor } = noteInfo;
|
|
|
|
|
|
|
|
const activity = Activity.makeCreate(
|
|
|
|
this._webServer(),
|
|
|
|
note.attributedTo,
|
|
|
|
note
|
|
|
|
);
|
2023-01-12 05:37:09 +00:00
|
|
|
|
2023-01-22 01:51:54 +00:00
|
|
|
// :TODO: Implement retry logic (connection issues, retryable HTTP status) ??
|
2023-01-26 01:41:47 +00:00
|
|
|
const inbox = remoteActor.inbox;
|
2023-01-23 21:45:56 +00:00
|
|
|
|
2023-01-26 01:41:47 +00:00
|
|
|
// const inbox = remoteActor.endpoints.sharedInbox;
|
|
|
|
// activity.object.to = 'https://www.w3.org/ns/activitystreams#Public';
|
2023-01-23 21:45:56 +00:00
|
|
|
|
2023-01-13 01:26:44 +00:00
|
|
|
activity.sendTo(
|
2023-01-23 21:45:56 +00:00
|
|
|
inbox,
|
2023-01-13 01:26:44 +00:00
|
|
|
fromUser,
|
|
|
|
this._webServer(),
|
|
|
|
(err, respBody, res) => {
|
|
|
|
if (err) {
|
2023-01-22 18:02:45 +00:00
|
|
|
this.log.warn(
|
|
|
|
{ error: err.message, inbox: remoteActor.inbox },
|
|
|
|
'Failed to send "Note" Activity to Inbox'
|
|
|
|
);
|
|
|
|
} else if (res.statusCode !== 202 && res.statusCode !== 200) {
|
2023-01-13 01:26:44 +00:00
|
|
|
this.log.warn(
|
|
|
|
{
|
|
|
|
inbox: remoteActor.inbox,
|
|
|
|
statusCode: res.statusCode,
|
|
|
|
body: _.truncate(respBody, 128),
|
|
|
|
},
|
|
|
|
'Unexpected status code'
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2023-01-22 18:02:45 +00:00
|
|
|
// carry on regardless if we sent and record
|
|
|
|
// the item in the user's Outbox collection
|
2023-01-13 01:38:42 +00:00
|
|
|
return callback(null, activity, fromUser);
|
|
|
|
}
|
|
|
|
);
|
|
|
|
},
|
|
|
|
(activity, fromUser, callback) => {
|
2023-01-22 18:02:45 +00:00
|
|
|
// If we failed to send above,
|
|
|
|
Collection.addOutboxItem(
|
|
|
|
fromUser,
|
|
|
|
activity,
|
|
|
|
message.isPrivate(),
|
2023-01-28 18:55:31 +00:00
|
|
|
this._webServer(),
|
2023-02-03 22:14:27 +00:00
|
|
|
false, // do not ignore dupes
|
2023-01-22 18:02:45 +00:00
|
|
|
(err, localId) => {
|
|
|
|
if (!err) {
|
|
|
|
this.log.debug(
|
|
|
|
{ localId, activityId: activity.id },
|
|
|
|
'Note Activity persisted to "outbox" collection"'
|
|
|
|
);
|
|
|
|
}
|
|
|
|
return callback(err, activity);
|
2023-01-13 01:26:44 +00:00
|
|
|
}
|
2023-01-22 18:02:45 +00:00
|
|
|
);
|
2023-01-13 01:26:44 +00:00
|
|
|
},
|
|
|
|
(activity, callback) => {
|
|
|
|
// mark exported
|
|
|
|
return message.persistMetaValue(
|
|
|
|
Message.WellKnownMetaCategories.System,
|
|
|
|
Message.SystemMetaNames.StateFlags0,
|
|
|
|
Message.StateFlags0.Exported.toString(),
|
|
|
|
err => {
|
|
|
|
return callback(err, activity);
|
|
|
|
}
|
|
|
|
);
|
|
|
|
},
|
|
|
|
(activity, callback) => {
|
|
|
|
// message -> Activity ID relation
|
|
|
|
return message.persistMetaValue(
|
|
|
|
Message.WellKnownMetaCategories.ActivityPub,
|
|
|
|
Message.ActivityPubPropertyNames.ActivityId,
|
|
|
|
activity.id,
|
|
|
|
err => {
|
|
|
|
return callback(err, activity);
|
|
|
|
}
|
|
|
|
);
|
|
|
|
},
|
2023-01-26 05:22:45 +00:00
|
|
|
(activity, callback) => {
|
|
|
|
return message.persistMetaValue(
|
|
|
|
Message.WellKnownMetaCategories.ActivityPub,
|
|
|
|
Message.ActivityPubPropertyNames.NoteId,
|
|
|
|
activity.object.id,
|
|
|
|
err => {
|
|
|
|
return callback(err, activity);
|
|
|
|
}
|
|
|
|
);
|
|
|
|
},
|
2023-01-13 01:26:44 +00:00
|
|
|
],
|
|
|
|
(err, activity) => {
|
2023-02-03 22:14:27 +00:00
|
|
|
// dupes aren't considered failure
|
2023-01-13 01:26:44 +00:00
|
|
|
if (err) {
|
2023-02-03 22:14:27 +00:00
|
|
|
if (err.code === 'SQLITE_CONSTRAINT') {
|
|
|
|
this.log.debug({ id: activity.id }, 'Ignoring duplicate');
|
|
|
|
} else {
|
|
|
|
this.log.error(
|
|
|
|
{ error: err.message, messageId: message.messageId },
|
|
|
|
'Failed to export message to ActivityPub'
|
|
|
|
);
|
|
|
|
}
|
2023-01-13 01:26:44 +00:00
|
|
|
} else {
|
2023-01-13 01:40:43 +00:00
|
|
|
this.log.info(
|
|
|
|
{ id: activity.id },
|
|
|
|
'Note Activity exported (published) successfully'
|
|
|
|
);
|
2023-01-12 05:37:09 +00:00
|
|
|
}
|
2023-01-13 01:26:44 +00:00
|
|
|
}
|
|
|
|
);
|
2023-01-12 05:37:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
_isEnabled() {
|
|
|
|
// :TODO: check config to see if AP integration is enabled/etc.
|
|
|
|
return this._webServer();
|
|
|
|
}
|
|
|
|
|
|
|
|
_shouldExportMessage(message) {
|
|
|
|
//
|
|
|
|
// - Private messages: Must be ActivityPub flavor
|
|
|
|
// - Public messages: Must be in area mapped for ActivityPub import/export
|
|
|
|
//
|
|
|
|
if (
|
|
|
|
Message.AddressFlavor.ActivityPub === message.getAddressFlavor() &&
|
2023-01-13 01:38:42 +00:00
|
|
|
message.isPrivate()
|
2023-01-12 05:37:09 +00:00
|
|
|
) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2023-01-13 01:38:42 +00:00
|
|
|
// :TODO: Implement the area mapping check for public
|
2023-01-12 05:37:09 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
_exportToActivityPub(message, cb) {
|
|
|
|
return cb(null);
|
|
|
|
}
|
|
|
|
|
|
|
|
_webServer() {
|
|
|
|
// we have to lazy init
|
|
|
|
if (undefined === this.webServer) {
|
|
|
|
this.webServer = getServer('codes.l33t.enigma.web.server') || null;
|
|
|
|
}
|
|
|
|
|
|
|
|
return this.webServer ? this.webServer.instance : null;
|
|
|
|
}
|
|
|
|
};
|