diff --git a/README.md b/README.md index b1d12540..49906d2f 100644 --- a/README.md +++ b/README.md @@ -24,9 +24,9 @@ ENiGMA½ is a modern BBS software with a nostalgic flair! ## In the Works * More ES6+ usage, and **documentation**! * File areas -* ACS support for more areas +* More ACS support coverage * SysOp dashboard (ye ol' WFC) -* Missing functionality such as searching, pipe code support in message areas, etc. +* Missing functionality such as searching, message area coloring, etc. * String localization * A lot more! Feel free to request features via [the issue tracker](https://github.com/NuSkooler/enigma-bbs/issues) @@ -61,6 +61,9 @@ Please see the [Quickstart](docs/index.md#quickstart) * [M. Griffin](https://github.com/M-griffin), author of [Enthral BBS](https://github.com/M-griffin/Enthral), [Oblivion/2 XRM](https://github.com/M-griffin/Oblivion2-XRM) and [EtherTerm](https://github.com/M-griffin/EtherTerm)! * [Caphood](http://www.reddit.com/user/Caphood), supreme SysOp of [BLACK ƒlag](http://www.bbsnexus.com/directory/listing/blackflag.html) BBS * Luciano Ayres of [Blocktronics](http://blocktronics.org/), creator of the "Mystery Skulls" default ENiGMA½ theme! +* Sudndeath for Xibalba ANSI work! +* Jack Phlash for kick ass ENiGMA½ and Xibalba ASCII (Check out [IMPURE60](http://pc.textmod.es/pack/impure60/)!!) +* Avon of [Agency BBS](http://bbs.geek.nz/) and fsxNet ## License Released under the [BSD 2-clause](https://opensource.org/licenses/BSD-2-Clause) license: diff --git a/core/bbs.js b/core/bbs.js index 56b24efc..552660f3 100644 --- a/core/bbs.js +++ b/core/bbs.js @@ -16,7 +16,7 @@ let async = require('async'); let util = require('util'); let _ = require('lodash'); let assert = require('assert'); -let mkdirp = require('mkdirp'); +let mkdirs = require('fs-extra').mkdirs; // our main entry point exports.bbsMain = bbsMain; @@ -78,7 +78,7 @@ function bbsMain() { ], function complete(err) { if(err) { - logger.log.error(err); + console.error('Error initializing: ' + util.inspect(err)); } } ); @@ -89,7 +89,7 @@ function initialize(cb) { [ function createMissingDirectories(callback) { async.each(Object.keys(conf.config.paths), function entry(pathKey, next) { - mkdirp(conf.config.paths[pathKey], function dirCreated(err) { + mkdirs(conf.config.paths[pathKey], function dirCreated(err) { if(err) { console.error('Could not create path: ' + conf.config.paths[pathKey] + ': ' + err.toString()); } diff --git a/core/ftn_util.js b/core/ftn_util.js index e46ee12c..f1d0860e 100644 --- a/core/ftn_util.js +++ b/core/ftn_util.js @@ -220,8 +220,8 @@ function getQuotePrefix(name) { // http://ftsc.org/docs/fts-0004.001 // function getOrigin(address) { - const origin = _.has(Config.messageNetworks.originName) ? - Config.messageNetworks.originName : + const origin = _.has(Config, 'messageNetworks.originLine') ? + Config.messageNetworks.originLine : Config.general.boardName; const addrStr = new Address(address).toString('5D'); diff --git a/core/scanner_tossers/ftn_bso.js b/core/scanner_tossers/ftn_bso.js index 2582c279..be633d77 100644 --- a/core/scanner_tossers/ftn_bso.js +++ b/core/scanner_tossers/ftn_bso.js @@ -15,13 +15,13 @@ let Message = require('../message.js'); let moment = require('moment'); let _ = require('lodash'); let paths = require('path'); -let mkdirp = require('mkdirp'); let async = require('async'); let fs = require('fs'); let later = require('later'); let temp = require('temp').track(); // track() cleans up temp dir/files for us let assert = require('assert'); let gaze = require('gaze'); +let fse = require('fs-extra'); exports.moduleInfo = { name : 'FTN BSO', @@ -627,7 +627,7 @@ function FTNMessageScanTossModule() { async.waterfall( [ function createOutgoingDir(callback) { - mkdirp(outgoingDir, err => { + fse.mkdirs(outgoingDir, err => { callback(err); }); }, @@ -680,13 +680,13 @@ function FTNMessageScanTossModule() { outgoingDir, `${paths.basename(oldPath, 'pk_')}${ext}`); - fs.rename(oldPath, newPath, nextFile); + fse.move(oldPath, newPath, nextFile); } else { const newPath = paths.join(outgoingDir, paths.basename(oldPath)); - fs.rename(oldPath, newPath, err => { + fse.move(oldPath, newPath, err => { if(err) { Log.warn( - { oldPath : oldPath, newPath : newPath }, + { oldPath : oldPath, newPath : newPath, error : err.toString() }, 'Failed moving temporary bundle file!'); return nextFile(); @@ -734,7 +734,7 @@ function FTNMessageScanTossModule() { } Message.getMessageIdsByMetaValue('FtnKludge', 'MSGID', message.meta.FtnKludge.REPLY, (err, msgIds) => { - if(!err) { + if(msgIds && msgIds.length > 0) { assert(1 === msgIds.length); message.replyToMsgId = msgIds[0]; } @@ -742,7 +742,7 @@ function FTNMessageScanTossModule() { }); }; - this.importNetMailToArea = function(localAreaTag, header, message, cb) { + this.importEchoMailToArea = function(localAreaTag, header, message, cb) { async.series( [ function validateDestinationAddress(callback) { @@ -807,6 +807,12 @@ function FTNMessageScanTossModule() { let packetHeader; const packetOpts = { keepTearAndOrigin : true }; + + let importStats = { + areaSuccess : {}, // areaTag->count + areaFail : {}, // areaTag->count + otherFail : 0, + }; new ftnMailPacket.Packet(packetOpts).read(packetPath, (entryType, entryData, next) => { if('header' === entryType) { @@ -831,15 +837,21 @@ function FTNMessageScanTossModule() { // const localAreaTag = self.getLocalAreaTagByFtnAreaTag(areaTag); if(localAreaTag) { - self.importNetMailToArea(localAreaTag, packetHeader, message, err => { + self.importEchoMailToArea(localAreaTag, packetHeader, message, err => { if(err) { + // bump area fail stats + importStats.areaFail[localAreaTag] = (importStats.areaFail[localAreaTag] || 0) + 1; + if('SQLITE_CONSTRAINT' === err.code) { Log.info( - { subject : message.subject, uuid : message.uuid }, + { area : localAreaTag, subject : message.subject, uuid : message.uuid }, 'Not importing non-unique message'); return next(null); } + } else { + // bump area success + importStats.areaSuccess[localAreaTag] = (importStats.areaSuccess[localAreaTag] || 0) + 1; } next(err); @@ -849,14 +861,25 @@ function FTNMessageScanTossModule() { // No local area configured for this import // // :TODO: Handle the "catch all" case, if configured + Log.warn( { areaTag : areaTag }, 'No local area configured for this packet file!'); + + // bump generic failure + importStats.otherFail += 1; + + return next(null); } } else { // // NetMail // + Log.warn('NetMail import not yet implemented!'); + return next(null); } } }, err => { + const finalStats = Object.assign(importStats, { packetPath : packetPath } ); + Log.info(finalStats, 'Import complete'); + cb(err); }); }; @@ -874,10 +897,13 @@ function FTNMessageScanTossModule() { }, function importPacketFiles(packetFiles, callback) { let rejects = []; - async.each(packetFiles, (packetFile, nextFile) => { - self.importMessagesFromPacketFile(paths.join(importDir, packetFile), '', err => { - // :TODO: check err -- log / track rejects, etc. + async.eachSeries(packetFiles, (packetFile, nextFile) => { + self.importMessagesFromPacketFile(paths.join(importDir, packetFile), '', err => { if(err) { + Log.debug( + { path : paths.join(importDir, packetFile), error : err.toString() }, + 'Failed to import packet file'); + rejects.push(packetFile); } nextFile(); @@ -920,7 +946,15 @@ function FTNMessageScanTossModule() { }, function discoverBundles(callback) { fs.readdir(importDir, (err, files) => { - files = files.filter(f => '.pkt' !== paths.extname(f)); + // :TODO: Need to be explicit about files to attempt an extract, e.g. *.su?, *.mo?, ... + // :TODO: if we do much more of this, probably just use the glob module + //files = files.filter(f => '.pkt' !== paths.extname(f)); + + const bundleRegExp = /\.(su|mo|tu|we|th|fr|sa)[0-9A-Za-z]/; + files = files.filter(f => { + const fext = paths.extname(f); + return bundleRegExp.test(fext); + }); async.map(files, (file, transform) => { const fullPath = paths.join(importDir, file); @@ -1059,6 +1093,15 @@ FTNMessageScanTossModule.prototype.startup = function(cb) { if(_.isObject(this.moduleConfig.schedule)) { const exportSchedule = this.parseScheduleString(this.moduleConfig.schedule.export); if(exportSchedule) { + Log.debug( + { + schedule : this.moduleConfig.schedule.export, + schedOK : -1 === exportSchedule.sched.error, + immediate : exportSchedule.immediate ? true : false, + }, + 'Export schedule loaded' + ); + if(exportSchedule.sched && this.exportingStart()) { this.exportTimer = later.setInterval( () => { @@ -1076,7 +1119,16 @@ FTNMessageScanTossModule.prototype.startup = function(cb) { } const importSchedule = this.parseScheduleString(this.moduleConfig.schedule.import); - if(importSchedule) { + if(importSchedule) { + Log.debug( + { + schedule : this.moduleConfig.schedule.import, + schedOK : -1 === importSchedule.sched.error, + watchFile : _.isString(importSchedule.watchFile) ? importSchedule.watchFile : 'None', + }, + 'Import schedule loaded' + ); + if(importSchedule.sched) { this.importTimer = later.setInterval( () => { tryImportNow('Performing scheduled message import/toss...'); @@ -1150,22 +1202,6 @@ FTNMessageScanTossModule.prototype.performExport = function(cb) { return cb(new Error('Missing or invalid configuration')); } - // - // Select all messages that have a message_id > our last scan ID. - // Additionally exclude messages that have a ftn_attr_flags FtnProperty meta - // as those came via import! - // - /* - const getNewUuidsSql = - `SELECT message_id, message_uuid - FROM message m - WHERE area_tag = ? AND message_id > ? AND - (SELECT COUNT(message_id) - FROM message_meta - WHERE message_id = m.message_id AND meta_category = 'FtnProperty' AND meta_name = 'ftn_attr_flags') = 0 - ORDER BY message_id;`; - */ - // // Select all messages with a |message_id| > |lastScanId|. // Additionally exclude messages with the System state_flags0 which will be present for @@ -1182,7 +1218,7 @@ FTNMessageScanTossModule.prototype.performExport = function(cb) { WHERE message_id = m.message_id AND meta_category = 'System' AND meta_name = 'state_flags0') = 0 ORDER BY message_id;`; - var self = this; + let self = this; async.each(Object.keys(Config.messageNetworks.ftn.areas), (areaTag, nextArea) => { const areaConfig = Config.messageNetworks.ftn.areas[areaTag]; diff --git a/docs/config.md b/docs/config.md index be2cad75..afdcf4e2 100644 --- a/docs/config.md +++ b/docs/config.md @@ -1,12 +1,12 @@ -## Configuration +# Configuration Configuration files in ENiGMA½ are simple UTF-8 encoded [HJSON](http://hjson.org/) files. HJSON is just like JSON but simplified and much more resilient to human error. -### System Configuraiton +## System Configuraiton The main system configuration file, `config.hjson` both overrides defaults and provides additional configuration such as message areas. The default path is `~/.config/enigma-bbs/config.hjson` though you can override this with the `--config` parameter when invoking `main.js`. Values found in core/config.js may be overridden by simply providing the object members you wish replace. **Windows note**: **~** resolves to *C:\Users\YOURLOGINNAME\* on modern installations, e.g. *C:\Users\NuSkooler\\.config\enigma-bbs\config.hjson* -#### Example: System Name +### Example: System Name `core/config.js` provides the default system name as follows: ```javascript general : { @@ -21,8 +21,34 @@ general: { } ``` -#### A Sample Configuration -Below is a **sample** `config.hjson` illustrating various (but not all!) elements that can be configured / tweaked. +### Specific Areas of Interest + +#### Archivers +External archivers can be configured for various tasks such as EchoMail bundle handling. + +TODO: Document further inc. Members & defaults + +**Example**: + +```hjson +archivers: {' + zip: { + // byte signature in HEX of ZIP archives + sig: "504b0304" + // offset of sig + offset: 0 + compressCmd: "7za" + compressArgs: [ "a", "-tzip", "{archivePath}", "{fileList}" ] + decompressCmd: "7za" + decompressArgs: [ "e", "-o{extractPath}", "{archivePath}" ] + } +} +``` + +### 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 @@ -106,5 +132,5 @@ Below is a **sample** `config.hjson` illustrating various (but not all!) element } ``` -### Menus +## Menus TODO: Documentation on menu.hjson, etc. \ No newline at end of file diff --git a/docs/index.md b/docs/index.md index ed848949..6e2e4bb1 100644 --- a/docs/index.md +++ b/docs/index.md @@ -35,7 +35,7 @@ npm install ``` ## Generate a SSH Private Key -To utilize the SSH server, a SSH Private Key will need generated. This step can be skipped if desired by disabling the SSH server in `config.hjson`. +To utilize the SSH server, a SSH Private Key will need generated. This step can be skipped if you do not wish to enable SSH access. ```bash openssl genrsa -des3 -out ./misc/ssh_private_key.pem 2048 ``` @@ -44,30 +44,33 @@ openssl genrsa -des3 -out ./misc/ssh_private_key.pem 2048 The main system configuration is handled via `~/.config/enigma-bbs/config.hjson`. This is a [HJSON](http://hjson.org/) file (compiliant JSON is also OK). See [Configuration](config.md) for more information. ```hjson -general: { - boardName: Super Awesome BBS -} +{ + general: { + boardName: Super Awesome BBS + } -servers: { - ssh: { - privateKeyPass: YOUR_PK_PASS - enabled: true /* set to false to disable the SSH server */ - } -} + servers: { + ssh: { + privateKeyPass: YOUR_PK_PASS + enabled: true /* set to false to disable the SSH server */ + } + } -messageConferences: { - local_general: { - name: Local - desc: Local Discussions - default: true + messageConferences: { + local_general: { + name: Local + desc: Local Discussions + default: true - areas: { - local_music: { - name: Music Discussion - desc: Music, bands, etc. - default: true - } - } + areas: { + local_music: { + name: Music Discussion + desc: Music, bands, etc. + default: true + } + } + } + } } ``` diff --git a/docs/msg_networks.md b/docs/msg_networks.md index 2fe4832a..5660643f 100644 --- a/docs/msg_networks.md +++ b/docs/msg_networks.md @@ -1,6 +1,10 @@ # Message Networks Message networks are configured in `messageNetworks` section of `config.hjson`. Each network type has it's own sub section such as `ftn` for FidoNet Technology Network (FTN) style networks. Message Networks tie directly with [Message Areas](msg_conf_area.md) that are also defined in `config.hjson`. +**Members**: + * `ftn`: Configure FTN networks (described below) + * `originLine` (optional): Overrwrite the default origin line for networks that support it. For example: `originLine: Xibalba - xibalba.l33t.codes:44510` + ## FidoNet Technology Network (FTN) FTN networks are configured under the `messageNetworks::ftn` section of `config.hjson`. diff --git a/mods/abracadabra.js b/mods/abracadabra.js index c5aaafa6..c617763c 100644 --- a/mods/abracadabra.js +++ b/mods/abracadabra.js @@ -1,26 +1,26 @@ /* jslint node: true */ 'use strict'; -var MenuModule = require('../core/menu_module.js').MenuModule; -var DropFile = require('../core/dropfile.js').DropFile; -var door = require('../core/door.js'); -var theme = require('../core/theme.js'); -var ansi = require('../core/ansi_term.js'); +let MenuModule = require('../core/menu_module.js').MenuModule; +let DropFile = require('../core/dropfile.js').DropFile; +let door = require('../core/door.js'); +let theme = require('../core/theme.js'); +let ansi = require('../core/ansi_term.js'); -var async = require('async'); -var assert = require('assert'); -var mkdirp = require('mkdirp'); -var paths = require('path'); -var _ = require('lodash'); -var net = require('net'); +let async = require('async'); +let assert = require('assert'); +let paths = require('path'); +let _ = require('lodash'); +let net = require('net'); +let mkdirs = require('fs-extra').mkdirs; // :TODO: This should really be a system module... needs a little work to allow for such exports.getModule = AbracadabraModule; -var activeDoorNodeInstances = {}; +let activeDoorNodeInstances = {}; -var doorInstances = {}; // name -> { count : , { : } } +let doorInstances = {}; // name -> { count : , { : } } exports.moduleInfo = { name : 'Abracadabra', @@ -66,7 +66,7 @@ exports.moduleInfo = { function AbracadabraModule(options) { MenuModule.call(this, options); - var self = this; + let self = this; this.config = options.menuConfig.config; @@ -128,7 +128,7 @@ function AbracadabraModule(options) { self.dropFile = new DropFile(self.client, self.config.dropFileType); var fullPath = self.dropFile.fullPath; - mkdirp(paths.dirname(fullPath), function dirCreated(err) { + mkdirs(paths.dirname(fullPath), function dirCreated(err) { if(err) { callback(err); } else { @@ -153,7 +153,7 @@ function AbracadabraModule(options) { this.runDoor = function() { - var exeInfo = { + const exeInfo = { cmd : self.config.cmd, args : self.config.args, io : self.config.io || 'stdio', @@ -163,9 +163,9 @@ function AbracadabraModule(options) { //inhSocket : self.client.output._handle.fd, }; - var doorInstance = new door.Door(self.client, exeInfo); + const doorInstance = new door.Door(self.client, exeInfo); - doorInstance.on('finished', function doorFinished() { + doorInstance.on('finished', () => { self.prevMenu(); }); diff --git a/package.json b/package.json index 0d1389ff..51d5b29b 100644 --- a/package.json +++ b/package.json @@ -23,14 +23,14 @@ "later": "1.2.0", "lodash": "^3.10.1", "minimist": "1.2.x", - "mkdirp": "0.5.x", "moment": "^2.11.0", "node-uuid": "^1.4.7", "ptyw.js": "^0.3.7", "sqlite3": "^3.1.1", "ssh2": "^0.4.13", "string-format": "davidchambers/string-format#mini-language", - "temp": "^0.8.3" + "temp": "^0.8.3", + "fs-extra" : "0.26.x" }, "engines": { "node": ">=0.12.2"