commit
6e0f47b1e9
|
@ -204,23 +204,37 @@ module.exports = class ArchiveUtil {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
compressTo(archType, archivePath, files, cb) {
|
compressTo(archType, archivePath, files, workDir, cb) {
|
||||||
const archiver = this.getArchiver(archType, paths.extname(archivePath));
|
const archiver = this.getArchiver(archType, paths.extname(archivePath));
|
||||||
|
|
||||||
if(!archiver) {
|
if(!archiver) {
|
||||||
return cb(Errors.Invalid(`Unknown archive type: ${archType}`));
|
return cb(Errors.Invalid(`Unknown archive type: ${archType}`));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!cb && _.isFunction(workDir)) {
|
||||||
|
cb = workDir;
|
||||||
|
workDir = null;
|
||||||
|
}
|
||||||
|
|
||||||
const fmtObj = {
|
const fmtObj = {
|
||||||
archivePath : archivePath,
|
archivePath : archivePath,
|
||||||
fileList : files.join(' '), // :TODO: probably need same hack as extractTo here!
|
fileList : files.join(' '), // :TODO: probably need same hack as extractTo here!
|
||||||
};
|
};
|
||||||
|
|
||||||
const args = archiver.compress.args.map( arg => stringFormat(arg, fmtObj) );
|
// :TODO: DRY with extractTo()
|
||||||
|
const args = archiver.compress.args.map( arg => {
|
||||||
|
return '{fileList}' === arg ? arg : stringFormat(arg, fmtObj);
|
||||||
|
});
|
||||||
|
|
||||||
|
const fileListPos = args.indexOf('{fileList}');
|
||||||
|
if(fileListPos > -1) {
|
||||||
|
// replace {fileList} with 0:n sep file list arguments
|
||||||
|
args.splice.apply(args, [fileListPos, 1].concat(files));
|
||||||
|
}
|
||||||
|
|
||||||
let proc;
|
let proc;
|
||||||
try {
|
try {
|
||||||
proc = pty.spawn(archiver.compress.cmd, args, this.getPtyOpts());
|
proc = pty.spawn(archiver.compress.cmd, args, this.getPtyOpts(workDir));
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
return cb(Errors.ExternalProcess(
|
return cb(Errors.ExternalProcess(
|
||||||
`Error spawning archiver process "${archiver.compress.cmd}" with args "${args.join(' ')}": ${e.message}`)
|
`Error spawning archiver process "${archiver.compress.cmd}" with args "${args.join(' ')}": ${e.message}`)
|
||||||
|
@ -332,15 +346,15 @@ module.exports = class ArchiveUtil {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getPtyOpts(extractPath) {
|
getPtyOpts(cwd) {
|
||||||
const opts = {
|
const opts = {
|
||||||
name : 'enigma-archiver',
|
name : 'enigma-archiver',
|
||||||
cols : 80,
|
cols : 80,
|
||||||
rows : 24,
|
rows : 24,
|
||||||
env : process.env,
|
env : process.env,
|
||||||
};
|
};
|
||||||
if(extractPath) {
|
if(cwd) {
|
||||||
opts.cwd = extractPath;
|
opts.cwd = cwd;
|
||||||
}
|
}
|
||||||
// :TODO: set cwd to supplied temp path if not sepcific extract
|
// :TODO: set cwd to supplied temp path if not sepcific extract
|
||||||
return opts;
|
return opts;
|
||||||
|
|
|
@ -0,0 +1,55 @@
|
||||||
|
|
||||||
|
|
||||||
|
const CP437UnicodeTable = [
|
||||||
|
'\u0000', '\u0001', '\u0002', '\u0003', '\u0004', '\u0005', '\u0006',
|
||||||
|
'\u0007', '\u0008', '\u0009', '\u000A', '\u000B', '\u000C', '\u000D',
|
||||||
|
'\u000E', '\u000F', '\u0010', '\u0011', '\u0012', '\u0013', '\u0014',
|
||||||
|
'\u0015', '\u0016', '\u0017', '\u0018', '\u0019', '\u001A', '\u001B',
|
||||||
|
'\u001C', '\u001D', '\u001E', '\u001F', '\u0020', '\u0021', '\u0022',
|
||||||
|
'\u0023', '\u0024', '\u0025', '\u0026', '\u0027', '\u0028', '\u0029',
|
||||||
|
'\u002A', '\u002B', '\u002C', '\u002D', '\u002E', '\u002F', '\u0030',
|
||||||
|
'\u0031', '\u0032', '\u0033', '\u0034', '\u0035', '\u0036', '\u0037',
|
||||||
|
'\u0038', '\u0039', '\u003A', '\u003B', '\u003C', '\u003D', '\u003E',
|
||||||
|
'\u003F', '\u0040', '\u0041', '\u0042', '\u0043', '\u0044', '\u0045',
|
||||||
|
'\u0046', '\u0047', '\u0048', '\u0049', '\u004A', '\u004B', '\u004C',
|
||||||
|
'\u004D', '\u004E', '\u004F', '\u0050', '\u0051', '\u0052', '\u0053',
|
||||||
|
'\u0054', '\u0055', '\u0056', '\u0057', '\u0058', '\u0059', '\u005A',
|
||||||
|
'\u005B', '\u005C', '\u005D', '\u005E', '\u005F', '\u0060', '\u0061',
|
||||||
|
'\u0062', '\u0063', '\u0064', '\u0065', '\u0066', '\u0067', '\u0068',
|
||||||
|
'\u0069', '\u006A', '\u006B', '\u006C', '\u006D', '\u006E', '\u006F',
|
||||||
|
'\u0070', '\u0071', '\u0072', '\u0073', '\u0074', '\u0075', '\u0076',
|
||||||
|
'\u0077', '\u0078', '\u0079', '\u007A', '\u007B', '\u007C', '\u007D',
|
||||||
|
'\u007E', '\u007F', '\u00C7', '\u00FC', '\u00E9', '\u00E2', '\u00E4',
|
||||||
|
'\u00E0', '\u00E5', '\u00E7', '\u00EA', '\u00EB', '\u00E8', '\u00EF',
|
||||||
|
'\u00EE', '\u00EC', '\u00C4', '\u00C5', '\u00C9', '\u00E6', '\u00C6',
|
||||||
|
'\u00F4', '\u00F6', '\u00F2', '\u00FB', '\u00F9', '\u00FF', '\u00D6',
|
||||||
|
'\u00DC', '\u00A2', '\u00A3', '\u00A5', '\u20A7', '\u0192', '\u00E1',
|
||||||
|
'\u00ED', '\u00F3', '\u00FA', '\u00F1', '\u00D1', '\u00AA', '\u00BA',
|
||||||
|
'\u00BF', '\u2310', '\u00AC', '\u00BD', '\u00BC', '\u00A1', '\u00AB',
|
||||||
|
'\u00BB', '\u2591', '\u2592', '\u2593', '\u2502', '\u2524', '\u2561',
|
||||||
|
'\u2562', '\u2556', '\u2555', '\u2563', '\u2551', '\u2557', '\u255D',
|
||||||
|
'\u255C', '\u255B', '\u2510', '\u2514', '\u2534', '\u252C', '\u251C',
|
||||||
|
'\u2500', '\u253C', '\u255E', '\u255F', '\u255A', '\u2554', '\u2569',
|
||||||
|
'\u2566', '\u2560', '\u2550', '\u256C', '\u2567', '\u2568', '\u2564',
|
||||||
|
'\u2565', '\u2559', '\u2558', '\u2552', '\u2553', '\u256B', '\u256A',
|
||||||
|
'\u2518', '\u250C', '\u2588', '\u2584', '\u258C', '\u2590', '\u2580',
|
||||||
|
'\u03B1', '\u00DF', '\u0393', '\u03C0', '\u03A3', '\u03C3', '\u00B5',
|
||||||
|
'\u03C4', '\u03A6', '\u0398', '\u03A9', '\u03B4', '\u221E', '\u03C6',
|
||||||
|
'\u03B5', '\u2229', '\u2261', '\u00B1', '\u2265', '\u2264', '\u2320',
|
||||||
|
'\u2321', '\u00F7', '\u2248', '\u00B0', '\u2219', '\u00B7', '\u221A',
|
||||||
|
'\u207F', '\u00B2', '\u25A0', '\u00A0'
|
||||||
|
];
|
||||||
|
|
||||||
|
const NonCP437EncodableRegExp = /[^\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007\u0008\u0009\u000A\u000B\u000C\u000D\u000E\u000F\u0010\u0011\u0012\u0013\u0014\u0015\u0016\u0017\u0018\u0019\u001A\u001B\u001C\u001D\u001E\u001F\u0020\u0021\u0022\u0023\u0024\u0025\u0026\u0027\u0028\u0029\u002A\u002B\u002C\u002D\u002E\u002F\u0030\u0031\u0032\u0033\u0034\u0035\u0036\u0037\u0038\u0039\u003A\u003B\u003C\u003D\u003E\u003F\u0040\u0041\u0042\u0043\u0044\u0045\u0046\u0047\u0048\u0049\u004A\u004B\u004C\u004D\u004E\u004F\u0050\u0051\u0052\u0053\u0054\u0055\u0056\u0057\u0058\u0059\u005A\u005B\u005C\u005D\u005E\u005F\u0060\u0061\u0062\u0063\u0064\u0065\u0066\u0067\u0068\u0069\u006A\u006B\u006C\u006D\u006E\u006F\u0070\u0071\u0072\u0073\u0074\u0075\u0076\u0077\u0078\u0079\u007A\u007B\u007C\u007D\u007E\u007F\u00C7\u00FC\u00E9\u00E2\u00E4\u00E0\u00E5\u00E7\u00EA\u00EB\u00E8\u00EF\u00EE\u00EC\u00C4\u00C5\u00C9\u00E6\u00C6\u00F4\u00F6\u00F2\u00FB\u00F9\u00FF\u00D6\u00DC\u00A2\u00A3\u00A5\u20A7\u0192\u00E1\u00ED\u00F3\u00FA\u00F1\u00D1\u00AA\u00BA\u00BF\u2310\u00AC\u00BD\u00BC\u00A1\u00AB\u00BB\u2591\u2592\u2593\u2502\u2524\u2561\u2562\u2556\u2555\u2563\u2551\u2557\u255D\u255C\u255B\u2510\u2514\u2534\u252C\u251C\u2500\u253C\u255E\u255F\u255A\u2554\u2569\u2566\u2560\u2550\u256C\u2567\u2568\u2564\u2565\u2559\u2558\u2552\u2553\u256B\u256A\u2518\u250C\u2588\u2584\u258C\u2590\u2580\u03B1\u00DF\u0393\u03C0\u03A3\u03C3\u00B5\u03C4\u03A6\u0398\u03A9\u03B4\u221E\u03C6\u03B5\u2229\u2261\u00B1\u2265\u2264\u2320\u2321\u00F7\u2248\u00B0\u2219\u00B7\u221A\u207F\u00B2\u25A0\u00A0]/;
|
||||||
|
const isCP437Encodable = (s) => {
|
||||||
|
if (!s.length) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return !NonCP437EncodableRegExp.test(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
CP437UnicodeTable,
|
||||||
|
isCP437Encodable,
|
||||||
|
}
|
|
@ -0,0 +1,59 @@
|
||||||
|
const { Errors } = require('./enig_error');
|
||||||
|
|
||||||
|
//
|
||||||
|
// Utils for dealing with Microsoft Binary Format (MBF) used
|
||||||
|
// in various BASIC languages, etc.
|
||||||
|
//
|
||||||
|
// - https://en.wikipedia.org/wiki/Microsoft_Binary_Format
|
||||||
|
// - https://stackoverflow.com/questions/2268191/how-to-convert-from-ieee-python-float-to-microsoft-basic-float
|
||||||
|
//
|
||||||
|
|
||||||
|
// Number to 32bit MBF
|
||||||
|
numToMbf32 = (v) => {
|
||||||
|
const mbf = Buffer.alloc(4);
|
||||||
|
|
||||||
|
if (0 === v) {
|
||||||
|
return mbf;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ieee = Buffer.alloc(4);
|
||||||
|
ieee.writeFloatLE(v, 0);
|
||||||
|
|
||||||
|
const sign = ieee[3] & 0x80;
|
||||||
|
let exp = (ieee[3] << 1) | (ieee[2] >> 7);
|
||||||
|
|
||||||
|
if (exp === 0xfe) {
|
||||||
|
throw Errors.Invalid(`${v} cannot be converted to mbf`);
|
||||||
|
}
|
||||||
|
|
||||||
|
exp += 2;
|
||||||
|
|
||||||
|
mbf[3] = exp;
|
||||||
|
mbf[2] = sign | (ieee[2] & 0x7f);
|
||||||
|
mbf[1] = ieee[1];
|
||||||
|
mbf[0] = ieee[0];
|
||||||
|
|
||||||
|
return mbf;
|
||||||
|
}
|
||||||
|
|
||||||
|
mbf32ToNum = (mbf) => {
|
||||||
|
if (0 === mbf[3]) {
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ieee = Buffer.alloc(4);
|
||||||
|
const sign = mbf[2] & 0x80;
|
||||||
|
const exp = mbf[3] - 2;
|
||||||
|
|
||||||
|
ieee[3] = sign | (exp >> 1);
|
||||||
|
ieee[2] = (exp << 7) | (mbf[2] & 0x7f);
|
||||||
|
ieee[1] = mbf[1];
|
||||||
|
ieee[0] = mbf[0];
|
||||||
|
|
||||||
|
return ieee.readFloatLE(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
numToMbf32,
|
||||||
|
mbf32ToNum,
|
||||||
|
}
|
|
@ -11,6 +11,9 @@ const {
|
||||||
sanitizeString,
|
sanitizeString,
|
||||||
getISOTimestampString } = require('./database.js');
|
getISOTimestampString } = require('./database.js');
|
||||||
|
|
||||||
|
const { isCP437Encodable } = require('./cp437util');
|
||||||
|
const { containsNonLatinCodepoints } = require('./string_util');
|
||||||
|
|
||||||
const {
|
const {
|
||||||
isAnsi, isFormattedLine,
|
isAnsi, isFormattedLine,
|
||||||
splitTextAtTerms,
|
splitTextAtTerms,
|
||||||
|
@ -49,7 +52,8 @@ const SYSTEM_META_NAMES = {
|
||||||
const ADDRESS_FLAVOR = {
|
const ADDRESS_FLAVOR = {
|
||||||
Local : 'local', // local / non-remote addressing
|
Local : 'local', // local / non-remote addressing
|
||||||
FTN : 'ftn', // FTN style
|
FTN : 'ftn', // FTN style
|
||||||
Email : 'email',
|
Email : 'email', // From email
|
||||||
|
QWK : 'qwk', // QWK packet
|
||||||
};
|
};
|
||||||
|
|
||||||
const STATE_FLAGS0 = {
|
const STATE_FLAGS0 = {
|
||||||
|
@ -87,6 +91,13 @@ const FTN_PROPERTY_NAMES = {
|
||||||
FtnSeenBy : 'ftn_seen_by', // http://ftsc.org/docs/fts-0004.001
|
FtnSeenBy : 'ftn_seen_by', // http://ftsc.org/docs/fts-0004.001
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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
|
||||||
|
};
|
||||||
|
|
||||||
// :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',
|
||||||
|
@ -137,6 +148,20 @@ module.exports = class Message {
|
||||||
return null !== _.get(this, 'meta.System.remote_from_user', null);
|
return null !== _.get(this, 'meta.System.remote_from_user', null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isCP437Encodable() {
|
||||||
|
return isCP437Encodable(this.toUserName) &&
|
||||||
|
isCP437Encodable(this.fromUserName) &&
|
||||||
|
isCP437Encodable(this.subject) &&
|
||||||
|
isCP437Encodable(this.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
containsNonLatinCodepoints() {
|
||||||
|
return containsNonLatinCodepoints(this.toUserName) ||
|
||||||
|
containsNonLatinCodepoints(this.fromUserName) ||
|
||||||
|
containsNonLatinCodepoints(this.subject) ||
|
||||||
|
containsNonLatinCodepoints(this.message);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
:TODO: finish me
|
:TODO: finish me
|
||||||
static checkUserHasDeleteRights(user, messageIdOrUuid, cb) {
|
static checkUserHasDeleteRights(user, messageIdOrUuid, cb) {
|
||||||
|
@ -183,6 +208,10 @@ module.exports = class Message {
|
||||||
return FTN_PROPERTY_NAMES;
|
return FTN_PROPERTY_NAMES;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static get QWKPropertyNames() {
|
||||||
|
return QWKPropertyNames;
|
||||||
|
}
|
||||||
|
|
||||||
setLocalToUserId(userId) {
|
setLocalToUserId(userId) {
|
||||||
this.meta.System = this.meta.System || {};
|
this.meta.System = this.meta.System || {};
|
||||||
this.meta.System[Message.SystemMetaNames.LocalToUserID] = userId;
|
this.meta.System[Message.SystemMetaNames.LocalToUserID] = userId;
|
||||||
|
|
|
@ -29,6 +29,7 @@ exports.getDefaultMessageAreaTagByConfTag = getDefaultMessageAreaTagByConfTag;
|
||||||
exports.getSuitableMessageConfAndAreaTags = getSuitableMessageConfAndAreaTags;
|
exports.getSuitableMessageConfAndAreaTags = getSuitableMessageConfAndAreaTags;
|
||||||
exports.getMessageConferenceByTag = getMessageConferenceByTag;
|
exports.getMessageConferenceByTag = getMessageConferenceByTag;
|
||||||
exports.getMessageAreaByTag = getMessageAreaByTag;
|
exports.getMessageAreaByTag = getMessageAreaByTag;
|
||||||
|
exports.getMessageConfTagByAreaTag = getMessageConfTagByAreaTag;
|
||||||
exports.changeMessageConference = changeMessageConference;
|
exports.changeMessageConference = changeMessageConference;
|
||||||
exports.changeMessageArea = changeMessageArea;
|
exports.changeMessageArea = changeMessageArea;
|
||||||
exports.hasMessageConfAndAreaRead = hasMessageConfAndAreaRead;
|
exports.hasMessageConfAndAreaRead = hasMessageConfAndAreaRead;
|
||||||
|
|
|
@ -170,6 +170,11 @@ Actions:
|
||||||
|
|
||||||
import-areas PATH Import areas using FidoNet *.NA or AREAS.BBS file
|
import-areas PATH Import areas using FidoNet *.NA or AREAS.BBS file
|
||||||
|
|
||||||
|
qwk-dump PATH Dumps a QWK packet to stdout.
|
||||||
|
qwk-export [AREA_TAGS] PATH Exports one or more configured message area to a QWK
|
||||||
|
packet in the directory specified by PATH. The QWK
|
||||||
|
BBS ID will be obtained by the final component of PATH.
|
||||||
|
|
||||||
import-areas arguments:
|
import-areas arguments:
|
||||||
--conf CONF_TAG Conference tag in which to import areas
|
--conf CONF_TAG Conference tag in which to import areas
|
||||||
--network NETWORK Network name/key to associate FTN areas
|
--network NETWORK Network name/key to associate FTN areas
|
||||||
|
@ -177,6 +182,11 @@ import-areas arguments:
|
||||||
--type TYPE Area import type
|
--type TYPE Area import type
|
||||||
|
|
||||||
Valid types are "bbs" and "na".
|
Valid types are "bbs" and "na".
|
||||||
|
|
||||||
|
qwk-export arguments:
|
||||||
|
--user USER User in which to export for. Defaults to the SysOp.
|
||||||
|
--after TIMESTAMP Export only messages with a timestamp later than
|
||||||
|
TIMESTAMP.
|
||||||
`
|
`
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -10,17 +10,19 @@ const {
|
||||||
initConfigAndDatabases,
|
initConfigAndDatabases,
|
||||||
getAnswers,
|
getAnswers,
|
||||||
writeConfig,
|
writeConfig,
|
||||||
} = require('./oputil_common.js');
|
} = require('./oputil_common.js');
|
||||||
const getHelpFor = require('./oputil_help.js').getHelpFor;
|
|
||||||
const Address = require('../ftn_address.js');
|
const getHelpFor = require('./oputil_help.js').getHelpFor;
|
||||||
const Errors = require('../enig_error.js').Errors;
|
const Address = require('../ftn_address.js');
|
||||||
|
const Errors = require('../enig_error.js').Errors;
|
||||||
|
|
||||||
// deps
|
// deps
|
||||||
const async = require('async');
|
const async = require('async');
|
||||||
const paths = require('path');
|
const paths = require('path');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const hjson = require('hjson');
|
const hjson = require('hjson');
|
||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
|
const moment = require('moment');
|
||||||
|
|
||||||
exports.handleMessageBaseCommand = handleMessageBaseCommand;
|
exports.handleMessageBaseCommand = handleMessageBaseCommand;
|
||||||
|
|
||||||
|
@ -434,6 +436,197 @@ function getImportEntries(importType, importData) {
|
||||||
return importEntries;
|
return importEntries;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function dumpQWKPacket() {
|
||||||
|
const packetPath = argv._[argv._.length - 1];
|
||||||
|
if(argv._.length < 3 || !packetPath || 0 === packetPath.length) {
|
||||||
|
return printUsageAndSetExitCode(getHelpFor('MessageBase'), ExitCodes.ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
async.waterfall(
|
||||||
|
[
|
||||||
|
(callback) => {
|
||||||
|
return initConfigAndDatabases(callback);
|
||||||
|
},
|
||||||
|
(callback) => {
|
||||||
|
const { QWKPacketReader } = require('../qwk_mail_packet');
|
||||||
|
const reader = new QWKPacketReader(packetPath);
|
||||||
|
|
||||||
|
reader.on('error', err => {
|
||||||
|
console.error(`ERROR: ${err.message}`);
|
||||||
|
return callback(err);
|
||||||
|
});
|
||||||
|
|
||||||
|
reader.on('done', () => {
|
||||||
|
return callback(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
reader.on('archive type', archiveType => {
|
||||||
|
console.info(`-> Archive type: ${archiveType}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
reader.on('creator', creator => {
|
||||||
|
console.info(`-> Creator: ${creator}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
reader.on('message', message => {
|
||||||
|
console.info('--- message ---');
|
||||||
|
console.info(`To: ${message.toUserName}`);
|
||||||
|
console.info(`From: ${message.fromUserName}`);
|
||||||
|
console.info(`Subject: ${message.subject}`);
|
||||||
|
console.info(`Message:\r\n${message.message}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
reader.read();
|
||||||
|
}
|
||||||
|
],
|
||||||
|
err => {
|
||||||
|
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function exportQWKPacket() {
|
||||||
|
let packetPath = argv._[argv._.length - 1];
|
||||||
|
if(argv._.length < 3 || !packetPath || 0 === packetPath.length) {
|
||||||
|
return printUsageAndSetExitCode(getHelpFor('MessageBase'), ExitCodes.ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
// oputil mb qwk-export TAGS PATH [--user USER] [--after TIMESTAMP]
|
||||||
|
// [areaTag1,areaTag2,...] PATH --user USER --after TIMESTAMP
|
||||||
|
let bbsID = 'ENIGMA';
|
||||||
|
const filename = paths.basename(packetPath);
|
||||||
|
if (filename) {
|
||||||
|
const ext = paths.extname(filename);
|
||||||
|
bbsID = paths.basename(filename, ext);
|
||||||
|
}
|
||||||
|
|
||||||
|
packetPath = paths.dirname(packetPath);
|
||||||
|
|
||||||
|
const posArgLen = argv._.length;
|
||||||
|
|
||||||
|
let areaTags;
|
||||||
|
if (4 === posArgLen) {
|
||||||
|
areaTags = argv._[posArgLen - 2].split(',');
|
||||||
|
} else {
|
||||||
|
areaTags = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
let newerThanTimestamp = null;
|
||||||
|
if (argv.after) {
|
||||||
|
const ts = moment(argv.after);
|
||||||
|
if (ts.isValid()) {
|
||||||
|
newerThanTimestamp = ts.format();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const userName = argv.user || '-';
|
||||||
|
|
||||||
|
let totalExported = 0;
|
||||||
|
async.waterfall(
|
||||||
|
[
|
||||||
|
(callback) => {
|
||||||
|
return initConfigAndDatabases(callback);
|
||||||
|
},
|
||||||
|
(callback) => {
|
||||||
|
const User = require('../../core/user.js');
|
||||||
|
|
||||||
|
User.getUserIdAndName(userName, (err, userId) => {
|
||||||
|
if (err) {
|
||||||
|
if ('-' === userName) {
|
||||||
|
userId = 1;
|
||||||
|
} else {
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return User.getUser(userId, callback);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
(user, callback) => {
|
||||||
|
// populate area tags with all available to user
|
||||||
|
// if they were not explicitly supplied
|
||||||
|
if (!areaTags.length) {
|
||||||
|
const {
|
||||||
|
getAvailableMessageConferences,
|
||||||
|
getAvailableMessageAreasByConfTag
|
||||||
|
} = require('../../core/message_area');
|
||||||
|
|
||||||
|
const confTags = Object.keys(getAvailableMessageConferences(null, { noClient : true }));
|
||||||
|
confTags.forEach( confTag => {
|
||||||
|
areaTags = areaTags.concat(Object.keys(getAvailableMessageAreasByConfTag(confTag)));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return callback(null, user);
|
||||||
|
},
|
||||||
|
(user, callback) => {
|
||||||
|
const Message = require('../message');
|
||||||
|
|
||||||
|
const filter = {
|
||||||
|
resultType : 'id',
|
||||||
|
areaTag : areaTags,
|
||||||
|
newerThanTimestamp,
|
||||||
|
};
|
||||||
|
|
||||||
|
// public
|
||||||
|
Message.findMessages(filter, (err, publicMessageIds) => {
|
||||||
|
if (err) {
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
delete filter.areaTag;
|
||||||
|
filter.privateTagUserId = user.userId;
|
||||||
|
|
||||||
|
Message.findMessages(filter, (err, privateMessageIds) => {
|
||||||
|
return callback(err, user, Message, privateMessageIds.concat(publicMessageIds));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
(user, Message, messageIds, callback) => {
|
||||||
|
const { QWKPacketWriter } = require('../qwk_mail_packet');
|
||||||
|
const writer = new QWKPacketWriter({
|
||||||
|
bbsID,
|
||||||
|
user,
|
||||||
|
});
|
||||||
|
|
||||||
|
writer.on('ready', () => {
|
||||||
|
async.eachSeries(messageIds, (messageId, nextMessageId) => {
|
||||||
|
const message = new Message();
|
||||||
|
message.load( { messageId }, err => {
|
||||||
|
if (!err) {
|
||||||
|
writer.appendMessage(message);
|
||||||
|
++totalExported;
|
||||||
|
}
|
||||||
|
return nextMessageId(err);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
(err) => {
|
||||||
|
writer.finish(packetPath);
|
||||||
|
if (err) {
|
||||||
|
console.error(`Failed to write one or more messages: ${err.message}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
writer.on('warning', err => {
|
||||||
|
console.warn(`!!! ${err.reason ? err.reason : err.message}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
writer.on('finished', () => {
|
||||||
|
return callback(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
writer.init();
|
||||||
|
}
|
||||||
|
],
|
||||||
|
err => {
|
||||||
|
if(err) {
|
||||||
|
return console.error(err.reason ? err.reason : err.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.info(`-> Exported ${totalExported} messages`);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function handleMessageBaseCommand() {
|
function handleMessageBaseCommand() {
|
||||||
|
|
||||||
function errUsage() {
|
function errUsage() {
|
||||||
|
@ -452,5 +645,7 @@ function handleMessageBaseCommand() {
|
||||||
return({
|
return({
|
||||||
areafix : areaFix,
|
areafix : areaFix,
|
||||||
'import-areas' : importAreas,
|
'import-areas' : importAreas,
|
||||||
|
'qwk-dump' : dumpQWKPacket,
|
||||||
|
'qwk-export' : exportQWKPacket,
|
||||||
}[action] || errUsage)();
|
}[action] || errUsage)();
|
||||||
}
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -13,6 +13,7 @@ exports.pad = pad;
|
||||||
exports.insert = insert;
|
exports.insert = insert;
|
||||||
exports.replaceAt = replaceAt;
|
exports.replaceAt = replaceAt;
|
||||||
exports.isPrintable = isPrintable;
|
exports.isPrintable = isPrintable;
|
||||||
|
exports.containsNonLatinCodepoints = containsNonLatinCodepoints;
|
||||||
exports.stripAllLineFeeds = stripAllLineFeeds;
|
exports.stripAllLineFeeds = stripAllLineFeeds;
|
||||||
exports.debugEscapedString = debugEscapedString;
|
exports.debugEscapedString = debugEscapedString;
|
||||||
exports.stringFromNullTermBuffer = stringFromNullTermBuffer;
|
exports.stringFromNullTermBuffer = stringFromNullTermBuffer;
|
||||||
|
@ -196,6 +197,20 @@ function isPrintable(s) {
|
||||||
return !RE_NON_PRINTABLE.test(s);
|
return !RE_NON_PRINTABLE.test(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const NonLatinCodePointsRegExp = /[^\u0000-\u00ff]/;
|
||||||
|
|
||||||
|
function containsNonLatinCodepoints(s) {
|
||||||
|
if (!s.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (s.charCodeAt(0) > 255) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return NonLatinCodepointsRegEx.test(s);
|
||||||
|
}
|
||||||
|
|
||||||
function stripAllLineFeeds(s) {
|
function stripAllLineFeeds(s) {
|
||||||
return s.replace(/\r?\n|[\r\u2028\u2029]/g, '');
|
return s.replace(/\r?\n|[\r\u2028\u2029]/g, '');
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,6 +33,7 @@
|
||||||
"hashids": "2.1.0",
|
"hashids": "2.1.0",
|
||||||
"hjson": "^3.2.1",
|
"hjson": "^3.2.1",
|
||||||
"iconv-lite": "0.5.0",
|
"iconv-lite": "0.5.0",
|
||||||
|
"ini-config-parser": "^1.0.4",
|
||||||
"inquirer": "^7.0.0",
|
"inquirer": "^7.0.0",
|
||||||
"later": "1.2.0",
|
"later": "1.2.0",
|
||||||
"lodash": "^4.17.15",
|
"lodash": "^4.17.15",
|
||||||
|
|
19
yarn.lock
19
yarn.lock
|
@ -312,6 +312,11 @@ code-point-at@^1.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77"
|
resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77"
|
||||||
integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=
|
integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=
|
||||||
|
|
||||||
|
coffee-script@^1.12.4:
|
||||||
|
version "1.12.7"
|
||||||
|
resolved "https://registry.yarnpkg.com/coffee-script/-/coffee-script-1.12.7.tgz#c05dae0cb79591d05b3070a8433a98c9a89ccc53"
|
||||||
|
integrity sha512-fLeEhqwymYat/MpTPUjSKHVYYl0ec2mOyALEMLmzr5i1isuG+6jfI2j2d5oBO3VIzgUXgBVIcOT9uH1TFxBckw==
|
||||||
|
|
||||||
collection-visit@^1.0.0:
|
collection-visit@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0"
|
resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0"
|
||||||
|
@ -408,6 +413,11 @@ decode-uri-component@^0.2.0:
|
||||||
resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545"
|
resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545"
|
||||||
integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=
|
integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=
|
||||||
|
|
||||||
|
deep-extend@^0.5.1:
|
||||||
|
version "0.5.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.5.1.tgz#b894a9dd90d3023fbf1c55a394fb858eb2066f1f"
|
||||||
|
integrity sha512-N8vBdOa+DF7zkRrDCsaOXoCs/E2fJfx9B9MrKnnSiHNh4ws7eSys6YQE4KvT1cecKmOASYQBhbKjeuDD9lT81w==
|
||||||
|
|
||||||
deep-extend@^0.6.0:
|
deep-extend@^0.6.0:
|
||||||
version "0.6.0"
|
version "0.6.0"
|
||||||
resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac"
|
resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac"
|
||||||
|
@ -883,6 +893,15 @@ inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.3:
|
||||||
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
|
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
|
||||||
integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=
|
integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=
|
||||||
|
|
||||||
|
ini-config-parser@^1.0.4:
|
||||||
|
version "1.0.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/ini-config-parser/-/ini-config-parser-1.0.4.tgz#0abc75cb68c506204712d2b4861400b6adbfda78"
|
||||||
|
integrity sha512-5hLh5Cqai67pTrLQ9q/K/3EtSP2Tzu41AZzwPLSegkkMkc42dGweLgkbiocCBiBBEg2fPhs6pKmdFhwj5Ul3Bg==
|
||||||
|
dependencies:
|
||||||
|
coffee-script "^1.12.4"
|
||||||
|
deep-extend "^0.5.1"
|
||||||
|
rimraf "^2.6.1"
|
||||||
|
|
||||||
ini@~1.3.0:
|
ini@~1.3.0:
|
||||||
version "1.3.5"
|
version "1.3.5"
|
||||||
resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927"
|
resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927"
|
||||||
|
|
Loading…
Reference in New Issue