Add attachment information to messages, fix duplicate handling to respond properly
This commit is contained in:
parent
5cfacf4ff0
commit
f97d1844e3
|
@ -38,7 +38,7 @@ module.exports = class Collection extends ActivityPubObject {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static addFollower(owningUser, followingActor, webServer, cb) {
|
static addFollower(owningUser, followingActor, webServer, ignoreDupes, cb) {
|
||||||
const collectionId =
|
const collectionId =
|
||||||
makeUserUrl(webServer, owningUser, '/ap/collections/') + '/followers';
|
makeUserUrl(webServer, owningUser, '/ap/collections/') + '/followers';
|
||||||
return Collection.addToCollection(
|
return Collection.addToCollection(
|
||||||
|
@ -48,11 +48,12 @@ module.exports = class Collection extends ActivityPubObject {
|
||||||
followingActor.id,
|
followingActor.id,
|
||||||
followingActor,
|
followingActor,
|
||||||
false,
|
false,
|
||||||
|
ignoreDupes,
|
||||||
cb
|
cb
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static addFollowRequest(owningUser, requestingActor, webServer, cb) {
|
static addFollowRequest(owningUser, requestingActor, webServer, ignoreDupes, cb) {
|
||||||
const collectionId =
|
const collectionId =
|
||||||
makeUserUrl(webServer, owningUser, '/ap/collections/') + '/follow-requests';
|
makeUserUrl(webServer, owningUser, '/ap/collections/') + '/follow-requests';
|
||||||
return Collection.addToCollection(
|
return Collection.addToCollection(
|
||||||
|
@ -62,6 +63,7 @@ module.exports = class Collection extends ActivityPubObject {
|
||||||
requestingActor.id,
|
requestingActor.id,
|
||||||
requestingActor,
|
requestingActor,
|
||||||
true,
|
true,
|
||||||
|
ignoreDupes,
|
||||||
cb
|
cb
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -70,7 +72,7 @@ module.exports = class Collection extends ActivityPubObject {
|
||||||
return Collection.publicOrderedById('outbox', collectionId, page, null, cb);
|
return Collection.publicOrderedById('outbox', collectionId, page, null, cb);
|
||||||
}
|
}
|
||||||
|
|
||||||
static addOutboxItem(owningUser, outboxItem, isPrivate, webServer, cb) {
|
static addOutboxItem(owningUser, outboxItem, isPrivate, webServer, ignoreDupes, cb) {
|
||||||
const collectionId =
|
const collectionId =
|
||||||
makeUserUrl(webServer, owningUser, '/ap/collections/') + '/outbox';
|
makeUserUrl(webServer, owningUser, '/ap/collections/') + '/outbox';
|
||||||
return Collection.addToCollection(
|
return Collection.addToCollection(
|
||||||
|
@ -80,11 +82,12 @@ module.exports = class Collection extends ActivityPubObject {
|
||||||
outboxItem.id,
|
outboxItem.id,
|
||||||
outboxItem,
|
outboxItem,
|
||||||
isPrivate,
|
isPrivate,
|
||||||
|
ignoreDupes,
|
||||||
cb
|
cb
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static addInboxItem(inboxItem, owningUser, webServer, cb) {
|
static addInboxItem(inboxItem, owningUser, webServer, ignoreDupes, cb) {
|
||||||
const collectionId =
|
const collectionId =
|
||||||
makeUserUrl(webServer, owningUser, '/ap/collections/') + '/inbox';
|
makeUserUrl(webServer, owningUser, '/ap/collections/') + '/inbox';
|
||||||
return Collection.addToCollection(
|
return Collection.addToCollection(
|
||||||
|
@ -94,11 +97,12 @@ module.exports = class Collection extends ActivityPubObject {
|
||||||
inboxItem.id,
|
inboxItem.id,
|
||||||
inboxItem,
|
inboxItem,
|
||||||
true,
|
true,
|
||||||
|
ignoreDupes,
|
||||||
cb
|
cb
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static addPublicInboxItem(inboxItem, cb) {
|
static addPublicInboxItem(inboxItem, ignoreDupes, cb) {
|
||||||
return Collection.addToCollection(
|
return Collection.addToCollection(
|
||||||
'publicInbox',
|
'publicInbox',
|
||||||
null, // N/A
|
null, // N/A
|
||||||
|
@ -106,6 +110,7 @@ module.exports = class Collection extends ActivityPubObject {
|
||||||
inboxItem.id,
|
inboxItem.id,
|
||||||
inboxItem,
|
inboxItem,
|
||||||
false,
|
false,
|
||||||
|
ignoreDupes,
|
||||||
cb
|
cb
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -325,6 +330,7 @@ module.exports = class Collection extends ActivityPubObject {
|
||||||
objectId,
|
objectId,
|
||||||
obj,
|
obj,
|
||||||
isPrivate,
|
isPrivate,
|
||||||
|
ignoreDupes,
|
||||||
cb
|
cb
|
||||||
) {
|
) {
|
||||||
if (!isString(obj)) {
|
if (!isString(obj)) {
|
||||||
|
@ -361,8 +367,8 @@ module.exports = class Collection extends ActivityPubObject {
|
||||||
],
|
],
|
||||||
function res(err) {
|
function res(err) {
|
||||||
// non-arrow for 'this' scope
|
// non-arrow for 'this' scope
|
||||||
if (err) {
|
if (err && 'SQLITE_CONSTRAINT' === err.code) {
|
||||||
if ('SQLITE_CONSTRAINT' === err.code) {
|
if (ignoreDupes) {
|
||||||
err = null; // ignore
|
err = null; // ignore
|
||||||
}
|
}
|
||||||
return cb(err);
|
return cb(err);
|
||||||
|
|
|
@ -172,17 +172,54 @@ module.exports = class Note extends ActivityPubObject {
|
||||||
|
|
||||||
// :TODO: it would be better to do some basic HTML to ANSI or pipe codes perhaps
|
// :TODO: it would be better to do some basic HTML to ANSI or pipe codes perhaps
|
||||||
message.message = htmlToMessageBody(this.content);
|
message.message = htmlToMessageBody(this.content);
|
||||||
|
message.subject = this._getSubject(message);
|
||||||
|
|
||||||
// If the summary is not present, build one using the message itself;
|
// List all attachments
|
||||||
// finally, default to a static subject so there is *something* if
|
if (Array.isArray(this.attachment) && this.attachment.length > 0) {
|
||||||
// all else fails.
|
let attachmentInfoLines = ['--[Attachments]--'];
|
||||||
if (this.summary) {
|
// https://socialhub.activitypub.rocks/t/representing-images/624
|
||||||
message.subject = this.summary;
|
this.attachment.forEach(att => {
|
||||||
} else {
|
switch (att.mediaType) {
|
||||||
let subject = message.message.replace(`@${message.toUserName} `, '');
|
case 'image/avif':
|
||||||
subject = truncate(subject, { length: 32, omission: '...' });
|
case 'image/apng':
|
||||||
subject = subject || APDefaultSummary;
|
case 'image/png':
|
||||||
message.subject = subject;
|
case 'image/x-png':
|
||||||
|
case 'image/jpeg':
|
||||||
|
case 'image/gif':
|
||||||
|
case 'image/svg+xml':
|
||||||
|
case 'image/webp':
|
||||||
|
{
|
||||||
|
let imgInfo;
|
||||||
|
if (att.height && att.width) {
|
||||||
|
imgInfo = `Image (${att.width} x ${att.height})`;
|
||||||
|
} else {
|
||||||
|
imgInfo = 'Image';
|
||||||
|
}
|
||||||
|
attachmentInfoLines.push(imgInfo);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
// :TODO: video
|
||||||
|
|
||||||
|
default:
|
||||||
|
attachmentInfoLines.push(att.mediaType);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (att.name) {
|
||||||
|
attachmentInfoLines.push(att.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
attachmentInfoLines.push(att.url);
|
||||||
|
attachmentInfoLines.push('');
|
||||||
|
attachmentInfoLines.push('');
|
||||||
|
});
|
||||||
|
|
||||||
|
message.message += '\r\n\r\n' + attachmentInfoLines.join('\r\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the Note is marked sensitive, prefix the subject
|
||||||
|
if (this.sensitive) {
|
||||||
|
message.subject = `[NSFW] ${message.subject}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -233,4 +270,28 @@ module.exports = class Note extends ActivityPubObject {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_getSubject(message) {
|
||||||
|
if (this.summary) {
|
||||||
|
return this.summary;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Build a subject from the message itself:
|
||||||
|
// - First few characters of the message, removing the @username
|
||||||
|
// prefix, if any
|
||||||
|
// - Truncate at the first line feed, the end of the message,
|
||||||
|
// or 32 characters in length, whichever comes first
|
||||||
|
// - If not end of string, we'll sub in '...'
|
||||||
|
//
|
||||||
|
let subject = message.message.replace(`@${message.toUserName} `, '').trim();
|
||||||
|
const m = /^(.+)\r?\n/.exec(subject);
|
||||||
|
if (m && m[1]) {
|
||||||
|
subject = m[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
subject = truncate(subject, { length: 32, omission: '...' });
|
||||||
|
subject = subject || APDefaultSummary;
|
||||||
|
return subject;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -211,8 +211,9 @@ function messageBodyToHtml(body) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function htmlToMessageBody(html) {
|
function htmlToMessageBody(html) {
|
||||||
// <br>, </br>, and <br/> -> \r\n
|
// <br>, </br>, and <br />, <br/> -> \r\n
|
||||||
html = html.replace(/<\/?br?\/?>/g, '\r\n');
|
// </p> -> \r\n
|
||||||
|
html = html.replace(/(?:<\/?br ?\/?>)|(?:<\/p>)/g, '\r\n');
|
||||||
return striptags(decode(html));
|
return striptags(decode(html));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -59,6 +59,8 @@ exports.Errors = {
|
||||||
Expired: (reason, reasonCode) => new EnigError('Expired', -32015, reason, reasonCode),
|
Expired: (reason, reasonCode) => new EnigError('Expired', -32015, reason, reasonCode),
|
||||||
BadFormData: (reason, reasonCode) =>
|
BadFormData: (reason, reasonCode) =>
|
||||||
new EnigError('Bad or missing form data', -32016, reason, reasonCode),
|
new EnigError('Bad or missing form data', -32016, reason, reasonCode),
|
||||||
|
Duplicate: (reason, reasonCode) =>
|
||||||
|
new EnigError('Duplicate', -32017, reason, reasonCode),
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.ErrorReasons = {
|
exports.ErrorReasons = {
|
||||||
|
|
|
@ -100,6 +100,7 @@ exports.getModule = class ActivityPubScannerTosser extends MessageScanTossModule
|
||||||
activity,
|
activity,
|
||||||
message.isPrivate(),
|
message.isPrivate(),
|
||||||
this._webServer(),
|
this._webServer(),
|
||||||
|
false, // do not ignore dupes
|
||||||
(err, localId) => {
|
(err, localId) => {
|
||||||
if (!err) {
|
if (!err) {
|
||||||
this.log.debug(
|
this.log.debug(
|
||||||
|
@ -145,11 +146,16 @@ exports.getModule = class ActivityPubScannerTosser extends MessageScanTossModule
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
(err, activity) => {
|
(err, activity) => {
|
||||||
|
// dupes aren't considered failure
|
||||||
if (err) {
|
if (err) {
|
||||||
this.log.error(
|
if (err.code === 'SQLITE_CONSTRAINT') {
|
||||||
{ error: err.message, messageId: message.messageId },
|
this.log.debug({ id: activity.id }, 'Ignoring duplicate');
|
||||||
'Failed to export message to ActivityPub'
|
} else {
|
||||||
);
|
this.log.error(
|
||||||
|
{ error: err.message, messageId: message.messageId },
|
||||||
|
'Failed to export message to ActivityPub'
|
||||||
|
);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
this.log.info(
|
this.log.info(
|
||||||
{ id: activity.id },
|
{ id: activity.id },
|
||||||
|
|
|
@ -281,14 +281,6 @@ exports.getModule = class ActivityPubWebHandler extends WebHandlerModule {
|
||||||
|
|
||||||
_sharedInboxCreateActivity(req, resp, activity) {
|
_sharedInboxCreateActivity(req, resp, activity) {
|
||||||
const deliverTo = activity.recipientIds();
|
const deliverTo = activity.recipientIds();
|
||||||
|
|
||||||
//Create a method to gather all to, cc, bcc, etc. dests (see spec) -> single array
|
|
||||||
// loop through, and attempt to fetch user-by-actor id for each; if found, deliver
|
|
||||||
// --we may need to add properties for ActivityPubFollowersId, ActivityPubFollowingId, etc.
|
|
||||||
// to user props for quick lookup -> user
|
|
||||||
// special handling of bcc (remove others before delivery), etc.
|
|
||||||
// const toActorIds = activity.recipientActorIds()
|
|
||||||
|
|
||||||
const createWhat = _.get(activity, 'object.type');
|
const createWhat = _.get(activity, 'object.type');
|
||||||
switch (createWhat) {
|
switch (createWhat) {
|
||||||
case 'Note':
|
case 'Note':
|
||||||
|
@ -322,7 +314,7 @@ exports.getModule = class ActivityPubWebHandler extends WebHandlerModule {
|
||||||
switch (actorId) {
|
switch (actorId) {
|
||||||
case Collection.PublicCollectionId:
|
case Collection.PublicCollectionId:
|
||||||
// :TODO: we should probably land this in a public areaTag as well for AP; allowing Message objects to be used/etc.
|
// :TODO: we should probably land this in a public areaTag as well for AP; allowing Message objects to be used/etc.
|
||||||
Collection.addPublicInboxItem(note, err => {
|
Collection.addPublicInboxItem(note, true, err => {
|
||||||
return nextActor(err);
|
return nextActor(err);
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
@ -342,7 +334,7 @@ exports.getModule = class ActivityPubWebHandler extends WebHandlerModule {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
err => {
|
err => {
|
||||||
if (err) {
|
if (err && err.code !== 'SQLITE_CONSTRAINT') {
|
||||||
return this.webServer.internalServerError(resp, err);
|
return this.webServer.internalServerError(resp, err);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -357,7 +349,7 @@ exports.getModule = class ActivityPubWebHandler extends WebHandlerModule {
|
||||||
return cb(null); // not found/etc., just bail
|
return cb(null); // not found/etc., just bail
|
||||||
}
|
}
|
||||||
|
|
||||||
Collection.addInboxItem(note, localUser, this.webServer, err => {
|
Collection.addInboxItem(note, localUser, this.webServer, false, err => {
|
||||||
if (err) {
|
if (err) {
|
||||||
return cb(err);
|
return cb(err);
|
||||||
}
|
}
|
||||||
|
@ -388,6 +380,8 @@ exports.getModule = class ActivityPubWebHandler extends WebHandlerModule {
|
||||||
},
|
},
|
||||||
'Note delivered as message to private mailbox'
|
'Note delivered as message to private mailbox'
|
||||||
);
|
);
|
||||||
|
} else if (err.code === 'SQLITE_CONSTRAINT') {
|
||||||
|
return cb(null);
|
||||||
}
|
}
|
||||||
return cb(err);
|
return cb(err);
|
||||||
});
|
});
|
||||||
|
@ -517,13 +511,19 @@ exports.getModule = class ActivityPubWebHandler extends WebHandlerModule {
|
||||||
this._recordAcceptedFollowRequest(localUser, remoteActor, activity);
|
this._recordAcceptedFollowRequest(localUser, remoteActor, activity);
|
||||||
return ok();
|
return ok();
|
||||||
} else {
|
} else {
|
||||||
Collection.addFollowRequest(localUser, remoteActor, this.webServer, err => {
|
Collection.addFollowRequest(
|
||||||
if (err) {
|
localUser,
|
||||||
return this.internalServerError(resp, err);
|
remoteActor,
|
||||||
}
|
this.webServer,
|
||||||
|
true, // ignore dupes
|
||||||
|
err => {
|
||||||
|
if (err) {
|
||||||
|
return this.internalServerError(resp, err);
|
||||||
|
}
|
||||||
|
|
||||||
return ok();
|
return ok();
|
||||||
});
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -631,6 +631,7 @@ exports.getModule = class ActivityPubWebHandler extends WebHandlerModule {
|
||||||
localUser,
|
localUser,
|
||||||
remoteActor,
|
remoteActor,
|
||||||
this.webServer,
|
this.webServer,
|
||||||
|
true, // ignore dupes
|
||||||
callback
|
callback
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@ -706,7 +707,7 @@ exports.getModule = class ActivityPubWebHandler extends WebHandlerModule {
|
||||||
}
|
}
|
||||||
|
|
||||||
_selfAsActorHandler(localUser, localActor, req, resp) {
|
_selfAsActorHandler(localUser, localActor, req, resp) {
|
||||||
this.log.trace(
|
this.log.info(
|
||||||
{ username: localUser.username },
|
{ username: localUser.username },
|
||||||
`Serving ActivityPub Actor for "${localUser.username}"`
|
`Serving ActivityPub Actor for "${localUser.username}"`
|
||||||
);
|
);
|
||||||
|
@ -744,6 +745,11 @@ exports.getModule = class ActivityPubWebHandler extends WebHandlerModule {
|
||||||
return this.webServer.resourceNotFound(resp);
|
return this.webServer.resourceNotFound(resp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.log.info(
|
||||||
|
{ username: localUser.username },
|
||||||
|
`Serving ActivityPub Profile for "${localUser.username}"`
|
||||||
|
);
|
||||||
|
|
||||||
const headers = {
|
const headers = {
|
||||||
'Content-Type': contentType,
|
'Content-Type': contentType,
|
||||||
'Content-Length': Buffer(body).length,
|
'Content-Length': Buffer(body).length,
|
||||||
|
|
Loading…
Reference in New Issue