diff --git a/core/abracadabra.js b/core/abracadabra.js deleted file mode 100644 index 4d63c11a..00000000 --- a/core/abracadabra.js +++ /dev/null @@ -1,18 +0,0 @@ -/* jslint node: true */ -'use strict'; - -var MenuModule = require('../core/menu_module.js').MenuModule; -var DropFile = require('./door.js').DropFile; - -exports.moduleInfo = { - name : 'Abracadabra', - desc : 'External BBS Door Module', - author : 'NuSkooler', -}; - -function AbracadabraModule(options) { - MenuModule.call(this, options); - -} - -require('util').inherits(AbracadabraModule, MenuModule); \ No newline at end of file diff --git a/core/bbs.js b/core/bbs.js index 30cf8877..625c7e87 100644 --- a/core/bbs.js +++ b/core/bbs.js @@ -112,6 +112,38 @@ function initialize(cb) { logger.log.info({ themeCount : themeCount }, 'Themes initialized'); callback(err); }); + }, + function loadSysOpInformation(callback) { + // + // If user 1 has been created, we have a SysOp. Cache some information + // into Config. + // + var user = require('./user.js'); // must late load + + user.getUserName(1, function unLoaded(err, sysOpUsername) { + if(err) { + callback(null); // non-fatal here + } else { + // + // Load some select properties to cache + // + var propLoadOpts = { + userId : 1, + names : [ 'real_name', 'sex', 'email_address' ], + } + user.loadProperties(propLoadOpts, function propsLoaded(err, props) { + if(!err) { + conf.config.general.sysOp = { + username : sysOpUsername, + properties : props, + }; + + logger.log.info( { sysOp : conf.config.general.sysOp }, 'System Operator information cached'); + } + callback(null); // any error is again, non-fatal here + }); + } + }); } ], function onComplete(err) { diff --git a/core/config.js b/core/config.js index 3859ce1b..e67d62d7 100644 --- a/core/config.js +++ b/core/config.js @@ -70,7 +70,7 @@ function getDefaultPath() { function getDefaultConfig() { return { general : { - boardName : 'Another Fine ENiGMA½ BBS', + boardName : 'Another Fine ENiGMA½ BBS', }, firstMenu : 'connected', @@ -115,6 +115,7 @@ function getDefaultConfig() { themes : paths.join(__dirname, './../mods/themes/'), logs : paths.join(__dirname, './../logs/'), // :TODO: set up based on system, e.g. /var/logs/enigmabbs or such db : paths.join(__dirname, './../db/'), + dropFiles : paths.join(__dirname, './../dropfiles/'), // + "/node/ }, servers : { diff --git a/core/door.js b/core/door.js index 56f32026..87890ee7 100644 --- a/core/door.js +++ b/core/door.js @@ -1,89 +1,73 @@ /* jslint node: true */ 'use strict'; -var Config = require('./config.js').config; +var spawn = require('child_process').spawn; +var events = require('events'); -var _ = require('lodash'); +var pty = require('pty'); -exports.DropFile = DropFile; +exports.Door = Door; -// -// Resources -// * http://goldfndr.home.mindspring.com/dropfile/ -// * https://en.wikipedia.org/wiki/Talk%3ADropfile -// * http://thoughtproject.com/libraries/bbs/Sysop/Doors/DropFiles/index.htm +function Door(client, exeInfo) { + events.EventEmitter.call(this); -// http://lord.lordlegacy.com/dosemu/ + this.client = client; + this.exeInfo = exeInfo; -function DropFile(options) { + // exeInfo.cmd + // exeInfo.args[] + // exeInfo.env{} + // exeInfo.cwd + // exeInfo.encoding - var self = this; - this.client = options.client; +}; - this.fileType = options.fileType || 'DORINFO'; - this.exe = options.exe || 'dosemu'; - this.exeParams = options.exeParams || []; +require('util').inherits(Door, events.EventEmitter); - Object.defineProperty(this, 'fileName', { - get : function() { - return { - DOOR : 'DOOR.SYS', // GAP BBS, many others - DOOR32 : 'DOOR32.SYS', // EleBBS / Mystic, Syncronet, Maximus, Telegard, AdeptXBBS, ... - CALLINFO : 'CALLINFO.BBS', // Citadel? - DORINFO : self.getDoorInfoXFileName(), // RBBS, RemoteAccess, QBBS, ... - CHAIN : 'CHAIN.TXT', // WWIV - CURRUSER : 'CURRUSER.BBS', // RyBBS - SFDOORS : 'SFDOORS.DAT', // Spitfire - PCBOARD : 'PCBOARD.SYS', // PCBoard - TRIBBS : 'TRIBBS.SYS', // TriBBS - USERINFO : 'USERINFO.DAT', // Wildcat! 3.0+ - JUMPER : 'JUMPER.DAT', // 2AM BBS - SXDOOR : // System/X, dESiRE - 'SXDOOR.' + _.pad(self.client.node.toString(), 3, '0'), - INFO : 'INFO.BBS', // Phoenix BBS - }[self.fileType.toUpperCase()]; - } + +Door.prototype.run = function() { + + var self = this; + + var doorProc = spawn(this.exeInfo.cmd, this.exeInfo.args); + +/* + doorProc.stderr.pipe(self.client.term.output); + doorProc.stdout.pipe(self.client.term.output); + doorProc.stdout.on('data', function stdOutData(data) { + console.log('got data') + self.client.term.write(data); }); - this.getDoorInfoXFileName = function() { - var x; - var node = self.client.node; - if(10 === node) { - x = 0; - } else if(node < 10) { - x = node; - } else { - x = String.fromCharCode('a'.charCodeAt(0) + (node - 11)); - } - return 'DORINFO' + x + '.DEF'; - }; + doorProc.stderr.on('data', function stdErrData(data) { + console.log('got error data') + self.client.term.write(data); + }); - this.getDoorInfoXContents = function() { - // :TODO: fix sysop first name, last name (load @ system load if avail) - // :TODO: fix time remaining + doorProc.on('close', function closed(exitCode) { + console.log('closed') + self.emit('closed', exitCode); // just fwd on + }); +*/ + var door = pty.spawn(this.exeInfo.cmd, this.exeInfo.args, { + cols : self.client.term.termWidth, + rows : self.client.term.termHeight, + }); - // - // Resources: - // * http://goldfndr.home.mindspring.com/dropfile/dorinfo.htm - // - return [ - Config.general.boardName, // "The name of the system." - 'SysOp First', // "The sysop's name up to the first space." - 'SysOp Last', // "The sysop's name following the first space." - 'COM1', // "The serial port the modem is connected to, or 0 if logged in on console." - '57600', // "The current port (DTE) rate." - '0', // "The number "0"" - /[^\s]*/.exec(self.client.user.username)[0], // "The current user's name, up to the first space." - /[^\s]*/.exec(self.client.user.username)[0], // "The current user's name, following the first space." - self.client.user.properties.location || '', // "Where the user lives, or a blank line if unknown." - '1', // "The number "0" if TTY, or "1" if ANSI." - self.client.user.isSysOp() ? '100' : '30', // "The number 5 for problem users, 30 for regular users, 80 for Aides, and 100 for Sysops." - '546', // "The number of minutes left in the current user's account, limited to 546 to keep from overflowing other software." - '-1' // "The number "-1" if using an external serial driver or "0" if using internal serial routines." - ].join('\r\n') + '\r\n'; - }; + //door.pipe(self.client.term.output); + self.client.term.output.pipe(door); -} + // :TODO: do this with pluggable pipe/filter classes -DropFile.fileTypes = [ 'DORINFO' ]; + door.setEncoding('cp437'); + door.on('data', function doorData(data) { + self.client.term.write(data); + //console.log(data); + }); +//*/ + + door.on('close', function closed() { + console.log('closed...') + }); +}; \ No newline at end of file diff --git a/core/dropfile.js b/core/dropfile.js new file mode 100644 index 00000000..55fbc38d --- /dev/null +++ b/core/dropfile.js @@ -0,0 +1,112 @@ +/* jslint node: true */ +'use strict'; + +var Config = require('./config.js').config; + +var fs = require('fs'); +var paths = require('path'); +var _ = require('lodash'); +var async = require('async'); + +exports.DropFile = DropFile; + +// +// Resources +// * http://goldfndr.home.mindspring.com/dropfile/ +// * https://en.wikipedia.org/wiki/Talk%3ADropfile +// * http://thoughtproject.com/libraries/bbs/Sysop/Doors/DropFiles/index.htm +// * http://thebbs.org/bbsfaq/ch06.02.htm + +// http://lord.lordlegacy.com/dosemu/ + +function DropFile(client, fileType) { + + var self = this; + this.client = client; + this.fileType = (fileType || 'DORINFO').toUpperCase(); + + Object.defineProperty(this, 'fullPath', { + get : function() { + return paths.join(Config.paths.dropFiles, ('node' + self.client.node), self.fileName); + } + }); + + Object.defineProperty(this, 'fileName', { + get : function() { + return { + DOOR : 'DOOR.SYS', // GAP BBS, many others + DOOR32 : 'DOOR32.SYS', // EleBBS / Mystic, Syncronet, Maximus, Telegard, AdeptXBBS, ... + CALLINFO : 'CALLINFO.BBS', // Citadel? + DORINFO : self.getDoorInfoFileName(), // RBBS, RemoteAccess, QBBS, ... + CHAIN : 'CHAIN.TXT', // WWIV + CURRUSER : 'CURRUSER.BBS', // RyBBS + SFDOORS : 'SFDOORS.DAT', // Spitfire + PCBOARD : 'PCBOARD.SYS', // PCBoard + TRIBBS : 'TRIBBS.SYS', // TriBBS + USERINFO : 'USERINFO.DAT', // Wildcat! 3.0+ + JUMPER : 'JUMPER.DAT', // 2AM BBS + SXDOOR : // System/X, dESiRE + 'SXDOOR.' + _.pad(self.client.node.toString(), 3, '0'), + INFO : 'INFO.BBS', // Phoenix BBS + }[self.fileType]; + } + }); + + Object.defineProperty(this, 'dropFileContents', { + get : function() { + return { + DORINFO : self.getDoorInfoBuffer(), + }[self.fileType]; + } + }); + + this.getDoorInfoFileName = function() { + var x; + var node = self.client.node; + if(10 === node) { + x = 0; + } else if(node < 10) { + x = node; + } else { + x = String.fromCharCode('a'.charCodeAt(0) + (node - 11)); + } + return 'DORINFO' + x + '.DEF'; + }; + + this.getDoorInfoBuffer = function() { + // :TODO: fix time remaining + + // + // Resources: + // * http://goldfndr.home.mindspring.com/dropfile/dorinfo.htm + // + // Note that usernames are just used for first/last names here + // + var opUn = /[^\s]*/.exec(Config.general.sysOp.username)[0]; + var un = /[^\s]*/.exec(self.client.user.username)[0]; + return new Buffer([ + Config.general.boardName, // "The name of the system." + opUn, // "The sysop's name up to the first space." + opUn, // "The sysop's name following the first space." + 'COM1', // "The serial port the modem is connected to, or 0 if logged in on console." + '57600', // "The current port (DTE) rate." + '0', // "The number "0"" + un, // "The current user's name, up to the first space." + un, // "The current user's name, following the first space." + self.client.user.properties.location || '', // "Where the user lives, or a blank line if unknown." + '1', // "The number "0" if TTY, or "1" if ANSI." + self.client.user.isSysOp() ? '100' : '30', // "The number 5 for problem users, 30 for regular users, 80 for Aides, and 100 for Sysops." + '546', // "The number of minutes left in the current user's account, limited to 546 to keep from overflowing other software." + '-1' // "The number "-1" if using an external serial driver or "0" if using internal serial routines." + ].join('\r\n') + '\r\n', 'cp437'); + }; + +} + +DropFile.fileTypes = [ 'DORINFO' ]; + +DropFile.prototype.createFile = function(cb) { + fs.writeFile(this.fullPath, this.dropFileContents, function written(err) { + cb(err); + }); +} diff --git a/core/menu_module.js b/core/menu_module.js index ef08bafb..c09d549d 100644 --- a/core/menu_module.js +++ b/core/menu_module.js @@ -29,6 +29,7 @@ function MenuModule(options) { this.initSequence = function() { var mciData = { }; + // :TODO: This could be .series() currently async.waterfall( [ function beforeDisplayArt(callback) { diff --git a/core/user.js b/core/user.js index ce943e18..0d3a0b06 100644 --- a/core/user.js +++ b/core/user.js @@ -13,6 +13,8 @@ var moment = require('moment'); exports.User = User; exports.getUserIdAndName = getUserIdAndName; +exports.getUserName = getUserName; +exports.loadProperties = loadProperties; function User() { var self = this; @@ -67,6 +69,10 @@ User.AccountStatus = { active : 1, }; +User.prototype.load = function(userId, cb) { + +}; + User.prototype.authenticate = function(username, password, cb) { var self = this; @@ -361,6 +367,25 @@ function getUserIdAndName(username, cb) { ); } +function getUserName(userId, cb) { + userDb.get( + 'SELECT user_name ' + + 'FROM user ' + + 'WHERE id=?;', [ userId ], + function got(err, row) { + if(err) { + cb(err); + } else { + if(row) { + cb(null, row.user_name); + } else { + cb(new Error('No matching user ID')); + } + } + } + ); +} + /////////////////////////////////////////////////////////////////////////////// // Internal utility methods /////////////////////////////////////////////////////////////////////////////// diff --git a/mods/abracadabra.js b/mods/abracadabra.js new file mode 100644 index 00000000..8a08d26e --- /dev/null +++ b/mods/abracadabra.js @@ -0,0 +1,109 @@ +/* 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 async = require('async'); +var assert = require('assert'); +var mkdirp = require('mkdirp'); +var paths = require('path'); + +// :TODO: This should really be a system module... needs a little work to allow for such + +exports.getModule = AbracadabraModule; + +exports.moduleInfo = { + name : 'Abracadabra', + desc : 'External BBS Door Module', + author : 'NuSkooler', +}; + +function AbracadabraModule(options) { + MenuModule.call(this, options); + + var self = this; + this.config = options.menuConfig.config || { + dropFileType : 'DORINFO', + }; + + this.config.args = this.config.args || []; + + /* + { + "config" : { + "name" : "LORD", + "cmd" : "...", + "args" : [ ... ], + "dropFileType" : "dorinfo", + "maxNodes" : 32, default=unlimited + "tooManyArt" : "..." (optional); default = "Too many active" message + ... + "dropFilePath" : "/.../LORD/", || Config.paths.dropFiles + } + } + */ + + this.initSequence = function() { + async.series( + [ + function validateNodeCount(callback) { + // :TODO: Check that node count for this door has not been reached + callback(null); + }, + function generateDropfile(callback) { + self.dropFile = new DropFile(self.client, self.config.dropFileType); + var fullPath = self.dropFile.fullPath; + + mkdirp(paths.dirname(fullPath), function dirCreated(err) { + if(err) { + callback(err); + } else { + self.dropFile.createFile(function created(err) { + callback(err); + }); + } + }); + } + ], + function complete(err) { + self.finishedLoading(); + } + ); + }; + + this.runDOSEmuDoor = function() { + + }; +} + +require('util').inherits(AbracadabraModule, MenuModule); + +AbracadabraModule.prototype.enter = function(client) { + AbracadabraModule.super_.prototype.enter.call(this, client); + +}; + +AbracadabraModule.prototype.leave = function() { + Abracadabra.super_.prototype.leave.call(this); + +}; + +AbracadabraModule.prototype.finishedLoading = function() { + var self = this; + + var exeInfo = { + cmd : this.config.cmd, + args : this.config.args, + }; + + // :TODO: this system should probably be generic + for(var i = 0; i < exeInfo.args.length; ++i) { + exeInfo.args[i] = exeInfo.args[i].replace(/\{dropfile\}/g, self.dropFile.fileName); + exeInfo.args[i] = exeInfo.args[i].replace(/\{node\}/g, self.client.node.toString()); + } + + var doorInstance = new door.Door(this.client, exeInfo); + doorInstance.run(); +}; \ No newline at end of file diff --git a/mods/fse.js b/mods/fse.js index 3d86f60b..a802786c 100644 --- a/mods/fse.js +++ b/mods/fse.js @@ -24,7 +24,7 @@ function FullScreenEditorModule(options) { MenuModule.call(this, options); var self = this; - this.menuConfig = options.menuConfig; + this.menuConfig = options.menuConfig; // :TODO: MenuModule does this... // // Editor Type ('editorType'): @@ -208,7 +208,7 @@ function FullScreenEditorModule(options) { assert(_.isString(art.body)); async.eachSeries( [ 'header', 'body' ], function dispArt(n, next) { - theme.displayThemedAsset1( + theme.displayThemedAsset( art[n], self.client, { font : self.menuConfig.font }, diff --git a/mods/menu.json b/mods/menu.json index 884015cf..1488576b 100644 --- a/mods/menu.json +++ b/mods/menu.json @@ -235,6 +235,10 @@ "value" : { "1" : "g" }, "action" : "@menu:logoff" }, + { + "value" : { "1" : "d" }, + "action" : "@menu:doorPimpWars" + }, { "value" : 1, "action" : "@menu:mainMenu" @@ -245,6 +249,14 @@ } } }, + "doorPimpWars" : { + "module" : "abracadabra", + "config" : { + "name" : "PimpWars", + "cmd" : "/usr/bin/dosemu", + "args" : [ "-quiet", "-f", "/home/nuskooler/DOS/X/LORD/dosemu.conf", "X:\\PW\\START.BAT {dropfile} {node}" ]//, "X:\\PW\\PIMPWARS.EXE", "X:\\DROP\\{dropfile}", "{node}" ] + } + }, //////////////////////////////////////////////////////////////////////// // Mods //////////////////////////////////////////////////////////////////////// diff --git a/package.json b/package.json index 5d5f361a..ad3ea3f5 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,8 @@ "gapbuffer" : "0.0.2", "node-uuid" : "1.4.x", "moment" : "2.10.x", - "gaze" : "0.5.x" + "gaze" : "0.5.x", + "mkdirp" : "0.5.x" }, "engine" : "node >= 0.12.2" } \ No newline at end of file