mirror of
https://github.com/NuSkooler/enigma-bbs.git
synced 2025-06-10 14:44:40 +02:00
Pardon the noise. More tab to space conversion!
This commit is contained in:
parent
c3635bb26b
commit
1d8be6b014
128 changed files with 8017 additions and 8017 deletions
440
core/message.js
440
core/message.js
|
@ -1,96 +1,96 @@
|
|||
/* jslint node: true */
|
||||
'use strict';
|
||||
|
||||
const msgDb = require('./database.js').dbs.message;
|
||||
const wordWrapText = require('./word_wrap.js').wordWrapText;
|
||||
const ftnUtil = require('./ftn_util.js');
|
||||
const createNamedUUID = require('./uuid_util.js').createNamedUUID;
|
||||
const Errors = require('./enig_error.js').Errors;
|
||||
const ANSI = require('./ansi_term.js');
|
||||
const msgDb = require('./database.js').dbs.message;
|
||||
const wordWrapText = require('./word_wrap.js').wordWrapText;
|
||||
const ftnUtil = require('./ftn_util.js');
|
||||
const createNamedUUID = require('./uuid_util.js').createNamedUUID;
|
||||
const Errors = require('./enig_error.js').Errors;
|
||||
const ANSI = require('./ansi_term.js');
|
||||
const {
|
||||
sanatizeString,
|
||||
getISOTimestampString } = require('./database.js');
|
||||
getISOTimestampString } = require('./database.js');
|
||||
|
||||
const {
|
||||
isAnsi, isFormattedLine,
|
||||
splitTextAtTerms,
|
||||
renderSubstr
|
||||
} = require('./string_util.js');
|
||||
} = require('./string_util.js');
|
||||
|
||||
const ansiPrep = require('./ansi_prep.js');
|
||||
const ansiPrep = require('./ansi_prep.js');
|
||||
|
||||
// deps
|
||||
const uuidParse = require('uuid-parse');
|
||||
const async = require('async');
|
||||
const _ = require('lodash');
|
||||
const assert = require('assert');
|
||||
const moment = require('moment');
|
||||
const iconvEncode = require('iconv-lite').encode;
|
||||
// deps
|
||||
const uuidParse = require('uuid-parse');
|
||||
const async = require('async');
|
||||
const _ = require('lodash');
|
||||
const assert = require('assert');
|
||||
const moment = require('moment');
|
||||
const iconvEncode = require('iconv-lite').encode;
|
||||
|
||||
const ENIGMA_MESSAGE_UUID_NAMESPACE = uuidParse.parse('154506df-1df8-46b9-98f8-ebb5815baaf8');
|
||||
const ENIGMA_MESSAGE_UUID_NAMESPACE = uuidParse.parse('154506df-1df8-46b9-98f8-ebb5815baaf8');
|
||||
|
||||
const WELL_KNOWN_AREA_TAGS = {
|
||||
Invalid : '',
|
||||
Private : 'private_mail',
|
||||
Bulletin : 'local_bulletin',
|
||||
Invalid : '',
|
||||
Private : 'private_mail',
|
||||
Bulletin : 'local_bulletin',
|
||||
};
|
||||
|
||||
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
|
||||
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
|
||||
// Types for Message.SystemMetaNames.ExternalFlavor meta
|
||||
const ADDRESS_FLAVOR = {
|
||||
Local : 'local', // local / non-remote addressing
|
||||
FTN : 'ftn', // FTN style
|
||||
Email : 'email',
|
||||
Local : 'local', // local / non-remote addressing
|
||||
FTN : 'ftn', // FTN style
|
||||
Email : 'email',
|
||||
};
|
||||
|
||||
const STATE_FLAGS0 = {
|
||||
None : 0x00000000,
|
||||
Imported : 0x00000001, // imported from foreign system
|
||||
Exported : 0x00000002, // exported to foreign system
|
||||
None : 0x00000000,
|
||||
Imported : 0x00000001, // imported from foreign system
|
||||
Exported : 0x00000002, // exported to foreign system
|
||||
};
|
||||
|
||||
// :TODO: these should really live elsewhere...
|
||||
// :TODO: these should really live elsewhere...
|
||||
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',
|
||||
// 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',
|
||||
// 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',
|
||||
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
|
||||
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
|
||||
};
|
||||
|
||||
// :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 = {
|
||||
reply_to_message_id : 'replyToMsgId',
|
||||
modified_timestamp : 'modTimestamp'
|
||||
reply_to_message_id : 'replyToMsgId',
|
||||
modified_timestamp : 'modTimestamp'
|
||||
};
|
||||
|
||||
module.exports = class Message {
|
||||
|
@ -102,28 +102,28 @@ module.exports = class Message {
|
|||
} = { }
|
||||
)
|
||||
{
|
||||
this.messageId = messageId;
|
||||
this.areaTag = areaTag;
|
||||
this.uuid = uuid;
|
||||
this.replyToMsgId = replyToMsgId;
|
||||
this.toUserName = toUserName;
|
||||
this.fromUserName = fromUserName;
|
||||
this.subject = subject;
|
||||
this.message = message;
|
||||
this.messageId = messageId;
|
||||
this.areaTag = areaTag;
|
||||
this.uuid = uuid;
|
||||
this.replyToMsgId = replyToMsgId;
|
||||
this.toUserName = toUserName;
|
||||
this.fromUserName = fromUserName;
|
||||
this.subject = subject;
|
||||
this.message = message;
|
||||
|
||||
if(_.isDate(modTimestamp) || _.isString(modTimestamp)) {
|
||||
modTimestamp = moment(modTimestamp);
|
||||
}
|
||||
|
||||
this.modTimestamp = modTimestamp;
|
||||
this.modTimestamp = modTimestamp;
|
||||
|
||||
this.meta = {};
|
||||
_.defaultsDeep(this.meta, { System : {} }, meta);
|
||||
|
||||
this.hashTags = hashTags;
|
||||
this.hashTags = hashTags;
|
||||
}
|
||||
|
||||
isValid() { return true; } // :TODO: obviously useless; look into this or remove it
|
||||
isValid() { return true; } // :TODO: obviously useless; look into this or remove it
|
||||
|
||||
static isPrivateAreaTag(areaTag) {
|
||||
return areaTag.toLowerCase() === Message.WellKnownAreaTags.Private;
|
||||
|
@ -187,10 +187,10 @@ module.exports = class Message {
|
|||
modTimestamp = moment(modTimestamp);
|
||||
}
|
||||
|
||||
areaTag = iconvEncode(areaTag.toUpperCase(), 'CP437');
|
||||
modTimestamp = iconvEncode(modTimestamp.format('DD MMM YY HH:mm:ss'), 'CP437');
|
||||
subject = iconvEncode(subject.toUpperCase().trim(), 'CP437');
|
||||
body = iconvEncode(body.replace(/\r\n|[\n\v\f\r\x85\u2028\u2029]/g, '').trim(), 'CP437');
|
||||
areaTag = iconvEncode(areaTag.toUpperCase(), 'CP437');
|
||||
modTimestamp = iconvEncode(modTimestamp.format('DD MMM YY HH:mm:ss'), 'CP437');
|
||||
subject = iconvEncode(subject.toUpperCase().trim(), 'CP437');
|
||||
body = iconvEncode(body.replace(/\r\n|[\n\v\f\r\x85\u2028\u2029]/g, '').trim(), 'CP437');
|
||||
|
||||
return uuidParse.unparse(createNamedUUID(ENIGMA_MESSAGE_UUID_NAMESPACE, Buffer.concat( [ areaTag, modTimestamp, subject, body ] )));
|
||||
}
|
||||
|
@ -198,7 +198,7 @@ module.exports = class Message {
|
|||
static getMessageFromRow(row) {
|
||||
const msg = {};
|
||||
_.each(row, (v, k) => {
|
||||
// :TODO: see notes around MESSAGE_ROW_MAP -- clean this up so we can just _camelCase()!
|
||||
// :TODO: see notes around MESSAGE_ROW_MAP -- clean this up so we can just _camelCase()!
|
||||
k = MESSAGE_ROW_MAP[k] || _.camelCase(k);
|
||||
msg[k] = v;
|
||||
});
|
||||
|
@ -206,38 +206,38 @@ module.exports = class Message {
|
|||
}
|
||||
|
||||
/*
|
||||
Find message IDs or UUIDs by filter. Available filters/options:
|
||||
Find message IDs or UUIDs by filter. Available filters/options:
|
||||
|
||||
filter.uuids - use with resultType='id'
|
||||
filter.ids - use with resultType='uuid'
|
||||
filter.toUserName
|
||||
filter.fromUserName
|
||||
filter.replyToMesageId
|
||||
filter.newerThanTimestamp
|
||||
filter.newerThanMessageId
|
||||
filter.areaTag - note if you want by conf, send in all areas for a conf
|
||||
*filter.metaTuples - {category, name, value}
|
||||
filter.uuids - use with resultType='id'
|
||||
filter.ids - use with resultType='uuid'
|
||||
filter.toUserName
|
||||
filter.fromUserName
|
||||
filter.replyToMesageId
|
||||
filter.newerThanTimestamp
|
||||
filter.newerThanMessageId
|
||||
filter.areaTag - note if you want by conf, send in all areas for a conf
|
||||
*filter.metaTuples - {category, name, value}
|
||||
|
||||
filter.terms - FTS search
|
||||
filter.terms - FTS search
|
||||
|
||||
filter.sort = modTimestamp | messageId
|
||||
filter.order = ascending | (descending)
|
||||
filter.sort = modTimestamp | messageId
|
||||
filter.order = ascending | (descending)
|
||||
|
||||
filter.limit
|
||||
filter.resultType = (id) | uuid | count
|
||||
filter.extraFields = []
|
||||
filter.limit
|
||||
filter.resultType = (id) | uuid | count
|
||||
filter.extraFields = []
|
||||
|
||||
filter.privateTagUserId = <userId> - if set, only private messages belonging to <userId> are processed
|
||||
- any other areaTag or confTag filters will be ignored
|
||||
- if NOT present, private areas are skipped
|
||||
filter.privateTagUserId = <userId> - if set, only private messages belonging to <userId> are processed
|
||||
- any other areaTag or confTag filters will be ignored
|
||||
- if NOT present, private areas are skipped
|
||||
|
||||
*=NYI
|
||||
*/
|
||||
*=NYI
|
||||
*/
|
||||
static findMessages(filter, cb) {
|
||||
filter = filter || {};
|
||||
|
||||
filter.resultType = filter.resultType || 'id';
|
||||
filter.extraFields = filter.extraFields || [];
|
||||
filter.resultType = filter.resultType || 'id';
|
||||
filter.extraFields = filter.extraFields || [];
|
||||
|
||||
if('messageList' === filter.resultType) {
|
||||
filter.extraFields = _.uniq(filter.extraFields.concat(
|
||||
|
@ -254,13 +254,13 @@ module.exports = class Message {
|
|||
let sql;
|
||||
if('count' === filter.resultType) {
|
||||
sql =
|
||||
`SELECT COUNT() AS count
|
||||
FROM message m`;
|
||||
`SELECT COUNT() AS count
|
||||
FROM message m`;
|
||||
|
||||
} else {
|
||||
sql =
|
||||
`SELECT DISTINCT m.${field}${filter.extraFields.length > 0 ? ', ' + filter.extraFields.map(f => `m.${f}`).join(', ') : ''}
|
||||
FROM message m`;
|
||||
`SELECT DISTINCT m.${field}${filter.extraFields.length > 0 ? ', ' + filter.extraFields.map(f => `m.${f}`).join(', ') : ''}
|
||||
FROM message m`;
|
||||
}
|
||||
|
||||
const sqlOrderDir = 'ascending' === filter.order ? 'ASC' : 'DESC';
|
||||
|
@ -276,7 +276,7 @@ module.exports = class Message {
|
|||
sqlWhere += clause;
|
||||
}
|
||||
|
||||
// currently only avail sort
|
||||
// currently only avail sort
|
||||
if('modTimestamp' === filter.sort) {
|
||||
sqlOrderBy = `ORDER BY m.modified_timestamp ${sqlOrderDir}`;
|
||||
} else {
|
||||
|
@ -297,10 +297,10 @@ module.exports = class Message {
|
|||
appendWhereClause(`m.area_tag = "${Message.WellKnownAreaTags.Private}"`);
|
||||
appendWhereClause(
|
||||
`m.message_id IN (
|
||||
SELECT message_id
|
||||
FROM message_meta
|
||||
WHERE meta_category = "System" AND meta_name = "${Message.SystemMetaNames.LocalToUserID}" AND meta_value = ${filter.privateTagUserId}
|
||||
)`);
|
||||
SELECT message_id
|
||||
FROM message_meta
|
||||
WHERE meta_category = "System" AND meta_name = "${Message.SystemMetaNames.LocalToUserID}" AND meta_value = ${filter.privateTagUserId}
|
||||
)`);
|
||||
} else {
|
||||
if(filter.areaTag && filter.areaTag.length > 0) {
|
||||
if(Array.isArray(filter.areaTag)) {
|
||||
|
@ -315,7 +315,7 @@ module.exports = class Message {
|
|||
}
|
||||
}
|
||||
|
||||
// explicit exclude of Private
|
||||
// explicit exclude of Private
|
||||
appendWhereClause(`m.area_tag != "${Message.WellKnownAreaTags.Private}"`);
|
||||
}
|
||||
|
||||
|
@ -338,13 +338,13 @@ module.exports = class Message {
|
|||
}
|
||||
|
||||
if(filter.terms && filter.terms.length > 0) {
|
||||
// note the ':' in MATCH expr., see https://www.sqlite.org/cvstrac/wiki?p=FullTextIndex
|
||||
// note the ':' in MATCH expr., see https://www.sqlite.org/cvstrac/wiki?p=FullTextIndex
|
||||
appendWhereClause(
|
||||
`m.message_id IN (
|
||||
SELECT rowid
|
||||
FROM message_fts
|
||||
WHERE message_fts MATCH ":${sanatizeString(filter.terms)}"
|
||||
)`
|
||||
SELECT rowid
|
||||
FROM message_fts
|
||||
WHERE message_fts MATCH ":${sanatizeString(filter.terms)}"
|
||||
)`
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -376,13 +376,13 @@ module.exports = class Message {
|
|||
}
|
||||
}
|
||||
|
||||
// :TODO: use findMessages, by uuid, limit=1
|
||||
// :TODO: use findMessages, by uuid, limit=1
|
||||
static getMessageIdByUuid(uuid, cb) {
|
||||
msgDb.get(
|
||||
`SELECT message_id
|
||||
FROM message
|
||||
WHERE message_uuid = ?
|
||||
LIMIT 1;`,
|
||||
FROM message
|
||||
WHERE message_uuid = ?
|
||||
LIMIT 1;`,
|
||||
[ uuid ],
|
||||
(err, row) => {
|
||||
if(err) {
|
||||
|
@ -398,27 +398,27 @@ module.exports = class Message {
|
|||
);
|
||||
}
|
||||
|
||||
// :TODO: use findMessages
|
||||
// :TODO: use findMessages
|
||||
static getMessageIdsByMetaValue(category, name, value, cb) {
|
||||
msgDb.all(
|
||||
`SELECT message_id
|
||||
FROM message_meta
|
||||
WHERE meta_category = ? AND meta_name = ? AND meta_value = ?;`,
|
||||
FROM message_meta
|
||||
WHERE meta_category = ? AND meta_name = ? AND meta_value = ?;`,
|
||||
[ category, name, value ],
|
||||
(err, rows) => {
|
||||
if(err) {
|
||||
return cb(err);
|
||||
}
|
||||
return cb(null, rows.map(r => parseInt(r.message_id))); // return array of ID(s)
|
||||
return cb(null, rows.map(r => parseInt(r.message_id))); // return array of ID(s)
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
static getMetaValuesByMessageId(messageId, category, name, cb) {
|
||||
const sql =
|
||||
`SELECT meta_value
|
||||
FROM message_meta
|
||||
WHERE message_id = ? AND meta_category = ? AND meta_name = ?;`;
|
||||
`SELECT meta_value
|
||||
FROM message_meta
|
||||
WHERE message_id = ? AND meta_category = ? AND meta_name = ?;`;
|
||||
|
||||
msgDb.all(sql, [ messageId, category, name ], (err, rows) => {
|
||||
if(err) {
|
||||
|
@ -429,12 +429,12 @@ module.exports = class Message {
|
|||
return cb(Errors.DoesNotExist('No value for category/name'));
|
||||
}
|
||||
|
||||
// single values are returned without an array
|
||||
// single values are returned without an array
|
||||
if(1 === rows.length) {
|
||||
return cb(null, rows[0].meta_value);
|
||||
}
|
||||
|
||||
return cb(null, rows.map(r => r.meta_value)); // map to array of values only
|
||||
return cb(null, rows.map(r => r.meta_value)); // map to array of values only
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -460,23 +460,23 @@ module.exports = class Message {
|
|||
|
||||
loadMeta(cb) {
|
||||
/*
|
||||
Example of loaded this.meta:
|
||||
Example of loaded this.meta:
|
||||
|
||||
meta: {
|
||||
System: {
|
||||
local_to_user_id: 1234,
|
||||
},
|
||||
FtnProperty: {
|
||||
ftn_seen_by: [ "1/102 103", "2/42 52 65" ]
|
||||
}
|
||||
}
|
||||
*/
|
||||
meta: {
|
||||
System: {
|
||||
local_to_user_id: 1234,
|
||||
},
|
||||
FtnProperty: {
|
||||
ftn_seen_by: [ "1/102 103", "2/42 52 65" ]
|
||||
}
|
||||
}
|
||||
*/
|
||||
const sql =
|
||||
`SELECT meta_category, meta_name, meta_value
|
||||
FROM message_meta
|
||||
WHERE message_id = ?;`;
|
||||
`SELECT meta_category, meta_name, meta_value
|
||||
FROM message_meta
|
||||
WHERE message_id = ?;`;
|
||||
|
||||
const self = this; // :TODO: not required - arrow functions below:
|
||||
const self = this; // :TODO: not required - arrow functions below:
|
||||
msgDb.each(sql, [ this.messageId ], (err, row) => {
|
||||
if(!(row.meta_category in self.meta)) {
|
||||
self.meta[row.meta_category] = { };
|
||||
|
@ -497,7 +497,7 @@ module.exports = class Message {
|
|||
});
|
||||
}
|
||||
|
||||
// :TODO: this should only take a UUID...
|
||||
// :TODO: this should only take a UUID...
|
||||
load(options, cb) {
|
||||
assert(_.isString(options.uuid));
|
||||
|
||||
|
@ -508,10 +508,10 @@ module.exports = class Message {
|
|||
function loadMessage(callback) {
|
||||
msgDb.get(
|
||||
`SELECT message_id, area_tag, message_uuid, reply_to_message_id, to_user_name, from_user_name, subject,
|
||||
message, modified_timestamp, view_count
|
||||
FROM message
|
||||
WHERE message_uuid=?
|
||||
LIMIT 1;`,
|
||||
message, modified_timestamp, view_count
|
||||
FROM message
|
||||
WHERE message_uuid=?
|
||||
LIMIT 1;`,
|
||||
[ options.uuid ],
|
||||
(err, msgRow) => {
|
||||
if(err) {
|
||||
|
@ -522,15 +522,15 @@ module.exports = class Message {
|
|||
return callback(Errors.DoesNotExist('Message (no longer) available'));
|
||||
}
|
||||
|
||||
self.messageId = msgRow.message_id;
|
||||
self.areaTag = msgRow.area_tag;
|
||||
self.messageUuid = msgRow.message_uuid;
|
||||
self.replyToMsgId = msgRow.reply_to_message_id;
|
||||
self.toUserName = msgRow.to_user_name;
|
||||
self.fromUserName = msgRow.from_user_name;
|
||||
self.subject = msgRow.subject;
|
||||
self.message = msgRow.message;
|
||||
self.modTimestamp = moment(msgRow.modified_timestamp);
|
||||
self.messageId = msgRow.message_id;
|
||||
self.areaTag = msgRow.area_tag;
|
||||
self.messageUuid = msgRow.message_uuid;
|
||||
self.replyToMsgId = msgRow.reply_to_message_id;
|
||||
self.toUserName = msgRow.to_user_name;
|
||||
self.fromUserName = msgRow.from_user_name;
|
||||
self.subject = msgRow.subject;
|
||||
self.message = msgRow.message;
|
||||
self.modTimestamp = moment(msgRow.modified_timestamp);
|
||||
|
||||
return callback(err);
|
||||
}
|
||||
|
@ -542,7 +542,7 @@ module.exports = class Message {
|
|||
});
|
||||
},
|
||||
function loadHashTags(callback) {
|
||||
// :TODO:
|
||||
// :TODO:
|
||||
return callback(null);
|
||||
}
|
||||
],
|
||||
|
@ -560,7 +560,7 @@ module.exports = class Message {
|
|||
|
||||
const metaStmt = transOrDb.prepare(
|
||||
`INSERT INTO message_meta (message_id, meta_category, meta_name, meta_value)
|
||||
VALUES (?, ?, ?, ?);`);
|
||||
VALUES (?, ?, ?, ?);`);
|
||||
|
||||
if(!_.isArray(value)) {
|
||||
value = [ value ];
|
||||
|
@ -590,7 +590,7 @@ module.exports = class Message {
|
|||
return msgDb.beginTransaction(callback);
|
||||
},
|
||||
function storeMessage(trans, callback) {
|
||||
// generate a UUID for this message if required (general case)
|
||||
// generate a UUID for this message if required (general case)
|
||||
const msgTimestamp = moment();
|
||||
if(!self.uuid) {
|
||||
self.uuid = Message.createMessageUUID(
|
||||
|
@ -603,9 +603,9 @@ module.exports = class Message {
|
|||
|
||||
trans.run(
|
||||
`INSERT INTO message (area_tag, message_uuid, reply_to_message_id, to_user_name, from_user_name, subject, message, modified_timestamp)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?);`,
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?);`,
|
||||
[ self.areaTag, self.uuid, self.replyToMsgId, self.toUserName, self.fromUserName, self.subject, self.message, getISOTimestampString(msgTimestamp) ],
|
||||
function inserted(err) { // use non-arrow function for 'this' scope
|
||||
function inserted(err) { // use non-arrow function for 'this' scope
|
||||
if(!err) {
|
||||
self.messageId = this.lastID;
|
||||
}
|
||||
|
@ -619,17 +619,17 @@ module.exports = class Message {
|
|||
return callback(null, trans);
|
||||
}
|
||||
/*
|
||||
Example of self.meta:
|
||||
Example of self.meta:
|
||||
|
||||
meta: {
|
||||
System: {
|
||||
local_to_user_id: 1234,
|
||||
},
|
||||
FtnProperty: {
|
||||
ftn_seen_by: [ "1/102 103", "2/42 52 65" ]
|
||||
}
|
||||
}
|
||||
*/
|
||||
meta: {
|
||||
System: {
|
||||
local_to_user_id: 1234,
|
||||
},
|
||||
FtnProperty: {
|
||||
ftn_seen_by: [ "1/102 103", "2/42 52 65" ]
|
||||
}
|
||||
}
|
||||
*/
|
||||
async.each(Object.keys(self.meta), (category, nextCat) => {
|
||||
async.each(Object.keys(self.meta[category]), (name, nextName) => {
|
||||
self.persistMetaValue(category, name, self.meta[category][name], trans, err => {
|
||||
|
@ -644,7 +644,7 @@ module.exports = class Message {
|
|||
});
|
||||
},
|
||||
function storeHashTags(trans, callback) {
|
||||
// :TODO: hash tag support
|
||||
// :TODO: hash tag support
|
||||
return callback(null, trans);
|
||||
}
|
||||
],
|
||||
|
@ -660,7 +660,7 @@ module.exports = class Message {
|
|||
);
|
||||
}
|
||||
|
||||
// :TODO: FTN stuff doesn't have any business here
|
||||
// :TODO: FTN stuff doesn't have any business here
|
||||
getFTNQuotePrefix(source) {
|
||||
source = source || 'fromUserName';
|
||||
|
||||
|
@ -677,32 +677,32 @@ module.exports = class Message {
|
|||
return cb(Errors.MissingParam());
|
||||
}
|
||||
|
||||
options.startCol = options.startCol || 1;
|
||||
options.includePrefix = _.get(options, 'includePrefix', true);
|
||||
options.ansiResetSgr = options.ansiResetSgr || ANSI.getSGRFromGraphicRendition( { fg : 39, bg : 49 }, true);
|
||||
options.ansiFocusPrefixSgr = options.ansiFocusPrefixSgr || ANSI.getSGRFromGraphicRendition( { intensity : 'bold', fg : 39, bg : 49 } );
|
||||
options.isAnsi = options.isAnsi || isAnsi(this.message); // :TODO: If this.isAnsi, use that setting
|
||||
options.startCol = options.startCol || 1;
|
||||
options.includePrefix = _.get(options, 'includePrefix', true);
|
||||
options.ansiResetSgr = options.ansiResetSgr || ANSI.getSGRFromGraphicRendition( { fg : 39, bg : 49 }, true);
|
||||
options.ansiFocusPrefixSgr = options.ansiFocusPrefixSgr || ANSI.getSGRFromGraphicRendition( { intensity : 'bold', fg : 39, bg : 49 } );
|
||||
options.isAnsi = options.isAnsi || isAnsi(this.message); // :TODO: If this.isAnsi, use that setting
|
||||
|
||||
/*
|
||||
Some long text that needs to be wrapped and quoted should look right after
|
||||
doing so, don't ya think? yeah I think so
|
||||
Some long text that needs to be wrapped and quoted should look right after
|
||||
doing so, don't ya think? yeah I think so
|
||||
|
||||
Nu> Some long text that needs to be wrapped and quoted should look right
|
||||
Nu> after doing so, don't ya think? yeah I think so
|
||||
Nu> Some long text that needs to be wrapped and quoted should look right
|
||||
Nu> after doing so, don't ya think? yeah I think so
|
||||
|
||||
Ot> Nu> Some long text that needs to be wrapped and quoted should look
|
||||
Ot> Nu> right after doing so, don't ya think? yeah I think so
|
||||
Ot> Nu> Some long text that needs to be wrapped and quoted should look
|
||||
Ot> Nu> right after doing so, don't ya think? yeah I think so
|
||||
|
||||
*/
|
||||
*/
|
||||
const quotePrefix = options.includePrefix ? this.getFTNQuotePrefix(options.prefixSource || 'fromUserName') : '';
|
||||
|
||||
function getWrapped(text, extraPrefix) {
|
||||
extraPrefix = extraPrefix ? ` ${extraPrefix}` : '';
|
||||
|
||||
const wrapOpts = {
|
||||
width : options.cols - (quotePrefix.length + extraPrefix.length),
|
||||
tabHandling : 'expand',
|
||||
tabWidth : 4,
|
||||
width : options.cols - (quotePrefix.length + extraPrefix.length),
|
||||
tabHandling : 'expand',
|
||||
tabWidth : 4,
|
||||
};
|
||||
|
||||
return wordWrapText(text, wrapOpts).wrapped.map( (w, i) => {
|
||||
|
@ -711,7 +711,7 @@ module.exports = class Message {
|
|||
}
|
||||
|
||||
function getFormattedLine(line) {
|
||||
// for pre-formatted text, we just append a line truncated to fit
|
||||
// for pre-formatted text, we just append a line truncated to fit
|
||||
let newLen;
|
||||
const total = line.length + quotePrefix.length;
|
||||
|
||||
|
@ -726,14 +726,14 @@ module.exports = class Message {
|
|||
|
||||
if(options.isAnsi) {
|
||||
ansiPrep(
|
||||
this.message.replace(/\r?\n/g, '\r\n'), // normalized LF -> CRLF
|
||||
this.message.replace(/\r?\n/g, '\r\n'), // normalized LF -> CRLF
|
||||
{
|
||||
termWidth : options.termWidth,
|
||||
termHeight : options.termHeight,
|
||||
cols : options.cols,
|
||||
rows : 'auto',
|
||||
startCol : options.startCol,
|
||||
forceLineTerm : true,
|
||||
termWidth : options.termWidth,
|
||||
termHeight : options.termHeight,
|
||||
cols : options.cols,
|
||||
rows : 'auto',
|
||||
startCol : options.startCol,
|
||||
forceLineTerm : true,
|
||||
},
|
||||
(err, prepped) => {
|
||||
prepped = prepped || this.message;
|
||||
|
@ -741,20 +741,20 @@ module.exports = class Message {
|
|||
let lastSgr = '';
|
||||
const split = splitTextAtTerms(prepped);
|
||||
|
||||
const quoteLines = [];
|
||||
const focusQuoteLines = [];
|
||||
const quoteLines = [];
|
||||
const focusQuoteLines = [];
|
||||
|
||||
//
|
||||
// Do not include quote prefixes (e.g. XX> ) on ANSI replies (and therefor quote builder)
|
||||
// as while this works in ENiGMA, other boards such as Mystic, WWIV, etc. will try to
|
||||
// strip colors, colorize the lines, etc. If we exclude the prefixes, this seems to do
|
||||
// the trick and allow them to leave them alone!
|
||||
// Do not include quote prefixes (e.g. XX> ) on ANSI replies (and therefor quote builder)
|
||||
// as while this works in ENiGMA, other boards such as Mystic, WWIV, etc. will try to
|
||||
// strip colors, colorize the lines, etc. If we exclude the prefixes, this seems to do
|
||||
// the trick and allow them to leave them alone!
|
||||
//
|
||||
split.forEach(l => {
|
||||
quoteLines.push(`${lastSgr}${l}`);
|
||||
|
||||
focusQuoteLines.push(`${options.ansiFocusPrefixSgr}>${lastSgr}${renderSubstr(l, 1, l.length - 1)}`);
|
||||
lastSgr = (l.match(/(?:\x1b\x5b)[?=;0-9]*m(?!.*(?:\x1b\x5b)[?=;0-9]*m)/) || [])[0] || ''; // eslint-disable-line no-control-regex
|
||||
lastSgr = (l.match(/(?:\x1b\x5b)[?=;0-9]*m(?!.*(?:\x1b\x5b)[?=;0-9]*m)/) || [])[0] || ''; // eslint-disable-line no-control-regex
|
||||
});
|
||||
|
||||
quoteLines[quoteLines.length - 1] += options.ansiResetSgr;
|
||||
|
@ -763,23 +763,23 @@ module.exports = class Message {
|
|||
}
|
||||
);
|
||||
} else {
|
||||
const QUOTE_RE = /^ ((?:[A-Za-z0-9]{2}> )+(?:[A-Za-z0-9]{2}>)*) */;
|
||||
const quoted = [];
|
||||
const input = _.trimEnd(this.message).replace(/\b/g, '');
|
||||
const QUOTE_RE = /^ ((?:[A-Za-z0-9]{2}> )+(?:[A-Za-z0-9]{2}>)*) */;
|
||||
const quoted = [];
|
||||
const input = _.trimEnd(this.message).replace(/\b/g, '');
|
||||
|
||||
// find *last* tearline
|
||||
// find *last* tearline
|
||||
let tearLinePos = this.getTearLinePosition(input);
|
||||
tearLinePos = -1 === tearLinePos ? input.length : tearLinePos; // we just want the index or the entire string
|
||||
tearLinePos = -1 === tearLinePos ? input.length : tearLinePos; // we just want the index or the entire string
|
||||
|
||||
input.slice(0, tearLinePos).split(/\r\n\r\n|\n\n/).forEach(paragraph => {
|
||||
//
|
||||
// For each paragraph, a state machine:
|
||||
// - New line - line
|
||||
// - New (pre)quoted line - quote_line
|
||||
// - Continuation of new/quoted line
|
||||
// For each paragraph, a state machine:
|
||||
// - New line - line
|
||||
// - New (pre)quoted line - quote_line
|
||||
// - Continuation of new/quoted line
|
||||
//
|
||||
// Also:
|
||||
// - Detect pre-formatted lines & try to keep them as-is
|
||||
// Also:
|
||||
// - Detect pre-formatted lines & try to keep them as-is
|
||||
//
|
||||
let state;
|
||||
let buf = '';
|
||||
|
@ -787,18 +787,18 @@ module.exports = class Message {
|
|||
|
||||
if(quoted.length > 0) {
|
||||
//
|
||||
// Preserve paragraph seperation.
|
||||
// Preserve paragraph seperation.
|
||||
//
|
||||
// FSC-0032 states something about leaving blank lines fully blank
|
||||
// (without a prefix) but it seems nicer (and more consistent with other systems)
|
||||
// to put 'em in.
|
||||
// FSC-0032 states something about leaving blank lines fully blank
|
||||
// (without a prefix) but it seems nicer (and more consistent with other systems)
|
||||
// to put 'em in.
|
||||
//
|
||||
quoted.push(quotePrefix);
|
||||
}
|
||||
|
||||
paragraph.split(/\r?\n/).forEach(line => {
|
||||
if(0 === line.trim().length) {
|
||||
// see blank line notes above
|
||||
// see blank line notes above
|
||||
return quoted.push(quotePrefix);
|
||||
}
|
||||
|
||||
|
@ -839,8 +839,8 @@ module.exports = class Message {
|
|||
if(isFormattedLine(line)) {
|
||||
quoted.push(getFormattedLine(line));
|
||||
} else {
|
||||
state = quoteMatch ? 'quote_line' : 'line';
|
||||
buf = 'line' === state ? line : line.replace(/\s/, ''); // trim *first* leading space, if any
|
||||
state = quoteMatch ? 'quote_line' : 'line';
|
||||
buf = 'line' === state ? line : line.replace(/\s/, ''); // trim *first* leading space, if any
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue