Replys to ActivityPub should start with @someone

This commit is contained in:
Bryan Ashby 2023-08-24 12:12:32 -06:00
parent 4d97922933
commit c25c600417
No known key found for this signature in database
GPG Key ID: C2C1B501E4EFD994
2 changed files with 159 additions and 103 deletions

View File

@ -21,6 +21,7 @@ const {
messageInfoFromAddressedToInfo, messageInfoFromAddressedToInfo,
setExternalAddressedToInfo, setExternalAddressedToInfo,
copyExternalAddressedToInfo, copyExternalAddressedToInfo,
getReplyToMessagePrefix,
} = require('./mail_util.js'); } = require('./mail_util.js');
const Events = require('./events.js'); const Events = require('./events.js');
const UserProps = require('./user_property.js'); const UserProps = require('./user_property.js');
@ -28,10 +29,10 @@ const SysProps = require('./system_property.js');
const FileArea = require('./file_base_area.js'); const FileArea = require('./file_base_area.js');
const FileEntry = require('./file_entry.js'); const FileEntry = require('./file_entry.js');
const DownloadQueue = require('./download_queue.js'); const DownloadQueue = require('./download_queue.js');
const EngiAssert = require('./enigma_assert.js');
// deps // deps
const async = require('async'); const async = require('async');
const assert = require('assert');
const _ = require('lodash'); const _ = require('lodash');
const moment = require('moment'); const moment = require('moment');
const fse = require('fs-extra'); const fse = require('fs-extra');
@ -123,7 +124,7 @@ exports.FullScreenEditorModule =
this.editorMode = config.editorMode; this.editorMode = config.editorMode;
if (config.messageAreaTag) { if (config.messageAreaTag) {
// :TODO: swtich to this.config.messageAreaTag so we can follow Object.assign pattern for config/extraArgs // :TODO: switch to this.config.messageAreaTag so we can follow Object.assign pattern for config/extraArgs
this.messageAreaTag = config.messageAreaTag; this.messageAreaTag = config.messageAreaTag;
} }
@ -251,7 +252,7 @@ exports.FullScreenEditorModule =
if (self.newQuoteBlock) { if (self.newQuoteBlock) {
self.newQuoteBlock = false; self.newQuoteBlock = false;
// :TODO: If replying to ANSI, add a blank sepration line here // :TODO: If replying to ANSI, add a blank separation line here
quoteMsgView.addText(self.getQuoteByHeader()); quoteMsgView.addText(self.getQuoteByHeader());
} }
@ -480,106 +481,108 @@ exports.FullScreenEditorModule =
this.message = message; this.message = message;
this.updateLastReadId(() => { this.updateLastReadId(() => {
if (this.isReady) { if (!this.isReady) {
this.initHeaderViewMode(); return;
this.initFooterViewMode(); }
const bodyMessageView = this.viewControllers.body.getView( this.initHeaderViewMode();
MciViewIds.body.message this.initFooterViewMode();
);
let msg = this.message.message;
if (bodyMessageView && _.has(this, 'message.message')) { const bodyMessageView = this.viewControllers.body.getView(
MciViewIds.body.message
);
let msg = this.message.message;
if (bodyMessageView && _.has(this, 'message.message')) {
//
// We handle ANSI messages differently than standard messages -- this is required as
// we don't want to do things like word wrap ANSI, but instead, trust that it's formatted
// how the author wanted it
//
if (isAnsi(msg)) {
// //
// We handle ANSI messages differently than standard messages -- this is required as // Find tearline - we want to color it differently.
// we don't want to do things like word wrap ANSI, but instead, trust that it's formatted
// how the author wanted it
// //
if (isAnsi(msg)) { const tearLinePos = Message.getTearLinePosition(msg);
//
// Find tearline - we want to color it differently.
//
const tearLinePos = Message.getTearLinePosition(msg);
if (tearLinePos > -1) { if (tearLinePos > -1) {
msg = insert( msg = insert(
msg, msg,
tearLinePos, tearLinePos,
bodyMessageView.getTextSgrPrefix() bodyMessageView.getTextSgrPrefix()
); );
}
bodyMessageView.setAnsi(
msg.replace(/\r?\n/g, '\r\n'), // messages are stored with CRLF -> LF
{
prepped: false,
forceLineTerm: true,
} }
);
} else {
msg = stripAnsiControlCodes(msg); // start clean
bodyMessageView.setAnsi( const styleToArray = (style, len) => {
msg.replace(/\r?\n/g, '\r\n'), // messages are stored with CRLF -> LF if (!Array.isArray(style)) {
{ style = [style];
prepped: false, }
forceLineTerm: true, while (style.length < len) {
style.push(style[0]);
}
return style;
};
//
// In *View* mode, if enabled, do a little prep work so we can stylize:
// - Quote indicators
// - Tear lines
// - Origins
//
if (this.menuConfig.config.quoteStyleLevel1) {
// can be a single style to cover 'XX> TEXT' or an array to cover 'XX', '>', and TEXT
// Non-standard (as for BBSes) single > TEXT, omitting space before XX, etc. are allowed
const styleL1 = styleToArray(
this.menuConfig.config.quoteStyleLevel1,
3
);
const QuoteRegex =
/^([ ]?)([!-~]{0,2})>([ ]*)([^\r\n]*\r?\n)/gm;
msg = msg.replace(
QuoteRegex,
(m, spc1, initials, spc2, text) => {
return `${spc1}${styleL1[0]}${initials}${styleL1[1]}>${spc2}${styleL1[2]}${text}${bodyMessageView.styleSGR1}`;
} }
); );
} else {
msg = stripAnsiControlCodes(msg); // start clean
const styleToArray = (style, len) => {
if (!Array.isArray(style)) {
style = [style];
}
while (style.length < len) {
style.push(style[0]);
}
return style;
};
//
// In *View* mode, if enabled, do a little prep work so we can stylize:
// - Quote indicators
// - Tear lines
// - Origins
//
if (this.menuConfig.config.quoteStyleLevel1) {
// can be a single style to cover 'XX> TEXT' or an array to cover 'XX', '>', and TEXT
// Non-standard (as for BBSes) single > TEXT, omitting space before XX, etc. are allowed
const styleL1 = styleToArray(
this.menuConfig.config.quoteStyleLevel1,
3
);
const QuoteRegex =
/^([ ]?)([!-~]{0,2})>([ ]*)([^\r\n]*\r?\n)/gm;
msg = msg.replace(
QuoteRegex,
(m, spc1, initials, spc2, text) => {
return `${spc1}${styleL1[0]}${initials}${styleL1[1]}>${spc2}${styleL1[2]}${text}${bodyMessageView.styleSGR1}`;
}
);
}
if (this.menuConfig.config.tearLineStyle) {
// '---' and TEXT
const style = styleToArray(
this.menuConfig.config.tearLineStyle,
2
);
const TearLineRegex = /^--- (.+)$(?![\s\S]*^--- .+$)/m;
msg = msg.replace(TearLineRegex, (m, text) => {
return `${style[0]}--- ${style[1]}${text}${bodyMessageView.styleSGR1}`;
});
}
if (this.menuConfig.config.originStyle) {
const style = styleToArray(
this.menuConfig.config.originStyle,
3
);
const OriginRegex = /^([ ]{1,2})\* Origin: (.+)$/m;
msg = msg.replace(OriginRegex, (m, spc, text) => {
return `${spc}${style[0]}* ${style[1]}Origin: ${style[2]}${text}${bodyMessageView.styleSGR1}`;
});
}
bodyMessageView.setText(controlCodesToAnsi(msg));
} }
if (this.menuConfig.config.tearLineStyle) {
// '---' and TEXT
const style = styleToArray(
this.menuConfig.config.tearLineStyle,
2
);
const TearLineRegex = /^--- (.+)$(?![\s\S]*^--- .+$)/m;
msg = msg.replace(TearLineRegex, (m, text) => {
return `${style[0]}--- ${style[1]}${text}${bodyMessageView.styleSGR1}`;
});
}
if (this.menuConfig.config.originStyle) {
const style = styleToArray(
this.menuConfig.config.originStyle,
3
);
const OriginRegex = /^([ ]{1,2})\* Origin: (.+)$/m;
msg = msg.replace(OriginRegex, (m, spc, text) => {
return `${spc}${style[0]}* ${style[1]}Origin: ${style[2]}${text}${bodyMessageView.styleSGR1}`;
});
}
bodyMessageView.setText(controlCodesToAnsi(msg));
} }
} }
}); });
@ -849,7 +852,7 @@ exports.FullScreenEditorModule =
const self = this; const self = this;
var art = self.menuConfig.config.art; var art = self.menuConfig.config.art;
assert(_.isObject(art)); EngiAssert(_.isObject(art));
async.waterfall( async.waterfall(
[ [
@ -1161,7 +1164,7 @@ exports.FullScreenEditorModule =
} }
initHeaderReplyEditMode() { initHeaderReplyEditMode() {
assert(_.isObject(this.replyToMessage)); EngiAssert(_.isObject(this.replyToMessage));
this.setHeaderText(MciViewIds.header.to, this.replyToMessage.fromUserName); this.setHeaderText(MciViewIds.header.to, this.replyToMessage.fromUserName);
@ -1177,6 +1180,20 @@ exports.FullScreenEditorModule =
this.setHeaderText(MciViewIds.header.subject, newSubj); this.setHeaderText(MciViewIds.header.subject, newSubj);
} }
initBodyReplyEditMode() {
EngiAssert(_.isObject(this.replyToMessage));
const bodyMessageView = this.viewControllers.body.getView(
MciViewIds.body.message
);
const messagePrefix = getReplyToMessagePrefix(
this.replyToMessage.fromUserName
);
bodyMessageView.setText(messagePrefix);
}
initFooterViewMode() { initFooterViewMode() {
this.setViewText( this.setViewText(
'footerView', 'footerView',
@ -1450,11 +1467,18 @@ exports.FullScreenEditorModule =
switchToBody() { switchToBody() {
const to = this.getView('header', MciViewIds.header.to).getData(); const to = this.getView('header', MciViewIds.header.to).getData();
const msgInfo = messageInfoFromAddressedToInfo(getAddressedToInfo(to)); const msgInfo = messageInfoFromAddressedToInfo(getAddressedToInfo(to));
const bodyView = this.getView('body', MciViewIds.body.message);
if (msgInfo.maxMessageLength > 0) { if (msgInfo.maxMessageLength > 0) {
const bodyView = this.getView('body', MciViewIds.body.message);
bodyView.maxLength = msgInfo.maxMessageLength; bodyView.maxLength = msgInfo.maxMessageLength;
} }
// first pass through, init body (we may need header values set)
const bodyText = bodyView.getData();
if (!bodyText && this.isReply()) {
this.initBodyReplyEditMode();
}
this.viewControllers.header.setFocus(false); this.viewControllers.header.setFocus(false);
this.viewControllers.body.switchFocus(1); this.viewControllers.body.switchFocus(1);

View File

@ -15,6 +15,7 @@ exports.setExternalAddressedToInfo = setExternalAddressedToInfo;
exports.copyExternalAddressedToInfo = copyExternalAddressedToInfo; exports.copyExternalAddressedToInfo = copyExternalAddressedToInfo;
exports.messageInfoFromAddressedToInfo = messageInfoFromAddressedToInfo; exports.messageInfoFromAddressedToInfo = messageInfoFromAddressedToInfo;
exports.getQuotePrefixFromName = getQuotePrefixFromName; exports.getQuotePrefixFromName = getQuotePrefixFromName;
exports.getReplyToMessagePrefix = getReplyToMessagePrefix;
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,}))$/;
@ -32,6 +33,11 @@ const EMAIL_REGEX =
foo@host.com { name : 'foo', flavor : 'email', remote : 'foo@host.com' } foo@host.com { name : 'foo', flavor : 'email', remote : 'foo@host.com' }
Bar <baz@foobar.net> { name : 'Bar', flavor : 'email', remote : 'baz@foobar.com' } Bar <baz@foobar.net> { name : 'Bar', flavor : 'email', remote : 'baz@foobar.com' }
@JoeUser@some.host.com { name : 'JoeUser', flavor : 'activitypub', remote 'JoeUser@some.host.com' } @JoeUser@some.host.com { name : 'JoeUser', flavor : 'activitypub', remote 'JoeUser@some.host.com' }
Fields:
- name : user/display name
- flavor : remote flavor - FTN/etc.
- remote : Address in remote format, if applicable
*/ */
function getAddressedToInfo(input) { function getAddressedToInfo(input) {
input = input.trim(); input = input.trim();
@ -41,17 +47,26 @@ 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: MessageConst.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: MessageConst.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: MessageConst.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));
@ -93,7 +108,10 @@ function getAddressedToInfo(input) {
}; };
} }
return { name: input, flavor: MessageConst.AddressFlavor.Local }; return {
name: input,
flavor: MessageConst.AddressFlavor.Local,
};
} }
let m = input.match(EMAIL_REGEX); let m = input.match(EMAIL_REGEX);
@ -107,7 +125,10 @@ function getAddressedToInfo(input) {
let addr = Address.fromString(input); // 5D? let addr = Address.fromString(input); // 5D?
if (Address.isValidAddress(addr)) { if (Address.isValidAddress(addr)) {
return { flavor: MessageConst.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());
@ -172,6 +193,17 @@ function messageInfoFromAddressedToInfo(addressInfo) {
} }
function getQuotePrefixFromName(name) { function getQuotePrefixFromName(name) {
const addrInfo = getAddressedToInfo(name); const addressInfo = getAddressedToInfo(name);
return getQuotePrefix(addrInfo.name || name); return getQuotePrefix(addressInfo.name || name);
}
function getReplyToMessagePrefix(name) {
const addressInfo = getAddressedToInfo(name);
// currently only ActivityPub
if (addressInfo.flavor === MessageConst.AddressFlavor.ActivityPub) {
return `@${addressInfo.name} `;
}
return '';
} }