Merge branch '0.0.9-alpha' of github.com:NuSkooler/enigma-bbs into user-interruptions

This commit is contained in:
Bryan Ashby 2018-11-24 20:26:19 -07:00
commit c0de3d7048
8 changed files with 437 additions and 467 deletions

View file

@ -216,35 +216,36 @@ function initialize(cb) {
}, },
function loadSysOpInformation(callback) { function loadSysOpInformation(callback) {
// //
// Copy over some +op information from the user DB -> system propertys. // Copy over some +op information from the user DB -> system properties.
// * Makes this accessible for MCI codes, easy non-blocking access, etc. // * Makes this accessible for MCI codes, easy non-blocking access, etc.
// * We do this every time as the op is free to change this information just // * We do this every time as the op is free to change this information just
// like any other user // like any other user
// //
const User = require('./user.js'); const User = require('./user.js');
const propLoadOpts = {
names : [
UserProps.RealName, UserProps.Sex, UserProps.EmailAddress,
UserProps.Location, UserProps.Affiliations,
],
};
async.waterfall( async.waterfall(
[ [
function getOpUserName(next) { function getOpUserName(next) {
return User.getUserName(1, next); return User.getUserName(1, next);
}, },
function getOpProps(opUserName, next) { function getOpProps(opUserName, next) {
const propLoadOpts = {
names : [
UserProps.RealName, UserProps.Sex, UserProps.EmailAddress,
UserProps.Location, UserProps.Affiliations,
],
};
User.loadProperties(User.RootUserID, propLoadOpts, (err, opProps) => { User.loadProperties(User.RootUserID, propLoadOpts, (err, opProps) => {
return next(err, opUserName, opProps, propLoadOpts); return next(err, opUserName, opProps);
}); });
} },
], ],
(err, opUserName, opProps, propLoadOpts) => { (err, opUserName, opProps) => {
const StatLog = require('./stat_log.js'); const StatLog = require('./stat_log.js');
if(err) { if(err) {
propLoadOpts.concat('username').forEach(v => { propLoadOpts.names.concat('username').forEach(v => {
StatLog.setNonPeristentSystemStat(`sysop_${v}`, 'N/A'); StatLog.setNonPeristentSystemStat(`sysop_${v}`, 'N/A');
}); });
} else { } else {

View file

@ -7,6 +7,11 @@ const db = require('../../core/database.js');
const _ = require('lodash'); const _ = require('lodash');
const async = require('async'); const async = require('async');
const inq = require('inquirer');
const fs = require('fs');
const hjson = require('hjson');
const packageJson = require('../../package.json');
exports.printUsageAndSetExitCode = printUsageAndSetExitCode; exports.printUsageAndSetExitCode = printUsageAndSetExitCode;
exports.getDefaultConfigPath = getDefaultConfigPath; exports.getDefaultConfigPath = getDefaultConfigPath;
@ -14,6 +19,17 @@ exports.getConfigPath = getConfigPath;
exports.initConfigAndDatabases = initConfigAndDatabases; exports.initConfigAndDatabases = initConfigAndDatabases;
exports.getAreaAndStorage = getAreaAndStorage; exports.getAreaAndStorage = getAreaAndStorage;
exports.looksLikePattern = looksLikePattern; exports.looksLikePattern = looksLikePattern;
exports.getAnswers = getAnswers;
exports.writeConfig = writeConfig;
const HJSONStringifyCommonOpts = exports.HJSONStringifyCommonOpts = {
emitRootBraces : true,
bracesSameLine : true,
space : 4,
keepWsc : true,
quotes : 'min',
eol : '\n',
};
const exitCodes = exports.ExitCodes = { const exitCodes = exports.ExitCodes = {
SUCCESS : 0, SUCCESS : 0,
@ -100,4 +116,23 @@ function looksLikePattern(tag) {
} }
return /[*?[\]!()+|^]/.test(tag); return /[*?[\]!()+|^]/.test(tag);
}
function getAnswers(questions, cb) {
inq.prompt(questions).then( answers => {
return cb(answers);
});
}
function writeConfig(config, path) {
config = hjson.stringify(config, HJSONStringifyCommonOpts)
.replace(/%ENIG_VERSION%/g, packageJson.version)
.replace(/%HJSON_VERSION%/g, hjson.version);
try {
fs.writeFileSync(path, config, 'utf8');
return true;
} catch(e) {
return false;
}
} }

View file

@ -9,10 +9,11 @@ const {
getConfigPath, getConfigPath,
argv, argv,
ExitCodes, ExitCodes,
initConfigAndDatabases getAnswers,
writeConfig,
HJSONStringifyCommonOpts,
} = require('./oputil_common.js'); } = require('./oputil_common.js');
const getHelpFor = require('./oputil_help.js').getHelpFor; const getHelpFor = require('./oputil_help.js').getHelpFor;
const Errors = require('../../core/enig_error.js').Errors;
// deps // deps
const async = require('async'); const async = require('async');
@ -24,17 +25,8 @@ const paths = require('path');
const _ = require('lodash'); const _ = require('lodash');
const sanatizeFilename = require('sanitize-filename'); const sanatizeFilename = require('sanitize-filename');
const packageJson = require('../../package.json');
exports.handleConfigCommand = handleConfigCommand; exports.handleConfigCommand = handleConfigCommand;
function getAnswers(questions, cb) {
inq.prompt(questions).then( answers => {
return cb(answers);
});
}
const ConfigIncludeKeys = [ const ConfigIncludeKeys = [
'theme', 'theme',
'users.preAuthIdleLogoutSeconds', 'users.idleLogoutSeconds', 'users.preAuthIdleLogoutSeconds', 'users.idleLogoutSeconds',
@ -46,15 +38,6 @@ const ConfigIncludeKeys = [
'logging.rotatingFile', 'logging.rotatingFile',
]; ];
const HJSONStringifyComonOpts = {
emitRootBraces : true,
bracesSameLine : true,
space : 4,
keepWsc : true,
quotes : 'min',
eol : '\n',
};
const QUESTIONS = { const QUESTIONS = {
Intro : [ Intro : [
{ {
@ -231,34 +214,21 @@ function askNewConfigQuestions(cb) {
); );
} }
function writeConfig(config, path) {
config = hjson.stringify(config, HJSONStringifyComonOpts)
.replace(/%ENIG_VERSION%/g, packageJson.version)
.replace(/%HJSON_VERSION%/g, hjson.version)
;
try {
fs.writeFileSync(path, config, 'utf8');
return true;
} catch(e) {
return false;
}
}
const copyFileSyncSilent = (to, from, flags) => { const copyFileSyncSilent = (to, from, flags) => {
try { try {
fs.copyFileSync(to, from, flags); fs.copyFileSync(to, from, flags);
} catch(e) {} } catch(e) {
/* absorb! */
}
}; };
function buildNewConfig() { function buildNewConfig() {
askNewConfigQuestions( (err, configPath, config) => { askNewConfigQuestions( (err, configPath, config) => {
if(err) { if(err) { return;
return;
} }
const bn = sanatizeFilename(config.general.boardName) const bn = sanatizeFilename(config.general.boardName)
.replace(/[^a-z0-9_\-]/ig, '_') .replace(/[^a-z0-9_-]/ig, '_')
.replace(/_+/g, '_') .replace(/_+/g, '_')
.toLowerCase(); .toLowerCase();
const menuFile = `${bn}-menu.hjson`; const menuFile = `${bn}-menu.hjson`;
@ -273,7 +243,7 @@ function buildNewConfig() {
paths.join(__dirname, '../../misc/prompt_template.in.hjson'), paths.join(__dirname, '../../misc/prompt_template.in.hjson'),
paths.join(__dirname, '../../config/', promptFile), paths.join(__dirname, '../../config/', promptFile),
fs.constants.COPYFILE_EXCL fs.constants.COPYFILE_EXCL
) );
config.general.menuFile = menuFile; config.general.menuFile = menuFile;
config.general.promptFile = promptFile; config.general.promptFile = promptFile;
@ -286,294 +256,10 @@ function buildNewConfig() {
}); });
} }
function validateUplinks(uplinks) {
const ftnAddress = require('../../core/ftn_address.js');
const valid = uplinks.every(ul => {
const addr = ftnAddress.fromString(ul);
return addr;
});
return valid;
}
function getMsgAreaImportType(path) {
if(argv.type) {
return argv.type.toLowerCase();
}
const ext = paths.extname(path).toLowerCase().substr(1);
return ext; // .bbs|.na|...
}
function importAreas() {
const importPath = argv._[argv._.length - 1];
if(argv._.length < 3 || !importPath || 0 === importPath.length) {
return printUsageAndSetExitCode(getHelpFor('Config'), ExitCodes.ERROR);
}
const importType = getMsgAreaImportType(importPath);
if('na' !== importType && 'bbs' !== importType) {
return console.error(`"${importType}" is not a recognized import file type`);
}
// optional data - we'll prompt if for anything not found
let confTag = argv.conf;
let networkName = argv.network;
let uplinks = argv.uplinks;
if(uplinks) {
uplinks = uplinks.split(/[\s,]+/);
}
let importEntries;
async.waterfall(
[
function readImportFile(callback) {
fs.readFile(importPath, 'utf8', (err, importData) => {
if(err) {
return callback(err);
}
importEntries = getImportEntries(importType, importData);
if(0 === importEntries.length) {
return callback(Errors.Invalid('Invalid or empty import file'));
}
// We should have enough to validate uplinks
if('bbs' === importType) {
for(let i = 0; i < importEntries.length; ++i) {
if(!validateUplinks(importEntries[i].uplinks)) {
return callback(Errors.Invalid('Invalid uplink(s)'));
}
}
} else {
if(!validateUplinks(uplinks)) {
return callback(Errors.Invalid('Invalid uplink(s)'));
}
}
return callback(null);
});
},
function init(callback) {
return initConfigAndDatabases(callback);
},
function validateAndCollectInput(callback) {
const msgArea = require('../../core/message_area.js');
const sysConfig = require('../../core/config.js').get();
let msgConfs = msgArea.getSortedAvailMessageConferences(null, { noClient : true } );
if(!msgConfs) {
return callback(Errors.DoesNotExist('No conferences exist in your configuration'));
}
msgConfs = msgConfs.map(mc => {
return {
name : mc.conf.name,
value : mc.confTag,
};
});
if(confTag && !msgConfs.find(mc => {
return confTag === mc.value;
}))
{
return callback(Errors.DoesNotExist(`Conference "${confTag}" does not exist`));
}
let existingNetworkNames = [];
if(_.has(sysConfig, 'messageNetworks.ftn.networks')) {
existingNetworkNames = Object.keys(sysConfig.messageNetworks.ftn.networks);
}
if(0 === existingNetworkNames.length) {
return callback(Errors.DoesNotExist('No FTN style networks exist in your configuration'));
}
if(networkName && !existingNetworkNames.find(net => networkName === net)) {
return callback(Errors.DoesNotExist(`FTN style Network "${networkName}" does not exist`));
}
getAnswers([
{
name : 'confTag',
message : 'Message conference:',
type : 'list',
choices : msgConfs,
pageSize : 10,
when : !confTag,
},
{
name : 'networkName',
message : 'Network name:',
type : 'list',
choices : existingNetworkNames,
when : !networkName,
},
{
name : 'uplinks',
message : 'Uplink(s) (comma separated):',
type : 'input',
validate : (input) => {
const inputUplinks = input.split(/[\s,]+/);
return validateUplinks(inputUplinks) ? true : 'Invalid uplink(s)';
},
when : !uplinks && 'bbs' !== importType,
}
],
answers => {
confTag = confTag || answers.confTag;
networkName = networkName || answers.networkName;
uplinks = uplinks || answers.uplinks;
importEntries.forEach(ie => {
ie.areaTag = ie.ftnTag.toLowerCase();
});
return callback(null);
});
},
function confirmWithUser(callback) {
const sysConfig = require('../../core/config.js').get();
console.info(`Importing the following for "${confTag}" - (${sysConfig.messageConferences[confTag].name} - ${sysConfig.messageConferences[confTag].desc})`);
importEntries.forEach(ie => {
console.info(` ${ie.ftnTag} - ${ie.name}`);
});
console.info('');
console.info('Importing will NOT create required FTN network configurations.');
console.info('If you have not yet done this, you will need to complete additional steps after importing.');
console.info('See docs/msg_networks.md for details.');
console.info('');
getAnswers([
{
name : 'proceed',
message : 'Proceed?',
type : 'confirm',
}
],
answers => {
return callback(answers.proceed ? null : Errors.General('User canceled'));
});
},
function loadConfigHjson(callback) {
const configPath = getConfigPath();
fs.readFile(configPath, 'utf8', (err, confData) => {
if(err) {
return callback(err);
}
let config;
try {
config = hjson.parse(confData, { keepWsc : true } );
} catch(e) {
return callback(e);
}
return callback(null, config);
});
},
function performImport(config, callback) {
const confAreas = { messageConferences : {} };
confAreas.messageConferences[confTag] = { areas : {} };
const msgNetworks = { messageNetworks : { ftn : { areas : {} } } };
importEntries.forEach(ie => {
const specificUplinks = ie.uplinks || uplinks; // AREAS.BBS has specific uplinks per area
confAreas.messageConferences[confTag].areas[ie.areaTag] = {
name : ie.name,
desc : ie.name,
};
msgNetworks.messageNetworks.ftn.areas[ie.areaTag] = {
network : networkName,
tag : ie.ftnTag,
uplinks : specificUplinks
};
});
const newConfig = _.defaultsDeep(config, confAreas, msgNetworks);
const configPath = getConfigPath();
if(!writeConfig(newConfig, configPath)) {
return callback(Errors.UnexpectedState('Failed writing configuration'));
}
return callback(null);
}
],
err => {
if(err) {
console.error(err.reason ? err.reason : err.message);
} else {
const addFieldUpd = 'bbs' === importType ? '"name" and "desc"' : '"desc"';
console.info('Configuration generated.');
console.info(`You may wish to validate changes made to ${getConfigPath()}`);
console.info(`as well as update ${addFieldUpd} fields, sorting, etc.`);
console.info('');
}
}
);
}
function getImportEntries(importType, importData) {
let importEntries = [];
if('na' === importType) {
//
// parse out
// TAG DESC
//
const re = /^([^\s]+)\s+([^\r\n]+)/gm;
let m;
while( (m = re.exec(importData) )) {
importEntries.push({
ftnTag : m[1],
name : m[2],
});
}
} else if ('bbs' === importType) {
//
// Various formats for AREAS.BBS seem to exist. We want to support as much as possible.
//
// SBBS http://www.synchro.net/docs/sbbsecho.html#AREAS.BBS
// CODE TAG UPLINKS
//
// VADV https://www.vadvbbs.com/products/vadv/support/docs/docs_vfido.php#AREAS.BBS
// TAG UPLINKS
//
// Misc
// PATH|OTHER TAG UPLINKS
//
// Assume the second item is TAG and 1:n UPLINKS (space and/or comma sep) after (at the end)
//
const re = /^[^\s]+\s+([^\s]+)\s+([^\n]+)$/gm;
let m;
while ( (m = re.exec(importData) )) {
const tag = m[1];
importEntries.push({
ftnTag : tag,
name : `Area: ${tag}`,
uplinks : m[2].split(/[\s,]+/),
});
}
}
return importEntries;
}
function catCurrentConfig() { function catCurrentConfig() {
try { try {
const config = hjson.rt.parse(fs.readFileSync(getConfigPath(), 'utf8')); const config = hjson.rt.parse(fs.readFileSync(getConfigPath(), 'utf8'));
const hjsonOpts = Object.assign({}, HJSONStringifyComonOpts, { const hjsonOpts = Object.assign({}, HJSONStringifyCommonOpts, {
colors : false === argv.colors ? false : true, colors : false === argv.colors ? false : true,
keepWsc : false === argv.comments ? false : true, keepWsc : false === argv.comments ? false : true,
}); });
@ -596,9 +282,8 @@ function handleConfigCommand() {
const action = argv._[1]; const action = argv._[1];
switch(action) { switch(action) {
case 'new' : return buildNewConfig(); case 'new' : return buildNewConfig();
case 'import-areas' : return importAreas(); case 'cat' : return catCurrentConfig();
case 'cat' : return catCurrentConfig();
default : return printUsageAndSetExitCode(getHelpFor('Config'), ExitCodes.ERROR); default : return printUsageAndSetExitCode(getHelpFor('Config'), ExitCodes.ERROR);
} }

View file

@ -39,15 +39,8 @@ actions:
actions: actions:
new generate a new/initial configuration new generate a new/initial configuration
import-areas PATH import areas using fidonet *.NA or AREAS.BBS file from PATH
cat cat current configuration to stdout cat cat current configuration to stdout
import-areas args:
--conf CONF_TAG specify conference tag in which to import areas
--network NETWORK specify network name/key to associate FTN areas
--uplinks UL1,UL2,... specify one or more comma separated uplinks
--type TYPE specifies area import type. valid options are "bbs" and "na"
cat args: cat args:
--no-color disable color --no-color disable color
--no-comments strip any comments --no-comments strip any comments
@ -99,12 +92,19 @@ general information:
FILE_ID a file identifier. see file.sqlite3 FILE_ID a file identifier. see file.sqlite3
`, `,
MessageBase : MessageBase :
`usage: oputil.js mb <action> [<args>] `usage: oputil.js mb <action> [<args>]
actions: actions:
areafix CMD1 CMD2 ... ADDR sends an AreaFix NetMail to ADDR with the supplied command(s) areafix CMD1 CMD2 ... ADDR sends an AreaFix NetMail to ADDR with the supplied command(s)
one or more commands may be supplied. commands that are multi one or more commands may be supplied. commands that are multi
part such as "%COMPRESS ZIP" should be quoted. part such as "%COMPRESS ZIP" should be quoted.
import-areas PATH import areas using fidonet *.NA or AREAS.BBS file from PATH
import-areas args:
--conf CONF_TAG conference tag in which to import areas
--network NETWORK network name/key to associate FTN areas
--uplinks UL1,UL2,... one or more comma separated uplinks
--type TYPE area import type. valid options are "bbs" and "na"
` `
}; };

View file

@ -2,16 +2,25 @@
/* eslint-disable no-console */ /* eslint-disable no-console */
'use strict'; 'use strict';
const printUsageAndSetExitCode = require('./oputil_common.js').printUsageAndSetExitCode; const {
const ExitCodes = require('./oputil_common.js').ExitCodes; printUsageAndSetExitCode,
const argv = require('./oputil_common.js').argv; getConfigPath,
const initConfigAndDatabases = require('./oputil_common.js').initConfigAndDatabases; ExitCodes,
argv,
initConfigAndDatabases,
getAnswers,
writeConfig,
} = require('./oputil_common.js');
const getHelpFor = require('./oputil_help.js').getHelpFor; const getHelpFor = require('./oputil_help.js').getHelpFor;
const Address = require('../ftn_address.js'); const Address = require('../ftn_address.js');
const Errors = require('../enig_error.js').Errors; const Errors = require('../enig_error.js').Errors;
// deps // deps
const async = require('async'); const async = require('async');
const paths = require('path');
const fs = require('fs');
const hjson = require('hjson');
const _ = require('lodash');
exports.handleMessageBaseCommand = handleMessageBaseCommand; exports.handleMessageBaseCommand = handleMessageBaseCommand;
@ -121,6 +130,310 @@ function areaFix() {
); );
} }
function validateUplinks(uplinks) {
const ftnAddress = require('../../core/ftn_address.js');
const valid = uplinks.every(ul => {
const addr = ftnAddress.fromString(ul);
return addr;
});
return valid;
}
function getMsgAreaImportType(path) {
if(argv.type) {
return argv.type.toLowerCase();
}
return paths.extname(path).substr(1).toLowerCase(); // bbs|na|...
}
function importAreas() {
const importPath = argv._[argv._.length - 1];
if(argv._.length < 3 || !importPath || 0 === importPath.length) {
return printUsageAndSetExitCode(getHelpFor('Config'), ExitCodes.ERROR);
}
const importType = getMsgAreaImportType(importPath);
if('na' !== importType && 'bbs' !== importType) {
return console.error(`"${importType}" is not a recognized import file type`);
}
// optional data - we'll prompt if for anything not found
let confTag = argv.conf;
let networkName = argv.network;
let uplinks = argv.uplinks;
if(uplinks) {
uplinks = uplinks.split(/[\s,]+/);
}
let importEntries;
async.waterfall(
[
function readImportFile(callback) {
fs.readFile(importPath, 'utf8', (err, importData) => {
if(err) {
return callback(err);
}
importEntries = getImportEntries(importType, importData);
if(0 === importEntries.length) {
return callback(Errors.Invalid('Invalid or empty import file'));
}
// We should have enough to validate uplinks
if('bbs' === importType) {
for(let i = 0; i < importEntries.length; ++i) {
if(!validateUplinks(importEntries[i].uplinks)) {
return callback(Errors.Invalid('Invalid uplink(s)'));
}
}
} else {
if(!validateUplinks(uplinks || [])) {
return callback(Errors.Invalid('Invalid uplink(s)'));
}
}
return callback(null);
});
},
function init(callback) {
return initConfigAndDatabases(callback);
},
function validateAndCollectInput(callback) {
const msgArea = require('../../core/message_area.js');
const sysConfig = require('../../core/config.js').get();
let msgConfs = msgArea.getSortedAvailMessageConferences(null, { noClient : true } );
if(!msgConfs) {
return callback(Errors.DoesNotExist('No conferences exist in your configuration'));
}
msgConfs = msgConfs.map(mc => {
return {
name : mc.conf.name,
value : mc.confTag,
};
});
if(confTag && !msgConfs.find(mc => {
return confTag === mc.value;
}))
{
return callback(Errors.DoesNotExist(`Conference "${confTag}" does not exist`));
}
const existingNetworkNames = Object.keys(_.get(sysConfig, 'messageNetworks.ftn.networks', {}));
if(networkName && !existingNetworkNames.find(net => networkName === net)) {
return callback(Errors.DoesNotExist(`FTN style Network "${networkName}" does not exist`));
}
// can't use --uplinks without a network
if(!networkName && 0 === existingNetworkNames.length && uplinks) {
return callback(Errors.Invalid('Cannot use --uplinks without an FTN network to import to'));
}
getAnswers([
{
name : 'confTag',
message : 'Message conference:',
type : 'list',
choices : msgConfs,
pageSize : 10,
when : !confTag,
},
{
name : 'networkName',
message : 'FTN network name:',
type : 'list',
choices : [ '-None-' ].concat(existingNetworkNames),
pageSize : 10,
when : !networkName && existingNetworkNames.length > 0,
filter : (choice) => {
return '-None-' === choice ? undefined : choice;
}
},
],
answers => {
confTag = confTag || answers.confTag;
networkName = networkName || answers.networkName;
uplinks = uplinks || answers.uplinks;
importEntries.forEach(ie => {
ie.areaTag = ie.ftnTag.toLowerCase();
});
return callback(null);
});
},
function collectUplinks(callback) {
if(!networkName || uplinks || 'bbs' === importType) {
return callback(null);
}
getAnswers([
{
name : 'uplinks',
message : 'Uplink(s) (comma separated):',
type : 'input',
validate : (input) => {
const inputUplinks = input.split(/[\s,]+/);
return validateUplinks(inputUplinks) ? true : 'Invalid uplink(s)';
},
}
],
answers => {
uplinks = answers.uplinks;
return callback(null);
});
},
function confirmWithUser(callback) {
const sysConfig = require('../../core/config.js').get();
console.info(`Importing the following for "${confTag}"`);
console.info(`(${sysConfig.messageConferences[confTag].name} - ${sysConfig.messageConferences[confTag].desc})`);
console.info('');
importEntries.forEach(ie => {
console.info(` ${ie.ftnTag} - ${ie.name}`);
});
if(networkName) {
console.info('');
console.info(`For FTN network: ${networkName}`);
console.info(`Uplinks: ${uplinks}`);
console.info('');
console.info('Importing will NOT create required FTN network configurations.');
console.info('If you have not yet done this, you will need to complete additional steps after importing.');
console.info('See Message Networks docs for details.');
console.info('');
}
getAnswers([
{
name : 'proceed',
message : 'Proceed?',
type : 'confirm',
}
],
answers => {
return callback(answers.proceed ? null : Errors.General('User canceled'));
});
},
function loadConfigHjson(callback) {
const configPath = getConfigPath();
fs.readFile(configPath, 'utf8', (err, confData) => {
if(err) {
return callback(err);
}
let config;
try {
config = hjson.parse(confData, { keepWsc : true } );
} catch(e) {
return callback(e);
}
return callback(null, config);
});
},
function performImport(config, callback) {
const confAreas = { messageConferences : {} };
confAreas.messageConferences[confTag] = { areas : {} };
const msgNetworks = { messageNetworks : { ftn : { areas : {} } } };
importEntries.forEach(ie => {
const specificUplinks = ie.uplinks || uplinks; // AREAS.BBS has specific uplinks per area
confAreas.messageConferences[confTag].areas[ie.areaTag] = {
name : ie.name,
desc : ie.name,
};
if(networkName) {
msgNetworks.messageNetworks.ftn.areas[ie.areaTag] = {
network : networkName,
tag : ie.ftnTag,
uplinks : specificUplinks
};
}
});
const newConfig = _.defaultsDeep(config, confAreas, msgNetworks);
const configPath = getConfigPath();
if(!writeConfig(newConfig, configPath)) {
return callback(Errors.UnexpectedState('Failed writing configuration'));
}
return callback(null);
}
],
err => {
if(err) {
console.error(err.reason ? err.reason : err.message);
} else {
const addFieldUpd = 'bbs' === importType ? '"name" and "desc"' : '"desc"';
console.info('Configuration generated.');
console.info(`You may wish to validate changes made to ${getConfigPath()}`);
console.info(`as well as update ${addFieldUpd} fields, sorting, etc.`);
console.info('');
}
}
);
}
function getImportEntries(importType, importData) {
let importEntries = [];
if('na' === importType) {
//
// parse out
// TAG DESC
//
const re = /^([^\s]+)\s+([^\r\n]+)/gm;
let m;
while( (m = re.exec(importData) )) {
importEntries.push({
ftnTag : m[1].trim(),
name : m[2].trim(),
});
}
} else if ('bbs' === importType) {
//
// Various formats for AREAS.BBS seem to exist. We want to support as much as possible.
//
// SBBS http://www.synchro.net/docs/sbbsecho.html#AREAS.BBS
// CODE TAG UPLINKS
//
// VADV https://www.vadvbbs.com/products/vadv/support/docs/docs_vfido.php#AREAS.BBS
// TAG UPLINKS
//
// Misc
// PATH|OTHER TAG UPLINKS
//
// Assume the second item is TAG and 1:n UPLINKS (space and/or comma sep) after (at the end)
//
const re = /^[^\s]+\s+([^\s]+)\s+([^\n]+)$/gm;
let m;
while ( (m = re.exec(importData) )) {
const tag = m[1].trim();
importEntries.push({
ftnTag : tag,
name : `Area: ${tag}`,
uplinks : m[2].trim().split(/[\s,]+/),
});
}
}
return importEntries;
}
function handleMessageBaseCommand() { function handleMessageBaseCommand() {
function errUsage() { function errUsage() {
@ -137,6 +450,7 @@ function handleMessageBaseCommand() {
const action = argv._[1]; const action = argv._[1];
return({ return({
areafix : areaFix, areafix : areaFix,
'import-areas' : importAreas,
}[action] || errUsage)(); }[action] || errUsage)();
} }

View file

@ -70,23 +70,18 @@ The `config` command allows sysops to perform various system configuration and m
usage: optutil.js config <action> [<args>] usage: optutil.js config <action> [<args>]
actions: actions:
new generate a new/initial configuration new generate a new/initial configuration
import-areas PATH import areas using fidonet *.NA or AREAS.BBS file from PATH cat cat current configuration to stdout
import-areas args: cat args:
--conf CONF_TAG specify conference tag in which to import areas --no-color disable color
--network NETWORK specify network name/key to associate FTN areas --no-comments strip any comments
--uplinks UL1,UL2,... specify one or more comma separated uplinks
--type TYPE specifies area import type. valid options are "bbs" and "na"
``` ```
| Action | Description | Examples | | Action | Description | Examples |
|-----------|-------------------|---------------------------------------| |-----------|-------------------|---------------------------------------|
| `new` | Generates a new/initial configuration | `./oputil.js config new` (follow the prompts) | | `new` | Generates a new/initial configuration | `./oputil.js config new` (follow the prompts) |
| `import-areas` | Imports areas using a FidoNet style *.NA or AREAS.BBS formatted file | `./oputil.js config import-areas /some/path/l33tnet.na` | | `cat` | Pretty prints current `config.hjson` configuration to stdout. | `./oputil.js config cat` |
When using the `import-areas` action, you will be prompted for any missing additional arguments described in "import-areas args".
## File Base Management ## File Base Management
The `fb` command provides a powerful file base management interface. The `fb` command provides a powerful file base management interface.
@ -189,3 +184,28 @@ file_crc32: fc6655d
file_md5: 3455f74bbbf9539e69bd38f45e039a4e file_md5: 3455f74bbbf9539e69bd38f45e039a4e
file_sha1: 558fab3b49a8ac302486e023a3c2a86bd4e4b948 file_sha1: 558fab3b49a8ac302486e023a3c2a86bd4e4b948
``` ```
## Message Base Management
The `mb` command provides various Message Base related tools:
```
usage: oputil.js mb <action> [<args>]
actions:
areafix CMD1 CMD2 ... ADDR sends an AreaFix NetMail to ADDR with the supplied command(s)
one or more commands may be supplied. commands that are multi
part such as "%COMPRESS ZIP" should be quoted.
import-areas PATH import areas using fidonet *.NA or AREAS.BBS file from PATH
import-areas args:
--conf CONF_TAG conference tag in which to import areas
--network NETWORK network name/key to associate FTN areas
--uplinks UL1,UL2,... one or more comma separated uplinks
--type TYPE area import type. valid options are "bbs" and "na"
```
| Action | Description | Examples |
|-----------|-------------------|---------------------------------------|
| `import-areas` | Imports areas using a FidoNet style *.NA or AREAS.BBS formatted file. Optionally maps areas to FTN networks. | `./oputil.js config import-areas /some/path/l33tnet.na` |
When using the `import-areas` action, you will be prompted for any missing additional arguments described in "import-areas args".

View file

@ -44,90 +44,5 @@ Below is a list of various configuration sections. There are many more, but this
* [File Base](/docs/filebase/index.md) * [File Base](/docs/filebase/index.md)
* [File Transfer Protocols](file-transfer-protocols.md): Oldschool file transfer protocols such as X/Y/Z-Modem! * [File Transfer Protocols](file-transfer-protocols.md): Oldschool file transfer protocols such as X/Y/Z-Modem!
* [Message Areas](/docs/messageareas/configuring-a-message-area.md), [Networks](/docs/messageareas/message-networks.md), [NetMail](/docs/messageareas/netmail.md), etc. * [Message Areas](/docs/messageareas/configuring-a-message-area.md), [Networks](/docs/messageareas/message-networks.md), [NetMail](/docs/messageareas/netmail.md), etc.
* ...and a **lot** more! Explore the docs! If you can't find something, please contact us!
### A Sample Configuration
Below is a **sample** `config.hjson` illustrating various (but certainly not all!) elements that can be configured / tweaked.
**This is for illustration purposes! Do not cut & paste this configuration!**
```hjson
{
general: {
boardName: A Sample BBS
menuFile: "your_bbs.hjson" // copy of menu.hjson file (and adapt to your needs)
}
theme: {
default: "super-fancy-theme" // default-assigned theme (for new users)
}
messageConferences: {
local_general: {
name: Local
desc: Local Discussions
default: true
areas: {
local_enigma_dev: {
name: ENiGMA 1/2 Development
desc: Discussion related to development and features of ENiGMA 1/2!
default: true
}
}
}
agoranet: {
name: Agoranet
desc: This network is for blatant exploitation of the greatest BBS scene art group ever.. ACiD.
areas: {
agoranet_bbs: {
name: BBS Discussion
desc: Discussion related to BBSs
}
}
}
}
messageNetworks: {
ftn: {
areas: {
agoranet_bbs: { /* hey kids, this matches above! */
// oh oh oh, and this one pairs up with a network below
network: agoranet
tag: AGN_BBS
uplinks: "46:1/100"
}
}
networks: {
agoranet: {
localAddress: "46:3/102"
}
}
}
}
scannerTossers: {
ftn_bso: {
schedule: {
import: every 1 hours or @watch:/home/enigma/bink/watchfile.txt
export: every 1 hours or @immediate
}
defaultZone: 46
defaultNetwork: agoranet
nodes: {
"46:*": {
archiveType: ZIP
encoding: utf8
}
}
}
}
}
```

View file

@ -2,29 +2,28 @@
layout: page layout: page
title: Configuring a Message Area title: Configuring a Message Area
--- ---
## Message Conferences
**Message Conferences** and **Areas** allow for grouping of message base topics. **Message Conferences** and **Areas** allow for grouping of message base topics.
## Message Conferences ## Conferences
Message Conferences are the top level container for 1:n Message Areas via the `messageConferences` section Message Conferences are the top level container for *1:n* Message *Areas* via the `messageConferences` block in `config.hjson`. A common setup may include a local conference and one or more conferences each dedicated to a particular message network such as fsxNet, ArakNet, etc.
in `config.hjson`. Common message conferences may include a local conference and one or more conferences
each dedicated to a particular message network such as FsxNet, AgoraNet, etc.
Each conference is represented by a entry under `messageConferences`. **The areas key is the conferences tag**. Each conference is represented by a entry under `messageConferences`. Each entries top level key is it's *conference tag*.
| Config Item | Required | Description | | Config Item | Required | Description |
|-------------|----------|---------------------------------------------------------------------------------| |-------------|----------|---------------------------------------------------------------------------------|
| `name` | :+1: | Friendly conference name | | `name` | :+1: | Friendly conference name |
| `desc` | :+1: | Friendly conference description | | `desc` | :+1: | Friendly conference description. |
| `sort` | :-1: | If supplied, provides a key used for sorting | | `sort` | :-1: | Set to a number to override the default alpha-numeric sort order based on the `name` field. |
| `default` | :-1: | Specify `true` to make this the default conference (e.g. assigned to new users) | | `default` | :-1: | Specify `true` to make this the default conference (e.g. assigned to new users) |
| `areas` | :+1: | Container of 1:n areas described below | | `areas` | :+1: | Container of 1:n areas described below |
### Example ### Example
```hjson ```hjson
{ {
messageConferences: { messageConferences: {
local: { local: { // conference tag
name: Local name: Local
desc: Local discussion desc: Local discussion
sort: 1 sort: 1
@ -35,16 +34,14 @@ Each conference is represented by a entry under `messageConferences`. **The area
``` ```
## Message Areas ## Message Areas
Message Areas are topic specific containers for messages that live within a particular conference. # Message Areas are topic specific containers for messages that live within a particular conference. The top level key for an area sets it's *area tag*. For example, "General Discussion" may live under a Local conference while an fsxNet conference may contain "BBS Discussion".
**The area's key is its area tag**. For example, "General Discussion" may live under a Local conference
while an AgoraNet conference may contain "BBS Discussion".
| Config Item | Required | Description | | Config Item | Required | Description |
|-------------|----------|---------------------------------------------------------------------------------| |-------------|----------|---------------------------------------------------------------------------------|
| `name` | :+1: | Friendly area name | | `name` | :+1: | Friendly area name. |
| `desc` | :+1: | Friendly area discription | | `desc` | :+1: | Friendly area description. |
| `sort` | :-1: | If supplied, provides a key used for sorting | | `sort` | :-1: | Set to a number to override the default alpha-numeric sort order based on the `name` field. |
| `default` | :-1: | Specify `true` to make this the default area (e.g. assigned to new users) | | `default` | :-1: | Specify `true` to make this the default area (e.g. assigned to new users) |
### Example ### Example
@ -62,4 +59,7 @@ messageConferences: {
} }
} }
} }
``` ```
## Importing
FidoNet style `.na` files as well as legacy `AREAS.BBS` files in common formats can be imported using `oputil.js mb import-areas`. See [The oputil CLI](/docs/admin/oputil.md) for more information and usage.