Merge branch 'master' of github.com:NuSkooler/enigma-bbs
This commit is contained in:
commit
4b8369a41f
25
core/bbs.js
25
core/bbs.js
|
@ -11,6 +11,7 @@ const logger = require('./logger.js');
|
||||||
const database = require('./database.js');
|
const database = require('./database.js');
|
||||||
const clientConns = require('./client_connections.js');
|
const clientConns = require('./client_connections.js');
|
||||||
const resolvePath = require('./misc_util.js').resolvePath;
|
const resolvePath = require('./misc_util.js').resolvePath;
|
||||||
|
const events = require('./events.js');
|
||||||
|
|
||||||
// deps
|
// deps
|
||||||
const async = require('async');
|
const async = require('async');
|
||||||
|
@ -27,7 +28,7 @@ exports.main = main;
|
||||||
const initServices = {};
|
const initServices = {};
|
||||||
|
|
||||||
const ENIGMA_COPYRIGHT = 'ENiGMA½ Copyright (c) 2014-2017 Bryan Ashby';
|
const ENIGMA_COPYRIGHT = 'ENiGMA½ Copyright (c) 2014-2017 Bryan Ashby';
|
||||||
const HELP =
|
const HELP =
|
||||||
`${ENIGMA_COPYRIGHT}
|
`${ENIGMA_COPYRIGHT}
|
||||||
usage: main.js <args>
|
usage: main.js <args>
|
||||||
|
|
||||||
|
@ -60,7 +61,7 @@ function main() {
|
||||||
conf.init(resolvePath(configPath), function configInit(err) {
|
conf.init(resolvePath(configPath), function configInit(err) {
|
||||||
|
|
||||||
//
|
//
|
||||||
// If the user supplied a path and we can't read/parse it
|
// If the user supplied a path and we can't read/parse it
|
||||||
// then it's a fatal error
|
// then it's a fatal error
|
||||||
//
|
//
|
||||||
if(err) {
|
if(err) {
|
||||||
|
@ -84,15 +85,17 @@ function main() {
|
||||||
}
|
}
|
||||||
return callback(err);
|
return callback(err);
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
function complete(err) {
|
function complete(err) {
|
||||||
|
events.registerModules();
|
||||||
|
|
||||||
// note this is escaped:
|
// note this is escaped:
|
||||||
fs.readFile(paths.join(__dirname, '../misc/startup_banner.asc'), 'utf8', (err, banner) => {
|
fs.readFile(paths.join(__dirname, '../misc/startup_banner.asc'), 'utf8', (err, banner) => {
|
||||||
console.info(ENIGMA_COPYRIGHT);
|
console.info(ENIGMA_COPYRIGHT);
|
||||||
if(!err) {
|
if(!err) {
|
||||||
console.info(banner);
|
console.info(banner);
|
||||||
}
|
}
|
||||||
console.info('System started!');
|
console.info('System started!');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -140,7 +143,7 @@ function shutdownSystem() {
|
||||||
},
|
},
|
||||||
function stopMsgNetwork(callback) {
|
function stopMsgNetwork(callback) {
|
||||||
require('./msg_network.js').shutdown(callback);
|
require('./msg_network.js').shutdown(callback);
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
() => {
|
() => {
|
||||||
console.info('Goodbye!');
|
console.info('Goodbye!');
|
||||||
|
@ -173,9 +176,9 @@ function initialize(cb) {
|
||||||
process.on('SIGINT', shutdownSystem);
|
process.on('SIGINT', shutdownSystem);
|
||||||
|
|
||||||
require('later').date.localTime(); // use local times for later.js/scheduling
|
require('later').date.localTime(); // use local times for later.js/scheduling
|
||||||
|
|
||||||
return callback(null);
|
return callback(null);
|
||||||
},
|
},
|
||||||
function initDatabases(callback) {
|
function initDatabases(callback) {
|
||||||
return database.initializeDatabases(callback);
|
return database.initializeDatabases(callback);
|
||||||
},
|
},
|
||||||
|
@ -195,7 +198,7 @@ function initialize(cb) {
|
||||||
// * 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');
|
||||||
|
|
||||||
async.waterfall(
|
async.waterfall(
|
||||||
|
@ -223,7 +226,7 @@ function initialize(cb) {
|
||||||
opProps.username = opUserName;
|
opProps.username = opUserName;
|
||||||
|
|
||||||
_.each(opProps, (v, k) => {
|
_.each(opProps, (v, k) => {
|
||||||
StatLog.setNonPeristentSystemStat(`sysop_${k}`, v);
|
StatLog.setNonPeristentSystemStat(`sysop_${k}`, v);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -235,7 +238,7 @@ function initialize(cb) {
|
||||||
return require('./predefined_mci.js').init(callback);
|
return require('./predefined_mci.js').init(callback);
|
||||||
},
|
},
|
||||||
function readyMessageNetworkSupport(callback) {
|
function readyMessageNetworkSupport(callback) {
|
||||||
return require('./msg_network.js').startup(callback);
|
return require('./msg_network.js').startup(callback);
|
||||||
},
|
},
|
||||||
function listenConnections(callback) {
|
function listenConnections(callback) {
|
||||||
return require('./listening_server.js').startup(callback);
|
return require('./listening_server.js').startup(callback);
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
// ENiGMA½
|
// ENiGMA½
|
||||||
const logger = require('./logger.js');
|
const logger = require('./logger.js');
|
||||||
|
const events = require('./events.js');
|
||||||
|
|
||||||
// deps
|
// deps
|
||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
|
@ -22,7 +23,7 @@ function getActiveConnections() { return clientConnections; }
|
||||||
function getActiveNodeList(authUsersOnly) {
|
function getActiveNodeList(authUsersOnly) {
|
||||||
|
|
||||||
if(!_.isBoolean(authUsersOnly)) {
|
if(!_.isBoolean(authUsersOnly)) {
|
||||||
authUsersOnly = true;
|
authUsersOnly = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const now = moment();
|
const now = moment();
|
||||||
|
@ -30,7 +31,7 @@ function getActiveNodeList(authUsersOnly) {
|
||||||
const activeConnections = getActiveConnections().filter(ac => {
|
const activeConnections = getActiveConnections().filter(ac => {
|
||||||
return ((authUsersOnly && ac.user.isAuthenticated()) || !authUsersOnly);
|
return ((authUsersOnly && ac.user.isAuthenticated()) || !authUsersOnly);
|
||||||
});
|
});
|
||||||
|
|
||||||
return _.map(activeConnections, ac => {
|
return _.map(activeConnections, ac => {
|
||||||
const entry = {
|
const entry = {
|
||||||
node : ac.node,
|
node : ac.node,
|
||||||
|
@ -41,7 +42,7 @@ function getActiveNodeList(authUsersOnly) {
|
||||||
|
|
||||||
//
|
//
|
||||||
// There may be a connection, but not a logged in user as of yet
|
// There may be a connection, but not a logged in user as of yet
|
||||||
//
|
//
|
||||||
if(ac.user.isAuthenticated()) {
|
if(ac.user.isAuthenticated()) {
|
||||||
entry.userName = ac.user.username;
|
entry.userName = ac.user.username;
|
||||||
entry.realName = ac.user.properties.real_name;
|
entry.realName = ac.user.properties.real_name;
|
||||||
|
@ -49,7 +50,7 @@ function getActiveNodeList(authUsersOnly) {
|
||||||
entry.affils = ac.user.properties.affiliation;
|
entry.affils = ac.user.properties.affiliation;
|
||||||
|
|
||||||
const diff = now.diff(moment(ac.user.properties.last_login_timestamp), 'minutes');
|
const diff = now.diff(moment(ac.user.properties.last_login_timestamp), 'minutes');
|
||||||
entry.timeOn = moment.duration(diff, 'minutes');
|
entry.timeOn = moment.duration(diff, 'minutes');
|
||||||
}
|
}
|
||||||
return entry;
|
return entry;
|
||||||
});
|
});
|
||||||
|
@ -59,7 +60,7 @@ function addNewClient(client, clientSock) {
|
||||||
const id = client.session.id = clientConnections.push(client) - 1;
|
const id = client.session.id = clientConnections.push(client) - 1;
|
||||||
const remoteAddress = client.remoteAddress = clientSock.remoteAddress;
|
const remoteAddress = client.remoteAddress = clientSock.remoteAddress;
|
||||||
|
|
||||||
// Create a client specific logger
|
// Create a client specific logger
|
||||||
// Note that this will be updated @ login with additional information
|
// Note that this will be updated @ login with additional information
|
||||||
client.log = logger.log.child( { clientId : id } );
|
client.log = logger.log.child( { clientId : id } );
|
||||||
|
|
||||||
|
@ -76,6 +77,8 @@ function addNewClient(client, clientSock) {
|
||||||
|
|
||||||
client.log.info(connInfo, 'Client connected');
|
client.log.info(connInfo, 'Client connected');
|
||||||
|
|
||||||
|
events.emit('codes.l33t.enigma.system.connected', {'client': client});
|
||||||
|
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -85,17 +88,19 @@ function removeClient(client) {
|
||||||
const i = clientConnections.indexOf(client);
|
const i = clientConnections.indexOf(client);
|
||||||
if(i > -1) {
|
if(i > -1) {
|
||||||
clientConnections.splice(i, 1);
|
clientConnections.splice(i, 1);
|
||||||
|
|
||||||
logger.log.info(
|
logger.log.info(
|
||||||
{
|
{
|
||||||
connectionCount : clientConnections.length,
|
connectionCount : clientConnections.length,
|
||||||
clientId : client.session.id
|
clientId : client.session.id
|
||||||
},
|
},
|
||||||
'Client disconnected'
|
'Client disconnected'
|
||||||
);
|
);
|
||||||
|
|
||||||
|
events.emit('codes.l33t.enigma.system.disconnected', {'client': client});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getConnectionByUserId(userId) {
|
function getConnectionByUserId(userId) {
|
||||||
return getActiveConnections().find( ac => userId === ac.user.userId );
|
return getActiveConnections().find( ac => userId === ac.user.userId );
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
// ENiGMA½
|
// ENiGMA½
|
||||||
const ansi = require('./ansi_term.js');
|
const ansi = require('./ansi_term.js');
|
||||||
|
const events = require('./events.js');
|
||||||
|
|
||||||
// deps
|
// deps
|
||||||
const async = require('async');
|
const async = require('async');
|
||||||
|
@ -82,7 +83,7 @@ function ansiQueryTermSizeIfNeeded(client, cb) {
|
||||||
//
|
//
|
||||||
if(h < 10 || w < 10) {
|
if(h < 10 || w < 10) {
|
||||||
client.log.warn(
|
client.log.warn(
|
||||||
{ height : h, width : w },
|
{ height : h, width : w },
|
||||||
'Ignoring ANSI CPR screen size query response due to very small values');
|
'Ignoring ANSI CPR screen size query response due to very small values');
|
||||||
return done(new Error('Term size <= 10 considered invalid'));
|
return done(new Error('Term size <= 10 considered invalid'));
|
||||||
}
|
}
|
||||||
|
@ -91,11 +92,11 @@ function ansiQueryTermSizeIfNeeded(client, cb) {
|
||||||
client.term.termWidth = w;
|
client.term.termWidth = w;
|
||||||
|
|
||||||
client.log.debug(
|
client.log.debug(
|
||||||
{
|
{
|
||||||
termWidth : client.term.termWidth,
|
termWidth : client.term.termWidth,
|
||||||
termHeight : client.term.termHeight,
|
termHeight : client.term.termHeight,
|
||||||
source : 'ANSI CPR'
|
source : 'ANSI CPR'
|
||||||
},
|
},
|
||||||
'Window size updated'
|
'Window size updated'
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -109,7 +110,7 @@ function ansiQueryTermSizeIfNeeded(client, cb) {
|
||||||
return done(new Error('No term size established by CPR within timeout'));
|
return done(new Error('No term size established by CPR within timeout'));
|
||||||
}, 2000);
|
}, 2000);
|
||||||
|
|
||||||
// Start the process: Query for CPR
|
// Start the process: Query for CPR
|
||||||
client.term.rawWrite(ansi.queryScreenSize());
|
client.term.rawWrite(ansi.queryScreenSize());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -123,7 +124,7 @@ function displayBanner(term) {
|
||||||
// note: intentional formatting:
|
// note: intentional formatting:
|
||||||
term.pipeWrite(`
|
term.pipeWrite(`
|
||||||
|06Connected to |02EN|10i|02GMA|10½ |06BBS version |12|VN
|
|06Connected to |02EN|10i|02GMA|10½ |06BBS version |12|VN
|
||||||
|06Copyright (c) 2014-2017 Bryan Ashby |14- |12http://l33t.codes/
|
|06Copyright (c) 2014-2017 Bryan Ashby |14- |12http://l33t.codes/
|
||||||
|06Updates & source |14- |12https://github.com/NuSkooler/enigma-bbs/
|
|06Updates & source |14- |12https://github.com/NuSkooler/enigma-bbs/
|
||||||
|00`
|
|00`
|
||||||
);
|
);
|
||||||
|
@ -153,11 +154,11 @@ function connectEntry(client, nextMenu) {
|
||||||
if(0 === term.termHeight || 0 === term.termWidth) {
|
if(0 === term.termHeight || 0 === term.termWidth) {
|
||||||
//
|
//
|
||||||
// We still don't have something good for term height/width.
|
// We still don't have something good for term height/width.
|
||||||
// Default to DOS size 80x25.
|
// Default to DOS size 80x25.
|
||||||
//
|
//
|
||||||
// :TODO: Netrunner is currenting hitting this and it feels wrong. Why is NAWS/ENV/CPR all failing???
|
// :TODO: Netrunner is currenting hitting this and it feels wrong. Why is NAWS/ENV/CPR all failing???
|
||||||
client.log.warn( { reason : err.message }, 'Failed to negotiate term size; Defaulting to 80x25!');
|
client.log.warn( { reason : err.message }, 'Failed to negotiate term size; Defaulting to 80x25!');
|
||||||
|
|
||||||
term.termHeight = 25;
|
term.termHeight = 25;
|
||||||
term.termWidth = 80;
|
term.termWidth = 80;
|
||||||
}
|
}
|
||||||
|
@ -165,8 +166,8 @@ function connectEntry(client, nextMenu) {
|
||||||
|
|
||||||
return callback(null);
|
return callback(null);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
() => {
|
() => {
|
||||||
prepareTerminal(term);
|
prepareTerminal(term);
|
||||||
|
|
||||||
|
@ -175,6 +176,9 @@ function connectEntry(client, nextMenu) {
|
||||||
//
|
//
|
||||||
displayBanner(term);
|
displayBanner(term);
|
||||||
|
|
||||||
|
// fire event
|
||||||
|
events.emit('codes.l33t.enigma.system.term_detected', {'client': client});
|
||||||
|
|
||||||
setTimeout( () => {
|
setTimeout( () => {
|
||||||
return client.menuStack.goto(nextMenu);
|
return client.menuStack.goto(nextMenu);
|
||||||
}, 500);
|
}, 500);
|
||||||
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
/* jslint node: true */
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const Config = require('./config.js');
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const events = require('events');
|
||||||
|
const logger = require('./logger.js');
|
||||||
|
|
||||||
|
var eventEmitter = new events.EventEmitter();
|
||||||
|
|
||||||
|
var self = module.exports = {
|
||||||
|
emit: function(eventName, args) {
|
||||||
|
logger.log.debug("Emit "+eventName);
|
||||||
|
eventEmitter.emit(eventName, args);
|
||||||
|
},
|
||||||
|
on: function(eventName, listener) {
|
||||||
|
logger.log.debug("Register listener for "+eventName);
|
||||||
|
eventEmitter.on(eventName, listener);
|
||||||
|
},
|
||||||
|
remove: function(eventName, listener) {
|
||||||
|
logger.log.debug("Remove listener for "+eventName);
|
||||||
|
eventEmitter.removeListener(eventName, listener);
|
||||||
|
},
|
||||||
|
registerModules: function() {
|
||||||
|
const moduleUtil = require('./module_util.js');
|
||||||
|
|
||||||
|
moduleUtil.getModulePaths().forEach(function(modulePath) {
|
||||||
|
var mods = fs.readdirSync(modulePath);
|
||||||
|
mods.forEach(function(item) {
|
||||||
|
var modPath = modulePath+item;
|
||||||
|
if (item.substr(item.length-3) != '.js') {
|
||||||
|
modPath += path.sep+item+'.js';
|
||||||
|
}
|
||||||
|
if (fs.existsSync(modPath)) {
|
||||||
|
var module = require(modPath);
|
||||||
|
|
||||||
|
if (module.registerEvents !== undefined) {
|
||||||
|
logger.log.debug(modPath+" calling registerEvents function");
|
||||||
|
module.registerEvents();
|
||||||
|
} else {
|
||||||
|
logger.log.debug(modPath+" has no registerEvents function");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.log.debug(modPath+" - file not found");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,6 +15,7 @@ const async = require('async');
|
||||||
exports.loadModuleEx = loadModuleEx;
|
exports.loadModuleEx = loadModuleEx;
|
||||||
exports.loadModule = loadModule;
|
exports.loadModule = loadModule;
|
||||||
exports.loadModulesForCategory = loadModulesForCategory;
|
exports.loadModulesForCategory = loadModulesForCategory;
|
||||||
|
exports.getModulePaths = getModulePaths;
|
||||||
|
|
||||||
function loadModuleEx(options, cb) {
|
function loadModuleEx(options, cb) {
|
||||||
assert(_.isObject(options));
|
assert(_.isObject(options));
|
||||||
|
@ -25,7 +26,7 @@ function loadModuleEx(options, cb) {
|
||||||
|
|
||||||
if(_.isObject(modConfig) && false === modConfig.enabled) {
|
if(_.isObject(modConfig) && false === modConfig.enabled) {
|
||||||
const err = new Error(`Module "${options.name}" is disabled`);
|
const err = new Error(`Module "${options.name}" is disabled`);
|
||||||
err.code = 'EENIGMODDISABLED';
|
err.code = 'EENIGMODDISABLED';
|
||||||
return cb(err);
|
return cb(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,7 +37,7 @@ function loadModuleEx(options, cb) {
|
||||||
//
|
//
|
||||||
let mod;
|
let mod;
|
||||||
let modPath = paths.join(options.path, `${options.name}.js`); // general case first
|
let modPath = paths.join(options.path, `${options.name}.js`); // general case first
|
||||||
try {
|
try {
|
||||||
mod = require(modPath);
|
mod = require(modPath);
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
if('MODULE_NOT_FOUND' === e.code) {
|
if('MODULE_NOT_FOUND' === e.code) {
|
||||||
|
@ -48,7 +49,7 @@ function loadModuleEx(options, cb) {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return cb(e);
|
return cb(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!_.isObject(mod.moduleInfo)) {
|
if(!_.isObject(mod.moduleInfo)) {
|
||||||
|
@ -75,7 +76,7 @@ function loadModule(name, category, cb) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadModulesForCategory(category, iterator, complete) {
|
function loadModulesForCategory(category, iterator, complete) {
|
||||||
|
|
||||||
fs.readdir(Config.paths[category], (err, files) => {
|
fs.readdir(Config.paths[category], (err, files) => {
|
||||||
if(err) {
|
if(err) {
|
||||||
return iterator(err);
|
return iterator(err);
|
||||||
|
@ -97,3 +98,9 @@ function loadModulesForCategory(category, iterator, complete) {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getModulePaths() {
|
||||||
|
return [
|
||||||
|
Config.paths.mods
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
|
@ -4,4 +4,4 @@
|
||||||
exports.PluginModule = PluginModule;
|
exports.PluginModule = PluginModule;
|
||||||
|
|
||||||
function PluginModule(options) {
|
function PluginModule(options) {
|
||||||
}
|
}
|
||||||
|
|
|
@ -293,9 +293,9 @@ function FTNMessageScanTossModule() {
|
||||||
async.detectSeries(EXT_SUFFIXES, (suffix, callback) => {
|
async.detectSeries(EXT_SUFFIXES, (suffix, callback) => {
|
||||||
const checkFileName = fileName + suffix;
|
const checkFileName = fileName + suffix;
|
||||||
fs.stat(paths.join(basePath, checkFileName), err => {
|
fs.stat(paths.join(basePath, checkFileName), err => {
|
||||||
callback((err && 'ENOENT' === err.code) ? true : false);
|
callback(null, (err && 'ENOENT' === err.code) ? true : false);
|
||||||
});
|
});
|
||||||
}, finalSuffix => {
|
}, (err, finalSuffix) => {
|
||||||
if(finalSuffix) {
|
if(finalSuffix) {
|
||||||
cb(null, paths.join(basePath, fileName + finalSuffix));
|
cb(null, paths.join(basePath, fileName + finalSuffix));
|
||||||
} else {
|
} else {
|
||||||
|
|
Loading…
Reference in New Issue