Split message consts to their own file, fix some HTTP responses, better subjects from ActivityPub messages, fix AP reply indicators, ...

This commit is contained in:
Bryan Ashby 2023-01-26 15:42:11 -07:00
parent 0bd2c3db1c
commit d624871a83
No known key found for this signature in database
GPG Key ID: C2C1B501E4EFD994
5 changed files with 150 additions and 128 deletions

View File

@ -171,10 +171,18 @@ 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.summary || // If the summary is not present, build one using the message itself;
truncate(message.message, { length: 32, omission: '...' }) || // finally, default to a static subject so there is *something* if
APDefaultSummary; // all else fails.
if (this.summary) {
message.subject = this.summary;
} else {
let subject = message.message.replace(`@${message.toUserName} `, '');
subject = truncate(subject, { length: 32, omission: '...' });
subject = subject || APDefaultSummary;
message.subject = subject;
}
try { try {
message.modTimestamp = moment(this.published); message.modTimestamp = moment(this.published);

View File

@ -3,12 +3,13 @@
const EnigmaAssert = require('./enigma_assert.js'); const EnigmaAssert = require('./enigma_assert.js');
const Address = require('./ftn_address.js'); const Address = require('./ftn_address.js');
const { AddressFlavor } = require('./message.js'); const MessageConst = require('./message_const');
const Message = require('./message.js'); const { getQuotePrefix } = require('./ftn_util');
exports.getAddressedToInfo = getAddressedToInfo; exports.getAddressedToInfo = getAddressedToInfo;
exports.setExternalAddressedToInfo = setExternalAddressedToInfo; exports.setExternalAddressedToInfo = setExternalAddressedToInfo;
exports.copyExternalAddressedToInfo = copyExternalAddressedToInfo; exports.copyExternalAddressedToInfo = copyExternalAddressedToInfo;
exports.getQuotePrefixFromName = getQuotePrefixFromName;
const EMAIL_REGEX = const EMAIL_REGEX =
/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[?[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}]?)|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[?[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}]?)|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
@ -35,29 +36,29 @@ function getAddressedToInfo(input) {
if (firstAtPos < 0) { if (firstAtPos < 0) {
let addr = Address.fromString(input); let addr = Address.fromString(input);
if (Address.isValidAddress(addr)) { if (Address.isValidAddress(addr)) {
return { flavor: AddressFlavor.FTN, remote: input }; return { flavor: MessageConst.AddressFlavor.FTN, remote: input };
} }
const lessThanPos = input.indexOf('<'); const lessThanPos = input.indexOf('<');
if (lessThanPos < 0) { if (lessThanPos < 0) {
return { name: input, flavor: AddressFlavor.Local }; return { name: input, flavor: MessageConst.AddressFlavor.Local };
} }
const greaterThanPos = input.indexOf('>'); const greaterThanPos = input.indexOf('>');
if (greaterThanPos < lessThanPos) { if (greaterThanPos < lessThanPos) {
return { name: input, flavor: AddressFlavor.Local }; return { name: input, flavor: MessageConst.AddressFlavor.Local };
} }
addr = Address.fromString(input.slice(lessThanPos + 1, greaterThanPos)); addr = Address.fromString(input.slice(lessThanPos + 1, greaterThanPos));
if (Address.isValidAddress(addr)) { if (Address.isValidAddress(addr)) {
return { return {
name: input.slice(0, lessThanPos).trim(), name: input.slice(0, lessThanPos).trim(),
flavor: AddressFlavor.FTN, flavor: MessageConst.AddressFlavor.FTN,
remote: addr.toString(), remote: addr.toString(),
}; };
} }
return { name: input, flavor: AddressFlavor.Local }; return { name: input, flavor: MessageConst.AddressFlavor.Local };
} }
if (firstAtPos === 0) { if (firstAtPos === 0) {
@ -67,7 +68,7 @@ function getAddressedToInfo(input) {
if (m) { if (m) {
return { return {
name: input.slice(1, secondAtPos), name: input.slice(1, secondAtPos),
flavor: AddressFlavor.ActivityPub, flavor: MessageConst.AddressFlavor.ActivityPub,
remote: input.slice(firstAtPos), remote: input.slice(firstAtPos),
}; };
} }
@ -82,38 +83,38 @@ function getAddressedToInfo(input) {
if (m) { if (m) {
return { return {
name: input.slice(0, lessThanPos).trim(), name: input.slice(0, lessThanPos).trim(),
flavor: AddressFlavor.Email, flavor: MessageConst.AddressFlavor.Email,
remote: addr, remote: addr,
}; };
} }
return { name: input, flavor: AddressFlavor.Local }; return { name: input, flavor: MessageConst.AddressFlavor.Local };
} }
let m = input.match(EMAIL_REGEX); let m = input.match(EMAIL_REGEX);
if (m) { if (m) {
return { return {
name: input.slice(0, firstAtPos), name: input.slice(0, firstAtPos),
flavor: AddressFlavor.Email, flavor: MessageConst.AddressFlavor.Email,
remote: input, remote: input,
}; };
} }
let addr = Address.fromString(input); // 5D? let addr = Address.fromString(input); // 5D?
if (Address.isValidAddress(addr)) { if (Address.isValidAddress(addr)) {
return { flavor: AddressFlavor.FTN, remote: addr.toString() }; return { flavor: MessageConst.AddressFlavor.FTN, remote: addr.toString() };
} }
addr = Address.fromString(input.slice(firstAtPos + 1).trim()); addr = Address.fromString(input.slice(firstAtPos + 1).trim());
if (Address.isValidAddress(addr)) { if (Address.isValidAddress(addr)) {
return { return {
name: input.slice(0, firstAtPos).trim(), name: input.slice(0, firstAtPos).trim(),
flavor: AddressFlavor.FTN, flavor: MessageConst.AddressFlavor.FTN,
remote: addr.toString(), remote: addr.toString(),
}; };
} }
return { name: input, flavor: AddressFlavor.Local }; return { name: input, flavor: MessageConst.AddressFlavor.Local };
} }
/// returns true if it's an external address /// returns true if it's an external address
@ -123,11 +124,11 @@ function setExternalAddressedToInfo(addressInfo, message) {
}; };
switch (addressInfo.flavor) { switch (addressInfo.flavor) {
case AddressFlavor.FTN: case MessageConst.AddressFlavor.FTN:
case AddressFlavor.Email: case MessageConst.AddressFlavor.Email:
case AddressFlavor.QWK: case MessageConst.AddressFlavor.QWK:
case AddressFlavor.NNTP: case MessageConst.AddressFlavor.NNTP:
case AddressFlavor.ActivityPub: case MessageConst.AddressFlavor.ActivityPub:
EnigmaAssert(isValidAddressInfo()); EnigmaAssert(isValidAddressInfo());
message.setRemoteToUser(addressInfo.remote); message.setRemoteToUser(addressInfo.remote);
@ -136,13 +137,18 @@ function setExternalAddressedToInfo(addressInfo, message) {
return true; return true;
default: default:
case AddressFlavor.Local: case MessageConst.AddressFlavor.Local:
return false; return false;
} }
} }
function copyExternalAddressedToInfo(fromMessage, toMessage) { function copyExternalAddressedToInfo(fromMessage, toMessage) {
const sm = Message.SystemMetaNames; const sm = MessageConst.SystemMetaNames;
toMessage.setRemoteToUser(fromMessage.meta.System[sm.RemoteFromUser]); toMessage.setRemoteToUser(fromMessage.meta.System[sm.RemoteFromUser]);
toMessage.setExternalFlavor(fromMessage.meta.System[sm.ExternalFlavor]); toMessage.setExternalFlavor(fromMessage.meta.System[sm.ExternalFlavor]);
} }
function getQuotePrefixFromName(name) {
const addrInfo = getAddressedToInfo(name);
return getQuotePrefix(addrInfo.name || name);
}

View File

@ -3,14 +3,14 @@
const msgDb = require('./database.js').dbs.message; const msgDb = require('./database.js').dbs.message;
const wordWrapText = require('./word_wrap.js').wordWrapText; const wordWrapText = require('./word_wrap.js').wordWrapText;
const ftnUtil = require('./ftn_util.js');
const createNamedUUID = require('./uuid_util.js').createNamedUUID; const createNamedUUID = require('./uuid_util.js').createNamedUUID;
const Errors = require('./enig_error.js').Errors; const Errors = require('./enig_error.js').Errors;
const ANSI = require('./ansi_term.js'); const ANSI = require('./ansi_term.js');
const { sanitizeString, getISOTimestampString } = require('./database.js'); const { sanitizeString, getISOTimestampString } = require('./database.js');
const { isCP437Encodable } = require('./cp437util'); const { isCP437Encodable } = require('./cp437util');
const { containsNonLatinCodepoints } = require('./string_util'); const { containsNonLatinCodepoints } = require('./string_util');
const MessageConst = require('./message_const');
const { getQuotePrefixFromName } = require('./mail_util');
const { const {
isAnsi, isAnsi,
@ -33,93 +33,6 @@ const ENIGMA_MESSAGE_UUID_NAMESPACE = uuidParse.parse(
'154506df-1df8-46b9-98f8-ebb5815baaf8' '154506df-1df8-46b9-98f8-ebb5815baaf8'
); );
const WELL_KNOWN_AREA_TAGS = {
Invalid: '',
Private: 'private_mail',
Bulletin: 'local_bulletin',
};
const WellKnownMetaCategories = {
System: 'System',
FtnProperty: 'FtnProperty',
FtnKludge: 'FtnKludge',
QwkProperty: 'QwkProperty',
QwkKludge: 'QwkKludge',
ActivityPub: 'ActivityPub',
};
// Category: WellKnownMetaCategories.System ("System")
const SYSTEM_META_NAMES = {
LocalToUserID: 'local_to_user_id',
LocalFromUserID: 'local_from_user_id',
StateFlags0: 'state_flags0', // See Message.StateFlags0
ExplicitEncoding: 'explicit_encoding', // Explicitly set encoding when exporting/etc.
ExternalFlavor: 'external_flavor', // "Flavor" of message - imported from or to be exported to. See Message.AddressFlavor
RemoteToUser: 'remote_to_user', // Opaque value depends on external system, e.g. FTN address
RemoteFromUser: 'remote_from_user', // Opaque value depends on external system, e.g. FTN address
};
// Types for Message.SystemMetaNames.ExternalFlavor meta
const ADDRESS_FLAVOR = {
Local: 'local', // local / non-remote addressing
FTN: 'ftn', // FTN style
Email: 'email', // From email
QWK: 'qwk', // QWK packet
NNTP: 'nntp', // NNTP article POST; often a email address
ActivityPub: 'activitypub', // ActivityPub, Mastodon, etc.
};
const STATE_FLAGS0 = {
None: 0x00000000,
Imported: 0x00000001, // imported from foreign system
Exported: 0x00000002, // exported to foreign system
};
// :TODO: these should really live elsewhere...
// Category: WellKnownMetaCategories.FtnProperty ("FtnProperty")
const FTN_PROPERTY_NAMES = {
// packet header oriented
FtnOrigNode: 'ftn_orig_node',
FtnDestNode: 'ftn_dest_node',
// :TODO: rename these to ftn_*_net vs network - ensure things won't break, may need mapping
FtnOrigNetwork: 'ftn_orig_network',
FtnDestNetwork: 'ftn_dest_network',
FtnAttrFlags: 'ftn_attr_flags',
FtnCost: 'ftn_cost',
FtnOrigZone: 'ftn_orig_zone',
FtnDestZone: 'ftn_dest_zone',
FtnOrigPoint: 'ftn_orig_point',
FtnDestPoint: 'ftn_dest_point',
// message header oriented
FtnMsgOrigNode: 'ftn_msg_orig_node',
FtnMsgDestNode: 'ftn_msg_dest_node',
FtnMsgOrigNet: 'ftn_msg_orig_net',
FtnMsgDestNet: 'ftn_msg_dest_net',
FtnAttribute: 'ftn_attribute',
FtnTearLine: 'ftn_tear_line', // http://ftsc.org/docs/fts-0004.001
FtnOrigin: 'ftn_origin', // http://ftsc.org/docs/fts-0004.001
FtnArea: 'ftn_area', // http://ftsc.org/docs/fts-0004.001
FtnSeenBy: 'ftn_seen_by', // http://ftsc.org/docs/fts-0004.001
};
// Category: WellKnownMetaCategories.QwkProperty
const QWKPropertyNames = {
MessageNumber: 'qwk_msg_num',
MessageStatus: 'qwk_msg_status', // See http://wiki.synchro.net/ref:qwk for a decent list
ConferenceNumber: 'qwk_conf_num',
InReplyToNum: 'qwk_in_reply_to_num', // note that we prefer the 'InReplyToMsgId' kludge if available
};
// Category: WellKnownMetaCategories.ActivityPub
const ActivityPubPropertyNames = {
ActivityId: 'activitypub_activity_id', // Activity ID; FK to AP table entries
InReplyTo: 'activitypub_in_reply_to', // Activity ID from 'inReplyTo' field
NoteId: 'activitypub_note_id', // Note ID specific to Note Activities
};
// :TODO: this is a ugly hack due to bad variable names - clean it up & just _.camelCase(k)! // :TODO: this is a ugly hack due to bad variable names - clean it up & just _.camelCase(k)!
const MESSAGE_ROW_MAP = { const MESSAGE_ROW_MAP = {
reply_to_message_id: 'replyToMsgId', reply_to_message_id: 'replyToMsgId',
@ -247,35 +160,35 @@ module.exports = class Message {
} }
static get WellKnownMetaCategories() { static get WellKnownMetaCategories() {
return WellKnownMetaCategories; return MessageConst.WellKnownMetaCategories;
} }
static get WellKnownAreaTags() { static get WellKnownAreaTags() {
return WELL_KNOWN_AREA_TAGS; return MessageConst.WellKnownAreaTags;
} }
static get SystemMetaNames() { static get SystemMetaNames() {
return SYSTEM_META_NAMES; return MessageConst.SystemMetaNames;
} }
static get AddressFlavor() { static get AddressFlavor() {
return ADDRESS_FLAVOR; return MessageConst.AddressFlavor;
} }
static get StateFlags0() { static get StateFlags0() {
return STATE_FLAGS0; return MessageConst.StateFlags0;
} }
static get FtnPropertyNames() { static get FtnPropertyNames() {
return FTN_PROPERTY_NAMES; return MessageConst.FtnPropertyNames;
} }
static get QWKPropertyNames() { static get QWKPropertyNames() {
return QWKPropertyNames; return MessageConst.QWKPropertyNames;
} }
static get ActivityPubPropertyNames() { static get ActivityPubPropertyNames() {
return ActivityPubPropertyNames; return MessageConst.ActivityPubPropertyNames;
} }
setLocalToUserId(userId) { setLocalToUserId(userId) {
@ -943,11 +856,13 @@ module.exports = class Message {
); );
} }
// :TODO: FTN stuff doesn't have any business here _getQuotePrefix(source) {
getFTNQuotePrefix(source) {
source = source || 'fromUserName'; source = source || 'fromUserName';
return ftnUtil.getQuotePrefix(this[source]); // grab out the name member, so we don't try to build
// quote prefixes such as "@N" for "@NuSkooler@some.host", etc.
const userName = this[source];
return getQuotePrefixFromName(userName);
} }
static getTearLinePosition(input) { static getTearLinePosition(input) {
@ -982,7 +897,7 @@ module.exports = class Message {
*/ */
const quotePrefix = options.includePrefix const quotePrefix = options.includePrefix
? this.getFTNQuotePrefix(options.prefixSource || 'fromUserName') ? this._getQuotePrefix(options.prefixSource || 'fromUserName')
: ''; : '';
function getWrapped(text, extraPrefix) { function getWrapped(text, extraPrefix) {

93
core/message_const.js Normal file
View File

@ -0,0 +1,93 @@
const WellKnownAreaTags = {
Invalid: '',
Private: 'private_mail',
Bulletin: 'local_bulletin',
};
exports.WellKnownAreaTags = WellKnownAreaTags;
const WellKnownMetaCategories = {
System: 'System',
FtnProperty: 'FtnProperty',
FtnKludge: 'FtnKludge',
QwkProperty: 'QwkProperty',
QwkKludge: 'QwkKludge',
ActivityPub: 'ActivityPub',
};
exports.WellKnownMetaCategories = WellKnownMetaCategories;
// Category: WellKnownMetaCategories.System ("System")
const SystemMetaNames = {
LocalToUserID: 'local_to_user_id',
LocalFromUserID: 'local_from_user_id',
StateFlags0: 'state_flags0', // See Message.StateFlags0
ExplicitEncoding: 'explicit_encoding', // Explicitly set encoding when exporting/etc.
ExternalFlavor: 'external_flavor', // "Flavor" of message - imported from or to be exported to. See Message.AddressFlavor
RemoteToUser: 'remote_to_user', // Opaque value depends on external system, e.g. FTN address
RemoteFromUser: 'remote_from_user', // Opaque value depends on external system, e.g. FTN address
};
exports.SystemMetaNames = SystemMetaNames;
// Types for Message.SystemMetaNames.ExternalFlavor meta
const AddressFlavor = {
Local: 'local', // local / non-remote addressing
FTN: 'ftn', // FTN style
Email: 'email', // From email
QWK: 'qwk', // QWK packet
NNTP: 'nntp', // NNTP article POST; often a email address
ActivityPub: 'activitypub', // ActivityPub, Mastodon, etc.
};
exports.AddressFlavor = AddressFlavor;
const StateFlags0 = {
None: 0x00000000,
Imported: 0x00000001, // imported from foreign system
Exported: 0x00000002, // exported to foreign system
};
exports.StateFlags0 = StateFlags0;
// Category: WellKnownMetaCategories.FtnProperty ("FtnProperty")
const FtnPropertyNames = {
// packet header oriented
FtnOrigNode: 'ftn_orig_node',
FtnDestNode: 'ftn_dest_node',
// :TODO: rename these to ftn_*_net vs network - ensure things won't break, may need mapping
FtnOrigNetwork: 'ftn_orig_network',
FtnDestNetwork: 'ftn_dest_network',
FtnAttrFlags: 'ftn_attr_flags',
FtnCost: 'ftn_cost',
FtnOrigZone: 'ftn_orig_zone',
FtnDestZone: 'ftn_dest_zone',
FtnOrigPoint: 'ftn_orig_point',
FtnDestPoint: 'ftn_dest_point',
// message header oriented
FtnMsgOrigNode: 'ftn_msg_orig_node',
FtnMsgDestNode: 'ftn_msg_dest_node',
FtnMsgOrigNet: 'ftn_msg_orig_net',
FtnMsgDestNet: 'ftn_msg_dest_net',
FtnAttribute: 'ftn_attribute',
FtnTearLine: 'ftn_tear_line', // http://ftsc.org/docs/fts-0004.001
FtnOrigin: 'ftn_origin', // http://ftsc.org/docs/fts-0004.001
FtnArea: 'ftn_area', // http://ftsc.org/docs/fts-0004.001
FtnSeenBy: 'ftn_seen_by', // http://ftsc.org/docs/fts-0004.001
};
exports.FtnPropertyNames = FtnPropertyNames;
// Category: WellKnownMetaCategories.QwkProperty
const QWKPropertyNames = {
MessageNumber: 'qwk_msg_num',
MessageStatus: 'qwk_msg_status', // See http://wiki.synchro.net/ref:qwk for a decent list
ConferenceNumber: 'qwk_conf_num',
InReplyToNum: 'qwk_in_reply_to_num', // note that we prefer the 'InReplyToMsgId' kludge if available
};
exports.QWKPropertyNames = QWKPropertyNames;
// Category: WellKnownMetaCategories.ActivityPub
const ActivityPubPropertyNames = {
ActivityId: 'activitypub_activity_id', // Activity ID; FK to AP table entries
InReplyTo: 'activitypub_in_reply_to', // Activity ID from 'inReplyTo' field
NoteId: 'activitypub_note_id', // Note ID specific to Note Activities
};
exports.ActivityPubPropertyNames = ActivityPubPropertyNames;

View File

@ -258,7 +258,7 @@ exports.getModule = class ActivityPubWebHandler extends WebHandlerModule {
{ type: activity.type }, { type: activity.type },
'Invalid or unknown Activity type' 'Invalid or unknown Activity type'
); );
return this.resourceNotFound(resp); return this.webServer.resourceNotFound(resp);
} }
}); });
} }
@ -279,7 +279,7 @@ exports.getModule = class ActivityPubWebHandler extends WebHandlerModule {
{ type: createWhat }, { type: createWhat },
'Invalid or unsupported "Create" type' 'Invalid or unsupported "Create" type'
); );
return this.resourceNotFound(resp); return this.webServer.resourceNotFound(resp);
} }
} }