First pass formatting with Prettier

* Added .prettierrc.json
* Added .prettierignore
* Formatted
This commit is contained in:
Bryan Ashby 2022-06-05 14:04:25 -06:00
parent eecfb33ad5
commit 4881c2123a
172 changed files with 23696 additions and 18029 deletions

View File

@ -3,9 +3,7 @@
"es6": true, "es6": true,
"node": true "node": true
}, },
"extends": [ "extends": ["eslint:recommended"],
"eslint:recommended"
],
"rules": { "rules": {
"indent": [ "indent": [
"error", "error",
@ -14,18 +12,9 @@
"SwitchCase": 1 "SwitchCase": 1
} }
], ],
"linebreak-style": [ "linebreak-style": ["error", "unix"],
"error", "quotes": ["error", "single"],
"unix" "semi": ["error", "always"],
],
"quotes": [
"error",
"single"
],
"semi": [
"error",
"always"
],
"comma-dangle": 0, "comma-dangle": 0,
"no-trailing-spaces": "warn" "no-trailing-spaces": "warn"
}, },

12
.prettierignore Normal file
View File

@ -0,0 +1,12 @@
art
config
db
docs
drop
gopher
logs
misc
www
mkdocs.yml
*.md
.github

View File

@ -7,10 +7,7 @@ const Door = require('./door.js');
const theme = require('./theme.js'); const theme = require('./theme.js');
const ansi = require('./ansi_term.js'); const ansi = require('./ansi_term.js');
const { Errors } = require('./enig_error.js'); const { Errors } = require('./enig_error.js');
const { const { trackDoorRunBegin, trackDoorRunEnd } = require('./door_util.js');
trackDoorRunBegin,
trackDoorRunEnd
} = require('./door_util.js');
const Log = require('./logger').log; const Log = require('./logger').log;
// deps // deps
@ -71,8 +68,8 @@ exports.getModule = class AbracadabraModule extends MenuModule {
this.config = options.menuConfig.config; this.config = options.menuConfig.config;
// :TODO: MenuModule.validateConfig(cb) -- validate config section gracefully instead of asserts! -- { key : type, key2 : type2, ... } // :TODO: MenuModule.validateConfig(cb) -- validate config section gracefully instead of asserts! -- { key : type, key2 : type2, ... }
// .. and/or EnigAssert // .. and/or EnigAssert
assert(_.isString(this.config.name, 'Config \'name\' is required')); assert(_.isString(this.config.name, "Config 'name' is required"));
assert(_.isString(this.config.cmd, 'Config \'cmd\' is required')); assert(_.isString(this.config.cmd, "Config 'cmd' is required"));
this.config.nodeMax = this.config.nodeMax || 0; this.config.nodeMax = this.config.nodeMax || 0;
this.config.args = this.config.args || []; this.config.args = this.config.args || [];
@ -100,29 +97,43 @@ exports.getModule = class AbracadabraModule extends MenuModule {
async.series( async.series(
[ [
function validateNodeCount(callback) { function validateNodeCount(callback) {
if(self.config.nodeMax > 0 && if (
self.config.nodeMax > 0 &&
_.isNumber(activeDoorNodeInstances[self.config.name]) && _.isNumber(activeDoorNodeInstances[self.config.name]) &&
activeDoorNodeInstances[self.config.name] + 1 > self.config.nodeMax) activeDoorNodeInstances[self.config.name] + 1 >
{ self.config.nodeMax
) {
self.client.log.info( self.client.log.info(
{ {
name: self.config.name, name: self.config.name,
activeCount : activeDoorNodeInstances[self.config.name] activeCount: activeDoorNodeInstances[self.config.name],
}, },
'Too many active instances'); 'Too many active instances'
);
if (_.isString(self.config.tooManyArt)) { if (_.isString(self.config.tooManyArt)) {
theme.displayThemeArt( { client : self.client, name : self.config.tooManyArt }, function displayed() { theme.displayThemeArt(
{ client: self.client, name: self.config.tooManyArt },
function displayed() {
self.pausePrompt(() => { self.pausePrompt(() => {
return callback(Errors.AccessDenied('Too many active instances')); return callback(
}); Errors.AccessDenied(
'Too many active instances'
)
);
}); });
}
);
} else { } else {
self.client.term.write('\nToo many active instances. Try again later.\n'); self.client.term.write(
'\nToo many active instances. Try again later.\n'
);
// :TODO: Use MenuModule.pausePrompt() // :TODO: Use MenuModule.pausePrompt()
self.pausePrompt(() => { self.pausePrompt(() => {
return callback(Errors.AccessDenied('Too many active instances')); return callback(
Errors.AccessDenied('Too many active instances')
);
}); });
} }
} else { } else {
@ -135,21 +146,26 @@ exports.getModule = class AbracadabraModule extends MenuModule {
return self.doorInstance.prepare(self.config.io || 'stdio', callback); return self.doorInstance.prepare(self.config.io || 'stdio', callback);
}, },
function generateDropfile(callback) { function generateDropfile(callback) {
if (!self.config.dropFileType || self.config.dropFileType.toLowerCase() === 'none') { if (
!self.config.dropFileType ||
self.config.dropFileType.toLowerCase() === 'none'
) {
return callback(null); return callback(null);
} }
self.dropFile = new DropFile( self.dropFile = new DropFile(self.client, {
self.client, fileType: self.config.dropFileType,
{ fileType : self.config.dropFileType } });
);
return self.dropFile.createFile(callback); return self.dropFile.createFile(callback);
} },
], ],
function complete(err) { function complete(err) {
if (err) { if (err) {
self.client.log.warn( { error : err.toString() }, 'Could not start door'); self.client.log.warn(
{ error: err.toString() },
'Could not start door'
);
self.lastError = err; self.lastError = err;
self.prevMenu(); self.prevMenu();
} else { } else {
@ -187,7 +203,10 @@ exports.getModule = class AbracadabraModule extends MenuModule {
if (exeInfo.dropFilePath) { if (exeInfo.dropFilePath) {
fs.unlink(exeInfo.dropFilePath, err => { fs.unlink(exeInfo.dropFilePath, err => {
if (err) { if (err) {
Log.warn({ error : err, path : exeInfo.dropFilePath }, 'Failed to remove drop file.'); Log.warn(
{ error: err, path: exeInfo.dropFilePath },
'Failed to remove drop file.'
);
} }
}); });
} }

View File

@ -7,23 +7,13 @@ const Config = require('./config.js').get;
const ConfigLoader = require('./config_loader'); const ConfigLoader = require('./config_loader');
const { getConfigPath } = require('./config_util'); const { getConfigPath } = require('./config_util');
const UserDb = require('./database.js').dbs.user; const UserDb = require('./database.js').dbs.user;
const { const { getISOTimestampString } = require('./database.js');
getISOTimestampString
} = require('./database.js');
const UserInterruptQueue = require('./user_interrupt_queue.js'); const UserInterruptQueue = require('./user_interrupt_queue.js');
const { const { getConnectionByUserId } = require('./client_connections.js');
getConnectionByUserId
} = require('./client_connections.js');
const UserProps = require('./user_property.js'); const UserProps = require('./user_property.js');
const { const { Errors, ErrorReasons } = require('./enig_error.js');
Errors,
ErrorReasons
} = require('./enig_error.js');
const { getThemeArt } = require('./theme.js'); const { getThemeArt } = require('./theme.js');
const { const { pipeToAnsi, stripMciColorCodes } = require('./color_codes.js');
pipeToAnsi,
stripMciColorCodes
} = require('./color_codes.js');
const stringFormat = require('./string_format.js'); const stringFormat = require('./string_format.js');
const StatLog = require('./stat_log.js'); const StatLog = require('./stat_log.js');
const Log = require('./logger.js').log; const Log = require('./logger.js').log;
@ -55,7 +45,8 @@ class Achievement {
achievement = new UserStatAchievement(data); achievement = new UserStatAchievement(data);
break; break;
default : return; default:
return;
} }
if (achievement.isValid()) { if (achievement.isValid()) {
@ -84,19 +75,24 @@ class Achievement {
} }
break; break;
default : return false; default:
return false;
} }
return true; return true;
} }
getMatchDetails(/*matchAgainst*/) { getMatchDetails(/*matchAgainst*/) {}
}
isValidMatchDetails(details) { isValidMatchDetails(details) {
if(!details || !_.isString(details.title) || !_.isString(details.text) || !_.isNumber(details.points)) { if (
!details ||
!_.isString(details.title) ||
!_.isString(details.text) ||
!_.isNumber(details.points)
) {
return false; return false;
} }
return (_.isString(details.globalText) || !details.globalText); return _.isString(details.globalText) || !details.globalText;
} }
} }
@ -105,7 +101,9 @@ class UserStatAchievement extends Achievement {
super(data); super(data);
// sort match keys for quick match lookup // sort match keys for quick match lookup
this.matchKeys = Object.keys(this.data.match || {}).map(k => parseInt(k)).sort( (a, b) => b - a); this.matchKeys = Object.keys(this.data.match || {})
.map(k => parseInt(k))
.sort((a, b) => b - a);
} }
isValid() { isValid() {
@ -167,7 +165,7 @@ class Achievements {
if (!err) { if (!err) {
configLoaded(); configLoaded();
} }
} },
}); });
this.config.init(configPath, err => { this.config.init(configPath, err => {
@ -202,14 +200,23 @@ class Achievements {
record(info, localInterruptItem, cb) { record(info, localInterruptItem, cb) {
StatLog.incrementUserStat(info.client.user, UserProps.AchievementTotalCount, 1); StatLog.incrementUserStat(info.client.user, UserProps.AchievementTotalCount, 1);
StatLog.incrementUserStat(info.client.user, UserProps.AchievementTotalPoints, info.details.points); StatLog.incrementUserStat(
info.client.user,
UserProps.AchievementTotalPoints,
info.details.points
);
const cleanTitle = stripMciColorCodes(localInterruptItem.title); const cleanTitle = stripMciColorCodes(localInterruptItem.title);
const cleanText = stripMciColorCodes(localInterruptItem.achievText); const cleanText = stripMciColorCodes(localInterruptItem.achievText);
const recordData = [ const recordData = [
info.client.user.userId, info.achievementTag, getISOTimestampString(info.timestamp), info.matchField, info.client.user.userId,
cleanTitle, cleanText, info.details.points, info.achievementTag,
getISOTimestampString(info.timestamp),
info.matchField,
cleanTitle,
cleanText,
info.details.points,
]; ];
UserDb.run( UserDb.run(
@ -221,16 +228,13 @@ class Achievements {
return cb(err); return cb(err);
} }
this.events.emit( this.events.emit(Events.getSystemEvents().UserAchievementEarned, {
Events.getSystemEvents().UserAchievementEarned,
{
user: info.client.user, user: info.client.user,
achievementTag: info.achievementTag, achievementTag: info.achievementTag,
points: info.details.points, points: info.details.points,
title: cleanTitle, title: cleanTitle,
text: cleanText, text: cleanText,
} });
);
return cb(null); return cb(null);
} }
@ -252,7 +256,7 @@ class Achievements {
recordAndDisplayAchievement(info, cb) { recordAndDisplayAchievement(info, cb) {
async.waterfall( async.waterfall(
[ [
(callback) => { callback => {
return this.createAchievementInterruptItems(info, callback); return this.createAchievementInterruptItems(info, callback);
}, },
(interruptItems, callback) => { (interruptItems, callback) => {
@ -262,7 +266,7 @@ class Achievements {
}, },
(interruptItems, callback) => { (interruptItems, callback) => {
return this.display(info, interruptItems, callback); return this.display(info, interruptItems, callback);
} },
], ],
err => { err => {
return cb(err); return cb(err);
@ -277,20 +281,31 @@ class Achievements {
const listenEvents = [ const listenEvents = [
Events.getSystemEvents().UserStatSet, Events.getSystemEvents().UserStatSet,
Events.getSystemEvents().UserStatIncrement Events.getSystemEvents().UserStatIncrement,
]; ];
this.userStatEventListeners = this.events.addMultipleEventListener(listenEvents, userStatEvent => { this.userStatEventListeners = this.events.addMultipleEventListener(
if([ UserProps.AchievementTotalCount, UserProps.AchievementTotalPoints ].includes(userStatEvent.statName)) { listenEvents,
userStatEvent => {
if (
[
UserProps.AchievementTotalCount,
UserProps.AchievementTotalPoints,
].includes(userStatEvent.statName)
) {
return; return;
} }
if(!_.isNumber(userStatEvent.statValue) && !_.isNumber(userStatEvent.statIncrementBy)) { if (
!_.isNumber(userStatEvent.statValue) &&
!_.isNumber(userStatEvent.statIncrementBy)
) {
return; return;
} }
// :TODO: Make this code generic - find + return factory created object // :TODO: Make this code generic - find + return factory created object
const achievementTags = Object.keys(_.pickBy( const achievementTags = Object.keys(
_.pickBy(
_.get(this.config.get(), 'achievements', {}), _.get(this.config.get(), 'achievements', {}),
achievement => { achievement => {
if (false === achievement.enabled) { if (false === achievement.enabled) {
@ -301,48 +316,78 @@ class Achievements {
Achievement.Types.UserStatInc, Achievement.Types.UserStatInc,
Achievement.Types.UserStatIncNewVal, Achievement.Types.UserStatIncNewVal,
]; ];
return acceptedTypes.includes(achievement.type) && achievement.statName === userStatEvent.statName; return (
acceptedTypes.includes(achievement.type) &&
achievement.statName === userStatEvent.statName
);
} }
)); )
);
if (0 === achievementTags.length) { if (0 === achievementTags.length) {
return; return;
} }
async.eachSeries(achievementTags, (achievementTag, nextAchievementTag) => { async.eachSeries(
const achievement = Achievement.factory(this.getAchievementByTag(achievementTag)); achievementTags,
(achievementTag, nextAchievementTag) => {
const achievement = Achievement.factory(
this.getAchievementByTag(achievementTag)
);
if (!achievement) { if (!achievement) {
return nextAchievementTag(null); return nextAchievementTag(null);
} }
const statValue = parseInt( const statValue = parseInt(
[ Achievement.Types.UserStatSet, Achievement.Types.UserStatIncNewVal ].includes(achievement.data.type) ? [
userStatEvent.statValue : Achievement.Types.UserStatSet,
userStatEvent.statIncrementBy Achievement.Types.UserStatIncNewVal,
].includes(achievement.data.type)
? userStatEvent.statValue
: userStatEvent.statIncrementBy
); );
if (isNaN(statValue)) { if (isNaN(statValue)) {
return nextAchievementTag(null); return nextAchievementTag(null);
} }
const [ details, matchField, matchValue ] = achievement.getMatchDetails(statValue); const [details, matchField, matchValue] =
achievement.getMatchDetails(statValue);
if (!details) { if (!details) {
return nextAchievementTag(null); return nextAchievementTag(null);
} }
async.waterfall( async.waterfall(
[ [
(callback) => { callback => {
this.loadAchievementHitCount(userStatEvent.user, achievementTag, matchField, (err, count) => { this.loadAchievementHitCount(
userStatEvent.user,
achievementTag,
matchField,
(err, count) => {
if (err) { if (err) {
return callback(err); return callback(err);
} }
return callback(count > 0 ? Errors.General('Achievement already acquired', ErrorReasons.TooMany) : null); return callback(
}); count > 0
? Errors.General(
'Achievement already acquired',
ErrorReasons.TooMany
)
: null
);
}
);
}, },
(callback) => { callback => {
const client = getConnectionByUserId(userStatEvent.user.userId); const client = getConnectionByUserId(
userStatEvent.user.userId
);
if (!client) { if (!client) {
return callback(Errors.UnexpectedState('Failed to get client for user ID')); return callback(
Errors.UnexpectedState(
'Failed to get client for user ID'
)
);
} }
const info = { const info = {
@ -365,8 +410,13 @@ class Achievements {
return callback(null, achievementsInfo); return callback(null, achievementsInfo);
} }
const index = achievement.matchKeys.findIndex(v => v < matchField); const index = achievement.matchKeys.findIndex(
if(-1 === index || !Array.isArray(achievement.matchKeys)) { v => v < matchField
);
if (
-1 === index ||
!Array.isArray(achievement.matchKeys)
) {
return callback(null, achievementsInfo); return callback(null, achievementsInfo);
} }
@ -375,56 +425,74 @@ class Achievements {
// ^---- we met here // ^---- we met here
// ^------------^ retroactive range // ^------------^ retroactive range
// //
async.eachSeries(achievement.matchKeys.slice(index), (k, nextKey) => { async.eachSeries(
const [ det, fld, val ] = achievement.getMatchDetails(k); achievement.matchKeys.slice(index),
(k, nextKey) => {
const [det, fld, val] =
achievement.getMatchDetails(k);
if (!det) { if (!det) {
return nextKey(null); return nextKey(null);
} }
this.loadAchievementHitCount(userStatEvent.user, achievementTag, fld, (err, count) => { this.loadAchievementHitCount(
if(!err || count && 0 === count) { userStatEvent.user,
achievementsInfo.push(Object.assign( achievementTag,
{}, fld,
basicInfo, (err, count) => {
{ if (!err || (count && 0 === count)) {
achievementsInfo.push(
Object.assign({}, basicInfo, {
details: det, details: det,
matchField: fld, matchField: fld,
achievedValue: fld, achievedValue: fld,
matchValue: val, matchValue: val,
} })
)); );
} }
return nextKey(null); return nextKey(null);
}); }
);
}, },
() => { () => {
return callback(null, achievementsInfo); return callback(null, achievementsInfo);
}); }
);
}, },
(achievementsInfo, callback) => { (achievementsInfo, callback) => {
// reverse achievementsInfo so we display smallest > largest // reverse achievementsInfo so we display smallest > largest
achievementsInfo.reverse(); achievementsInfo.reverse();
async.eachSeries(achievementsInfo, (achInfo, nextAchInfo) => { async.eachSeries(
return this.recordAndDisplayAchievement(achInfo, err => { achievementsInfo,
(achInfo, nextAchInfo) => {
return this.recordAndDisplayAchievement(
achInfo,
err => {
return nextAchInfo(err); return nextAchInfo(err);
}); }
);
}, },
err => { err => {
return callback(err); return callback(err);
});
} }
);
},
], ],
err => { err => {
if (err && ErrorReasons.TooMany !== err.reasonCode) { if (err && ErrorReasons.TooMany !== err.reasonCode) {
Log.warn( { error : err.message, userStatEvent }, 'Error handling achievement for user stat event'); Log.warn(
{ error: err.message, userStatEvent },
'Error handling achievement for user stat event'
);
} }
return nextAchievementTag(null); // always try the next, regardless return nextAchievementTag(null); // always try the next, regardless
} }
); );
}); }
}); );
}
);
} }
stopMonitoringUserStatEvents() { stopMonitoringUserStatEvents() {
@ -453,12 +521,16 @@ class Achievements {
} }
getFormattedTextFor(info, textType, defaultSgr = '|07') { getFormattedTextFor(info, textType, defaultSgr = '|07') {
const themeDefaults = _.get(info.client.currentTheme, 'achievements.defaults', {}); const themeDefaults = _.get(
info.client.currentTheme,
'achievements.defaults',
{}
);
const textTypeSgr = themeDefaults[`${textType}SGR`] || defaultSgr; const textTypeSgr = themeDefaults[`${textType}SGR`] || defaultSgr;
const formatObj = this.getFormatObject(info); const formatObj = this.getFormatObject(info);
const wrap = (input) => { const wrap = input => {
const re = new RegExp(`{(${Object.keys(formatObj).join('|')})([^}]*)}`, 'g'); const re = new RegExp(`{(${Object.keys(formatObj).join('|')})([^}]*)}`, 'g');
return input.replace(re, (m, formatVar, formatOpts) => { return input.replace(re, (m, formatVar, formatOpts) => {
const varSgr = themeDefaults[`${formatVar}SGR`] || textTypeSgr; const varSgr = themeDefaults[`${formatVar}SGR`] || textTypeSgr;
@ -512,10 +584,12 @@ class Achievements {
itemTypes.push('global'); itemTypes.push('global');
} }
async.each(itemTypes, (itemType, nextItemType) => { async.each(
itemTypes,
(itemType, nextItemType) => {
async.waterfall( async.waterfall(
[ [
(callback) => { callback => {
getArt(`${itemType}Header`, headerArt => { getArt(`${itemType}Header`, headerArt => {
return callback(null, headerArt); return callback(null, headerArt);
}); });
@ -534,24 +608,40 @@ class Achievements {
pause: true, pause: true,
}; };
if (headerArt || footerArt) { if (headerArt || footerArt) {
const themeDefaults = _.get(info.client.currentTheme, 'achievements.defaults', {}); const themeDefaults = _.get(
info.client.currentTheme,
'achievements.defaults',
{}
);
const defaultContentsFormat = '{title}\r\n{message}'; const defaultContentsFormat = '{title}\r\n{message}';
const contentsFormat = 'global' === itemType ? const contentsFormat =
themeDefaults.globalFormat || defaultContentsFormat : 'global' === itemType
themeDefaults.format || defaultContentsFormat; ? themeDefaults.globalFormat ||
defaultContentsFormat
: themeDefaults.format || defaultContentsFormat;
const formatObj = Object.assign(this.getFormatObject(info), { const formatObj = Object.assign(
title : this.getFormattedTextFor(info, 'title', ''), // ''=defaultSgr this.getFormatObject(info),
{
title: this.getFormattedTextFor(
info,
'title',
''
), // ''=defaultSgr
message: itemText, message: itemText,
}); }
);
const contents = pipeToAnsi(stringFormat(contentsFormat, formatObj)); const contents = pipeToAnsi(
stringFormat(contentsFormat, formatObj)
);
interruptItems[itemType].contents = interruptItems[itemType].contents = `${
`${headerArt || ''}\r\n${contents}\r\n${footerArt || ''}`; headerArt || ''
}\r\n${contents}\r\n${footerArt || ''}`;
} }
return callback(null); return callback(null);
} },
], ],
err => { err => {
return nextItemType(err); return nextItemType(err);
@ -560,7 +650,8 @@ class Achievements {
}, },
err => { err => {
return cb(err, interruptItems); return cb(err, interruptItems);
}); }
);
} }
} }
@ -582,9 +673,11 @@ function getAchievementsEarnedByUser(userId, cb) {
return cb(err); return cb(err);
} }
const earned = rows.map(row => { const earned = rows
.map(row => {
const achievement = Achievement.factory(achievementsInstance.getAchievementByTag(row.achievement_tag)); const achievement = Achievement.factory(
achievementsInstance.getAchievementByTag(row.achievement_tag)
);
if (!achievement) { if (!achievement) {
return; return;
} }
@ -608,7 +701,8 @@ function getAchievementsEarnedByUser(userId, cb) {
} }
return earnedInfo; return earnedInfo;
}).filter(a => a); // remove any empty records (ie: no achievement.hjson entry exists anymore). })
.filter(a => a); // remove any empty records (ie: no achievement.hjson entry exists anymore).
return cb(null, earned); return cb(null, earned);
} }

View File

@ -100,7 +100,10 @@ class ACS {
try { try {
return checkAcs(cond.acs, { subject: this.subject }); return checkAcs(cond.acs, { subject: this.subject });
} catch (e) { } catch (e) {
Log.warn( { exception : e, acs : cond }, 'Exception caught checking ACS'); Log.warn(
{ exception: e, acs: cond },
'Exception caught checking ACS'
);
return false; return false;
} }
} else { } else {

View File

@ -4,10 +4,12 @@
* http://pegjs.org/ * http://pegjs.org/
*/ */
"use strict"; 'use strict';
function peg$subclass(child, parent) { function peg$subclass(child, parent) {
function ctor() { this.constructor = child; } function ctor() {
this.constructor = child;
}
ctor.prototype = parent.prototype; ctor.prototype = parent.prototype;
child.prototype = new ctor(); child.prototype = new ctor();
} }
@ -17,9 +19,9 @@ function peg$SyntaxError(message, expected, found, location) {
this.expected = expected; this.expected = expected;
this.found = found; this.found = found;
this.location = location; this.location = location;
this.name = "SyntaxError"; this.name = 'SyntaxError';
if (typeof Error.captureStackTrace === "function") { if (typeof Error.captureStackTrace === 'function') {
Error.captureStackTrace(this, peg$SyntaxError); Error.captureStackTrace(this, peg$SyntaxError);
} }
} }
@ -29,33 +31,36 @@ peg$subclass(peg$SyntaxError, Error);
peg$SyntaxError.buildMessage = function (expected, found) { peg$SyntaxError.buildMessage = function (expected, found) {
var DESCRIBE_EXPECTATION_FNS = { var DESCRIBE_EXPECTATION_FNS = {
literal: function (expectation) { literal: function (expectation) {
return "\"" + literalEscape(expectation.text) + "\""; return '"' + literalEscape(expectation.text) + '"';
}, },
"class": function(expectation) { class: function (expectation) {
var escapedParts = "", var escapedParts = '',
i; i;
for (i = 0; i < expectation.parts.length; i++) { for (i = 0; i < expectation.parts.length; i++) {
escapedParts += expectation.parts[i] instanceof Array escapedParts +=
? classEscape(expectation.parts[i][0]) + "-" + classEscape(expectation.parts[i][1]) expectation.parts[i] instanceof Array
? classEscape(expectation.parts[i][0]) +
'-' +
classEscape(expectation.parts[i][1])
: classEscape(expectation.parts[i]); : classEscape(expectation.parts[i]);
} }
return "[" + (expectation.inverted ? "^" : "") + escapedParts + "]"; return '[' + (expectation.inverted ? '^' : '') + escapedParts + ']';
}, },
any: function (expectation) { any: function (expectation) {
return "any character"; return 'any character';
}, },
end: function (expectation) { end: function (expectation) {
return "end of input"; return 'end of input';
}, },
other: function (expectation) { other: function (expectation) {
return expectation.description; return expectation.description;
} },
}; };
function hex(ch) { function hex(ch) {
@ -70,8 +75,12 @@ peg$SyntaxError.buildMessage = function(expected, found) {
.replace(/\t/g, '\\t') .replace(/\t/g, '\\t')
.replace(/\n/g, '\\n') .replace(/\n/g, '\\n')
.replace(/\r/g, '\\r') .replace(/\r/g, '\\r')
.replace(/[\x00-\x0F]/g, function(ch) { return '\\x0' + hex(ch); }) .replace(/[\x00-\x0F]/g, function (ch) {
.replace(/[\x10-\x1F\x7F-\x9F]/g, function(ch) { return '\\x' + hex(ch); }); return '\\x0' + hex(ch);
})
.replace(/[\x10-\x1F\x7F-\x9F]/g, function (ch) {
return '\\x' + hex(ch);
});
} }
function classEscape(s) { function classEscape(s) {
@ -84,8 +93,12 @@ peg$SyntaxError.buildMessage = function(expected, found) {
.replace(/\t/g, '\\t') .replace(/\t/g, '\\t')
.replace(/\n/g, '\\n') .replace(/\n/g, '\\n')
.replace(/\r/g, '\\r') .replace(/\r/g, '\\r')
.replace(/[\x00-\x0F]/g, function(ch) { return '\\x0' + hex(ch); }) .replace(/[\x00-\x0F]/g, function (ch) {
.replace(/[\x10-\x1F\x7F-\x9F]/g, function(ch) { return '\\x' + hex(ch); }); return '\\x0' + hex(ch);
})
.replace(/[\x10-\x1F\x7F-\x9F]/g, function (ch) {
return '\\x' + hex(ch);
});
} }
function describeExpectation(expectation) { function describeExpectation(expectation) {
@ -94,7 +107,8 @@ peg$SyntaxError.buildMessage = function(expected, found) {
function describeExpected(expected) { function describeExpected(expected) {
var descriptions = new Array(expected.length), var descriptions = new Array(expected.length),
i, j; i,
j;
for (i = 0; i < expected.length; i++) { for (i = 0; i < expected.length; i++) {
descriptions[i] = describeExpectation(expected[i]); descriptions[i] = describeExpectation(expected[i]);
@ -117,78 +131,110 @@ peg$SyntaxError.buildMessage = function(expected, found) {
return descriptions[0]; return descriptions[0];
case 2: case 2:
return descriptions[0] + " or " + descriptions[1]; return descriptions[0] + ' or ' + descriptions[1];
default: default:
return descriptions.slice(0, -1).join(", ") return (
+ ", or " descriptions.slice(0, -1).join(', ') +
+ descriptions[descriptions.length - 1]; ', or ' +
descriptions[descriptions.length - 1]
);
} }
} }
function describeFound(found) { function describeFound(found) {
return found ? "\"" + literalEscape(found) + "\"" : "end of input"; return found ? '"' + literalEscape(found) + '"' : 'end of input';
} }
return "Expected " + describeExpected(expected) + " but " + describeFound(found) + " found."; return (
'Expected ' +
describeExpected(expected) +
' but ' +
describeFound(found) +
' found.'
);
}; };
function peg$parse(input, options) { function peg$parse(input, options) {
options = options !== void 0 ? options : {}; options = options !== void 0 ? options : {};
var peg$FAILED = {}, var peg$FAILED = {},
peg$startRuleFunctions = { start: peg$parsestart }, peg$startRuleFunctions = { start: peg$parsestart },
peg$startRuleFunction = peg$parsestart, peg$startRuleFunction = peg$parsestart,
peg$c0 = '|',
peg$c0 = "|", peg$c1 = peg$literalExpectation('|', false),
peg$c1 = peg$literalExpectation("|", false), peg$c2 = '&',
peg$c2 = "&", peg$c3 = peg$literalExpectation('&', false),
peg$c3 = peg$literalExpectation("&", false), peg$c4 = '!',
peg$c4 = "!", peg$c5 = peg$literalExpectation('!', false),
peg$c5 = peg$literalExpectation("!", false), peg$c6 = '(',
peg$c6 = "(", peg$c7 = peg$literalExpectation('(', false),
peg$c7 = peg$literalExpectation("(", false), peg$c8 = ')',
peg$c8 = ")", peg$c9 = peg$literalExpectation(')', false),
peg$c9 = peg$literalExpectation(")", false), peg$c10 = function (left, right) {
peg$c10 = function(left, right) { return left || right; }, return left || right;
peg$c11 = function(left, right) { return left && right; }, },
peg$c12 = function(value) { return !value; }, peg$c11 = function (left, right) {
peg$c13 = function(value) { return value; }, return left && right;
peg$c14 = ",", },
peg$c15 = peg$literalExpectation(",", false), peg$c12 = function (value) {
peg$c16 = " ", return !value;
peg$c17 = peg$literalExpectation(" ", false), },
peg$c18 = "[", peg$c13 = function (value) {
peg$c19 = peg$literalExpectation("[", false), return value;
peg$c20 = "]", },
peg$c21 = peg$literalExpectation("]", false), peg$c14 = ',',
peg$c22 = function(acs, a) { return checkAccess(acs, a); }, peg$c15 = peg$literalExpectation(',', false),
peg$c16 = ' ',
peg$c17 = peg$literalExpectation(' ', false),
peg$c18 = '[',
peg$c19 = peg$literalExpectation('[', false),
peg$c20 = ']',
peg$c21 = peg$literalExpectation(']', false),
peg$c22 = function (acs, a) {
return checkAccess(acs, a);
},
peg$c23 = /^[A-Z]/, peg$c23 = /^[A-Z]/,
peg$c24 = peg$classExpectation([["A", "Z"]], false, false), peg$c24 = peg$classExpectation([['A', 'Z']], false, false),
peg$c25 = function(c) { return c.join(''); }, peg$c25 = function (c) {
return c.join('');
},
peg$c26 = /^[A-Za-z0-9\-_+]/, peg$c26 = /^[A-Za-z0-9\-_+]/,
peg$c27 = peg$classExpectation([["A", "Z"], ["a", "z"], ["0", "9"], "-", "_", "+"], false, false), peg$c27 = peg$classExpectation(
peg$c28 = function(a) { return a.join('') }, [['A', 'Z'], ['a', 'z'], ['0', '9'], '-', '_', '+'],
peg$c29 = function(v) { return v; }, false,
peg$c30 = function(start, last) { return start.concat(last); }, false
peg$c31 = function(l) { return l; }, ),
peg$c28 = function (a) {
return a.join('');
},
peg$c29 = function (v) {
return v;
},
peg$c30 = function (start, last) {
return start.concat(last);
},
peg$c31 = function (l) {
return l;
},
peg$c32 = /^[0-9]/, peg$c32 = /^[0-9]/,
peg$c33 = peg$classExpectation([["0", "9"]], false, false), peg$c33 = peg$classExpectation([['0', '9']], false, false),
peg$c34 = function(d) { return parseInt(d.join(''), 10); }, peg$c34 = function (d) {
return parseInt(d.join(''), 10);
},
peg$currPos = 0, peg$currPos = 0,
peg$savedPos = 0, peg$savedPos = 0,
peg$posDetailsCache = [{ line: 1, column: 1 }], peg$posDetailsCache = [{ line: 1, column: 1 }],
peg$maxFailPos = 0, peg$maxFailPos = 0,
peg$maxFailExpected = [], peg$maxFailExpected = [],
peg$silentFails = 0, peg$silentFails = 0,
peg$result; peg$result;
if ("startRule" in options) { if ('startRule' in options) {
if (!(options.startRule in peg$startRuleFunctions)) { if (!(options.startRule in peg$startRuleFunctions)) {
throw new Error("Can't start parsing from rule \"" + options.startRule + "\"."); throw new Error(
'Can\'t start parsing from rule "' + options.startRule + '".'
);
} }
peg$startRuleFunction = peg$startRuleFunctions[options.startRule]; peg$startRuleFunction = peg$startRuleFunctions[options.startRule];
@ -203,7 +249,10 @@ function peg$parse(input, options) {
} }
function expected(description, location) { function expected(description, location) {
location = location !== void 0 ? location : peg$computeLocation(peg$savedPos, peg$currPos) location =
location !== void 0
? location
: peg$computeLocation(peg$savedPos, peg$currPos);
throw peg$buildStructuredError( throw peg$buildStructuredError(
[peg$otherExpectation(description)], [peg$otherExpectation(description)],
@ -213,33 +262,42 @@ function peg$parse(input, options) {
} }
function error(message, location) { function error(message, location) {
location = location !== void 0 ? location : peg$computeLocation(peg$savedPos, peg$currPos) location =
location !== void 0
? location
: peg$computeLocation(peg$savedPos, peg$currPos);
throw peg$buildSimpleError(message, location); throw peg$buildSimpleError(message, location);
} }
function peg$literalExpectation(text, ignoreCase) { function peg$literalExpectation(text, ignoreCase) {
return { type: "literal", text: text, ignoreCase: ignoreCase }; return { type: 'literal', text: text, ignoreCase: ignoreCase };
} }
function peg$classExpectation(parts, inverted, ignoreCase) { function peg$classExpectation(parts, inverted, ignoreCase) {
return { type: "class", parts: parts, inverted: inverted, ignoreCase: ignoreCase }; return {
type: 'class',
parts: parts,
inverted: inverted,
ignoreCase: ignoreCase,
};
} }
function peg$anyExpectation() { function peg$anyExpectation() {
return { type: "any" }; return { type: 'any' };
} }
function peg$endExpectation() { function peg$endExpectation() {
return { type: "end" }; return { type: 'end' };
} }
function peg$otherExpectation(description) { function peg$otherExpectation(description) {
return { type: "other", description: description }; return { type: 'other', description: description };
} }
function peg$computePosDetails(pos) { function peg$computePosDetails(pos) {
var details = peg$posDetailsCache[pos], p; var details = peg$posDetailsCache[pos],
p;
if (details) { if (details) {
return details; return details;
@ -252,7 +310,7 @@ function peg$parse(input, options) {
details = peg$posDetailsCache[p]; details = peg$posDetailsCache[p];
details = { details = {
line: details.line, line: details.line,
column: details.column column: details.column,
}; };
while (p < pos) { while (p < pos) {
@ -279,18 +337,20 @@ function peg$parse(input, options) {
start: { start: {
offset: startPos, offset: startPos,
line: startPosDetails.line, line: startPosDetails.line,
column: startPosDetails.column column: startPosDetails.column,
}, },
end: { end: {
offset: endPos, offset: endPos,
line: endPosDetails.line, line: endPosDetails.line,
column: endPosDetails.column column: endPosDetails.column,
} },
}; };
} }
function peg$fail(expected) { function peg$fail(expected) {
if (peg$currPos < peg$maxFailPos) { return; } if (peg$currPos < peg$maxFailPos) {
return;
}
if (peg$currPos > peg$maxFailPos) { if (peg$currPos > peg$maxFailPos) {
peg$maxFailPos = peg$currPos; peg$maxFailPos = peg$currPos;
@ -329,7 +389,9 @@ function peg$parse(input, options) {
peg$currPos++; peg$currPos++;
} else { } else {
s0 = peg$FAILED; s0 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$c1); } if (peg$silentFails === 0) {
peg$fail(peg$c1);
}
} }
return s0; return s0;
@ -343,7 +405,9 @@ function peg$parse(input, options) {
peg$currPos++; peg$currPos++;
} else { } else {
s0 = peg$FAILED; s0 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$c3); } if (peg$silentFails === 0) {
peg$fail(peg$c3);
}
} }
return s0; return s0;
@ -357,7 +421,9 @@ function peg$parse(input, options) {
peg$currPos++; peg$currPos++;
} else { } else {
s0 = peg$FAILED; s0 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$c5); } if (peg$silentFails === 0) {
peg$fail(peg$c5);
}
} }
return s0; return s0;
@ -371,7 +437,9 @@ function peg$parse(input, options) {
peg$currPos++; peg$currPos++;
} else { } else {
s0 = peg$FAILED; s0 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$c7); } if (peg$silentFails === 0) {
peg$fail(peg$c7);
}
} }
return s0; return s0;
@ -385,7 +453,9 @@ function peg$parse(input, options) {
peg$currPos++; peg$currPos++;
} else { } else {
s0 = peg$FAILED; s0 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$c9); } if (peg$silentFails === 0) {
peg$fail(peg$c9);
}
} }
return s0; return s0;
@ -524,7 +594,9 @@ function peg$parse(input, options) {
peg$currPos++; peg$currPos++;
} else { } else {
s0 = peg$FAILED; s0 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$c15); } if (peg$silentFails === 0) {
peg$fail(peg$c15);
}
} }
return s0; return s0;
@ -538,7 +610,9 @@ function peg$parse(input, options) {
peg$currPos++; peg$currPos++;
} else { } else {
s0 = peg$FAILED; s0 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$c17); } if (peg$silentFails === 0) {
peg$fail(peg$c17);
}
} }
return s0; return s0;
@ -565,7 +639,9 @@ function peg$parse(input, options) {
peg$currPos++; peg$currPos++;
} else { } else {
s0 = peg$FAILED; s0 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$c19); } if (peg$silentFails === 0) {
peg$fail(peg$c19);
}
} }
return s0; return s0;
@ -579,7 +655,9 @@ function peg$parse(input, options) {
peg$currPos++; peg$currPos++;
} else { } else {
s0 = peg$FAILED; s0 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$c21); } if (peg$silentFails === 0) {
peg$fail(peg$c21);
}
} }
return s0; return s0;
@ -618,7 +696,9 @@ function peg$parse(input, options) {
peg$currPos++; peg$currPos++;
} else { } else {
s2 = peg$FAILED; s2 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$c24); } if (peg$silentFails === 0) {
peg$fail(peg$c24);
}
} }
if (s2 !== peg$FAILED) { if (s2 !== peg$FAILED) {
if (peg$c23.test(input.charAt(peg$currPos))) { if (peg$c23.test(input.charAt(peg$currPos))) {
@ -626,7 +706,9 @@ function peg$parse(input, options) {
peg$currPos++; peg$currPos++;
} else { } else {
s3 = peg$FAILED; s3 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$c24); } if (peg$silentFails === 0) {
peg$fail(peg$c24);
}
} }
if (s3 !== peg$FAILED) { if (s3 !== peg$FAILED) {
s2 = [s2, s3]; s2 = [s2, s3];
@ -658,7 +740,9 @@ function peg$parse(input, options) {
peg$currPos++; peg$currPos++;
} else { } else {
s2 = peg$FAILED; s2 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$c27); } if (peg$silentFails === 0) {
peg$fail(peg$c27);
}
} }
if (s2 !== peg$FAILED) { if (s2 !== peg$FAILED) {
while (s2 !== peg$FAILED) { while (s2 !== peg$FAILED) {
@ -668,7 +752,9 @@ function peg$parse(input, options) {
peg$currPos++; peg$currPos++;
} else { } else {
s2 = peg$FAILED; s2 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$c27); } if (peg$silentFails === 0) {
peg$fail(peg$c27);
}
} }
} }
} else { } else {
@ -804,7 +890,9 @@ function peg$parse(input, options) {
peg$currPos++; peg$currPos++;
} else { } else {
s2 = peg$FAILED; s2 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$c33); } if (peg$silentFails === 0) {
peg$fail(peg$c33);
}
} }
if (s2 !== peg$FAILED) { if (s2 !== peg$FAILED) {
while (s2 !== peg$FAILED) { while (s2 !== peg$FAILED) {
@ -814,7 +902,9 @@ function peg$parse(input, options) {
peg$currPos++; peg$currPos++;
} else { } else {
s2 = peg$FAILED; s2 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$c33); } if (peg$silentFails === 0) {
peg$fail(peg$c33);
}
} }
} }
} else { } else {
@ -843,7 +933,6 @@ function peg$parse(input, options) {
return s0; return s0;
} }
const UserProps = require('./user_property.js'); const UserProps = require('./user_property.js');
const Log = require('./logger.js').log; const Log = require('./logger.js').log;
const User = require('./user.js'); const User = require('./user.js');
@ -870,15 +959,24 @@ function peg$parse(input, options) {
if (!Array.isArray(value)) { if (!Array.isArray(value)) {
value = [value]; value = [value];
} }
const userAccountStatus = user.getPropertyAsNumber(UserProps.AccountStatus); const userAccountStatus = user.getPropertyAsNumber(
UserProps.AccountStatus
);
return value.map(n => parseInt(n, 10)).includes(userAccountStatus); return value.map(n => parseInt(n, 10)).includes(userAccountStatus);
}, },
EC: function isEncoding() { EC: function isEncoding() {
const encoding = _.get(client, 'term.outputEncoding', '').toLowerCase(); const encoding = _.get(
client,
'term.outputEncoding',
''
).toLowerCase();
switch (value) { switch (value) {
case 0 : return 'cp437' === encoding; case 0:
case 1 : return 'utf-8' === encoding; return 'cp437' === encoding;
default : return false; case 1:
return 'utf-8' === encoding;
default:
return false;
} }
}, },
GM: function isOneOfGroups() { GM: function isOneOfGroups() {
@ -917,19 +1015,24 @@ function peg$parse(input, options) {
if (!user) { if (!user) {
return false; return false;
} }
const accountCreated = moment(user.getProperty(UserProps.AccountCreated)); const accountCreated = moment(
user.getProperty(UserProps.AccountCreated)
);
const now = moment(); const now = moment();
const daysOld = accountCreated.diff(moment(), 'days'); const daysOld = accountCreated.diff(moment(), 'days');
return !isNaN(value) && return (
!isNaN(value) &&
accountCreated.isValid() && accountCreated.isValid() &&
now.isAfter(accountCreated) && now.isAfter(accountCreated) &&
daysOld >= value; daysOld >= value
);
}, },
BU: function bytesUploaded() { BU: function bytesUploaded() {
if (!user) { if (!user) {
return false; return false;
} }
const bytesUp = user.getPropertyAsNumber(UserProps.FileUlTotalBytes) || 0; const bytesUp =
user.getPropertyAsNumber(UserProps.FileUlTotalBytes) || 0;
return !isNaN(value) && bytesUp >= value; return !isNaN(value) && bytesUp >= value;
}, },
UP: function uploads() { UP: function uploads() {
@ -943,7 +1046,8 @@ function peg$parse(input, options) {
if (!user) { if (!user) {
return false; return false;
} }
const bytesDown = user.getPropertyAsNumber(UserProps.FileDlTotalBytes) || 0; const bytesDown =
user.getPropertyAsNumber(UserProps.FileDlTotalBytes) || 0;
return !isNaN(value) && bytesDown >= value; return !isNaN(value) && bytesDown >= value;
}, },
DL: function downloads() { DL: function downloads() {
@ -957,8 +1061,10 @@ function peg$parse(input, options) {
if (!user) { if (!user) {
return false; return false;
} }
const ulCount = user.getPropertyAsNumber(UserProps.FileUlTotalCount) || 0; const ulCount =
const dlCount = user.getPropertyAsNumber(UserProps.FileDlTotalCount) || 0; user.getPropertyAsNumber(UserProps.FileUlTotalCount) || 0;
const dlCount =
user.getPropertyAsNumber(UserProps.FileDlTotalCount) || 0;
const ratio = ~~((ulCount / dlCount) * 100); const ratio = ~~((ulCount / dlCount) * 100);
return !isNaN(value) && ratio >= value; return !isNaN(value) && ratio >= value;
}, },
@ -966,8 +1072,10 @@ function peg$parse(input, options) {
if (!user) { if (!user) {
return false; return false;
} }
const ulBytes = user.getPropertyAsNumber(UserProps.FileUlTotalBytes) || 0; const ulBytes =
const dlBytes = user.getPropertyAsNumber(UserProps.FileDlTotalBytes) || 0; user.getPropertyAsNumber(UserProps.FileUlTotalBytes) || 0;
const dlBytes =
user.getPropertyAsNumber(UserProps.FileDlTotalBytes) || 0;
const ratio = ~~((ulBytes / dlBytes) * 100); const ratio = ~~((ulBytes / dlBytes) * 100);
return !isNaN(value) && ratio >= value; return !isNaN(value) && ratio >= value;
}, },
@ -976,7 +1084,8 @@ function peg$parse(input, options) {
return false; return false;
} }
const postCount = user.getPropertyAsNumber(UserProps.PostCount) || 0; const postCount = user.getPropertyAsNumber(UserProps.PostCount) || 0;
const loginCount = user.getPropertyAsNumber(UserProps.LoginCount) || 0; const loginCount =
user.getPropertyAsNumber(UserProps.LoginCount) || 0;
const ratio = ~~((postCount / loginCount) * 100); const ratio = ~~((postCount / loginCount) * 100);
return !isNaN(value) && ratio >= value; return !isNaN(value) && ratio >= value;
}, },
@ -994,9 +1103,14 @@ function peg$parse(input, options) {
return false; return false;
} }
switch (value) { switch (value) {
case 1 : return true; case 1:
case 2 : return user.getProperty(UserProps.AuthFactor2OTP) ? true : false; return true;
default : return false; case 2:
return user.getProperty(UserProps.AuthFactor2OTP)
? true
: false;
default:
return false;
} }
}, },
ML: function minutesLeft() { ML: function minutesLeft() {
@ -1038,7 +1152,7 @@ function peg$parse(input, options) {
}, },
MM: function isMinutesPastMidnight() { MM: function isMinutesPastMidnight() {
const now = moment(); const now = moment();
const midnight = now.clone().startOf('day') const midnight = now.clone().startOf('day');
const minutesPastMidnight = now.diff(midnight, 'minutes'); const minutesPastMidnight = now.diff(midnight, 'minutes');
return !isNaN(value) && minutesPastMidnight >= value; return !isNaN(value) && minutesPastMidnight >= value;
}, },
@ -1046,14 +1160,16 @@ function peg$parse(input, options) {
if (!user) { if (!user) {
return false; return false;
} }
const count = user.getPropertyAsNumber(UserProps.AchievementTotalCount) || 0; const count =
user.getPropertyAsNumber(UserProps.AchievementTotalCount) || 0;
return !isNan(value) && points >= value; return !isNan(value) && points >= value;
}, },
AP: function achievementPoints() { AP: function achievementPoints() {
if (!user) { if (!user) {
return false; return false;
} }
const points = user.getPropertyAsNumber(UserProps.AchievementTotalPoints) || 0; const points =
user.getPropertyAsNumber(UserProps.AchievementTotalPoints) || 0;
return !isNan(value) && points >= value; return !isNan(value) && points >= value;
}, },
PV: function userPropValue() { PV: function userPropValue() {
@ -1063,7 +1179,7 @@ function peg$parse(input, options) {
const [propName, propValue] = value; const [propName, propValue] = value;
const actualPropValue = user.getProperty(propName); const actualPropValue = user.getProperty(propName);
return actualPropValue === propValue; return actualPropValue === propValue;
} },
}[acsCode](value); }[acsCode](value);
} catch (e) { } catch (e) {
const logger = _.get(client, 'log', Log); const logger = _.get(client, 'log', Log);
@ -1073,7 +1189,6 @@ function peg$parse(input, options) {
} }
} }
peg$result = peg$startRuleFunction(); peg$result = peg$startRuleFunction();
if (peg$result !== peg$FAILED && peg$currPos === input.length) { if (peg$result !== peg$FAILED && peg$currPos === input.length) {
@ -1095,5 +1210,5 @@ function peg$parse(input, options) {
module.exports = { module.exports = {
SyntaxError: peg$SyntaxError, SyntaxError: peg$SyntaxError,
parse: peg$parse parse: peg$parse,
}; };

View File

@ -34,13 +34,11 @@ function ANSIEscapeParser(options) {
trailingLF: 'default', // default|omit|no|yes, ... trailingLF: 'default', // default|omit|no|yes, ...
}); });
this.mciReplaceChar = miscUtil.valueWithDefault(options.mciReplaceChar, ''); this.mciReplaceChar = miscUtil.valueWithDefault(options.mciReplaceChar, '');
this.termHeight = miscUtil.valueWithDefault(options.termHeight, 25); this.termHeight = miscUtil.valueWithDefault(options.termHeight, 25);
this.termWidth = miscUtil.valueWithDefault(options.termWidth, 80); this.termWidth = miscUtil.valueWithDefault(options.termWidth, 80);
this.trailingLF = miscUtil.valueWithDefault(options.trailingLF, 'default'); this.trailingLF = miscUtil.valueWithDefault(options.trailingLF, 'default');
this.row = Math.min(options?.startRow ?? 1, this.termHeight); this.row = Math.min(options?.startRow ?? 1, this.termHeight);
self.moveCursor = function (cols, rows) { self.moveCursor = function (cols, rows) {
@ -57,7 +55,7 @@ function ANSIEscapeParser(options) {
self.saveCursorPosition = function () { self.saveCursorPosition = function () {
self.savedPosition = { self.savedPosition = {
row: self.row, row: self.row,
column : self.column column: self.column,
}; };
}; };
@ -76,7 +74,6 @@ function ANSIEscapeParser(options) {
self.emit('clear screen'); self.emit('clear screen');
}; };
self.positionUpdated = function () { self.positionUpdated = function () {
self.emit('position update', self.row, self.column); self.emit('position update', self.row, self.column);
}; };
@ -181,32 +178,36 @@ function ANSIEscapeParser(options) {
// if MCI codes are changing, save off the current color // if MCI codes are changing, save off the current color
var fullMciCode = mciCode + (id || ''); var fullMciCode = mciCode + (id || '');
if (self.lastMciCode !== fullMciCode) { if (self.lastMciCode !== fullMciCode) {
self.lastMciCode = fullMciCode; self.lastMciCode = fullMciCode;
self.graphicRenditionForErase = _.clone(self.graphicRendition); self.graphicRenditionForErase = _.clone(self.graphicRendition);
} }
self.emit('mci', { self.emit('mci', {
position: [self.row, self.column], position: [self.row, self.column],
mci: mciCode, mci: mciCode,
id: id ? parseInt(id, 10) : null, id: id ? parseInt(id, 10) : null,
args: args, args: args,
SGR : ansi.getSGRFromGraphicRendition(self.graphicRendition, true) SGR: ansi.getSGRFromGraphicRendition(self.graphicRendition, true),
}); });
if (self.mciReplaceChar.length > 0) { if (self.mciReplaceChar.length > 0) {
const sgrCtrl = ansi.getSGRFromGraphicRendition(self.graphicRenditionForErase); const sgrCtrl = ansi.getSGRFromGraphicRendition(
self.graphicRenditionForErase
);
self.emit('control', sgrCtrl, 'm', sgrCtrl.slice(2).split(/[;m]/).slice(0, 3)); self.emit(
'control',
sgrCtrl,
'm',
sgrCtrl.slice(2).split(/[;m]/).slice(0, 3)
);
literal(new Array(match[0].length + 1).join(self.mciReplaceChar)); literal(new Array(match[0].length + 1).join(self.mciReplaceChar));
} else { } else {
literal(match[0]); literal(match[0]);
} }
} }
} while (0 !== mciRe.lastIndex); } while (0 !== mciRe.lastIndex);
if (pos < buffer.length) { if (pos < buffer.length) {
@ -449,7 +450,10 @@ function ANSIEscapeParser(options) {
break; break;
default: default:
Log.trace( { attribute : arg }, 'Unknown attribute while parsing ANSI'); Log.trace(
{ attribute: arg },
'Unknown attribute while parsing ANSI'
);
break; break;
} }
} }
@ -484,7 +488,7 @@ ANSIEscapeParser.foregroundColors = {
37: 'white', 37: 'white',
39: 'default', // same as white for most implementations 39: 'default', // same as white for most implementations
90 : 'grey' 90: 'grey',
}; };
Object.freeze(ANSIEscapeParser.foregroundColors); Object.freeze(ANSIEscapeParser.foregroundColors);
@ -532,4 +536,3 @@ ANSIEscapeParser.styles = {
28: 'invisibleOff', // Not supported by most BBS-like terminals 28: 'invisibleOff', // Not supported by most BBS-like terminals
}; };
Object.freeze(ANSIEscapeParser.styles); Object.freeze(ANSIEscapeParser.styles);

View File

@ -4,10 +4,7 @@
// ENiGMA½ // ENiGMA½
const ANSIEscapeParser = require('./ansi_escape_parser.js').ANSIEscapeParser; const ANSIEscapeParser = require('./ansi_escape_parser.js').ANSIEscapeParser;
const ANSI = require('./ansi_term.js'); const ANSI = require('./ansi_term.js');
const { const { splitTextAtTerms, renderStringLength } = require('./string_util.js');
splitTextAtTerms,
renderStringLength
} = require('./string_util.js');
// deps // deps
const _ = require('lodash'); const _ = require('lodash');
@ -27,8 +24,14 @@ module.exports = function ansiPrep(input, options, cb) {
options.indent = options.indent || 0; options.indent = options.indent || 0;
// in auto we start out at 25 rows, but can always expand for more // in auto we start out at 25 rows, but can always expand for more
const canvas = Array.from( { length : 'auto' === options.rows ? 25 : options.rows }, () => Array.from( { length : options.cols}, () => new Object() ) ); const canvas = Array.from(
const parser = new ANSIEscapeParser( { termHeight : options.termHeight, termWidth : options.termWidth } ); { length: 'auto' === options.rows ? 25 : options.rows },
() => Array.from({ length: options.cols }, () => new Object())
);
const parser = new ANSIEscapeParser({
termHeight: options.termHeight,
termWidth: options.termWidth,
});
const state = { const state = {
row: 0, row: 0,
@ -63,7 +66,10 @@ module.exports = function ansiPrep(input, options, cb) {
literal = literal.replace(/\r?\n|[\r\u2028\u2029]/g, ''); literal = literal.replace(/\r?\n|[\r\u2028\u2029]/g, '');
for (let c of literal) { for (let c of literal) {
if(state.col < options.cols && ('auto' === options.rows || state.row < options.rows)) { if (
state.col < options.cols &&
('auto' === options.rows || state.row < options.rows)
) {
ensureRow(state.row); ensureRow(state.row);
if (0 === state.col) { if (0 === state.col) {
@ -113,16 +119,21 @@ module.exports = function ansiPrep(input, options, cb) {
const lastCol = getLastPopulatedColumn(row) + 1; const lastCol = getLastPopulatedColumn(row) + 1;
let i; let i;
line = options.indent ? line = options.indent
output.length > 0 ? ' '.repeat(options.indent) : '' : ? output.length > 0
''; ? ' '.repeat(options.indent)
: ''
: '';
for (i = 0; i < lastCol; ++i) { for (i = 0; i < lastCol; ++i) {
const col = row[i]; const col = row[i];
sgr = !options.asciiMode && 0 === i ? sgr =
col.initialSgr ? ANSI.getSGRFromGraphicRendition(col.initialSgr) : '' : !options.asciiMode && 0 === i
''; ? col.initialSgr
? ANSI.getSGRFromGraphicRendition(col.initialSgr)
: ''
: '';
if (!options.asciiMode && col.sgr) { if (!options.asciiMode && col.sgr) {
sgr += ANSI.getSGRFromGraphicRendition(col.sgr); sgr += ANSI.getSGRFromGraphicRendition(col.sgr);
@ -136,7 +147,10 @@ module.exports = function ansiPrep(input, options, cb) {
if (i < row.length) { if (i < row.length) {
output += `${options.asciiMode ? '' : ANSI.blackBG()}`; output += `${options.asciiMode ? '' : ANSI.blackBG()}`;
if (options.fillLines) { if (options.fillLines) {
output += `${row.slice(i).map( () => ' ').join('')}`;//${lastSgr}`; output += `${row
.slice(i)
.map(() => ' ')
.join('')}`; //${lastSgr}`;
} }
} }
@ -202,7 +216,8 @@ module.exports = function ansiPrep(input, options, cb) {
renderStart += renderStringLength(part); renderStart += renderStringLength(part);
exportOutput += `${part}\r\n`; exportOutput += `${part}\r\n`;
if(fullLine.length > 0) { // more to go for this line? if (fullLine.length > 0) {
// more to go for this line?
exportOutput += `${ANSI.up()}${ANSI.right(renderStart)}`; exportOutput += `${ANSI.up()}${ANSI.right(renderStart)}`;
} else { } else {
exportOutput += ANSI.up(); exportOutput += ANSI.up();

View File

@ -194,7 +194,10 @@ const SGRValues = {
function getFullMatchRegExp(flags = 'g') { function getFullMatchRegExp(flags = 'g') {
// :TODO: expand this a bit - see strip-ansi/etc. // :TODO: expand this a bit - see strip-ansi/etc.
// :TODO: \u009b ? // :TODO: \u009b ?
return new RegExp(/[\u001b][[()#;?]*([0-9]{1,4}(?:;[0-9]{0,4})*)?([0-9A-ORZcf-npqrsuy=><])/, flags); // eslint-disable-line no-control-regex return new RegExp(
/[\u001b][[()#;?]*([0-9]{1,4}(?:;[0-9]{0,4})*)?([0-9A-ORZcf-npqrsuy=><])/,
flags
); // eslint-disable-line no-control-regex
} }
function getFGColorValue(name) { function getFGColorValue(name) {
@ -205,7 +208,6 @@ function getBGColorValue(name) {
return SGRValues[name + 'BG']; return SGRValues[name + 'BG'];
} }
// See http://cvs.synchro.net/cgi-bin/viewcvs.cgi/*checkout*/src/conio/cterm.txt // See http://cvs.synchro.net/cgi-bin/viewcvs.cgi/*checkout*/src/conio/cterm.txt
// :TODO: document // :TODO: document
// :TODO: Create mappings for aliases... maybe make this a map to values instead // :TODO: Create mappings for aliases... maybe make this a map to values instead
@ -275,49 +277,48 @@ const SYNCTERM_FONT_AND_ENCODING_TABLE = [
// replaced with '_' for lookup purposes. // replaced with '_' for lookup purposes.
// //
const FONT_ALIAS_TO_SYNCTERM_MAP = { const FONT_ALIAS_TO_SYNCTERM_MAP = {
'cp437' : 'cp437', cp437: 'cp437',
'ibm_vga' : 'cp437', ibm_vga: 'cp437',
'ibmpc' : 'cp437', ibmpc: 'cp437',
'ibm_pc' : 'cp437', ibm_pc: 'cp437',
'pc' : 'cp437', pc: 'cp437',
'cp437_art' : 'cp437', cp437_art: 'cp437',
'ibmpcart' : 'cp437', ibmpcart: 'cp437',
'ibmpc_art' : 'cp437', ibmpc_art: 'cp437',
'ibm_pc_art' : 'cp437', ibm_pc_art: 'cp437',
'msdos_art' : 'cp437', msdos_art: 'cp437',
'msdosart' : 'cp437', msdosart: 'cp437',
'pc_art' : 'cp437', pc_art: 'cp437',
'pcart' : 'cp437', pcart: 'cp437',
'ibm_vga50' : 'cp437', ibm_vga50: 'cp437',
'ibm_vga25g' : 'cp437', ibm_vga25g: 'cp437',
'ibm_ega' : 'cp437', ibm_ega: 'cp437',
'ibm_ega43' : 'cp437', ibm_ega43: 'cp437',
'topaz' : 'topaz', topaz: 'topaz',
'amiga_topaz_1' : 'topaz', amiga_topaz_1: 'topaz',
'amiga_topaz_1+': 'topaz_plus', 'amiga_topaz_1+': 'topaz_plus',
'topazplus' : 'topaz_plus', topazplus: 'topaz_plus',
'topaz_plus' : 'topaz_plus', topaz_plus: 'topaz_plus',
'amiga_topaz_2' : 'topaz', amiga_topaz_2: 'topaz',
'amiga_topaz_2+': 'topaz_plus', 'amiga_topaz_2+': 'topaz_plus',
'topaz2plus' : 'topaz_plus', topaz2plus: 'topaz_plus',
'pot_noodle' : 'pot_noodle', pot_noodle: 'pot_noodle',
'p0tnoodle' : 'pot_noodle', p0tnoodle: 'pot_noodle',
'amiga_p0t-noodle': 'pot_noodle', 'amiga_p0t-noodle': 'pot_noodle',
'mo_soul' : 'mo_soul', mo_soul: 'mo_soul',
'mosoul' : 'mo_soul', mosoul: 'mo_soul',
'mo\'soul' : 'mo_soul', "mo'soul": 'mo_soul',
'amiga_mosoul' : 'mo_soul', amiga_mosoul: 'mo_soul',
'amiga_microknight' : 'microknight', amiga_microknight: 'microknight',
'amiga_microknight+': 'microknight_plus', 'amiga_microknight+': 'microknight_plus',
'atari' : 'atari', atari: 'atari',
'atarist' : 'atari', atarist: 'atari',
}; };
function setSyncTermFont(name, fontPage) { function setSyncTermFont(name, fontPage) {
@ -344,7 +345,7 @@ function setSyncTermFontWithAlias(nameOrAlias) {
const DEC_CURSOR_STYLE = { const DEC_CURSOR_STYLE = {
'blinking block': 0, 'blinking block': 0,
'default' : 1, default: 1,
'steady block': 2, 'steady block': 2,
'blinking underline': 3, 'blinking underline': 3,
'steady underline': 4, 'steady underline': 4,
@ -358,7 +359,6 @@ function setCursorStyle(cursorStyle) {
return `${ESC_CSI}${ps} q`; return `${ESC_CSI}${ps} q`;
} }
return ''; return '';
} }
// Create methods such as up(), nextLine(),... // Create methods such as up(), nextLine(),...
@ -476,7 +476,8 @@ function disableVT100LineWrapping() {
} }
function setEmulatedBaudRate(rate) { function setEmulatedBaudRate(rate) {
const speed = { const speed =
{
unlimited: 0, unlimited: 0,
off: 0, off: 0,
0: 0, 0: 0,
@ -502,6 +503,9 @@ function vtxHyperlink(client, url, len) {
len = len || url.length; len = len || url.length;
url = url.split('').map(c => c.charCodeAt(0)).join(';'); url = url
.split('')
.map(c => c.charCodeAt(0))
.join(';');
return `${ESC_CSI}1;${len};1;1;${url}\\`; return `${ESC_CSI}1;${len};1;1;${url}\\`;
} }

View File

@ -38,7 +38,9 @@ exports.getModule = class ArchaicNETModule extends MenuModule {
const reqConfs = ['username', 'password', 'bbsTag']; const reqConfs = ['username', 'password', 'bbsTag'];
for (let req of reqConfs) { for (let req of reqConfs) {
if (!_.isString(_.get(self, ['config', req]))) { if (!_.isString(_.get(self, ['config', req]))) {
return callback(Errors.MissingConfig(`Config requires "${req}"`)); return callback(
Errors.MissingConfig(`Config requires "${req}"`)
);
} }
} }
return callback(null); return callback(null);
@ -61,14 +63,21 @@ exports.getModule = class ArchaicNETModule extends MenuModule {
sshClient.on('ready', () => { sshClient.on('ready', () => {
// track client termination so we can clean up early // track client termination so we can clean up early
self.client.once('end', () => { self.client.once('end', () => {
self.client.log.info('Connection ended. Terminating ArchaicNET connection'); self.client.log.info(
'Connection ended. Terminating ArchaicNET connection'
);
clientTerminated = true; clientTerminated = true;
return sshClient.end(); return sshClient.end();
}); });
// establish tunnel for rlogin // establish tunnel for rlogin
const fwdPort = self.config.rloginPort + self.client.node; const fwdPort = self.config.rloginPort + self.client.node;
sshClient.forwardOut('127.0.0.1', fwdPort, self.config.host, self.config.rloginPort, (err, stream) => { sshClient.forwardOut(
'127.0.0.1',
fwdPort,
self.config.host,
self.config.rloginPort,
(err, stream) => {
if (err) { if (err) {
return sshClient.end(); return sshClient.end();
} }
@ -81,13 +90,17 @@ exports.getModule = class ArchaicNETModule extends MenuModule {
// we need to filter I/O for escape/de-escaping zmodem and the like // we need to filter I/O for escape/de-escaping zmodem and the like
self.client.setTemporaryDirectDataHandler(data => { self.client.setTemporaryDirectDataHandler(data => {
const tmp = data.toString('binary').replace(/\xff{2}/g, '\xff'); // de-escape const tmp = data
.toString('binary')
.replace(/\xff{2}/g, '\xff'); // de-escape
stream.write(Buffer.from(tmp, 'binary')); stream.write(Buffer.from(tmp, 'binary'));
}); });
needRestore = true; needRestore = true;
stream.on('data', data => { stream.on('data', data => {
const tmp = data.toString('binary').replace(/\xff/g, '\xff\xff'); // escape const tmp = data
.toString('binary')
.replace(/\xff/g, '\xff\xff'); // escape
self.client.term.rawWrite(Buffer.from(tmp, 'binary')); self.client.term.rawWrite(Buffer.from(tmp, 'binary'));
}); });
@ -95,11 +108,14 @@ exports.getModule = class ArchaicNETModule extends MenuModule {
restorePipe(); restorePipe();
return sshClient.end(); return sshClient.end();
}); });
}); }
);
}); });
sshClient.on('error', err => { sshClient.on('error', err => {
return self.client.log.info(`ArchaicNET SSH client error: ${err.message}`); return self.client.log.info(
`ArchaicNET SSH client error: ${err.message}`
);
}); });
sshClient.on('close', hadError => { sshClient.on('close', hadError => {
@ -110,14 +126,17 @@ exports.getModule = class ArchaicNETModule extends MenuModule {
return callback(null); return callback(null);
}); });
self.client.log.trace( { host : self.config.host, port : self.config.sshPort }, 'Connecting to ArchaicNET'); self.client.log.trace(
{ host: self.config.host, port: self.config.sshPort },
'Connecting to ArchaicNET'
);
sshClient.connect({ sshClient.connect({
host: self.config.host, host: self.config.host,
port: self.config.sshPort, port: self.config.sshPort,
username: self.config.username, username: self.config.username,
password: self.config.password, password: self.config.password,
}); });
} },
], ],
err => { err => {
if (err) { if (err) {
@ -132,4 +151,3 @@ exports.getModule = class ArchaicNETModule extends MenuModule {
); );
} }
}; };

View File

@ -33,17 +33,28 @@ class Archiver {
return false; return false;
} }
return _.isString(this[what].cmd) && Array.isArray(this[what].args) && this[what].args.length > 0; return (
_.isString(this[what].cmd) &&
Array.isArray(this[what].args) &&
this[what].args.length > 0
);
} }
canCompress() { return this.can('compress'); } canCompress() {
canDecompress() { return this.can('decompress'); } return this.can('compress');
canList() { return this.can('list'); } // :TODO: validate entryMatch }
canExtract() { return this.can('extract'); } canDecompress() {
return this.can('decompress');
}
canList() {
return this.can('list');
} // :TODO: validate entryMatch
canExtract() {
return this.can('extract');
}
} }
module.exports = class ArchiveUtil { module.exports = class ArchiveUtil {
constructor() { constructor() {
this.archivers = {}; this.archivers = {};
this.longestSignature = 0; this.longestSignature = 0;
@ -71,7 +82,6 @@ module.exports = class ArchiveUtil {
const config = Config(); const config = Config();
if (_.has(config, 'archives.archivers')) { if (_.has(config, 'archives.archivers')) {
Object.keys(config.archives.archivers).forEach(archKey => { Object.keys(config.archives.archivers).forEach(archKey => {
const archConfig = config.archives.archivers[archKey]; const archConfig = config.archives.archivers[archKey];
const archiver = new Archiver(archConfig); const archiver = new Archiver(archConfig);
@ -84,7 +94,7 @@ module.exports = class ArchiveUtil {
} }
if (_.isObject(config.fileTypes)) { if (_.isObject(config.fileTypes)) {
const updateSig = (ft) => { const updateSig = ft => {
ft.sig = Buffer.from(ft.sig, 'hex'); ft.sig = Buffer.from(ft.sig, 'hex');
ft.offset = ft.offset || 0; ft.offset = ft.offset || 0;
@ -113,7 +123,8 @@ module.exports = class ArchiveUtil {
getArchiver(mimeTypeOrExtension, justExtention) { getArchiver(mimeTypeOrExtension, justExtention) {
const mimeType = resolveMimeType(mimeTypeOrExtension); const mimeType = resolveMimeType(mimeTypeOrExtension);
if(!mimeType) { // lookup returns false on failure if (!mimeType) {
// lookup returns false on failure
return; return;
} }
@ -149,8 +160,10 @@ module.exports = class ArchiveUtil {
*/ */
detectType(path, cb) { detectType(path, cb) {
const closeFile = (fd) => { const closeFile = fd => {
fs.close(fd, () => { /* sadface */ }); fs.close(fd, () => {
/* sadface */
});
}; };
fs.open(path, 'r', (err, fd) => { fs.open(path, 'r', (err, fd) => {
@ -166,7 +179,9 @@ module.exports = class ArchiveUtil {
} }
const archFormat = _.findKey(Config().fileTypes, fileTypeInfo => { const archFormat = _.findKey(Config().fileTypes, fileTypeInfo => {
const fileTypeInfos = Array.isArray(fileTypeInfo) ? fileTypeInfo : [ fileTypeInfo ]; const fileTypeInfos = Array.isArray(fileTypeInfo)
? fileTypeInfo
: [fileTypeInfo];
return fileTypeInfos.find(fti => { return fileTypeInfos.find(fti => {
if (!fti.sig || !fti.archiveHandler) { if (!fti.sig || !fti.archiveHandler) {
return false; return false;
@ -179,7 +194,7 @@ module.exports = class ArchiveUtil {
} }
const comp = buf.slice(fti.offset, fti.offset + fti.sig.length); const comp = buf.slice(fti.offset, fti.offset + fti.sig.length);
return (fti.sig.equals(comp)); return fti.sig.equals(comp);
}); });
}); });
@ -200,7 +215,13 @@ module.exports = class ArchiveUtil {
}); });
proc.once('exit', exitCode => { proc.once('exit', exitCode => {
return cb(exitCode ? Errors.ExternalProcess(`${action} failed with exit code: ${exitCode}`) : err); return cb(
exitCode
? Errors.ExternalProcess(
`${action} failed with exit code: ${exitCode}`
)
: err
);
}); });
} }
@ -236,8 +257,12 @@ module.exports = class ArchiveUtil {
try { try {
proc = pty.spawn(archiver.compress.cmd, args, this.getPtyOpts(workDir)); proc = pty.spawn(archiver.compress.cmd, args, this.getPtyOpts(workDir));
} catch (e) { } catch (e) {
return cb(Errors.ExternalProcess( return cb(
`Error spawning archiver process "${archiver.compress.cmd}" with args "${args.join(' ')}": ${e.message}`) Errors.ExternalProcess(
`Error spawning archiver process "${
archiver.compress.cmd
}" with args "${args.join(' ')}": ${e.message}`
)
); );
} }
@ -288,12 +313,16 @@ module.exports = class ArchiveUtil {
try { try {
proc = pty.spawn(archiver[action].cmd, args, this.getPtyOpts(extractPath)); proc = pty.spawn(archiver[action].cmd, args, this.getPtyOpts(extractPath));
} catch (e) { } catch (e) {
return cb(Errors.ExternalProcess( return cb(
`Error spawning archiver process "${archiver[action].cmd}" with args "${args.join(' ')}": ${e.message}`) Errors.ExternalProcess(
`Error spawning archiver process "${
archiver[action].cmd
}" with args "${args.join(' ')}": ${e.message}`
)
); );
} }
return this.spawnHandler(proc, (haveFileList ? 'Extraction' : 'Decompression'), cb); return this.spawnHandler(proc, haveFileList ? 'Extraction' : 'Decompression', cb);
} }
listEntries(archivePath, archType, cb) { listEntries(archivePath, archType, cb) {
@ -313,8 +342,12 @@ module.exports = class ArchiveUtil {
try { try {
proc = pty.spawn(archiver.list.cmd, args, this.getPtyOpts()); proc = pty.spawn(archiver.list.cmd, args, this.getPtyOpts());
} catch (e) { } catch (e) {
return cb(Errors.ExternalProcess( return cb(
`Error spawning archiver process "${archiver.list.cmd}" with args "${args.join(' ')}": ${e.message}`) Errors.ExternalProcess(
`Error spawning archiver process "${
archiver.list.cmd
}" with args "${args.join(' ')}": ${e.message}`
)
); );
} }
@ -327,10 +360,15 @@ module.exports = class ArchiveUtil {
proc.once('exit', exitCode => { proc.once('exit', exitCode => {
if (exitCode) { if (exitCode) {
return cb(Errors.ExternalProcess(`List failed with exit code: ${exitCode}`)); return cb(
Errors.ExternalProcess(`List failed with exit code: ${exitCode}`)
);
} }
const entryGroupOrder = archiver.list.entryGroupOrder || { byteSize : 1, fileName : 2 }; const entryGroupOrder = archiver.list.entryGroupOrder || {
byteSize: 1,
fileName: 2,
};
const entries = []; const entries = [];
const entryMatchRe = new RegExp(archiver.list.entryMatch, 'gm'); const entryMatchRe = new RegExp(archiver.list.entryMatch, 'gm');

View File

@ -88,7 +88,10 @@ function getArtFromPath(path, options, cb) {
return iconv.decode(data, encoding); return iconv.decode(data, encoding);
} else { } else {
const eofMarker = defaultEofFromExtension(ext); const eofMarker = defaultEofFromExtension(ext);
return iconv.decode(eofMarker ? sliceAtEOF(data, eofMarker) : data, encoding); return iconv.decode(
eofMarker ? sliceAtEOF(data, eofMarker) : data,
encoding
);
} }
} }
@ -153,7 +156,9 @@ function getArt(name, options, cb) {
// If an extension is provided, just read the file now // If an extension is provided, just read the file now
if ('' !== ext) { if ('' !== ext) {
const directPath = paths.isAbsolute(name) ? name : paths.join(options.basePath, name); const directPath = paths.isAbsolute(name)
? name
: paths.join(options.basePath, name);
return getArtFromPath(directPath, options, cb); return getArtFromPath(directPath, options, cb);
} }
@ -210,7 +215,10 @@ function getArt(name, options, cb) {
// //
let readPath; let readPath;
if (options.random) { if (options.random) {
readPath = paths.join(options.basePath, filtered[Math.floor(Math.random() * filtered.length)]); readPath = paths.join(
options.basePath,
filtered[Math.floor(Math.random() * filtered.length)]
);
} else { } else {
assert(1 === filtered.length); assert(1 === filtered.length);
readPath = paths.join(options.basePath, filtered[0]); readPath = paths.join(options.basePath, filtered[0]);
@ -257,7 +265,7 @@ function display(client, art, options, cb) {
if (!_.isBoolean(options.iceColors)) { if (!_.isBoolean(options.iceColors)) {
// try to detect from SAUCE // try to detect from SAUCE
if(_.has(options, 'sauce.ansiFlags') && (options.sauce.ansiFlags & (1 << 0))) { if (_.has(options, 'sauce.ansiFlags') && options.sauce.ansiFlags & (1 << 0)) {
options.iceColors = true; options.iceColors = true;
} }
} }
@ -295,7 +303,6 @@ function display(client, art, options, cb) {
++generatedId; ++generatedId;
} }
} }
}); });
ansiParser.on('literal', literal => client.term.write(literal, false)); ansiParser.on('literal', literal => client.term.write(literal, false));

View File

@ -30,8 +30,7 @@ const ALL_ASSETS = [
]; ];
const ASSET_RE = new RegExp( const ASSET_RE = new RegExp(
'^@(' + ALL_ASSETS.join('|') + ')' + '^@(' + ALL_ASSETS.join('|') + ')' + /:(?:([^:]+):)?([A-Za-z0-9_\-.]+)$/.source
/:(?:([^:]+):)?([A-Za-z0-9_\-.]+)$/.source
); );
function parseAsset(s) { function parseAsset(s) {

View File

@ -27,12 +27,14 @@ const MciViewIds = {
exports.getModule = class UserAutoSigEditorModule extends MenuModule { exports.getModule = class UserAutoSigEditorModule extends MenuModule {
constructor(options) { constructor(options) {
super(options); super(options);
this.config = Object.assign({}, _.get(options, 'menuConfig.config'), { extraArgs : options.extraArgs }); this.config = Object.assign({}, _.get(options, 'menuConfig.config'), {
extraArgs: options.extraArgs,
});
this.menuMethods = { this.menuMethods = {
saveChanges: (formData, extraArgs, cb) => { saveChanges: (formData, extraArgs, cb) => {
return this.saveChanges(cb); return this.saveChanges(cb);
} },
}; };
} }
@ -44,18 +46,24 @@ exports.getModule = class UserAutoSigEditorModule extends MenuModule {
async.series( async.series(
[ [
(callback) => { callback => {
return this.prepViewController('edit', FormIds.edit, mciData.menu, callback); return this.prepViewController(
'edit',
FormIds.edit,
mciData.menu,
callback
);
}, },
(callback) => { callback => {
const requiredCodes = [MciViewIds.editor, MciViewIds.save]; const requiredCodes = [MciViewIds.editor, MciViewIds.save];
return this.validateMCIByViewIds('edit', requiredCodes, callback); return this.validateMCIByViewIds('edit', requiredCodes, callback);
}, },
(callback) => { callback => {
const sig = this.client.user.getProperty(UserProps.AutoSignature) || ''; const sig =
this.client.user.getProperty(UserProps.AutoSignature) || '';
this.setViewText('edit', MciViewIds.editor, sig); this.setViewText('edit', MciViewIds.editor, sig);
return callback(null); return callback(null);
} },
], ],
err => { err => {
return cb(err); return cb(err);

View File

@ -30,11 +30,12 @@ exports.main = main;
const initServices = {}; const initServices = {};
// only include bbs.js once @ startup; this should be fine // only include bbs.js once @ startup; this should be fine
const COPYRIGHT = fs.readFileSync(paths.join(__dirname, '../LICENSE.TXT'), 'utf8').split(/\r?\n/g)[0]; const COPYRIGHT = fs
.readFileSync(paths.join(__dirname, '../LICENSE.TXT'), 'utf8')
.split(/\r?\n/g)[0];
const FULL_COPYRIGHT = `ENiGMA½ ${COPYRIGHT}`; const FULL_COPYRIGHT = `ENiGMA½ ${COPYRIGHT}`;
const HELP = const HELP = `${FULL_COPYRIGHT}
`${FULL_COPYRIGHT}
usage: main.js <args> usage: main.js <args>
eg : main.js --config /enigma_install_path/config/ eg : main.js --config /enigma_install_path/config/
@ -71,7 +72,11 @@ function main() {
const configOverridePath = argv.config; const configOverridePath = argv.config;
return callback(null, configOverridePath || conf.Config.getDefaultPath(), _.isString(configOverridePath)); return callback(
null,
configOverridePath || conf.Config.getDefaultPath(),
_.isString(configOverridePath)
);
}, },
function initConfig(configPath, configPathSupplied, callback) { function initConfig(configPath, configPathSupplied, callback) {
const configFile = configPath + 'config.hjson'; const configFile = configPath + 'config.hjson';
@ -84,7 +89,9 @@ function main() {
if (err) { if (err) {
if ('ENOENT' === err.code) { if ('ENOENT' === err.code) {
if (configPathSupplied) { if (configPathSupplied) {
console.error('Configuration file does not exist: ' + configFile); console.error(
'Configuration file does not exist: ' + configFile
);
} else { } else {
configPathSupplied = null; // make non-fatal; we'll go with defaults configPathSupplied = null; // make non-fatal; we'll go with defaults
} }
@ -109,18 +116,22 @@ function main() {
} }
return callback(err); return callback(err);
}); });
} },
], ],
function complete(err) { function complete(err) {
if (!err) { if (!err) {
// 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(FULL_COPYRIGHT); console.info(FULL_COPYRIGHT);
if (!err) { if (!err) {
console.info(banner); console.info(banner);
} }
console.info('System started!'); console.info('System started!');
}); }
);
} }
if (err && !errorDisplayed) { if (err && !errorDisplayed) {
@ -145,7 +156,9 @@ function shutdownSystem() {
while (i--) { while (i--) {
const activeTerm = activeConnections[i].term; const activeTerm = activeConnections[i].term;
if (activeTerm) { if (activeTerm) {
activeTerm.write('\n\nServer is shutting down NOW! Disconnecting...\n\n'); activeTerm.write(
'\n\nServer is shutting down NOW! Disconnecting...\n\n'
);
} }
ClientConns.removeClient(activeConnections[i]); ClientConns.removeClient(activeConnections[i]);
} }
@ -172,7 +185,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!');
@ -186,16 +199,25 @@ function initialize(cb) {
[ [
function createMissingDirectories(callback) { function createMissingDirectories(callback) {
const Config = conf.get(); const Config = conf.get();
async.each(Object.keys(Config.paths), function entry(pathKey, next) { async.each(
Object.keys(Config.paths),
function entry(pathKey, next) {
mkdirs(Config.paths[pathKey], function dirCreated(err) { mkdirs(Config.paths[pathKey], function dirCreated(err) {
if (err) { if (err) {
console.error('Could not create path: ' + Config.paths[pathKey] + ': ' + err.toString()); console.error(
'Could not create path: ' +
Config.paths[pathKey] +
': ' +
err.toString()
);
} }
return next(err); return next(err);
}); });
}, function dirCreationComplete(err) { },
function dirCreationComplete(err) {
return callback(err); return callback(err);
}); }
);
}, },
function basicInit(callback) { function basicInit(callback) {
logger.init(); logger.init();
@ -237,8 +259,11 @@ function initialize(cb) {
const propLoadOpts = { const propLoadOpts = {
names: [ names: [
UserProps.RealName, UserProps.Sex, UserProps.EmailAddress, UserProps.RealName,
UserProps.Location, UserProps.Affiliations, UserProps.Sex,
UserProps.EmailAddress,
UserProps.Location,
UserProps.Affiliations,
], ],
}; };
@ -248,9 +273,13 @@ function initialize(cb) {
return User.getUserName(1, next); return User.getUserName(1, next);
}, },
function getOpProps(opUserName, next) { function getOpProps(opUserName, next) {
User.loadProperties(User.RootUserID, propLoadOpts, (err, opProps) => { User.loadProperties(
User.RootUserID,
propLoadOpts,
(err, opProps) => {
return next(err, opUserName, opProps); return next(err, opUserName, opProps);
}); }
);
}, },
], ],
(err, opUserName, opProps) => { (err, opUserName, opProps) => {
@ -282,7 +311,10 @@ function initialize(cb) {
StatLog.findSystemLogEntries(filter, (err, callsToday) => { StatLog.findSystemLogEntries(filter, (err, callsToday) => {
if (!err) { if (!err) {
StatLog.setNonPersistentSystemStat(SysProps.LoginsToday, callsToday); StatLog.setNonPersistentSystemStat(
SysProps.LoginsToday,
callsToday
);
} }
return callback(null); return callback(null);
}); });
@ -312,7 +344,8 @@ function initialize(cb) {
return require('./file_area_web.js').startup(callback); return require('./file_area_web.js').startup(callback);
}, },
function readyPasswordReset(callback) { function readyPasswordReset(callback) {
const WebPasswordReset = require('./web_password_reset.js').WebPasswordReset; const WebPasswordReset =
require('./web_password_reset.js').WebPasswordReset;
return WebPasswordReset.startup(callback); return WebPasswordReset.startup(callback);
}, },
function ready2FA_OTPRegister(callback) { function ready2FA_OTPRegister(callback) {
@ -320,7 +353,8 @@ function initialize(cb) {
return User2FA_OTPWebRegister.startup(callback); return User2FA_OTPWebRegister.startup(callback);
}, },
function readyEventScheduler(callback) { function readyEventScheduler(callback) {
const EventSchedulerModule = require('./event_scheduler.js').EventSchedulerModule; const EventSchedulerModule =
require('./event_scheduler.js').EventSchedulerModule;
EventSchedulerModule.loadAndStart((err, modInst) => { EventSchedulerModule.loadAndStart((err, modInst) => {
initServices.eventScheduler = modInst; initServices.eventScheduler = modInst;
return callback(err); return callback(err);
@ -328,7 +362,7 @@ function initialize(cb) {
}, },
function listenUserEventsForStatLog(callback) { function listenUserEventsForStatLog(callback) {
return require('./stat_log.js').initUserEvents(callback); return require('./stat_log.js').initUserEvents(callback);
} },
], ],
function onComplete(err) { function onComplete(err) {
return cb(err); return cb(err);

View File

@ -4,10 +4,7 @@
const { MenuModule } = require('./menu_module.js'); const { MenuModule } = require('./menu_module.js');
const { resetScreen } = require('./ansi_term.js'); const { resetScreen } = require('./ansi_term.js');
const { Errors } = require('./enig_error.js'); const { Errors } = require('./enig_error.js');
const { const { trackDoorRunBegin, trackDoorRunEnd } = require('./door_util.js');
trackDoorRunBegin,
trackDoorRunEnd
} = require('./door_util.js');
// deps // deps
const async = require('async'); const async = require('async');
@ -86,15 +83,22 @@ exports.getModule = class BBSLinkModule extends MenuModule {
callback(ex); callback(ex);
} else { } else {
randomKey = buf.toString('base64').substr(0, 6); randomKey = buf.toString('base64').substr(0, 6);
self.simpleHttpRequest('/token.php?key=' + randomKey, null, function resp(err, body) { self.simpleHttpRequest(
'/token.php?key=' + randomKey,
null,
function resp(err, body) {
if (err) { if (err) {
callback(err); callback(err);
} else { } else {
token = body.trim(); token = body.trim();
self.client.log.trace( { token : token }, 'BBSLink token'); self.client.log.trace(
{ token: token },
'BBSLink token'
);
callback(null); callback(null);
} }
}); }
);
} }
}); });
}, },
@ -105,8 +109,14 @@ exports.getModule = class BBSLinkModule extends MenuModule {
const headers = { const headers = {
'X-User': self.client.user.userId.toString(), 'X-User': self.client.user.userId.toString(),
'X-System': self.config.sysCode, 'X-System': self.config.sysCode,
'X-Auth' : crypto.createHash('md5').update(self.config.authCode + token).digest('hex'), 'X-Auth': crypto
'X-Code' : crypto.createHash('md5').update(self.config.schemeCode + token).digest('hex'), .createHash('md5')
.update(self.config.authCode + token)
.digest('hex'),
'X-Code': crypto
.createHash('md5')
.update(self.config.schemeCode + token)
.digest('hex'),
'X-Rows': self.client.term.termHeight.toString(), 'X-Rows': self.client.term.termHeight.toString(),
'X-Key': randomKey, 'X-Key': randomKey,
'X-Door': self.config.door, 'X-Door': self.config.door,
@ -115,14 +125,22 @@ exports.getModule = class BBSLinkModule extends MenuModule {
'X-Version': packageJson.version, 'X-Version': packageJson.version,
}; };
self.simpleHttpRequest('/auth.php?key=' + randomKey, headers, function resp(err, body) { self.simpleHttpRequest(
'/auth.php?key=' + randomKey,
headers,
function resp(err, body) {
var status = body.trim(); var status = body.trim();
if ('complete' === status) { if ('complete' === status) {
return callback(null); return callback(null);
} }
return callback(Errors.AccessDenied(`Bad authentication status: ${status}`)); return callback(
}); Errors.AccessDenied(
`Bad authentication status: ${status}`
)
);
}
);
}, },
function createTelnetBridge(callback) { function createTelnetBridge(callback) {
// //
@ -137,25 +155,38 @@ exports.getModule = class BBSLinkModule extends MenuModule {
let dataOut; let dataOut;
self.client.term.write(resetScreen()); self.client.term.write(resetScreen());
self.client.term.write(` Connecting to ${self.config.host}, please wait...\n`); self.client.term.write(
` Connecting to ${self.config.host}, please wait...\n`
);
const doorTracking = trackDoorRunBegin(self.client, `bbslink_${self.config.door}`); const doorTracking = trackDoorRunBegin(
self.client,
`bbslink_${self.config.door}`
);
const bridgeConnection = net.createConnection(connectOpts, function connected() { const bridgeConnection = net.createConnection(
self.client.log.info(connectOpts, 'BBSLink bridge connection established'); connectOpts,
function connected() {
self.client.log.info(
connectOpts,
'BBSLink bridge connection established'
);
dataOut = (data) => { dataOut = data => {
return bridgeConnection.write(data); return bridgeConnection.write(data);
}; };
self.client.term.output.on('data', dataOut); self.client.term.output.on('data', dataOut);
self.client.once('end', function clientEnd() { self.client.once('end', function clientEnd() {
self.client.log.info('Connection ended. Terminating BBSLink connection'); self.client.log.info(
'Connection ended. Terminating BBSLink connection'
);
clientTerminated = true; clientTerminated = true;
bridgeConnection.end(); bridgeConnection.end();
}); });
}); }
);
const restore = () => { const restore = () => {
if (dataOut && self.client.term.output) { if (dataOut && self.client.term.output) {
@ -174,19 +205,28 @@ exports.getModule = class BBSLinkModule extends MenuModule {
bridgeConnection.on('end', function connectionEnd() { bridgeConnection.on('end', function connectionEnd() {
restore(); restore();
return callback(clientTerminated ? Errors.General('Client connection terminated') : null); return callback(
clientTerminated
? Errors.General('Client connection terminated')
: null
);
}); });
bridgeConnection.on('error', function error(err) { bridgeConnection.on('error', function error(err) {
self.client.log.info('BBSLink bridge connection error: ' + err.message); self.client.log.info(
'BBSLink bridge connection error: ' + err.message
);
restore(); restore();
return callback(err); return callback(err);
}); });
} },
], ],
function complete(err) { function complete(err) {
if (err) { if (err) {
self.client.log.warn( { error : err.toString() }, 'BBSLink connection error'); self.client.log.warn(
{ error: err.toString() },
'BBSLink connection error'
);
} }
if (!clientTerminated) { if (!clientTerminated) {

View File

@ -4,10 +4,7 @@
// ENiGMA½ // ENiGMA½
const MenuModule = require('./menu_module.js').MenuModule; const MenuModule = require('./menu_module.js').MenuModule;
const { const { getModDatabasePath, getTransactionDatabase } = require('./database.js');
getModDatabasePath,
getTransactionDatabase
} = require('./database.js');
const ViewController = require('./view_controller.js').ViewController; const ViewController = require('./view_controller.js').ViewController;
const ansi = require('./ansi_term.js'); const ansi = require('./ansi_term.js');
@ -22,12 +19,12 @@ const _ = require('lodash');
// :TODO: add notes field // :TODO: add notes field
const moduleInfo = exports.moduleInfo = { const moduleInfo = (exports.moduleInfo = {
name: 'BBS List', name: 'BBS List',
desc: 'List of other BBSes', desc: 'List of other BBSes',
author: 'Andrew Pamment', author: 'Andrew Pamment',
packageName : 'com.magickabbs.enigma.bbslist' packageName: 'com.magickabbs.enigma.bbslist',
}; });
const MciViewIds = { const MciViewIds = {
view: { view: {
@ -50,7 +47,7 @@ const MciViewIds = {
Software: 6, Software: 6,
Notes: 7, Notes: 7,
Error: 8, Error: 8,
} },
}; };
const FormIds = { const FormIds = {
@ -103,9 +100,15 @@ exports.getModule = class BBSListModule extends MenuModule {
return cb(null); return cb(null);
} }
const entriesView = self.viewControllers.view.getView(MciViewIds.view.BBSList); const entriesView = self.viewControllers.view.getView(
MciViewIds.view.BBSList
);
if(self.entries[self.selectedBBS].submitterUserId !== self.client.user.userId && !self.client.user.isSysOp()) { if (
self.entries[self.selectedBBS].submitterUserId !==
self.client.user.userId &&
!self.client.user.isSysOp()
) {
// must be owner or +op // must be owner or +op
return cb(null); return cb(null);
} }
@ -121,7 +124,10 @@ exports.getModule = class BBSListModule extends MenuModule {
[entry.id], [entry.id],
err => { err => {
if (err) { if (err) {
self.client.log.error( { err : err }, 'Error deleting from BBS list'); self.client.log.error(
{ err: err },
'Error deleting from BBS list'
);
} else { } else {
self.entries.splice(self.selectedBBS, 1); self.entries.splice(self.selectedBBS, 1);
@ -139,10 +145,14 @@ exports.getModule = class BBSListModule extends MenuModule {
); );
}, },
submitBBS: function (formData, extraArgs, cb) { submitBBS: function (formData, extraArgs, cb) {
let ok = true; let ok = true;
['BBSName', 'Sysop', 'Telnet'].forEach(mciName => { ['BBSName', 'Sysop', 'Telnet'].forEach(mciName => {
if('' === self.viewControllers.add.getView(MciViewIds.add[mciName]).getData()) { if (
'' ===
self.viewControllers.add
.getView(MciViewIds.add[mciName])
.getData()
) {
ok = false; ok = false;
} }
}); });
@ -155,12 +165,21 @@ exports.getModule = class BBSListModule extends MenuModule {
`INSERT INTO bbs_list (bbs_name, sysop, telnet, www, location, software, submitter_user_id, notes) `INSERT INTO bbs_list (bbs_name, sysop, telnet, www, location, software, submitter_user_id, notes)
VALUES(?, ?, ?, ?, ?, ?, ?, ?);`, VALUES(?, ?, ?, ?, ?, ?, ?, ?);`,
[ [
formData.value.name, formData.value.sysop, formData.value.telnet, formData.value.www, formData.value.name,
formData.value.location, formData.value.software, self.client.user.userId, formData.value.notes formData.value.sysop,
formData.value.telnet,
formData.value.www,
formData.value.location,
formData.value.software,
self.client.user.userId,
formData.value.notes,
], ],
err => { err => {
if (err) { if (err) {
self.client.log.error( { err : err }, 'Error adding to BBS list'); self.client.log.error(
{ err: err },
'Error adding to BBS list'
);
} }
self.clearAddForm(); self.clearAddForm();
@ -171,7 +190,7 @@ exports.getModule = class BBSListModule extends MenuModule {
cancelSubmit: function (formData, extraArgs, cb) { cancelSubmit: function (formData, extraArgs, cb) {
self.clearAddForm(); self.clearAddForm();
self.displayBBSList(true, cb); self.displayBBSList(true, cb);
} },
}; };
} }
@ -184,7 +203,7 @@ exports.getModule = class BBSListModule extends MenuModule {
}, },
function display(callback) { function display(callback) {
self.displayBBSList(false, callback); self.displayBBSList(false, callback);
} },
], ],
err => { err => {
if (err) { if (err) {
@ -201,14 +220,21 @@ exports.getModule = class BBSListModule extends MenuModule {
this.setViewText('view', MciViewIds.view[mciName], ''); this.setViewText('view', MciViewIds.view[mciName], '');
}); });
} else { } else {
const youSubmittedFormat = this.menuConfig.youSubmittedFormat || '{submitter} (You!)'; const youSubmittedFormat =
this.menuConfig.youSubmittedFormat || '{submitter} (You!)';
Object.keys(SELECTED_MCI_NAME_TO_ENTRY).forEach(mciName => { Object.keys(SELECTED_MCI_NAME_TO_ENTRY).forEach(mciName => {
const t = entry[SELECTED_MCI_NAME_TO_ENTRY[mciName]]; const t = entry[SELECTED_MCI_NAME_TO_ENTRY[mciName]];
if (MciViewIds.view[mciName]) { if (MciViewIds.view[mciName]) {
if (
if('SelectedBBSSubmitter' == mciName && entry.submitterUserId == this.client.user.userId) { 'SelectedBBSSubmitter' == mciName &&
this.setViewText('view',MciViewIds.view.SelectedBBSSubmitter, stringFormat(youSubmittedFormat, entry)); entry.submitterUserId == this.client.user.userId
) {
this.setViewText(
'view',
MciViewIds.view.SelectedBBSSubmitter,
stringFormat(youSubmittedFormat, entry)
);
} else { } else {
this.setViewText('view', MciViewIds.view[mciName], t); this.setViewText('view', MciViewIds.view[mciName], t);
} }
@ -246,7 +272,10 @@ exports.getModule = class BBSListModule extends MenuModule {
if (_.isUndefined(self.viewControllers.add)) { if (_.isUndefined(self.viewControllers.add)) {
const vc = self.addViewController( const vc = self.addViewController(
'view', 'view',
new ViewController( { client : self.client, formId : FormIds.View } ) new ViewController({
client: self.client,
formId: FormIds.View,
})
); );
const loadOpts = { const loadOpts = {
@ -258,12 +287,16 @@ exports.getModule = class BBSListModule extends MenuModule {
return vc.loadFromMenuConfig(loadOpts, callback); return vc.loadFromMenuConfig(loadOpts, callback);
} else { } else {
self.viewControllers.view.setFocus(true); self.viewControllers.view.setFocus(true);
self.viewControllers.view.getView(MciViewIds.view.BBSList).redraw(); self.viewControllers.view
.getView(MciViewIds.view.BBSList)
.redraw();
return callback(null); return callback(null);
} }
}, },
function fetchEntries(callback) { function fetchEntries(callback) {
const entriesView = self.viewControllers.view.getView(MciViewIds.view.BBSList); const entriesView = self.viewControllers.view.getView(
MciViewIds.view.BBSList
);
self.entries = []; self.entries = [];
self.database.each( self.database.each(
@ -291,7 +324,9 @@ exports.getModule = class BBSListModule extends MenuModule {
); );
}, },
function getUserNames(entriesView, callback) { function getUserNames(entriesView, callback) {
async.each(self.entries, (entry, next) => { async.each(
self.entries,
(entry, next) => {
User.getUserName(entry.submitterUserId, (err, username) => { User.getUserName(entry.submitterUserId, (err, username) => {
if (username) { if (username) {
entry.submitter = username; entry.submitter = username;
@ -300,9 +335,11 @@ exports.getModule = class BBSListModule extends MenuModule {
} }
return next(); return next();
}); });
}, () => { },
() => {
return callback(null, entriesView); return callback(null, entriesView);
}); }
);
}, },
function populateEntries(entriesView, callback) { function populateEntries(entriesView, callback) {
self.setEntries(entriesView); self.setEntries(entriesView);
@ -331,7 +368,7 @@ exports.getModule = class BBSListModule extends MenuModule {
entriesView.redraw(); entriesView.redraw();
return callback(null); return callback(null);
} },
], ],
err => { err => {
if (cb) { if (cb) {
@ -363,7 +400,10 @@ exports.getModule = class BBSListModule extends MenuModule {
if (_.isUndefined(self.viewControllers.add)) { if (_.isUndefined(self.viewControllers.add)) {
const vc = self.addViewController( const vc = self.addViewController(
'add', 'add',
new ViewController( { client : self.client, formId : FormIds.Add } ) new ViewController({
client: self.client,
formId: FormIds.Add,
})
); );
const loadOpts = { const loadOpts = {
@ -379,7 +419,7 @@ exports.getModule = class BBSListModule extends MenuModule {
self.viewControllers.add.switchFocus(MciViewIds.add.BBSName); self.viewControllers.add.switchFocus(MciViewIds.add.BBSName);
return callback(null); return callback(null);
} }
} },
], ],
err => { err => {
if (cb) { if (cb) {
@ -390,7 +430,16 @@ exports.getModule = class BBSListModule extends MenuModule {
} }
clearAddForm() { clearAddForm() {
[ 'BBSName', 'Sysop', 'Telnet', 'Www', 'Location', 'Software', 'Error', 'Notes' ].forEach( mciName => { [
'BBSName',
'Sysop',
'Telnet',
'Www',
'Location',
'Software',
'Error',
'Notes',
].forEach(mciName => {
this.setViewText('add', MciViewIds.add[mciName], ''); this.setViewText('add', MciViewIds.add[mciName], '');
}); });
} }
@ -401,10 +450,9 @@ exports.getModule = class BBSListModule extends MenuModule {
async.series( async.series(
[ [
function openDatabase(callback) { function openDatabase(callback) {
self.database = getTransactionDatabase(new sqlite3.Database( self.database = getTransactionDatabase(
getModDatabasePath(moduleInfo), new sqlite3.Database(getModDatabasePath(moduleInfo), callback)
callback );
));
}, },
function createTables(callback) { function createTables(callback) {
self.database.serialize(() => { self.database.serialize(() => {
@ -423,7 +471,7 @@ exports.getModule = class BBSListModule extends MenuModule {
); );
}); });
callback(null); callback(null);
} },
], ],
err => { err => {
return cb(err); return cb(err);

View File

@ -21,7 +21,7 @@ function ButtonView(options) {
util.inherits(ButtonView, TextView); util.inherits(ButtonView, TextView);
ButtonView.prototype.onKeyPress = function (ch, key) { ButtonView.prototype.onKeyPress = function (ch, key) {
if(this.isKeyMapped('accept', (key ? key.name : ch)) || ' ' === ch) { if (this.isKeyMapped('accept', key ? key.name : ch) || ' ' === ch) {
this.submitData = 'accept'; this.submitData = 'accept';
this.emit('action', 'accept'); this.emit('action', 'accept');
delete this.submitData; delete this.submitData;

View File

@ -60,22 +60,27 @@ const RE_DSR_RESPONSE_ANYWHERE = /(?:\u001b\[)([0-9;]+)(R)/;
const RE_DEV_ATTR_RESPONSE_ANYWHERE = /(?:\u001b\[)[=?]([0-9a-zA-Z;]+)(c)/; const RE_DEV_ATTR_RESPONSE_ANYWHERE = /(?:\u001b\[)[=?]([0-9a-zA-Z;]+)(c)/;
const RE_META_KEYCODE_ANYWHERE = /(?:\u001b)([a-zA-Z0-9])/; const RE_META_KEYCODE_ANYWHERE = /(?:\u001b)([a-zA-Z0-9])/;
const RE_META_KEYCODE = new RegExp('^' + RE_META_KEYCODE_ANYWHERE.source + '$'); const RE_META_KEYCODE = new RegExp('^' + RE_META_KEYCODE_ANYWHERE.source + '$');
const RE_FUNCTION_KEYCODE_ANYWHERE = new RegExp('(?:\u001b+)(O|N|\\[|\\[\\[)(?:' + [ const RE_FUNCTION_KEYCODE_ANYWHERE = new RegExp(
'(?:\u001b+)(O|N|\\[|\\[\\[)(?:' +
[
'(\\d+)(?:;(\\d+))?([~^$])', '(\\d+)(?:;(\\d+))?([~^$])',
'(?:M([@ #!a`])(.)(.))', // mouse stuff '(?:M([@ #!a`])(.)(.))', // mouse stuff
'(?:1;)?(\\d+)?([a-zA-Z@])' '(?:1;)?(\\d+)?([a-zA-Z@])',
].join('|') + ')'); ].join('|') +
')'
);
/* eslint-enable no-control-regex */ /* eslint-enable no-control-regex */
const RE_FUNCTION_KEYCODE = new RegExp('^' + RE_FUNCTION_KEYCODE_ANYWHERE.source); const RE_FUNCTION_KEYCODE = new RegExp('^' + RE_FUNCTION_KEYCODE_ANYWHERE.source);
const RE_ESC_CODE_ANYWHERE = new RegExp( [ const RE_ESC_CODE_ANYWHERE = new RegExp(
[
RE_FUNCTION_KEYCODE_ANYWHERE.source, RE_FUNCTION_KEYCODE_ANYWHERE.source,
RE_META_KEYCODE_ANYWHERE.source, RE_META_KEYCODE_ANYWHERE.source,
RE_DSR_RESPONSE_ANYWHERE.source, RE_DSR_RESPONSE_ANYWHERE.source,
RE_DEV_ATTR_RESPONSE_ANYWHERE.source, RE_DEV_ATTR_RESPONSE_ANYWHERE.source,
/\u001b./.source // eslint-disable-line no-control-regex /\u001b./.source, // eslint-disable-line no-control-regex
].join('|')); ].join('|')
);
function Client(/*input, output*/) { function Client(/*input, output*/) {
stream.call(this); stream.call(this);
@ -100,25 +105,25 @@ function Client(/*input, output*/) {
author: 'N/A', author: 'N/A',
description: 'N/A', description: 'N/A',
group: 'N/A', group: 'N/A',
} },
}; };
} }
}, },
set : (theme) => { set: theme => {
this.currentThemeConfig = theme; this.currentThemeConfig = theme;
} },
}); });
Object.defineProperty(this, 'node', { Object.defineProperty(this, 'node', {
get: function () { get: function () {
return self.session.id; return self.session.id;
} },
}); });
Object.defineProperty(this, 'currentMenuModule', { Object.defineProperty(this, 'currentMenuModule', {
get: function () { get: function () {
return self.menuStack.currentModule; return self.menuStack.currentModule;
} },
}); });
this.setTemporaryDirectDataHandler = function (handler) { this.setTemporaryDirectDataHandler = function (handler) {
@ -135,7 +140,9 @@ function Client(/*input, output*/) {
this.themeChangedListener = function ({ themeId }) { this.themeChangedListener = function ({ themeId }) {
if (_.get(self.currentTheme, 'info.themeId') === themeId) { if (_.get(self.currentTheme, 'info.themeId') === themeId) {
self.currentThemeConfig = require('./theme.js').getAvailableThemes().get(themeId); self.currentThemeConfig = require('./theme.js')
.getAvailableThemes()
.get(themeId);
} }
}; };
@ -174,31 +181,33 @@ function Client(/*input, output*/) {
/* eslint-disable no-control-regex */ /* eslint-disable no-control-regex */
this.isMouseInput = function (data) { this.isMouseInput = function (data) {
return /\x1b\[M/.test(data) || return (
/\x1b\[M/.test(data) ||
/\u001b\[M([\x00\u0020-\uffff]{3})/.test(data) || /\u001b\[M([\x00\u0020-\uffff]{3})/.test(data) ||
/\u001b\[(\d+;\d+;\d+)M/.test(data) || /\u001b\[(\d+;\d+;\d+)M/.test(data) ||
/\u001b\[<(\d+;\d+;\d+)([mM])/.test(data) || /\u001b\[<(\d+;\d+;\d+)([mM])/.test(data) ||
/\u001b\[<(\d+;\d+;\d+;\d+)&w/.test(data) || /\u001b\[<(\d+;\d+;\d+;\d+)&w/.test(data) ||
/\u001b\[24([0135])~\[(\d+),(\d+)\]\r/.test(data) || /\u001b\[24([0135])~\[(\d+),(\d+)\]\r/.test(data) ||
/\u001b\[(O|I)/.test(data); /\u001b\[(O|I)/.test(data)
);
}; };
/* eslint-enable no-control-regex */ /* eslint-enable no-control-regex */
this.getKeyComponentsFromCode = function (code) { this.getKeyComponentsFromCode = function (code) {
return { return {
// xterm/gnome // xterm/gnome
'OP' : { name : 'f1' }, OP: { name: 'f1' },
'OQ' : { name : 'f2' }, OQ: { name: 'f2' },
'OR' : { name : 'f3' }, OR: { name: 'f3' },
'OS' : { name : 'f4' }, OS: { name: 'f4' },
'OA' : { name : 'up arrow' }, OA: { name: 'up arrow' },
'OB' : { name : 'down arrow' }, OB: { name: 'down arrow' },
'OC' : { name : 'right arrow' }, OC: { name: 'right arrow' },
'OD' : { name : 'left arrow' }, OD: { name: 'left arrow' },
'OE' : { name : 'clear' }, OE: { name: 'clear' },
'OF' : { name : 'end' }, OF: { name: 'end' },
'OH' : { name : 'home' }, OH: { name: 'home' },
// xterm/rxvt // xterm/rxvt
'[11~': { name: 'f1' }, '[11~': { name: 'f1' },
@ -261,11 +270,11 @@ function Client(/*input, output*/) {
'[7$': { name: 'home', shift: true }, '[7$': { name: 'home', shift: true },
'[8$': { name: 'end', shift: true }, '[8$': { name: 'end', shift: true },
'Oa' : { name : 'up arrow', ctrl : true }, Oa: { name: 'up arrow', ctrl: true },
'Ob' : { name : 'down arrow', ctrl : true }, Ob: { name: 'down arrow', ctrl: true },
'Oc' : { name : 'right arrow', ctrl : true }, Oc: { name: 'right arrow', ctrl: true },
'Od' : { name : 'left arrow', ctrl : true }, Od: { name: 'left arrow', ctrl: true },
'Oe' : { name : 'clear', ctrl : true }, Oe: { name: 'clear', ctrl: true },
'[2^': { name: 'insert', ctrl: true }, '[2^': { name: 'insert', ctrl: true },
'[3^': { name: 'delete', ctrl: true }, '[3^': { name: 'delete', ctrl: true },
@ -321,7 +330,7 @@ function Client(/*input, output*/) {
if ((parts = RE_DSR_RESPONSE_ANYWHERE.exec(s))) { if ((parts = RE_DSR_RESPONSE_ANYWHERE.exec(s))) {
if ('R' === parts[2]) { if ('R' === parts[2]) {
const cprArgs = parts[1].split(';').map(v => (parseInt(v, 10) || 0) ); const cprArgs = parts[1].split(';').map(v => parseInt(v, 10) || 0);
if (2 === cprArgs.length) { if (2 === cprArgs.length) {
if (self.cprOffset) { if (self.cprOffset) {
cprArgs[0] = cprArgs[0] + self.cprOffset; cprArgs[0] = cprArgs[0] + self.cprOffset;
@ -359,14 +368,14 @@ function Client(/*input, output*/) {
} else if ('\b' === s || '\x1b\x7f' === s || '\x1b\b' === s) { } else if ('\b' === s || '\x1b\x7f' === s || '\x1b\b' === s) {
// backspace, CTRL-H // backspace, CTRL-H
key.name = 'backspace'; key.name = 'backspace';
key.meta = ('\x1b' === s.charAt(0)); key.meta = '\x1b' === s.charAt(0);
} else if ('\x1b' === s || '\x1b\x1b' === s) { } else if ('\x1b' === s || '\x1b\x1b' === s) {
key.name = 'escape'; key.name = 'escape';
key.meta = (2 === s.length); key.meta = 2 === s.length;
} else if (' ' === s || '\x1b ' === s) { } else if (' ' === s || '\x1b ' === s) {
// rather annoying that space can come in other than just " " // rather annoying that space can come in other than just " "
key.name = 'space'; key.name = 'space';
key.meta = (2 === s.length); key.meta = 2 === s.length;
} else if (1 === s.length && s <= '\x1a') { } else if (1 === s.length && s <= '\x1a') {
// CTRL-<letter> // CTRL-<letter>
key.name = String.fromCharCode(s.charCodeAt(0) + 'a'.charCodeAt(0) - 1); key.name = String.fromCharCode(s.charCodeAt(0) + 'a'.charCodeAt(0) - 1);
@ -384,8 +393,10 @@ function Client(/*input, output*/) {
key.shift = /^[A-Z]$/.test(parts[1]); key.shift = /^[A-Z]$/.test(parts[1]);
} else if ((parts = RE_FUNCTION_KEYCODE.exec(s))) { } else if ((parts = RE_FUNCTION_KEYCODE.exec(s))) {
var code = var code =
(parts[1] || '') + (parts[2] || '') + (parts[1] || '') +
(parts[4] || '') + (parts[9] || ''); (parts[2] || '') +
(parts[4] || '') +
(parts[9] || '');
var modifier = (parts[3] || parts[8] || 1) - 1; var modifier = (parts[3] || parts[8] || 1) - 1;
@ -474,17 +485,17 @@ Client.prototype.startIdleMonitor = function() {
// every user, but want at least some updates for various things // every user, but want at least some updates for various things
// such as achievements. Send off every 5m. // such as achievements. Send off every 5m.
// //
const minOnline = this.user.incrementProperty(UserProps.MinutesOnlineTotalCount, 1); const minOnline = this.user.incrementProperty(
if(0 === (minOnline % 5)) { UserProps.MinutesOnlineTotalCount,
Events.emit( 1
Events.getSystemEvents().UserStatIncrement, );
{ if (0 === minOnline % 5) {
Events.emit(Events.getSystemEvents().UserStatIncrement, {
user: this.user, user: this.user,
statName: UserProps.MinutesOnlineTotalCount, statName: UserProps.MinutesOnlineTotalCount,
statIncrementBy: 1, statIncrementBy: 1,
statValue : minOnline statValue: minOnline,
} });
);
} }
} else { } else {
idleLogoutSeconds = Config().users.preAuthIdleLogoutSeconds; idleLogoutSeconds = Config().users.preAuthIdleLogoutSeconds;
@ -493,7 +504,10 @@ Client.prototype.startIdleMonitor = function() {
// use override value if set // use override value if set
idleLogoutSeconds = this.idleLogoutSecondsOverride || idleLogoutSeconds; idleLogoutSeconds = this.idleLogoutSecondsOverride || idleLogoutSeconds;
if(idleLogoutSeconds > 0 && (nowMs - this.lastActivityTime >= (idleLogoutSeconds * 1000))) { if (
idleLogoutSeconds > 0 &&
nowMs - this.lastActivityTime >= idleLogoutSeconds * 1000
) {
this.emit('idle timeout'); this.emit('idle timeout');
} }
}, 1000 * 60); }, 1000 * 60);
@ -523,7 +537,10 @@ Client.prototype.end = function () {
this.term.disconnect(); this.term.disconnect();
} }
Events.removeListener(Events.getSystemEvents().ThemeChanged, this.themeChangedListener); Events.removeListener(
Events.getSystemEvents().ThemeChanged,
this.themeChangedListener
);
const currentModule = this.menuStack.getCurrentModule; const currentModule = this.menuStack.getCurrentModule;
@ -591,7 +608,6 @@ Client.prototype.defaultHandlerMissingMod = function() {
self.term.write('This has been logged for your SysOp to review.\n'); self.term.write('This has been logged for your SysOp to review.\n');
self.term.write('\nGoodbye!\n'); self.term.write('\nGoodbye!\n');
//self.term.write(err); //self.term.write(err);
//if(miscUtil.isDevelopment() && err.stack) { //if(miscUtil.isDevelopment() && err.stack) {

View File

@ -23,12 +23,11 @@ exports.clientConnections = clientConnections;
function getActiveConnections(authUsersOnly = false) { function getActiveConnections(authUsersOnly = false) {
return clientConnections.filter(conn => { return clientConnections.filter(conn => {
return ((authUsersOnly && conn.user.isAuthenticated()) || !authUsersOnly); return (authUsersOnly && conn.user.isAuthenticated()) || !authUsersOnly;
}); });
} }
function getActiveConnectionList(authUsersOnly) { function getActiveConnectionList(authUsersOnly) {
if (!_.isBoolean(authUsersOnly)) { if (!_.isBoolean(authUsersOnly)) {
authUsersOnly = true; authUsersOnly = true;
} }
@ -52,7 +51,10 @@ function getActiveConnectionList(authUsersOnly) {
entry.location = ac.user.properties[UserProps.Location]; entry.location = ac.user.properties[UserProps.Location];
entry.affils = entry.affiliation = ac.user.properties[UserProps.Affiliations]; entry.affils = entry.affiliation = ac.user.properties[UserProps.Affiliations];
const diff = now.diff(moment(ac.user.properties[UserProps.LastLoginTs]), 'minutes'); const diff = now.diff(
moment(ac.user.properties[UserProps.LastLoginTs]),
'minutes'
);
entry.timeOn = moment.duration(diff, 'minutes'); entry.timeOn = moment.duration(diff, 'minutes');
} }
return entry; return entry;
@ -72,9 +74,12 @@ function addNewClient(client, clientSock) {
} }
client.session.id = nodeId; client.session.id = nodeId;
const remoteAddress = client.remoteAddress = clientSock.remoteAddress; const remoteAddress = (client.remoteAddress = clientSock.remoteAddress);
// create a unique identifier one-time ID for this session // create a unique identifier one-time ID for this session
client.session.uniqueId = new hashids('ENiGMA½ClientSession').encode([ nodeId, moment().valueOf() ]); client.session.uniqueId = new hashids('ENiGMA½ClientSession').encode([
nodeId,
moment().valueOf(),
]);
clientConnections.push(client); clientConnections.push(client);
clientConnections.sort((c1, c2) => c1.session.id - c2.session.id); clientConnections.sort((c1, c2) => c1.session.id - c2.session.id);
@ -96,10 +101,10 @@ function addNewClient(client, clientSock) {
client.log.info(connInfo, 'Client connected'); client.log.info(connInfo, 'Client connected');
Events.emit( Events.emit(Events.getSystemEvents().ClientConnected, {
Events.getSystemEvents().ClientConnected, client: client,
{ client : client, connectionCount : clientConnections.length } connectionCount: clientConnections.length,
); });
return nodeId; return nodeId;
} }
@ -120,14 +125,20 @@ function removeClient(client) {
); );
if (client.user && client.user.isValid()) { if (client.user && client.user.isValid()) {
const minutesOnline = moment().diff(moment(client.user.properties[UserProps.LastLoginTs]), 'minutes'); const minutesOnline = moment().diff(
Events.emit(Events.getSystemEvents().UserLogoff, { user : client.user, minutesOnline } ); moment(client.user.properties[UserProps.LastLoginTs]),
'minutes'
);
Events.emit(Events.getSystemEvents().UserLogoff, {
user: client.user,
minutesOnline,
});
} }
Events.emit( Events.emit(Events.getSystemEvents().ClientDisconnected, {
Events.getSystemEvents().ClientDisconnected, client: client,
{ client : client, connectionCount : clientConnections.length } connectionCount: clientConnections.length,
); });
} }
} }

View File

@ -9,7 +9,6 @@ var iconv = require('iconv-lite');
var assert = require('assert'); var assert = require('assert');
var _ = require('lodash'); var _ = require('lodash');
exports.ClientTerminal = ClientTerminal; exports.ClientTerminal = ClientTerminal;
function ClientTerminal(output) { function ClientTerminal(output) {
@ -47,7 +46,7 @@ function ClientTerminal(output) {
} else { } else {
Log.warn({ encoding: enc }, 'Unknown encoding'); Log.warn({ encoding: enc }, 'Unknown encoding');
} }
} },
}); });
Object.defineProperty(this, 'termType', { Object.defineProperty(this, 'termType', {
@ -68,8 +67,11 @@ function ClientTerminal(output) {
// Windows telnet will send "VTNT". If so, set termClient='windows' // Windows telnet will send "VTNT". If so, set termClient='windows'
// there are some others on the page as well // there are some others on the page as well
Log.debug( { encoding : this.outputEncoding }, 'Set output encoding due to terminal type change'); Log.debug(
} { encoding: this.outputEncoding },
'Set output encoding due to terminal type change'
);
},
}); });
Object.defineProperty(this, 'termWidth', { Object.defineProperty(this, 'termWidth', {
@ -80,7 +82,7 @@ function ClientTerminal(output) {
if (width > 0) { if (width > 0) {
termWidth = width; termWidth = width;
} }
} },
}); });
Object.defineProperty(this, 'termHeight', { Object.defineProperty(this, 'termHeight', {
@ -91,7 +93,7 @@ function ClientTerminal(output) {
if (height > 0) { if (height > 0) {
termHeight = height; termHeight = height;
} }
} },
}); });
Object.defineProperty(this, 'termClient', { Object.defineProperty(this, 'termClient', {
@ -102,7 +104,7 @@ function ClientTerminal(output) {
termClient = tc; termClient = tc;
Log.debug({ termClient: this.termClient }, 'Set known terminal client'); Log.debug({ termClient: this.termClient }, 'Set known terminal client');
} },
}); });
} }
@ -193,5 +195,3 @@ ClientTerminal.prototype.encode = function(s, convertLineFeeds) {
} }
return iconv.encode(s, this.outputEncoding); return iconv.encode(s, this.outputEncoding);
}; };

View File

@ -23,7 +23,8 @@ function pipeStringLength(s) {
} }
function ansiSgrFromRenegadeColorCode(cc) { function ansiSgrFromRenegadeColorCode(cc) {
return ANSI.sgr({ return ANSI.sgr(
{
0: ['reset', 'black'], 0: ['reset', 'black'],
1: ['reset', 'blue'], 1: ['reset', 'blue'],
2: ['reset', 'green'], 2: ['reset', 'green'],
@ -59,11 +60,13 @@ function ansiSgrFromRenegadeColorCode(cc) {
29: ['blink', 'magentaBG'], 29: ['blink', 'magentaBG'],
30: ['blink', 'yellowBG'], 30: ['blink', 'yellowBG'],
31: ['blink', 'whiteBG'], 31: ['blink', 'whiteBG'],
}[cc] || 'normal'); }[cc] || 'normal'
);
} }
function ansiSgrFromCnetStyleColorCode(cc) { function ansiSgrFromCnetStyleColorCode(cc) {
return ANSI.sgr({ return ANSI.sgr(
{
c0: ['reset', 'black'], c0: ['reset', 'black'],
c1: ['reset', 'red'], c1: ['reset', 'red'],
c2: ['reset', 'green'], c2: ['reset', 'green'],
@ -90,7 +93,8 @@ function ansiSgrFromCnetStyleColorCode(cc) {
z5: ['magentaBG'], z5: ['magentaBG'],
z6: ['cyanBG'], z6: ['cyanBG'],
z7: ['whiteBG'], z7: ['whiteBG'],
}[cc] || 'normal'); }[cc] || 'normal'
);
} }
function renegadeToAnsi(s, client) { function renegadeToAnsi(s, client) {
@ -121,7 +125,7 @@ function renegadeToAnsi(s, client) {
lastIndex = re.lastIndex; lastIndex = re.lastIndex;
} }
return (0 === result.length ? s : result + s.substr(lastIndex)); return 0 === result.length ? s : result + s.substr(lastIndex);
} }
// //
@ -144,7 +148,8 @@ function renegadeToAnsi(s, client) {
// * https://archive.org/stream/C-Net_Pro_3.0_1994_Perspective_Software/C-Net_Pro_3.0_1994_Perspective_Software_djvu.txt // * https://archive.org/stream/C-Net_Pro_3.0_1994_Perspective_Software/C-Net_Pro_3.0_1994_Perspective_Software_djvu.txt
// //
function controlCodesToAnsi(s, client) { function controlCodesToAnsi(s, client) {
const RE = /(\|([A-Z0-9]{2})|\|)|(@X([0-9A-F]{2}))|(@([0-9A-F]{2})@)|(\x03[0-9]|\x03)|(\x19(c[0-9a-f]|z[0-7]|n1|f1|q1)|\x19)|(\x11(c[0-9a-f]|z[0-7]|n1|f1|q1)}|\x11)/g; // eslint-disable-line no-control-regex const RE =
/(\|([A-Z0-9]{2})|\|)|(@X([0-9A-F]{2}))|(@([0-9A-F]{2})@)|(\x03[0-9]|\x03)|(\x19(c[0-9a-f]|z[0-7]|n1|f1|q1)|\x19)|(\x11(c[0-9a-f]|z[0-7]|n1|f1|q1)}|\x11)/g; // eslint-disable-line no-control-regex
let m; let m;
let result = ''; let result = '';
@ -231,7 +236,8 @@ function controlCodesToAnsi(s, client) {
if (isNaN(v)) { if (isNaN(v)) {
v += m[0]; v += m[0];
} else { } else {
v = ANSI.sgr({ v = ANSI.sgr(
{
0: ['reset', 'black'], 0: ['reset', 'black'],
1: ['bold', 'cyan'], 1: ['bold', 'cyan'],
2: ['bold', 'yellow'], 2: ['bold', 'yellow'],
@ -242,7 +248,8 @@ function controlCodesToAnsi(s, client) {
7: ['bold', 'blue'], 7: ['bold', 'blue'],
8: ['reset', 'blue'], 8: ['reset', 'blue'],
9: ['reset', 'cyan'], 9: ['reset', 'cyan'],
}[v] || 'normal'); }[v] || 'normal'
);
} }
result += s.substr(lastIndex, m.index - lastIndex) + v; result += s.substr(lastIndex, m.index - lastIndex) + v;
@ -270,5 +277,5 @@ function controlCodesToAnsi(s, client) {
lastIndex = RE.lastIndex; lastIndex = RE.lastIndex;
} }
return (0 === result.length ? s : result + s.substr(lastIndex)); return 0 === result.length ? s : result + s.substr(lastIndex);
} }

View File

@ -5,10 +5,7 @@
const { MenuModule } = require('../core/menu_module.js'); const { MenuModule } = require('../core/menu_module.js');
const { resetScreen } = require('../core/ansi_term.js'); const { resetScreen } = require('../core/ansi_term.js');
const { Errors } = require('./enig_error.js'); const { Errors } = require('./enig_error.js');
const { const { trackDoorRunBegin, trackDoorRunEnd } = require('./door_util.js');
trackDoorRunBegin,
trackDoorRunEnd
} = require('./door_util.js');
// deps // deps
const async = require('async'); const async = require('async');
@ -54,7 +51,10 @@ exports.getModule = class CombatNetModule extends MenuModule {
const restorePipeToNormal = function () { const restorePipeToNormal = function () {
if (self.client.term.output) { if (self.client.term.output) {
self.client.term.output.removeListener('data', sendToRloginBuffer); self.client.term.output.removeListener(
'data',
sendToRloginBuffer
);
if (doorTracking) { if (doorTracking) {
trackDoorRunEnd(doorTracking); trackDoorRunEnd(doorTracking);
@ -62,20 +62,20 @@ exports.getModule = class CombatNetModule extends MenuModule {
} }
}; };
const rlogin = new RLogin( const rlogin = new RLogin({
{
clientUsername: self.config.password, clientUsername: self.config.password,
serverUsername: `${self.config.bbsTag}${self.client.user.username}`, serverUsername: `${self.config.bbsTag}${self.client.user.username}`,
host: self.config.host, host: self.config.host,
port: self.config.rloginPort, port: self.config.rloginPort,
terminalType: self.client.term.termClient, terminalType: self.client.term.termClient,
terminalSpeed : 57600 terminalSpeed: 57600,
} });
);
// If there was an error ... // If there was an error ...
rlogin.on('error', err => { rlogin.on('error', err => {
self.client.log.info(`CombatNet rlogin client error: ${err.message}`); self.client.log.info(
`CombatNet rlogin client error: ${err.message}`
);
restorePipeToNormal(); restorePipeToNormal();
return callback(err); return callback(err);
}); });
@ -91,7 +91,8 @@ exports.getModule = class CombatNetModule extends MenuModule {
rlogin.send(buffer); rlogin.send(buffer);
} }
rlogin.on('connect', rlogin.on(
'connect',
/* The 'connect' event handler will be supplied with one argument, /* The 'connect' event handler will be supplied with one argument,
a boolean indicating whether or not the connection was established. */ a boolean indicating whether or not the connection was established. */
@ -102,13 +103,17 @@ exports.getModule = class CombatNetModule extends MenuModule {
doorTracking = trackDoorRunBegin(self.client); doorTracking = trackDoorRunBegin(self.client);
} else { } else {
return callback(Errors.General('Failed to establish establish CombatNet connection')); return callback(
Errors.General(
'Failed to establish establish CombatNet connection'
)
);
} }
} }
); );
// If data (a Buffer) has been received from the server ... // If data (a Buffer) has been received from the server ...
rlogin.on('data', (data) => { rlogin.on('data', data => {
self.client.term.rawWrite(data); self.client.term.rawWrite(data);
}); });
@ -116,7 +121,7 @@ exports.getModule = class CombatNetModule extends MenuModule {
rlogin.connect(); rlogin.connect();
// note: no explicit callback() until we're finished! // note: no explicit callback() until we're finished!
} },
], ],
err => { err => {
if (err) { if (err) {

View File

@ -25,9 +25,7 @@ exports.Config = class Config extends ConfigLoader {
'loginServers.ssh.algorithms.compress', 'loginServers.ssh.algorithms.compress',
]; ];
const replaceKeys = [ const replaceKeys = ['args', 'sendArgs', 'recvArgs', 'recvArgsNonBatch'];
'args', 'sendArgs', 'recvArgs', 'recvArgsNonBatch',
];
const configOptions = Object.assign({}, options, { const configOptions = Object.assign({}, options, {
defaultConfig: DefaultConfig, defaultConfig: DefaultConfig,

View File

@ -8,8 +8,7 @@ const hjson = require('hjson');
const sane = require('sane'); const sane = require('sane');
const _ = require('lodash'); const _ = require('lodash');
module.exports = new class ConfigCache module.exports = new (class ConfigCache {
{
constructor() { constructor() {
this.cache = new Map(); // path->parsed config this.cache = new Map(); // path->parsed config
} }
@ -22,23 +21,30 @@ module.exports = new class ConfigCache
this.recacheConfigFromFile(options.filePath, (err, config) => { this.recacheConfigFromFile(options.filePath, (err, config) => {
if (!err && !cached) { if (!err && !cached) {
if (options.hotReload) { if (options.hotReload) {
const watcher = sane( const watcher = sane(paths.dirname(options.filePath), {
paths.dirname(options.filePath), glob: `**/${paths.basename(options.filePath)}`,
{ });
glob : `**/${paths.basename(options.filePath)}`
}
);
watcher.on('change', (fileName, fileRoot) => { watcher.on('change', (fileName, fileRoot) => {
require('./logger.js').log.info( { fileName, fileRoot }, 'Configuration file changed; re-caching'); require('./logger.js').log.info(
{ fileName, fileRoot },
'Configuration file changed; re-caching'
);
this.recacheConfigFromFile(paths.join(fileRoot, fileName), err => { this.recacheConfigFromFile(
paths.join(fileRoot, fileName),
err => {
if (!err) { if (!err) {
if (options.callback) { if (options.callback) {
options.callback( { fileName, fileRoot, configCache : this } ); options.callback({
} fileName,
} fileRoot,
configCache: this,
}); });
}
}
}
);
}); });
} }
} }
@ -65,7 +71,10 @@ module.exports = new class ConfigCache
this.cache.set(path, parsed); this.cache.set(path, parsed);
} catch (e) { } catch (e) {
try { try {
require('./logger.js').log.error( { filePath : path, error : e.message }, 'Failed to re-cache' ); require('./logger.js').log.error(
{ filePath: path, error: e.message },
'Failed to re-cache'
);
} catch (ignored) { } catch (ignored) {
// nothing - we may be failing to parse the config in which we can't log here! // nothing - we may be failing to parse the config in which we can't log here!
} }
@ -75,4 +84,4 @@ module.exports = new class ConfigCache
return cb(null, parsed); return cb(null, parsed);
}); });
} }
}; })();

View File

@ -30,9 +30,28 @@ module.exports = () => {
checkAnsiHomePosition: true, checkAnsiHomePosition: true,
// List of terms that should be assumed to use cp437 encoding // List of terms that should be assumed to use cp437 encoding
cp437TermList : ['ansi', 'pcansi', 'pc-ansi', 'ansi-bbs', 'qansi', 'scoansi', 'syncterm', 'ansi-256color', 'ansi-256color-rgb'], cp437TermList: [
'ansi',
'pcansi',
'pc-ansi',
'ansi-bbs',
'qansi',
'scoansi',
'syncterm',
'ansi-256color',
'ansi-256color-rgb',
],
// List of terms that should be assumed to use utf8 encoding // List of terms that should be assumed to use utf8 encoding
utf8TermList : ['xterm', 'linux', 'screen', 'dumb', 'rxvt', 'konsole', 'gnome', 'x11 terminal emulator'], utf8TermList: [
'xterm',
'linux',
'screen',
'dumb',
'rxvt',
'konsole',
'gnome',
'x11 terminal emulator',
],
}, },
users: { users: {
@ -68,9 +87,19 @@ module.exports = () => {
newUserNames: ['new', 'apply'], // Names reserved for applying newUserNames: ['new', 'apply'], // Names reserved for applying
badUserNames: [ badUserNames: [
'sysop', 'admin', 'administrator', 'root', 'all', 'sysop',
'areamgr', 'filemgr', 'filefix', 'areafix', 'allfix', 'admin',
'server', 'client', 'notme' 'administrator',
'root',
'all',
'areamgr',
'filemgr',
'filefix',
'areafix',
'allfix',
'server',
'client',
'notme',
], ],
preAuthIdleLogoutSeconds: 60 * 3, // 3m preAuthIdleLogoutSeconds: 60 * 3, // 3m
@ -87,11 +116,20 @@ module.exports = () => {
method: 'googleAuth', method: 'googleAuth',
otp: { otp: {
registerEmailText : paths.join(__dirname, '../misc/otp_register_email.template.txt'), registerEmailText: paths.join(
registerEmailHtml : paths.join(__dirname, '../misc/otp_register_email.template.html'), __dirname,
registerPageTemplate : paths.join(__dirname, '../www/otp_register.template.html'), '../misc/otp_register_email.template.txt'
} ),
} registerEmailHtml: paths.join(
__dirname,
'../misc/otp_register_email.template.html'
),
registerPageTemplate: paths.join(
__dirname,
'../www/otp_register.template.html'
),
},
},
}, },
theme: { theme: {
@ -109,7 +147,7 @@ module.exports = () => {
dateTimeFormat: { dateTimeFormat: {
short: 'MM/DD/YYYY h:mm a', short: 'MM/DD/YYYY h:mm a',
long: 'ddd, MMMM Do, YYYY, h:mm a', long: 'ddd, MMMM Do, YYYY, h:mm a',
} },
}, },
menus: { menus: {
@ -167,7 +205,10 @@ module.exports = () => {
// - https://blog.sleeplessbeastie.eu/2017/12/28/how-to-generate-private-key/ // - https://blog.sleeplessbeastie.eu/2017/12/28/how-to-generate-private-key/
// - https://gist.github.com/briansmith/2ee42439923d8e65a266994d0f70180b // - https://gist.github.com/briansmith/2ee42439923d8e65a266994d0f70180b
// //
privateKeyPem : paths.join(__dirname, './../config/security/ssh_private_key.pem'), privateKeyPem: paths.join(
__dirname,
'./../config/security/ssh_private_key.pem'
),
firstMenu: 'sshConnected', firstMenu: 'sshConnected',
firstMenuNewUser: 'sshConnectedNewUser', firstMenuNewUser: 'sshConnectedNewUser',
@ -220,7 +261,7 @@ module.exports = () => {
'hmac-md5-96', 'hmac-md5-96',
], ],
// note that we disable compression by default due to issues with many clients. YMMV. // note that we disable compression by default due to issues with many clients. YMMV.
compress : [ 'none' ] compress: ['none'],
}, },
}, },
webSocket: { webSocket: {
@ -257,12 +298,21 @@ module.exports = () => {
// URL to POST submit reset form. // URL to POST submit reset form.
// templates for pw reset *email* // templates for pw reset *email*
resetPassEmailText : paths.join(__dirname, '../misc/reset_password_email.template.txt'), // plain text version resetPassEmailText: paths.join(
resetPassEmailHtml : paths.join(__dirname, '../misc/reset_password_email.template.html'), // HTML version __dirname,
'../misc/reset_password_email.template.txt'
), // plain text version
resetPassEmailHtml: paths.join(
__dirname,
'../misc/reset_password_email.template.html'
), // HTML version
// tempalte for pw reset *landing page* // tempalte for pw reset *landing page*
// //
resetPageTemplate : paths.join(__dirname, './../www/reset_password.template.html'), resetPageTemplate: paths.join(
__dirname,
'./../www/reset_password.template.html'
),
}, },
http: { http: {
@ -274,7 +324,7 @@ module.exports = () => {
port: 8443, port: 8443,
certPem: paths.join(__dirname, './../config/https_cert.pem'), certPem: paths.join(__dirname, './../config/https_cert.pem'),
keyPem: paths.join(__dirname, './../config/https_cert_key.pem'), keyPem: paths.join(__dirname, './../config/https_cert_key.pem'),
} },
}, },
gopher: { gopher: {
@ -314,8 +364,8 @@ module.exports = () => {
port: 8563, port: 8563,
certPem: paths.join(__dirname, './../config/nntps_cert.pem'), certPem: paths.join(__dirname, './../config/nntps_cert.pem'),
keyPem: paths.join(__dirname, './../config/nntps_key.pem'), keyPem: paths.join(__dirname, './../config/nntps_key.pem'),
} },
} },
}, },
chatServers: { chatServers: {
@ -325,7 +375,7 @@ module.exports = () => {
serverPort: 5000, serverPort: 5000,
retryDelay: 10000, retryDelay: 10000,
multiplexerPort: 5000, multiplexerPort: 5000,
} },
}, },
infoExtractUtils: { infoExtractUtils: {
@ -335,22 +385,33 @@ module.exports = () => {
Exiftool: { Exiftool: {
cmd: 'exiftool', cmd: 'exiftool',
args: [ args: [
'-charset', 'utf8', '{filePath}', '-charset',
'utf8',
'{filePath}',
// exclude the following: // exclude the following:
'--directory', '--filepermissions', '--exiftoolversion', '--filename', '--filesize', '--directory',
'--filemodifydate', '--fileaccessdate', '--fileinodechangedate', '--createdate', '--modifydate', '--filepermissions',
'--metadatadate', '--xmptoolkit' '--exiftoolversion',
] '--filename',
'--filesize',
'--filemodifydate',
'--fileaccessdate',
'--fileinodechangedate',
'--createdate',
'--modifydate',
'--metadatadate',
'--xmptoolkit',
],
}, },
XDMS2Desc: { XDMS2Desc: {
// http://manpages.ubuntu.com/manpages/trusty/man1/xdms.1.html // http://manpages.ubuntu.com/manpages/trusty/man1/xdms.1.html
cmd: 'xdms', cmd: 'xdms',
args : [ 'd', '{filePath}' ] args: ['d', '{filePath}'],
}, },
XDMS2LongDesc: { XDMS2LongDesc: {
// http://manpages.ubuntu.com/manpages/trusty/man1/xdms.1.html // http://manpages.ubuntu.com/manpages/trusty/man1/xdms.1.html
cmd: 'xdms', cmd: 'xdms',
args : [ 'f', '{filePath}' ] args: ['f', '{filePath}'],
}, },
}, },
@ -498,29 +559,37 @@ module.exports = () => {
sig: '9602', // 16bit sum of "NICKATARI" sig: '9602', // 16bit sum of "NICKATARI"
ext: '.atr', ext: '.atr',
archiveHandler: 'Atr', archiveHandler: 'Atr',
} },
] ],
}, },
archives: { archives: {
archivers: { archivers: {
'7Zip' : { // p7zip package '7Zip': {
// p7zip package
compress: { compress: {
cmd: '7za', cmd: '7za',
args: ['a', '-tzip', '{archivePath}', '{fileList}'], args: ['a', '-tzip', '{archivePath}', '{fileList}'],
}, },
decompress: { decompress: {
cmd: '7za', cmd: '7za',
args : [ 'e', '-y', '-o{extractPath}', '{archivePath}' ] // :TODO: should be 'x'? args: ['e', '-y', '-o{extractPath}', '{archivePath}'], // :TODO: should be 'x'?
}, },
list: { list: {
cmd: '7za', cmd: '7za',
args: ['l', '{archivePath}'], args: ['l', '{archivePath}'],
entryMatch : '^[0-9]{4}-[0-9]{2}-[0-9]{2}\\s[0-9]{2}:[0-9]{2}:[0-9]{2}\\s[A-Za-z\\.]{5}\\s+([0-9]+)\\s+[0-9]+\\s+([^\\r\\n]+)$', entryMatch:
'^[0-9]{4}-[0-9]{2}-[0-9]{2}\\s[0-9]{2}:[0-9]{2}:[0-9]{2}\\s[A-Za-z\\.]{5}\\s+([0-9]+)\\s+[0-9]+\\s+([^\\r\\n]+)$',
}, },
extract: { extract: {
cmd: '7za', cmd: '7za',
args : [ 'e', '-y', '-o{extractPath}', '{archivePath}', '{fileList}' ], args: [
'e',
'-y',
'-o{extractPath}',
'{archivePath}',
'{fileList}',
],
}, },
}, },
@ -537,12 +606,19 @@ module.exports = () => {
cmd: 'unzip', cmd: 'unzip',
args: ['-l', '{archivePath}'], args: ['-l', '{archivePath}'],
// Annoyingly, dates can be in YYYY-MM-DD or MM-DD-YYYY format // Annoyingly, dates can be in YYYY-MM-DD or MM-DD-YYYY format
entryMatch : '^\\s*([0-9]+)\\s+[0-9]{2,4}-[0-9]{2}-[0-9]{2,4}\\s+[0-9]{2}:[0-9]{2}\\s+([^\\r\\n]+)$', entryMatch:
'^\\s*([0-9]+)\\s+[0-9]{2,4}-[0-9]{2}-[0-9]{2,4}\\s+[0-9]{2}:[0-9]{2}\\s+([^\\r\\n]+)$',
}, },
extract: { extract: {
cmd: 'unzip', cmd: 'unzip',
args : [ '-n', '{archivePath}', '{fileList}', '-d', '{extractPath}' ], args: [
} '-n',
'{archivePath}',
'{fileList}',
'-d',
'{extractPath}',
],
},
}, },
Lha: { Lha: {
@ -559,12 +635,13 @@ module.exports = () => {
list: { list: {
cmd: 'lha', cmd: 'lha',
args: ['-l', '{archivePath}'], args: ['-l', '{archivePath}'],
entryMatch : '^[\\[a-z\\]]+(?:\\s+[0-9]+\\s+[0-9]+|\\s+)([0-9]+)\\s+[0-9]{2}\\.[0-9]\\%\\s+[A-Za-z]{3}\\s+[0-9]{1,2}\\s+[0-9]{4}\\s+([^\\r\\n]+)$', entryMatch:
'^[\\[a-z\\]]+(?:\\s+[0-9]+\\s+[0-9]+|\\s+)([0-9]+)\\s+[0-9]{2}\\.[0-9]\\%\\s+[A-Za-z]{3}\\s+[0-9]{1,2}\\s+[0-9]{4}\\s+([^\\r\\n]+)$',
}, },
extract: { extract: {
cmd: 'lha', cmd: 'lha',
args : [ '-efw={extractPath}', '{archivePath}', '{fileList}' ] args: ['-efw={extractPath}', '{archivePath}', '{fileList}'],
} },
}, },
Lzx: { Lzx: {
@ -582,8 +659,9 @@ module.exports = () => {
list: { list: {
cmd: 'unlzx', cmd: 'unlzx',
args: ['-v', '{archivePath}'], args: ['-v', '{archivePath}'],
entryMatch : '^\\s+([0-9]+)\\s+[^\\s]+\\s+[0-9]{2}:[0-9]{2}:[0-9]{2}\\s+[0-9]{1,2}-[a-z]{3}-[0-9]{4}\\s+[a-z\\-]+\\s+\\"([^"]+)\\"$', entryMatch:
} '^\\s+([0-9]+)\\s+[^\\s]+\\s+[0-9]{2}:[0-9]{2}:[0-9]{2}\\s+[0-9]{1,2}-[a-z]{3}-[0-9]{4}\\s+[a-z\\-]+\\s+\\"([^"]+)\\"$',
},
}, },
Arj: { Arj: {
@ -598,16 +676,18 @@ module.exports = () => {
list: { list: {
cmd: 'arj', cmd: 'arj',
args: ['l', '{archivePath}'], args: ['l', '{archivePath}'],
entryMatch : '^([^\\s]+)\\s+([0-9]+)\\s+[0-9]+\\s[0-9\\.]+\\s+[0-9]{2}\\-[0-9]{2}\\-[0-9]{2}\\s[0-9]{2}\\:[0-9]{2}\\:[0-9]{2}\\s+(?:[^\\r\\n]+)$', entryMatch:
entryGroupOrder : { // defaults to { byteSize : 1, fileName : 2 } '^([^\\s]+)\\s+([0-9]+)\\s+[0-9]+\\s[0-9\\.]+\\s+[0-9]{2}\\-[0-9]{2}\\-[0-9]{2}\\s[0-9]{2}\\:[0-9]{2}\\:[0-9]{2}\\s+(?:[^\\r\\n]+)$',
entryGroupOrder: {
// defaults to { byteSize : 1, fileName : 2 }
fileName: 1, fileName: 1,
byteSize: 2, byteSize: 2,
} },
}, },
extract: { extract: {
cmd: 'arj', cmd: 'arj',
args: ['e', '{archivePath}', '{extractPath}', '{fileList}'], args: ['e', '{archivePath}', '{extractPath}', '{fileList}'],
} },
}, },
Rar: { Rar: {
@ -618,46 +698,69 @@ module.exports = () => {
list: { list: {
cmd: 'unrar', cmd: 'unrar',
args: ['l', '{archivePath}'], args: ['l', '{archivePath}'],
entryMatch : '^\\s+[\\.A-Z]+\\s+([\\d]+)\\s{2}[0-9]{2,4}\\-[0-9]{2}\\-[0-9]{2}\\s[0-9]{2}\\:[0-9]{2}\\s{2}([^\\r\\n]+)$', entryMatch:
'^\\s+[\\.A-Z]+\\s+([\\d]+)\\s{2}[0-9]{2,4}\\-[0-9]{2}\\-[0-9]{2}\\s[0-9]{2}\\:[0-9]{2}\\s{2}([^\\r\\n]+)$',
}, },
extract: { extract: {
cmd: 'unrar', cmd: 'unrar',
args: ['e', '{archivePath}', '{extractPath}', '{fileList}'], args: ['e', '{archivePath}', '{extractPath}', '{fileList}'],
} },
}, },
TarGz: { TarGz: {
decompress: { decompress: {
cmd: 'tar', cmd: 'tar',
args : [ '-xf', '{archivePath}', '-C', '{extractPath}', '--strip-components=1' ], args: [
'-xf',
'{archivePath}',
'-C',
'{extractPath}',
'--strip-components=1',
],
}, },
list: { list: {
cmd: 'tar', cmd: 'tar',
args: ['-tvf', '{archivePath}'], args: ['-tvf', '{archivePath}'],
entryMatch : '^[drwx\\-]{10}\\s[A-Za-z0-9\\/]+\\s+([0-9]+)\\s[0-9]{4}\\-[0-9]{2}\\-[0-9]{2}\\s[0-9]{2}\\:[0-9]{2}\\s([^\\r\\n]+)$', entryMatch:
'^[drwx\\-]{10}\\s[A-Za-z0-9\\/]+\\s+([0-9]+)\\s[0-9]{4}\\-[0-9]{2}\\-[0-9]{2}\\s[0-9]{2}\\:[0-9]{2}\\s([^\\r\\n]+)$',
}, },
extract: { extract: {
cmd: 'tar', cmd: 'tar',
args : [ '-xvf', '{archivePath}', '-C', '{extractPath}', '{fileList}' ], args: [
} '-xvf',
'{archivePath}',
'-C',
'{extractPath}',
'{fileList}',
],
},
}, },
Atr: { Atr: {
decompress: { decompress: {
cmd: 'atr', cmd: 'atr',
args : [ '{archivePath}', 'x', '-a', '-o', '{extractPath}' ] args: ['{archivePath}', 'x', '-a', '-o', '{extractPath}'],
}, },
list: { list: {
cmd: 'atr', cmd: 'atr',
args: ['{archivePath}', 'ls', '-la1'], args: ['{archivePath}', 'ls', '-la1'],
entryMatch : '^[rwxs-]{5}\\s+([0-9]+)\\s\\([0-9\\s]+\\)\\s([^\\r\\n\\s]*)(?:[^\\r\\n]+)?$', entryMatch:
'^[rwxs-]{5}\\s+([0-9]+)\\s\\([0-9\\s]+\\)\\s([^\\r\\n\\s]*)(?:[^\\r\\n]+)?$',
}, },
extract: { extract: {
cmd: 'atr', cmd: 'atr',
// note: -l converts Atari 0x9b line feeds to 0x0a; not ideal if we're dealing with a binary of course. // note: -l converts Atari 0x9b line feeds to 0x0a; not ideal if we're dealing with a binary of course.
args : [ '{archivePath}', 'x', '-a', '-l', '-o', '{extractPath}', '{fileList}' ] args: [
} '{archivePath}',
} 'x',
'-a',
'-l',
'-o',
'{extractPath}',
'{fileList}',
],
},
},
}, },
}, },
@ -677,7 +780,7 @@ module.exports = () => {
recvCmd: 'sexyz', recvCmd: 'sexyz',
recvArgs: ['-telnet', '-8', 'rz', '{uploadDir}'], recvArgs: ['-telnet', '-8', 'rz', '{uploadDir}'],
recvArgsNonBatch: ['-telnet', '-8', 'rz', '{fileName}'], recvArgsNonBatch: ['-telnet', '-8', 'rz', '{fileName}'],
} },
}, },
xmodemSexyz: { xmodemSexyz: {
@ -688,8 +791,8 @@ module.exports = () => {
sendCmd: 'sexyz', sendCmd: 'sexyz',
sendArgs: ['-telnet', 'sX', '@{fileListPath}'], sendArgs: ['-telnet', 'sX', '@{fileListPath}'],
recvCmd: 'sexyz', recvCmd: 'sexyz',
recvArgsNonBatch : [ '-telnet', 'rC', '{fileName}' ] recvArgsNonBatch: ['-telnet', 'rC', '{fileName}'],
} },
}, },
ymodemSexyz: { ymodemSexyz: {
@ -701,7 +804,7 @@ module.exports = () => {
sendArgs: ['-telnet', 'sY', '@{fileListPath}'], sendArgs: ['-telnet', 'sY', '@{fileListPath}'],
recvCmd: 'sexyz', recvCmd: 'sexyz',
recvArgs: ['-telnet', 'ry', '{uploadDir}'], recvArgs: ['-telnet', 'ry', '{uploadDir}'],
} },
}, },
zmodem8kSz: { zmodem8kSz: {
@ -712,15 +815,22 @@ module.exports = () => {
sendCmd: 'sz', // Avail on Debian/Ubuntu based systems as the package "lrzsz" sendCmd: 'sz', // Avail on Debian/Ubuntu based systems as the package "lrzsz"
sendArgs: [ sendArgs: [
// :TODO: try -q // :TODO: try -q
'--zmodem', '--try-8k', '--binary', '--restricted', '{filePaths}' '--zmodem',
'--try-8k',
'--binary',
'--restricted',
'{filePaths}',
], ],
recvCmd: 'rz', // Avail on Debian/Ubuntu based systems as the package "lrzsz" recvCmd: 'rz', // Avail on Debian/Ubuntu based systems as the package "lrzsz"
recvArgs: [ recvArgs: [
'--zmodem', '--binary', '--restricted', '--keep-uppercase', // dumps to CWD which is set to {uploadDir} '--zmodem',
'--binary',
'--restricted',
'--keep-uppercase', // dumps to CWD which is set to {uploadDir}
], ],
processIACs: true, // escape/de-escape IACs (0xff) processIACs: true, // escape/de-escape IACs (0xff)
} },
} },
}, },
messageAreaDefaults: { messageAreaDefaults: {
@ -746,9 +856,9 @@ module.exports = () => {
local_bulletin: { local_bulletin: {
name: 'System Bulletins', name: 'System Bulletins',
desc: 'Bulletin messages for all users', desc: 'Bulletin messages for all users',
} },
} },
} },
}, },
scannerTossers: { scannerTossers: {
@ -777,8 +887,8 @@ module.exports = () => {
uploadBy: 'ENiGMA TIC', // default upload by username (override @ network) uploadBy: 'ENiGMA TIC', // default upload by username (override @ network)
allowReplace: false, // use "Replaces" TIC field allowReplace: false, // use "Replaces" TIC field
descPriority: 'diz', // May be diz=.DIZ/etc., or tic=from TIC Ldesc descPriority: 'diz', // May be diz=.DIZ/etc., or tic=from TIC Ldesc
} },
} },
}, },
fileBase: { fileBase: {
@ -793,24 +903,25 @@ module.exports = () => {
// FILE_ID.DIZ - https://en.wikipedia.org/wiki/FILE_ID.DIZ // FILE_ID.DIZ - https://en.wikipedia.org/wiki/FILE_ID.DIZ
// Some groups include a FILE_ID.ANS. We try to use that over FILE_ID.DIZ if available. // Some groups include a FILE_ID.ANS. We try to use that over FILE_ID.DIZ if available.
desc: [ desc: [
'^.*FILE_ID\.ANS$', '^.*FILE_ID\.DIZ$', // eslint-disable-line no-useless-escape '^.*FILE_ID.ANS$',
'^.*DESC\.SDI$', // eslint-disable-line no-useless-escape '^.*FILE_ID.DIZ$', // eslint-disable-line no-useless-escape
'^.*DESCRIPT\.ION$', // eslint-disable-line no-useless-escape '^.*DESC.SDI$', // eslint-disable-line no-useless-escape
'^.*FILE\.DES$', // eslint-disable-line no-useless-escape '^.*DESCRIPT.ION$', // eslint-disable-line no-useless-escape
'^.*FILE\.SDI$', // eslint-disable-line no-useless-escape '^.*FILE.DES$', // eslint-disable-line no-useless-escape
'^.*DISK\.ID$' // eslint-disable-line no-useless-escape '^.*FILE.SDI$', // eslint-disable-line no-useless-escape
'^.*DISK.ID$', // eslint-disable-line no-useless-escape
], ],
// common README filename - https://en.wikipedia.org/wiki/README // common README filename - https://en.wikipedia.org/wiki/README
descLong: [ descLong: [
'^[^/\]*\.NFO$', // eslint-disable-line no-useless-escape '^[^/]*.NFO$', // eslint-disable-line no-useless-escape
'^.*README\.1ST$', // eslint-disable-line no-useless-escape '^.*README.1ST$', // eslint-disable-line no-useless-escape
'^.*README\.NOW$', // eslint-disable-line no-useless-escape '^.*README.NOW$', // eslint-disable-line no-useless-escape
'^.*README\.TXT$', // eslint-disable-line no-useless-escape '^.*README.TXT$', // eslint-disable-line no-useless-escape
'^.*READ\.ME$', // eslint-disable-line no-useless-escape '^.*READ.ME$', // eslint-disable-line no-useless-escape
'^.*README$', // eslint-disable-line no-useless-escape '^.*README$', // eslint-disable-line no-useless-escape
'^.*README\.md$', // eslint-disable-line no-useless-escape '^.*README.md$', // eslint-disable-line no-useless-escape
'^RELEASE-INFO.ASC$' // eslint-disable-line no-useless-escape '^RELEASE-INFO.ASC$', // eslint-disable-line no-useless-escape
], ],
}, },
@ -829,7 +940,7 @@ module.exports = () => {
'\\b(?:jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec|january|february|march|april|may|june|july|august|september|october|november|december),?\\s[0-9]+(?:st|nd|rd|th)?,?\\s((?:[0-9]{2})?[0-9]{2})\\b', // November 29th, 1997 '\\b(?:jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec|january|february|march|april|may|june|july|august|september|october|november|december),?\\s[0-9]+(?:st|nd|rd|th)?,?\\s((?:[0-9]{2})?[0-9]{2})\\b', // November 29th, 1997
'\\(((?:19|20)[0-9]{2})\\)', // (19xx) or (20xx) -- with parens -- do this before 19xx 20xx such that this has priority '\\(((?:19|20)[0-9]{2})\\)', // (19xx) or (20xx) -- with parens -- do this before 19xx 20xx such that this has priority
'\\b((?:19|20)[0-9]{2})\\b', // simple 19xx or 20xx with word boundaries '\\b((?:19|20)[0-9]{2})\\b', // simple 19xx or 20xx with word boundaries
'\\b\'([17-9][0-9])\\b', // '95, '17, ... "\\b'([17-9][0-9])\\b", // '95, '17, ...
// :TODO: DD/MMM/YY, DD/MMMM/YY, DD/MMM/YYYY, etc. // :TODO: DD/MMM/YY, DD/MMMM/YY, DD/MMM/YYYY, etc.
], ],
@ -859,12 +970,11 @@ module.exports = () => {
name: 'System Temporary Downloads', name: 'System Temporary Downloads',
desc: 'Temporary downloadables', desc: 'Temporary downloadables',
storageTags: ['sys_temp_download'], storageTags: ['sys_temp_download'],
} },
} },
}, },
eventScheduler: { eventScheduler: {
events: { events: {
dailyMaintenance: { dailyMaintenance: {
schedule: 'at 11:59pm', schedule: 'at 11:59pm',
@ -896,7 +1006,7 @@ module.exports = () => {
forgotPasswordMaintenance: { forgotPasswordMaintenance: {
schedule: 'every 24 hours', schedule: 'every 24 hours',
action: '@method:core/web_password_reset.js:performMaintenanceTask', action: '@method:core/web_password_reset.js:performMaintenanceTask',
args : [ '24 hours' ] // items older than this will be removed args: ['24 hours'], // items older than this will be removed
}, },
twoFactorRegisterTokenMaintenance: { twoFactorRegisterTokenMaintenance: {
@ -905,7 +1015,7 @@ module.exports = () => {
args: [ args: [
'auth_factor2_otp_register', 'auth_factor2_otp_register',
'24 hours', // expire time '24 hours', // expire time
] ],
}, },
// //
@ -918,17 +1028,18 @@ module.exports = () => {
action : '@method:core/file_base_list_export.js:updateFileBaseDescFilesScheduledEvent', action : '@method:core/file_base_list_export.js:updateFileBaseDescFilesScheduledEvent',
} }
*/ */
} },
}, },
logging: { logging: {
rotatingFile : { // set to 'disabled' or false to disable rotatingFile: {
// set to 'disabled' or false to disable
type: 'rotating-file', type: 'rotating-file',
fileName: 'enigma-bbs.log', fileName: 'enigma-bbs.log',
period: '1d', period: '1d',
count: 3, count: 3,
level: 'debug', level: 'debug',
} },
// :TODO: syslog - https://github.com/mcavage/node-bunyan-syslog // :TODO: syslog - https://github.com/mcavage/node-bunyan-syslog
}, },
@ -940,7 +1051,7 @@ module.exports = () => {
statLog: { statLog: {
systemEvents: { systemEvents: {
loginHistoryMax: -1, // set to -1 for forever loginHistoryMax: -1, // set to -1 for forever
} },
}, },
}; };
}; };

View File

@ -14,16 +14,14 @@ module.exports = class ConfigLoader {
defaultsCustomizer = null, defaultsCustomizer = null,
onReload = null, onReload = null,
keepWsc = false, keepWsc = false,
} = } = {
{
hotReload: true, hotReload: true,
defaultConfig: {}, defaultConfig: {},
defaultsCustomizer: null, defaultsCustomizer: null,
onReload: null, onReload: null,
keepWsc: false, keepWsc: false,
} }
) ) {
{
this.current = {}; this.current = {};
this.hotReload = hotReload; this.hotReload = hotReload;
@ -61,7 +59,7 @@ module.exports = class ConfigLoader {
// //
async.waterfall( async.waterfall(
[ [
(callback) => { callback => {
return this._loadConfigFile(baseConfigPath, callback); return this._loadConfigFile(baseConfigPath, callback);
}, },
(config, callback) => { (config, callback) => {
@ -72,7 +70,8 @@ module.exports = class ConfigLoader {
config, config,
(defaultVal, configVal, key, target, source) => { (defaultVal, configVal, key, target, source) => {
var path; var path;
while (true) { // eslint-disable-line no-constant-condition while (true) {
// eslint-disable-line no-constant-condition
if (!stack.length) { if (!stack.length) {
stack.push({ source, path: [] }); stack.push({ source, path: [] });
} }
@ -89,7 +88,12 @@ module.exports = class ConfigLoader {
} }
path = path.join('.'); path = path.join('.');
return this.defaultsCustomizer(defaultVal, configVal, key, path); return this.defaultsCustomizer(
defaultVal,
configVal,
key,
path
);
} }
); );
@ -120,7 +124,7 @@ module.exports = class ConfigLoader {
switch (type) { switch (type) {
case 'bool': case 'bool':
case 'boolean': case 'boolean':
value = ('1' === value || 'true' === value.toLowerCase()); value = '1' === value || 'true' === value.toLowerCase();
break; break;
case 'number': case 'number':
@ -162,7 +166,9 @@ module.exports = class ConfigLoader {
let value = process.env[varName]; let value = process.env[varName];
if (!value) { if (!value) {
// console is about as good as we can do here // console is about as good as we can do here
return console.info(`WARNING: environment variable "${varName}" from spec "${spec}" not found!`); return console.info(
`WARNING: environment variable "${varName}" from spec "${spec}" not found!`
);
} }
if ('array' === array) { if ('array' === array) {
@ -212,7 +218,9 @@ module.exports = class ConfigLoader {
// If a included file is changed, we need to re-cache, so this // If a included file is changed, we need to re-cache, so this
// must be tracked... // must be tracked...
const includePaths = config.includes.map(inc => paths.join(configRoot, inc)); const includePaths = config.includes.map(inc => paths.join(configRoot, inc));
async.eachSeries(includePaths, (includePath, nextIncludePath) => { async.eachSeries(
includePaths,
(includePath, nextIncludePath) => {
this._loadConfigFile(includePath, (err, includedConfig) => { this._loadConfigFile(includePath, (err, includedConfig) => {
if (err) { if (err) {
return nextIncludePath(err); return nextIncludePath(err);
@ -225,13 +233,12 @@ module.exports = class ConfigLoader {
err => { err => {
this.configPaths = [this.baseConfigPath, ...includePaths]; this.configPaths = [this.baseConfigPath, ...includePaths];
return cb(err, config); return cb(err, config);
}); }
);
} }
_resolveAtSpecs(config) { _resolveAtSpecs(config) {
return mapValuesDeep( return mapValuesDeep(config, value => {
config,
value => {
if (_.isString(value) && '@' === value.charAt(0)) { if (_.isString(value) && '@' === value.charAt(0)) {
if (value.startsWith('@reference:')) { if (value.startsWith('@reference:')) {
const refPath = value.slice(11); const refPath = value.slice(11);
@ -242,7 +249,6 @@ module.exports = class ConfigLoader {
} }
return value; return value;
} });
);
} }
}; };

View File

@ -21,7 +21,7 @@ const withCursorPositionReport = (client, cprHandler, failMessage, cb) => {
return cb(err); return cb(err);
}; };
const cprListener = (pos) => { const cprListener = pos => {
cprHandler(pos); cprHandler(pos);
return done(null); return done(null);
}; };
@ -32,7 +32,7 @@ const withCursorPositionReport = (client, cprHandler, failMessage, cb) => {
giveUpTimer = setTimeout(() => { giveUpTimer = setTimeout(() => {
return done(Errors.General(failMessage)); return done(Errors.General(failMessage));
}, 2000); }, 2000);
} };
function ansiDiscoverHomePosition(client, cb) { function ansiDiscoverHomePosition(client, cb) {
// //
@ -55,10 +55,13 @@ function ansiDiscoverHomePosition(client, cb) {
// We expect either 0,0, or 1,1. Anything else will be filed as bad data // We expect either 0,0, or 1,1. Anything else will be filed as bad data
// //
if (h > 1 || w > 1) { if (h > 1 || w > 1) {
return client.log.warn( { height : h, width : w }, 'Ignoring ANSI home position CPR due to unexpected values'); return client.log.warn(
{ height: h, width: w },
'Ignoring ANSI home position CPR due to unexpected values'
);
} }
if(0 === h & 0 === w) { if ((0 === h) & (0 === w)) {
// //
// Store a CPR offset in the client. All CPR's from this point on will offset by this amount // Store a CPR offset in the client. All CPR's from this point on will offset by this amount
// //
@ -99,17 +102,21 @@ function ansiAttemptDetectUTF8(client, cb) {
pos => { pos => {
initialPosition = pos; initialPosition = pos;
withCursorPositionReport(client, withCursorPositionReport(
client,
pos => { pos => {
const [_, w] = pos; const [_, w] = pos;
const len = w - initialPosition[1]; const len = w - initialPosition[1];
if(!isNaN(len) && len >= ASCIIPortion.length + 6) { // CP437 displays 3 chars each Unicode skull if (!isNaN(len) && len >= ASCIIPortion.length + 6) {
client.log.info('Terminal identified as UTF-8 but does not appear to be. Overriding to "ansi".'); // CP437 displays 3 chars each Unicode skull
client.log.info(
'Terminal identified as UTF-8 but does not appear to be. Overriding to "ansi".'
);
client.setTermType('ansi'); client.setTermType('ansi');
} }
}, },
'Detect UTF-8 stage 2 timed out', 'Detect UTF-8 stage 2 timed out',
cb, cb
); );
client.term.rawWrite(`\u9760${ASCIIPortion}\u9760`); // Unicode skulls on each side client.term.rawWrite(`\u9760${ASCIIPortion}\u9760`); // Unicode skulls on each side
@ -150,7 +157,9 @@ const ansiQuerySyncTermFontSupport = (client, cb) => {
const [_, w] = pos; const [_, w] = pos;
if (w === 1) { if (w === 1) {
// cursor didn't move // cursor didn't move
client.log.info('Client supports SyncTERM fonts or properly ignores unknown ESC sequence'); client.log.info(
'Client supports SyncTERM fonts or properly ignores unknown ESC sequence'
);
client.term.syncTermFontsEnabled = true; client.term.syncTermFontsEnabled = true;
} }
}, },
@ -158,8 +167,10 @@ const ansiQuerySyncTermFontSupport = (client, cb) => {
cb cb
); );
client.term.rawWrite(`${ansi.goto(1, 1)}${ansi.setSyncTermFont('cp437')}${ansi.queryPos()}`); client.term.rawWrite(
} `${ansi.goto(1, 1)}${ansi.setSyncTermFont('cp437')}${ansi.queryPos()}`
);
};
function ansiQueryTermSizeIfNeeded(client, cb) { function ansiQueryTermSizeIfNeeded(client, cb) {
if (client.term.termHeight > 0 || client.term.termWidth > 0) { if (client.term.termHeight > 0 || client.term.termWidth > 0) {
@ -185,7 +196,8 @@ function ansiQueryTermSizeIfNeeded(client, cb) {
if (h < 10 || h === 999 || w < 10 || w === 999) { if (h < 10 || h === 999 || w < 10 || w === 999) {
return client.log.warn( return client.log.warn(
{ height: h, width: w }, { height: h, width: w },
'Ignoring ANSI CPR screen size query response due to non-sane values'); 'Ignoring ANSI CPR screen size query response due to non-sane values'
);
} }
client.term.termHeight = h; client.term.termHeight = h;
@ -195,7 +207,7 @@ function ansiQueryTermSizeIfNeeded(client, cb) {
{ {
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'
); );
@ -226,8 +238,7 @@ function displayBanner(term) {
|06Connected to |02EN|10i|02GMA|10½ |06BBS version |12|VN |06Connected to |02EN|10i|02GMA|10½ |06BBS version |12|VN
|06Copyright (c) 2014-2022 Bryan Ashby |14- |12http://l33t.codes/ |06Copyright (c) 2014-2022 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`);
);
} }
function connectEntry(client, nextMenu) { function connectEntry(client, nextMenu) {
@ -255,7 +266,10 @@ function connectEntry(client, nextMenu) {
// Default to DOS size 80x25. // Default to DOS size 80x25.
// //
// :TODO: Netrunner is currently hitting this and it feels wrong. Why is NAWS/ENV/CPR all failing??? // :TODO: Netrunner is currently 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;

View File

@ -1,47 +1,265 @@
const CP437UnicodeTable = [ const CP437UnicodeTable = [
'\u0000', '\u0001', '\u0002', '\u0003', '\u0004', '\u0005', '\u0006', '\u0000',
'\u0007', '\u0008', '\u0009', '\u000A', '\u000B', '\u000C', '\u000D', '\u0001',
'\u000E', '\u000F', '\u0010', '\u0011', '\u0012', '\u0013', '\u0014', '\u0002',
'\u0015', '\u0016', '\u0017', '\u0018', '\u0019', '\u001A', '\u001B', '\u0003',
'\u001C', '\u001D', '\u001E', '\u001F', '\u0020', '\u0021', '\u0022', '\u0004',
'\u0023', '\u0024', '\u0025', '\u0026', '\u0027', '\u0028', '\u0029', '\u0005',
'\u002A', '\u002B', '\u002C', '\u002D', '\u002E', '\u002F', '\u0030', '\u0006',
'\u0031', '\u0032', '\u0033', '\u0034', '\u0035', '\u0036', '\u0037', '\u0007',
'\u0038', '\u0039', '\u003A', '\u003B', '\u003C', '\u003D', '\u003E', '\u0008',
'\u003F', '\u0040', '\u0041', '\u0042', '\u0043', '\u0044', '\u0045', '\u0009',
'\u0046', '\u0047', '\u0048', '\u0049', '\u004A', '\u004B', '\u004C', '\u000A',
'\u004D', '\u004E', '\u004F', '\u0050', '\u0051', '\u0052', '\u0053', '\u000B',
'\u0054', '\u0055', '\u0056', '\u0057', '\u0058', '\u0059', '\u005A', '\u000C',
'\u005B', '\u005C', '\u005D', '\u005E', '\u005F', '\u0060', '\u0061', '\u000D',
'\u0062', '\u0063', '\u0064', '\u0065', '\u0066', '\u0067', '\u0068', '\u000E',
'\u0069', '\u006A', '\u006B', '\u006C', '\u006D', '\u006E', '\u006F', '\u000F',
'\u0070', '\u0071', '\u0072', '\u0073', '\u0074', '\u0075', '\u0076', '\u0010',
'\u0077', '\u0078', '\u0079', '\u007A', '\u007B', '\u007C', '\u007D', '\u0011',
'\u007E', '\u007F', '\u00C7', '\u00FC', '\u00E9', '\u00E2', '\u00E4', '\u0012',
'\u00E0', '\u00E5', '\u00E7', '\u00EA', '\u00EB', '\u00E8', '\u00EF', '\u0013',
'\u00EE', '\u00EC', '\u00C4', '\u00C5', '\u00C9', '\u00E6', '\u00C6', '\u0014',
'\u00F4', '\u00F6', '\u00F2', '\u00FB', '\u00F9', '\u00FF', '\u00D6', '\u0015',
'\u00DC', '\u00A2', '\u00A3', '\u00A5', '\u20A7', '\u0192', '\u00E1', '\u0016',
'\u00ED', '\u00F3', '\u00FA', '\u00F1', '\u00D1', '\u00AA', '\u00BA', '\u0017',
'\u00BF', '\u2310', '\u00AC', '\u00BD', '\u00BC', '\u00A1', '\u00AB', '\u0018',
'\u00BB', '\u2591', '\u2592', '\u2593', '\u2502', '\u2524', '\u2561', '\u0019',
'\u2562', '\u2556', '\u2555', '\u2563', '\u2551', '\u2557', '\u255D', '\u001A',
'\u255C', '\u255B', '\u2510', '\u2514', '\u2534', '\u252C', '\u251C', '\u001B',
'\u2500', '\u253C', '\u255E', '\u255F', '\u255A', '\u2554', '\u2569', '\u001C',
'\u2566', '\u2560', '\u2550', '\u256C', '\u2567', '\u2568', '\u2564', '\u001D',
'\u2565', '\u2559', '\u2558', '\u2552', '\u2553', '\u256B', '\u256A', '\u001E',
'\u2518', '\u250C', '\u2588', '\u2584', '\u258C', '\u2590', '\u2580', '\u001F',
'\u03B1', '\u00DF', '\u0393', '\u03C0', '\u03A3', '\u03C3', '\u00B5', '\u0020',
'\u03C4', '\u03A6', '\u0398', '\u03A9', '\u03B4', '\u221E', '\u03C6', '\u0021',
'\u03B5', '\u2229', '\u2261', '\u00B1', '\u2265', '\u2264', '\u2320', '\u0022',
'\u2321', '\u00F7', '\u2248', '\u00B0', '\u2219', '\u00B7', '\u221A', '\u0023',
'\u207F', '\u00B2', '\u25A0', '\u00A0' '\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]/; // eslint-disable-line no-control-regex const NonCP437EncodableRegExp =
const isCP437Encodable = (s) => { /[^\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]/; // eslint-disable-line no-control-regex
const isCP437Encodable = s => {
if (!s.length) { if (!s.length) {
return true; return true;
} }

View File

@ -38,7 +38,7 @@ const CRC32_TABLE = new Int32Array([
0x3e6e77db, 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5,
0x47b2cf7f, 0x30b5ffe9, 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0x47b2cf7f, 0x30b5ffe9, 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605,
0xcdd70693, 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 0xcdd70693, 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d,
]); ]);
exports.CRC32 = class CRC32 { exports.CRC32 = class CRC32 {
@ -86,6 +86,6 @@ exports.CRC32 = class CRC32 {
} }
finalize() { finalize() {
return (this.crc ^ (-1)) >>> 0; return (this.crc ^ -1) >>> 0;
} }
}; };

View File

@ -40,7 +40,8 @@ function getModDatabasePath(moduleInfo, suffix) {
// We expect that moduleInfo defines packageName which will be the base of the modules // We expect that moduleInfo defines packageName which will be the base of the modules
// filename. An optional suffix may be supplied as well. // filename. An optional suffix may be supplied as well.
// //
const HOST_RE = /^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9-]*[A-Za-z0-9])$/; const HOST_RE =
/^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9-]*[A-Za-z0-9])$/;
assert(_.isObject(moduleInfo)); assert(_.isObject(moduleInfo));
assert(_.isString(moduleInfo.packageName), 'moduleInfo must define "packageName"!'); assert(_.isString(moduleInfo.packageName), 'moduleInfo must define "packageName"!');
@ -51,20 +52,20 @@ function getModDatabasePath(moduleInfo, suffix) {
} }
assert( assert(
(full.split('.').length > 1 && HOST_RE.test(full)), full.split('.').length > 1 && HOST_RE.test(full),
'packageName must follow Reverse Domain Name Notation - https://en.wikipedia.org/wiki/Reverse_domain_name_notation'); 'packageName must follow Reverse Domain Name Notation - https://en.wikipedia.org/wiki/Reverse_domain_name_notation'
);
const Config = conf.get(); const Config = conf.get();
return paths.join(Config.paths.modsDb, `${full}.sqlite3`); return paths.join(Config.paths.modsDb, `${full}.sqlite3`);
} }
function loadDatabaseForMod(modInfo, cb) { function loadDatabaseForMod(modInfo, cb) {
const db = getTransactionDatabase(new sqlite3.Database( const db = getTransactionDatabase(
getModDatabasePath(modInfo), new sqlite3.Database(getModDatabasePath(modInfo), err => {
err => {
return cb(err, db); return cb(err, db);
} })
)); );
} }
function getISOTimestampString(ts) { function getISOTimestampString(ts) {
@ -79,17 +80,24 @@ function getISOTimestampString(ts) {
} }
function sanitizeString(s) { function sanitizeString(s) {
return s.replace(/[\0\x08\x09\x1a\n\r"'\\%]/g, c => { // eslint-disable-line no-control-regex return s.replace(/[\0\x08\x09\x1a\n\r"'\\%]/g, c => {
// eslint-disable-line no-control-regex
switch (c) { switch (c) {
case '\0' : return '\\0'; case '\0':
case '\x08' : return '\\b'; return '\\0';
case '\x09' : return '\\t'; case '\x08':
case '\x1a' : return '\\z'; return '\\b';
case '\n' : return '\\n'; case '\x09':
case '\r' : return '\\r'; return '\\t';
case '\x1a':
return '\\z';
case '\n':
return '\\n';
case '\r':
return '\\r';
case '"': case '"':
case '\'' : case "'":
return `${c}${c}`; return `${c}${c}`;
case '\\': case '\\':
@ -100,8 +108,11 @@ function sanitizeString(s) {
} }
function initializeDatabases(cb) { function initializeDatabases(cb) {
async.eachSeries( [ 'system', 'user', 'message', 'file' ], (dbName, next) => { async.eachSeries(
dbs[dbName] = sqlite3Trans.wrap(new sqlite3.Database(getDatabasePath(dbName), err => { ['system', 'user', 'message', 'file'],
(dbName, next) => {
dbs[dbName] = sqlite3Trans.wrap(
new sqlite3.Database(getDatabasePath(dbName), err => {
if (err) { if (err) {
return cb(err); return cb(err);
} }
@ -111,10 +122,13 @@ function initializeDatabases(cb) {
return next(null); return next(null);
}); });
}); });
})); })
}, err => { );
},
err => {
return cb(err); return cb(err);
}); }
);
} }
function enableForeignKeys(db) { function enableForeignKeys(db) {
@ -122,7 +136,7 @@ function enableForeignKeys(db) {
} }
const DB_INIT_TABLE = { const DB_INIT_TABLE = {
system : (cb) => { system: cb => {
enableForeignKeys(dbs.system); enableForeignKeys(dbs.system);
// Various stat/event logging - see stat_log.js // Various stat/event logging - see stat_log.js
@ -160,7 +174,7 @@ const DB_INIT_TABLE = {
return cb(null); return cb(null);
}, },
user : (cb) => { user: cb => {
enableForeignKeys(dbs.user); enableForeignKeys(dbs.user);
dbs.user.run( dbs.user.run(
@ -229,7 +243,7 @@ const DB_INIT_TABLE = {
return cb(null); return cb(null);
}, },
message : (cb) => { message: cb => {
enableForeignKeys(dbs.message); enableForeignKeys(dbs.message);
dbs.message.run( dbs.message.run(
@ -296,7 +310,6 @@ const DB_INIT_TABLE = {
);` );`
); );
// :TODO: need SQL to ensure cleaned up if delete from message? // :TODO: need SQL to ensure cleaned up if delete from message?
/* /*
dbs.message.run( dbs.message.run(
@ -337,7 +350,7 @@ const DB_INIT_TABLE = {
return cb(null); return cb(null);
}, },
file : (cb) => { file: cb => {
enableForeignKeys(dbs.file); enableForeignKeys(dbs.file);
dbs.file.run( dbs.file.run(
@ -457,5 +470,5 @@ const DB_INIT_TABLE = {
); );
return cb(null); return cb(null);
} },
}; };

View File

@ -35,12 +35,16 @@ module.exports = class DescriptIonFile {
// DESCRIPT.ION entries are terminated with a CR and/or LF // DESCRIPT.ION entries are terminated with a CR and/or LF
const lines = iconv.decode(descData, 'cp437').split(/\r?\n/g); const lines = iconv.decode(descData, 'cp437').split(/\r?\n/g);
async.each(lines, (entryData, nextLine) => { async.each(
lines,
(entryData, nextLine) => {
// //
// We allow quoted (long) filenames or non-quoted filenames. // We allow quoted (long) filenames or non-quoted filenames.
// FILENAME<SPC>DESC<0x04><program data><CR/LF> // FILENAME<SPC>DESC<0x04><program data><CR/LF>
// //
const parts = entryData.match(/^(?:(?:"([^"]+)" )|(?:([^ ]+) ))([^\x04]+)\x04(.)[^\r\n]*$/); // eslint-disable-line no-control-regex const parts = entryData.match(
/^(?:(?:"([^"]+)" )|(?:([^ ]+) ))([^\x04]+)\x04(.)[^\r\n]*$/
); // eslint-disable-line no-control-regex
if (!parts) { if (!parts) {
return nextLine(null); return nextLine(null);
} }
@ -54,24 +58,25 @@ module.exports = class DescriptIonFile {
// //
const desc = parts[3].replace(/\\r\\n|\\n|[^@]@n/g, '\r\n'); const desc = parts[3].replace(/\\r\\n|\\n|[^@]@n/g, '\r\n');
descIonFile.entries.set( descIonFile.entries.set(fileName, {
fileName,
{
desc: desc, desc: desc,
programId: parts[4], programId: parts[4],
programData: parts[5], programData: parts[5],
} });
);
return nextLine(null); return nextLine(null);
}, },
() => { () => {
return cb( return cb(
descIonFile.entries.size > 0 ? null : Errors.Invalid('Invalid or unrecognized DESCRIPT.ION format'), descIonFile.entries.size > 0
? null
: Errors.Invalid(
'Invalid or unrecognized DESCRIPT.ION format'
),
descIonFile descIonFile
); );
}); }
);
}); });
} }
}; };

View File

@ -32,7 +32,10 @@ module.exports = class Door {
}); });
conn.once('error', err => { conn.once('error', err => {
this.client.log.info( { error : err.message }, 'Door socket server connection'); this.client.log.info(
{ error: err.message },
'Door socket server connection'
);
return this.restoreIo(conn); return this.restoreIo(conn);
}); });
@ -93,7 +96,10 @@ module.exports = class Door {
// PID is launched. Make sure it's killed off if the user disconnects. // PID is launched. Make sure it's killed off if the user disconnects.
// //
Events.once(Events.getSystemEvents().ClientDisconnected, evt => { Events.once(Events.getSystemEvents().ClientDisconnected, evt => {
if (this.doorPty && this.client.session.uniqueId === _.get(evt, 'client.session.uniqueId')) { if (
this.doorPty &&
this.client.session.uniqueId === _.get(evt, 'client.session.uniqueId')
) {
this.client.log.info( this.client.log.info(
{ pid: this.doorPty.pid }, { pid: this.doorPty.pid },
'User has disconnected; Killing door process.' 'User has disconnected; Killing door process.'
@ -103,7 +109,8 @@ module.exports = class Door {
}); });
this.client.log.debug( this.client.log.debug(
{ processId : this.doorPty.pid }, 'External door process spawned' { processId: this.doorPty.pid },
'External door process spawned'
); );
if ('stdio' === this.io) { if ('stdio' === this.io) {
@ -118,7 +125,10 @@ module.exports = class Door {
}); });
} else if ('socket' === this.io) { } else if ('socket' === this.io) {
this.client.log.debug( this.client.log.debug(
{ srvPort : this.sockServer.address().port, srvSocket : this.sockServerSocket }, {
srvPort: this.sockServer.address().port,
srvSocket: this.sockServerSocket,
},
'Using temporary socket server for door I/O' 'Using temporary socket server for door I/O'
); );
} }

View File

@ -5,10 +5,7 @@
const { MenuModule } = require('./menu_module.js'); const { MenuModule } = require('./menu_module.js');
const { resetScreen } = require('./ansi_term.js'); const { resetScreen } = require('./ansi_term.js');
const { Errors } = require('./enig_error.js'); const { Errors } = require('./enig_error.js');
const { const { trackDoorRunBegin, trackDoorRunEnd } = require('./door_util.js');
trackDoorRunBegin,
trackDoorRunEnd
} = require('./door_util.js');
// deps // deps
const async = require('async'); const async = require('async');
@ -75,15 +72,24 @@ exports.getModule = class DoorPartyModule extends MenuModule {
sshClient.on('ready', () => { sshClient.on('ready', () => {
// track client termination so we can clean up early // track client termination so we can clean up early
self.client.once('end', () => { self.client.once('end', () => {
self.client.log.info('Connection ended. Terminating DoorParty connection'); self.client.log.info(
'Connection ended. Terminating DoorParty connection'
);
clientTerminated = true; clientTerminated = true;
sshClient.end(); sshClient.end();
}); });
// establish tunnel for rlogin // establish tunnel for rlogin
sshClient.forwardOut('127.0.0.1', self.config.sshPort, self.config.host, self.config.rloginPort, (err, stream) => { sshClient.forwardOut(
'127.0.0.1',
self.config.sshPort,
self.config.host,
self.config.rloginPort,
(err, stream) => {
if (err) { if (err) {
return callback(Errors.General('Failed to establish tunnel')); return callback(
Errors.General('Failed to establish tunnel')
);
} }
doorTracking = trackDoorRunBegin(self.client); doorTracking = trackDoorRunBegin(self.client);
@ -112,11 +118,14 @@ exports.getModule = class DoorPartyModule extends MenuModule {
restorePipe(); restorePipe();
sshClient.end(); sshClient.end();
}); });
}); }
);
}); });
sshClient.on('error', err => { sshClient.on('error', err => {
self.client.log.info(`DoorParty SSH client error: ${err.message}`); self.client.log.info(
`DoorParty SSH client error: ${err.message}`
);
trackDoorRunEnd(doorTracking); trackDoorRunEnd(doorTracking);
}); });
@ -133,7 +142,7 @@ exports.getModule = class DoorPartyModule extends MenuModule {
}); });
// note: no explicit callback() until we're finished! // note: no explicit callback() until we're finished!
} },
], ],
err => { err => {
if (err) { if (err) {

View File

@ -29,7 +29,11 @@ function trackDoorRunEnd(trackInfo) {
const runTimeMinutes = Math.floor(diff.asMinutes()); const runTimeMinutes = Math.floor(diff.asMinutes());
if (runTimeMinutes > 0) { if (runTimeMinutes > 0) {
StatLog.incrementUserStat(client.user, UserProps.DoorRunTotalMinutes, runTimeMinutes); StatLog.incrementUserStat(
client.user,
UserProps.DoorRunTotalMinutes,
runTimeMinutes
);
const eventInfo = { const eventInfo = {
runTimeMinutes, runTimeMinutes,

View File

@ -14,7 +14,9 @@ module.exports = class DownloadQueue {
if (!Array.isArray(this.client.user.downloadQueue)) { if (!Array.isArray(this.client.user.downloadQueue)) {
if (this.client.user.properties[UserProps.DownloadQueue]) { if (this.client.user.properties[UserProps.DownloadQueue]) {
this.loadFromProperty(this.client.user.properties[UserProps.DownloadQueue]); this.loadFromProperty(
this.client.user.properties[UserProps.DownloadQueue]
);
} else { } else {
this.client.user.downloadQueue = []; this.client.user.downloadQueue = [];
} }
@ -35,7 +37,9 @@ module.exports = class DownloadQueue {
toggle(fileEntry, systemFile = false) { toggle(fileEntry, systemFile = false) {
if (this.isQueued(fileEntry)) { if (this.isQueued(fileEntry)) {
this.client.user.downloadQueue = this.client.user.downloadQueue.filter(e => fileEntry.fileId !== e.fileId); this.client.user.downloadQueue = this.client.user.downloadQueue.filter(
e => fileEntry.fileId !== e.fileId
);
} else { } else {
this.add(fileEntry, systemFile); this.add(fileEntry, systemFile);
} }
@ -57,7 +61,10 @@ module.exports = class DownloadQueue {
fileIds = [fileIds]; fileIds = [fileIds];
} }
const [ remain, removed ] = _.partition(this.client.user.downloadQueue, e => ( -1 === fileIds.indexOf(e.fileId) )); const [remain, removed] = _.partition(
this.client.user.downloadQueue,
e => -1 === fileIds.indexOf(e.fileId)
);
this.client.user.downloadQueue = remain; this.client.user.downloadQueue = remain;
return removed; return removed;
} }
@ -67,10 +74,14 @@ module.exports = class DownloadQueue {
entryOrId = entryOrId.fileId; entryOrId = entryOrId.fileId;
} }
return this.client.user.downloadQueue.find(e => entryOrId === e.fileId) ? true : false; return this.client.user.downloadQueue.find(e => entryOrId === e.fileId)
? true
: false;
} }
toProperty() { return JSON.stringify(this.client.user.downloadQueue); } toProperty() {
return JSON.stringify(this.client.user.downloadQueue);
}
loadFromProperty(prop) { loadFromProperty(prop) {
try { try {
@ -78,7 +89,10 @@ module.exports = class DownloadQueue {
} catch (e) { } catch (e) {
this.client.user.downloadQueue = []; this.client.user.downloadQueue = [];
this.client.log.error( { error : e.message, property : prop }, 'Failed parsing download queue property'); this.client.log.error(
{ error: e.message, property: prop },
'Failed parsing download queue property'
);
} }
} }
@ -92,9 +106,15 @@ module.exports = class DownloadQueue {
FileEntry.removeEntry(entry, { removePhysFile: true }, err => { FileEntry.removeEntry(entry, { removePhysFile: true }, err => {
const Log = require('./logger').log; const Log = require('./logger').log;
if (err) { if (err) {
Log.warn( { fileId : entry.fileId, path : entry.filePath }, 'Failed removing temporary session download' ); Log.warn(
{ fileId: entry.fileId, path: entry.filePath },
'Failed removing temporary session download'
);
} else { } else {
Log.debug( { fileId : entry.fileId, path : entry.filePath }, 'Removed temporary session download item' ); Log.debug(
{ fileId: entry.fileId, path: entry.filePath },
'Removed temporary session download item'
);
} }
}); });
} }

View File

@ -25,14 +25,17 @@ const { mkdirs } = require('fs-extra');
// * http://lord.lordlegacy.com/dosemu/ // * http://lord.lordlegacy.com/dosemu/
// //
module.exports = class DropFile { module.exports = class DropFile {
constructor(client, { fileType = 'DORINFO', baseDir = Config().paths.dropFiles } = {} ) { constructor(
client,
{ fileType = 'DORINFO', baseDir = Config().paths.dropFiles } = {}
) {
this.client = client; this.client = client;
this.fileType = fileType.toUpperCase(); this.fileType = fileType.toUpperCase();
this.baseDir = baseDir; this.baseDir = baseDir;
} }
get fullPath() { get fullPath() {
return paths.join(this.baseDir, ('node' + this.client.node), this.fileName); return paths.join(this.baseDir, 'node' + this.client.node, this.fileName);
} }
get fileName() { get fileName() {
@ -91,13 +94,18 @@ module.exports = class DropFile {
const bd = moment(prop[UserProps.Birthdate]).format('MM/DD/YY'); const bd = moment(prop[UserProps.Birthdate]).format('MM/DD/YY');
const upK = Math.floor((parseInt(prop[UserProps.FileUlTotalBytes]) || 0) / 1024); const upK = Math.floor((parseInt(prop[UserProps.FileUlTotalBytes]) || 0) / 1024);
const downK = Math.floor((parseInt(prop[UserProps.FileDlTotalBytes]) || 0) / 1024); const downK = Math.floor(
(parseInt(prop[UserProps.FileDlTotalBytes]) || 0) / 1024
);
const timeOfCall = moment(prop[UserProps.LastLoginTs] || moment()).format('hh:mm'); const timeOfCall = moment(prop[UserProps.LastLoginTs] || moment()).format(
'hh:mm'
);
// :TODO: fix time remaining // :TODO: fix time remaining
// :TODO: fix default protocol -- user prop: transfer_protocol // :TODO: fix default protocol -- user prop: transfer_protocol
return iconv.encode( [ return iconv.encode(
[
'COM1:', // "Comm Port - COM0: = LOCAL MODE" 'COM1:', // "Comm Port - COM0: = LOCAL MODE"
'57600', // "Baud Rate - 300 to 38400" (Note: set as 57600 instead!) '57600', // "Baud Rate - 300 to 38400" (Note: set as 57600 instead!)
'8', // "Parity - 7 or 8" '8', // "Parity - 7 or 8"
@ -152,7 +160,9 @@ module.exports = class DropFile {
prop[UserProps.UserComment] || 'None', // "User Comment" prop[UserProps.UserComment] || 'None', // "User Comment"
'0', // "Total Doors Opened" '0', // "Total Doors Opened"
'0', // "Total Messages Left" '0', // "Total Messages Left"
].join('\r\n') + '\r\n', 'cp437'); ].join('\r\n') + '\r\n',
'cp437'
);
} }
getDoor32Buffer() { getDoor32Buffer() {
@ -170,7 +180,8 @@ module.exports = class DropFile {
const commType = Door32CommTypes.Telnet; const commType = Door32CommTypes.Telnet;
return iconv.encode([ return iconv.encode(
[
commType.toString(), commType.toString(),
'-1', '-1',
'115200', '115200',
@ -182,7 +193,9 @@ module.exports = class DropFile {
'546', // :TODO: Minutes left! '546', // :TODO: Minutes left!
'1', // ANSI '1', // ANSI
this.client.node.toString(), this.client.node.toString(),
].join('\r\n') + '\r\n', 'cp437'); ].join('\r\n') + '\r\n',
'cp437'
);
} }
getDoorInfoDefBuffer() { getDoorInfoDefBuffer() {
@ -194,12 +207,15 @@ module.exports = class DropFile {
// //
// Note that usernames are just used for first/last names here // Note that usernames are just used for first/last names here
// //
const opUserName = /[^\s]*/.exec(StatLog.getSystemStat(SysProps.SysOpUsername))[0]; const opUserName = /[^\s]*/.exec(
StatLog.getSystemStat(SysProps.SysOpUsername)
)[0];
const userName = /[^\s]*/.exec(this.client.user.getSanitizedName())[0]; const userName = /[^\s]*/.exec(this.client.user.getSanitizedName())[0];
const secLevel = this.client.user.getLegacySecurityLevel().toString(); const secLevel = this.client.user.getLegacySecurityLevel().toString();
const location = this.client.user.properties[UserProps.Location]; const location = this.client.user.properties[UserProps.Location];
return iconv.encode( [ return iconv.encode(
[
Config().general.boardName, // "The name of the system." Config().general.boardName, // "The name of the system."
opUserName, // "The sysop's name up to the first space." opUserName, // "The sysop's name up to the first space."
opUserName, // "The sysop's name following the first space." opUserName, // "The sysop's name following the first space."
@ -212,8 +228,10 @@ module.exports = class DropFile {
'1', // "The number "0" if TTY, or "1" if ANSI." '1', // "The number "0" if TTY, or "1" if ANSI."
secLevel, // "The number 5 for problem users, 30 for regular users, 80 for Aides, and 100 for Sysops." secLevel, // "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." '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." '-1', // "The number "-1" if using an external serial driver or "0" if using internal serial routines."
].join('\r\n') + '\r\n', 'cp437'); ].join('\r\n') + '\r\n',
'cp437'
);
} }
createFile(cb) { createFile(cb) {

View File

@ -41,10 +41,12 @@ function EditTextView(options) {
this.cursorPos.col -= 1; this.cursorPos.col -= 1;
if (this.cursorPos.col >= 0) { if (this.cursorPos.col >= 0) {
const fillCharSGR = this.getStyleSGR(1) || this.getSGR(); const fillCharSGR = this.getStyleSGR(1) || this.getSGR();
this.client.term.write(`\b${fillCharSGR}${this.fillChar}\b${this.getFocusSGR()}`); this.client.term.write(
} `\b${fillCharSGR}${this.fillChar}\b${this.getFocusSGR()}`
);
} }
} }
};
} }
require('util').inherits(EditTextView, TextView); require('util').inherits(EditTextView, TextView);

View File

@ -18,7 +18,7 @@ class EnigError extends Error {
if (typeof Error.captureStackTrace === 'function') { if (typeof Error.captureStackTrace === 'function') {
Error.captureStackTrace(this, this.constructor); Error.captureStackTrace(this, this.constructor);
} else { } else {
this.stack = (new Error(message)).stack; this.stack = new Error(message).stack;
} }
} }
} }
@ -26,19 +26,31 @@ class EnigError extends Error {
exports.EnigError = EnigError; exports.EnigError = EnigError;
exports.Errors = { exports.Errors = {
General : (reason, reasonCode) => new EnigError('An error occurred', -33000, reason, reasonCode), General: (reason, reasonCode) =>
MenuStack : (reason, reasonCode) => new EnigError('Menu stack error', -33001, reason, reasonCode), new EnigError('An error occurred', -33000, reason, reasonCode),
DoesNotExist : (reason, reasonCode) => new EnigError('Object does not exist', -33002, reason, reasonCode), MenuStack: (reason, reasonCode) =>
AccessDenied : (reason, reasonCode) => new EnigError('Access denied', -32003, reason, reasonCode), new EnigError('Menu stack error', -33001, reason, reasonCode),
DoesNotExist: (reason, reasonCode) =>
new EnigError('Object does not exist', -33002, reason, reasonCode),
AccessDenied: (reason, reasonCode) =>
new EnigError('Access denied', -32003, reason, reasonCode),
Invalid: (reason, reasonCode) => new EnigError('Invalid', -32004, reason, reasonCode), Invalid: (reason, reasonCode) => new EnigError('Invalid', -32004, reason, reasonCode),
ExternalProcess : (reason, reasonCode) => new EnigError('External process error', -32005, reason, reasonCode), ExternalProcess: (reason, reasonCode) =>
MissingConfig : (reason, reasonCode) => new EnigError('Missing configuration', -32006, reason, reasonCode), new EnigError('External process error', -32005, reason, reasonCode),
UnexpectedState : (reason, reasonCode) => new EnigError('Unexpected state', -32007, reason, reasonCode), MissingConfig: (reason, reasonCode) =>
MissingParam : (reason, reasonCode) => new EnigError('Missing paramter(s)', -32008, reason, reasonCode), new EnigError('Missing configuration', -32006, reason, reasonCode),
MissingMci : (reason, reasonCode) => new EnigError('Missing required MCI code(s)', -32009, reason, reasonCode), UnexpectedState: (reason, reasonCode) =>
BadLogin : (reason, reasonCode) => new EnigError('Bad login attempt', -32010, reason, reasonCode), new EnigError('Unexpected state', -32007, reason, reasonCode),
UserInterrupt : (reason, reasonCode) => new EnigError('User interrupted', -32011, reason, reasonCode), MissingParam: (reason, reasonCode) =>
NothingToDo : (reason, reasonCode) => new EnigError('Nothing to do', -32012, reason, reasonCode), new EnigError('Missing paramter(s)', -32008, reason, reasonCode),
MissingMci: (reason, reasonCode) =>
new EnigError('Missing required MCI code(s)', -32009, reason, reasonCode),
BadLogin: (reason, reasonCode) =>
new EnigError('Bad login attempt', -32010, reason, reasonCode),
UserInterrupt: (reason, reasonCode) =>
new EnigError('User interrupted', -32011, reason, reasonCode),
NothingToDo: (reason, reasonCode) =>
new EnigError('Nothing to do', -32012, reason, reasonCode),
}; };
exports.ErrorReasons = { exports.ErrorReasons = {

View File

@ -11,7 +11,7 @@ const assert = require('assert');
module.exports = function (condition, message) { module.exports = function (condition, message) {
if (Config().debug.assertsEnabled) { if (Config().debug.assertsEnabled) {
assert.apply(this, arguments); assert.apply(this, arguments);
} else if(!(condition)) { } else if (!condition) {
const stack = new Error().stack; const stack = new Error().stack;
Log.error({ condition: condition, stack: stack }, message || 'Assertion failed'); Log.error({ condition: condition, stack: stack }, message || 'Assertion failed');
} }

View File

@ -39,7 +39,11 @@ class ScheduledEvent {
} }
get isValid() { get isValid() {
if((!this.schedule || (!this.schedule.sched && !this.schedule.watchFile)) || !this.action) { if (
!this.schedule ||
(!this.schedule.sched && !this.schedule.watchFile) ||
!this.action
) {
return false; return false;
} }
@ -108,7 +112,10 @@ class ScheduledEvent {
} }
executeAction(reason, cb) { executeAction(reason, cb) {
Log.info( { eventName : this.name, action : this.action, reason : reason }, 'Executing scheduled event action...'); Log.info(
{ eventName: this.name, action: this.action, reason: reason },
'Executing scheduled event action...'
);
if ('method' === this.action.type) { if ('method' === this.action.type) {
const modulePath = path.join(__dirname, '../', this.action.location); // enigma-bbs base + supplied location (path/file.js') const modulePath = path.join(__dirname, '../', this.action.location); // enigma-bbs base + supplied location (path/file.js')
@ -117,8 +124,13 @@ class ScheduledEvent {
methodModule[this.action.what](this.action.args, err => { methodModule[this.action.what](this.action.args, err => {
if (err) { if (err) {
Log.debug( Log.debug(
{ error : err.message, eventName : this.name, action : this.action }, {
'Error performing scheduled event action'); error: err.message,
eventName: this.name,
action: this.action,
},
'Error performing scheduled event action'
);
} }
return cb(err); return cb(err);
@ -126,7 +138,8 @@ class ScheduledEvent {
} catch (e) { } catch (e) {
Log.warn( Log.warn(
{ error: e.message, eventName: this.name, action: this.action }, { error: e.message, eventName: this.name, action: this.action },
'Failed to perform scheduled event action'); 'Failed to perform scheduled event action'
);
return cb(e); return cb(e);
} }
@ -143,16 +156,14 @@ class ScheduledEvent {
try { try {
proc = pty.spawn(this.action.what, this.action.args, opts); proc = pty.spawn(this.action.what, this.action.args, opts);
} catch (e) { } catch (e) {
Log.warn( Log.warn({
{
error: 'Failed to spawn @execute process', error: 'Failed to spawn @execute process',
reason: e.message, reason: e.message,
eventName: this.name, eventName: this.name,
action: this.action, action: this.action,
what: this.action.what, what: this.action.what,
args : this.action.args args: this.action.args,
} });
);
return cb(e); return cb(e);
} }
@ -160,9 +171,16 @@ class ScheduledEvent {
if (exitCode) { if (exitCode) {
Log.warn( Log.warn(
{ eventName: this.name, action: this.action, exitCode: exitCode }, { eventName: this.name, action: this.action, exitCode: exitCode },
'Bad exit code while performing scheduled event action'); 'Bad exit code while performing scheduled event action'
);
} }
return cb(exitCode ? Errors.ExternalProcess(`Bad exit code while performing scheduled event action: ${exitCode}`) : null); return cb(
exitCode
? Errors.ExternalProcess(
`Bad exit code while performing scheduled event action: ${exitCode}`
)
: null
);
}); });
} }
} }
@ -214,7 +232,6 @@ EventSchedulerModule.loadAndStart = function(cb) {
}; };
EventSchedulerModule.prototype.startup = function (cb) { EventSchedulerModule.prototype.startup = function (cb) {
this.eventTimers = []; this.eventTimers = [];
const self = this; const self = this;
@ -234,24 +251,27 @@ EventSchedulerModule.prototype.startup = function(cb) {
eventName: schedEvent.name, eventName: schedEvent.name,
schedule: this.moduleConfig.events[schedEvent.name].schedule, schedule: this.moduleConfig.events[schedEvent.name].schedule,
action: schedEvent.action, action: schedEvent.action,
next : schedEvent.schedule.sched ? moment(later.schedule(schedEvent.schedule.sched).next(1)).format('ddd, MMM Do, YYYY @ h:m:ss a') : 'N/A', next: schedEvent.schedule.sched
? moment(
later.schedule(schedEvent.schedule.sched).next(1)
).format('ddd, MMM Do, YYYY @ h:m:ss a')
: 'N/A',
}, },
'Scheduled event loaded' 'Scheduled event loaded'
); );
if (schedEvent.schedule.sched) { if (schedEvent.schedule.sched) {
this.eventTimers.push(later.setInterval( () => { this.eventTimers.push(
later.setInterval(() => {
self.performAction(schedEvent, 'Schedule'); self.performAction(schedEvent, 'Schedule');
}, schedEvent.schedule.sched)); }, schedEvent.schedule.sched)
);
} }
if (schedEvent.schedule.watchFile) { if (schedEvent.schedule.watchFile) {
const watcher = sane( const watcher = sane(paths.dirname(schedEvent.schedule.watchFile), {
paths.dirname(schedEvent.schedule.watchFile), glob: `**/${paths.basename(schedEvent.schedule.watchFile)}`,
{ });
glob : `**/${paths.basename(schedEvent.schedule.watchFile)}`
}
);
// :TODO: should track watched files & stop watching @ shutdown? // :TODO: should track watched files & stop watching @ shutdown?
@ -266,7 +286,10 @@ EventSchedulerModule.prototype.startup = function(cb) {
fse.exists(schedEvent.schedule.watchFile, exists => { fse.exists(schedEvent.schedule.watchFile, exists => {
if (exists) { if (exists) {
self.performAction(schedEvent, `Watch file: ${schedEvent.schedule.watchFile}`); self.performAction(
schedEvent,
`Watch file: ${schedEvent.schedule.watchFile}`
);
} }
}); });
} }

View File

@ -8,7 +8,7 @@ const SystemEvents = require('./system_events.js');
// deps // deps
const _ = require('lodash'); const _ = require('lodash');
module.exports = new class Events extends events.EventEmitter { module.exports = new (class Events extends events.EventEmitter {
constructor() { constructor() {
super(); super();
this.setMaxListeners(64); // :TODO: play with this... this.setMaxListeners(64); // :TODO: play with this...
@ -73,4 +73,4 @@ module.exports = new class Events extends events.EventEmitter {
startup(cb) { startup(cb) {
return cb(null); return cb(null);
} }
}; })();

View File

@ -7,13 +7,8 @@ const { resetScreen } = require('./ansi_term.js');
const Config = require('./config.js').get; const Config = require('./config.js').get;
const { Errors } = require('./enig_error.js'); const { Errors } = require('./enig_error.js');
const Log = require('./logger.js').log; const Log = require('./logger.js').log;
const { const { getEnigmaUserAgent } = require('./misc_util.js');
getEnigmaUserAgent const { trackDoorRunBegin, trackDoorRunEnd } = require('./door_util.js');
} = require('./misc_util.js');
const {
trackDoorRunBegin,
trackDoorRunEnd
} = require('./door_util.js');
// deps // deps
const async = require('async'); const async = require('async');
@ -66,17 +61,17 @@ exports.getModule = class ExodusModule extends MenuModule {
this.config = options.menuConfig.config || {}; this.config = options.menuConfig.config || {};
this.config.ticketHost = this.config.ticketHost || 'oddnetwork.org'; this.config.ticketHost = this.config.ticketHost || 'oddnetwork.org';
this.config.ticketPort = this.config.ticketPort || 1984, (this.config.ticketPort = this.config.ticketPort || 1984),
this.config.ticketPath = this.config.ticketPath || '/exodus'; (this.config.ticketPath = this.config.ticketPath || '/exodus');
this.config.rejectUnauthorized = _.get(this.config, 'rejectUnauthorized', true); this.config.rejectUnauthorized = _.get(this.config, 'rejectUnauthorized', true);
this.config.sshHost = this.config.sshHost || this.config.ticketHost; this.config.sshHost = this.config.sshHost || this.config.ticketHost;
this.config.sshPort = this.config.sshPort || 22; this.config.sshPort = this.config.sshPort || 22;
this.config.sshUser = this.config.sshUser || 'exodus_server'; this.config.sshUser = this.config.sshUser || 'exodus_server';
this.config.sshKeyPem = this.config.sshKeyPem || joinPath(Config().paths.misc, 'exodus.id_rsa'); this.config.sshKeyPem =
this.config.sshKeyPem || joinPath(Config().paths.misc, 'exodus.id_rsa');
} }
initSequence() { initSequence() {
const self = this; const self = this;
let clientTerminated = false; let clientTerminated = false;
@ -84,9 +79,15 @@ exports.getModule = class ExodusModule extends MenuModule {
[ [
function validateConfig(callback) { function validateConfig(callback) {
// very basic validation on optionals // very basic validation on optionals
async.each( [ 'board', 'key', 'door' ], (key, next) => { async.each(
return _.isString(self.config[key]) ? next(null) : next(Errors.MissingConfig(`Config requires "${key}"!`)); ['board', 'key', 'door'],
}, callback); (key, next) => {
return _.isString(self.config[key])
? next(null)
: next(Errors.MissingConfig(`Config requires "${key}"!`));
},
callback
);
}, },
function loadCertAuthorities(callback) { function loadCertAuthorities(callback) {
if (!_.isString(self.config.caPem)) { if (!_.isString(self.config.caPem)) {
@ -99,7 +100,10 @@ exports.getModule = class ExodusModule extends MenuModule {
}, },
function getTicket(certAuthorities, callback) { function getTicket(certAuthorities, callback) {
const now = moment.utc().unix(); const now = moment.utc().unix();
const sha256 = crypto.createHash('sha256').update(`${self.config.key}${now}`).digest('hex'); const sha256 = crypto
.createHash('sha256')
.update(`${self.config.key}${now}`)
.digest('hex');
const token = `${sha256}|${now}`; const token = `${sha256}|${now}`;
const postData = querystring.stringify({ const postData = querystring.stringify({
@ -119,7 +123,7 @@ exports.getModule = class ExodusModule extends MenuModule {
'Content-Type': 'application/x-www-form-urlencoded', 'Content-Type': 'application/x-www-form-urlencoded',
'Content-Length': postData.length, 'Content-Length': postData.length,
'User-Agent': getEnigmaUserAgent(), 'User-Agent': getEnigmaUserAgent(),
} },
}; };
if (certAuthorities) { if (certAuthorities) {
@ -134,7 +138,9 @@ exports.getModule = class ExodusModule extends MenuModule {
res.on('end', () => { res.on('end', () => {
if (ticket.length !== 36) { if (ticket.length !== 36) {
return callback(Errors.Invalid(`Invalid Exodus ticket: ${ticket}`)); return callback(
Errors.Invalid(`Invalid Exodus ticket: ${ticket}`)
);
} }
return callback(null, ticket); return callback(null, ticket);
@ -154,7 +160,6 @@ exports.getModule = class ExodusModule extends MenuModule {
}); });
}, },
function establishSecureConnection(ticket, privateKey, callback) { function establishSecureConnection(ticket, privateKey, callback) {
let pipeRestored = false; let pipeRestored = false;
let pipedStream; let pipedStream;
let doorTracking; let doorTracking;
@ -171,7 +176,9 @@ exports.getModule = class ExodusModule extends MenuModule {
} }
self.client.term.write(resetScreen()); self.client.term.write(resetScreen());
self.client.term.write('Connecting to Exodus server, please wait...\n'); self.client.term.write(
'Connecting to Exodus server, please wait...\n'
);
const sshClient = new SSHClient(); const sshClient = new SSHClient();
@ -191,13 +198,18 @@ exports.getModule = class ExodusModule extends MenuModule {
sshClient.on('ready', () => { sshClient.on('ready', () => {
self.client.once('end', () => { self.client.once('end', () => {
self.client.log.info('Connection ended. Terminating Exodus connection'); self.client.log.info(
'Connection ended. Terminating Exodus connection'
);
clientTerminated = true; clientTerminated = true;
return sshClient.end(); return sshClient.end();
}); });
sshClient.shell(window, options, (err, stream) => { sshClient.shell(window, options, (err, stream) => {
doorTracking = trackDoorRunBegin(self.client, `exodus_${self.config.door}`); doorTracking = trackDoorRunBegin(
self.client,
`exodus_${self.config.door}`
);
pipedStream = stream; // :TODO: ewwwwwwwww hack pipedStream = stream; // :TODO: ewwwwwwwww hack
self.client.term.output.pipe(stream); self.client.term.output.pipe(stream);
@ -212,7 +224,10 @@ exports.getModule = class ExodusModule extends MenuModule {
}); });
stream.on('error', err => { stream.on('error', err => {
Log.warn( { error : err.message }, 'Exodus SSH client stream error'); Log.warn(
{ error: err.message },
'Exodus SSH client stream error'
);
}); });
}); });
}); });
@ -228,7 +243,7 @@ exports.getModule = class ExodusModule extends MenuModule {
username: self.config.sshUser, username: self.config.sshUser,
privateKey: privateKey, privateKey: privateKey,
}); });
} },
], ],
err => { err => {
if (err) { if (err) {

View File

@ -4,7 +4,8 @@
// ENiGMA½ // ENiGMA½
const MenuModule = require('./menu_module.js').MenuModule; const MenuModule = require('./menu_module.js').MenuModule;
const ViewController = require('./view_controller.js').ViewController; const ViewController = require('./view_controller.js').ViewController;
const getSortedAvailableFileAreas = require('./file_base_area.js').getSortedAvailableFileAreas; const getSortedAvailableFileAreas =
require('./file_base_area.js').getSortedAvailableFileAreas;
const FileBaseFilters = require('./file_base_filter.js'); const FileBaseFilters = require('./file_base_filter.js');
const stringFormat = require('./string_format.js'); const stringFormat = require('./string_format.js');
const UserProps = require('./user_property.js'); const UserProps = require('./user_property.js');
@ -32,7 +33,7 @@ const MciViewIds = {
selectedFilterInfo: 10, // { ...filter object ... } selectedFilterInfo: 10, // { ...filter object ... }
activeFilterInfo: 11, // { ...filter object ... } activeFilterInfo: 11, // { ...filter object ... }
error: 12, // validation errors error: 12, // validation errors
} },
}; };
exports.getModule = class FileAreaFilterEdit extends MenuModule { exports.getModule = class FileAreaFilterEdit extends MenuModule {
@ -56,7 +57,10 @@ exports.getModule = class FileAreaFilterEdit extends MenuModule {
} }
} }
return filterA.name.localeCompare(filterB.name, { sensitivity : false, numeric : true } ); return filterA.name.localeCompare(filterB.name, {
sensitivity: false,
numeric: true,
});
}); });
this.menuMethods = { this.menuMethods = {
@ -108,17 +112,21 @@ exports.getModule = class FileAreaFilterEdit extends MenuModule {
const filters = new FileBaseFilters(this.client); const filters = new FileBaseFilters(this.client);
filters.remove(filterUuid); filters.remove(filterUuid);
filters.persist(() => { filters.persist(() => {
// //
// If the item was also the active filter, we need to make a new one active // If the item was also the active filter, we need to make a new one active
// //
if(filterUuid === this.client.user.properties[UserProps.FileBaseFilterActiveUuid]) { if (
filterUuid ===
this.client.user.properties[UserProps.FileBaseFilterActiveUuid]
) {
const newActive = this.filtersArray[this.currentFilterIndex]; const newActive = this.filtersArray[this.currentFilterIndex];
if (newActive) { if (newActive) {
filters.setActive(newActive.uuid); filters.setActive(newActive.uuid);
} else { } else {
// nothing to set active to // nothing to set active to
this.client.user.removeProperty('file_base_filter_active_uuid'); this.client.user.removeProperty(
'file_base_filter_active_uuid'
);
} }
} }
@ -135,7 +143,9 @@ exports.getModule = class FileAreaFilterEdit extends MenuModule {
}, },
viewValidationListener: (err, cb) => { viewValidationListener: (err, cb) => {
const errorView = this.viewControllers.editor.getView(MciViewIds.editor.error); const errorView = this.viewControllers.editor.getView(
MciViewIds.editor.error
);
let newFocusId; let newFocusId;
if (errorView) { if (errorView) {
@ -170,15 +180,23 @@ exports.getModule = class FileAreaFilterEdit extends MenuModule {
} }
const self = this; const self = this;
const vc = self.addViewController( 'editor', new ViewController( { client : this.client } ) ); const vc = self.addViewController(
'editor',
new ViewController({ client: this.client })
);
async.series( async.series(
[ [
function loadFromConfig(callback) { function loadFromConfig(callback) {
return vc.loadFromMenuConfig( { callingMenu : self, mciMap : mciData.menu }, callback); return vc.loadFromMenuConfig(
{ callingMenu: self, mciMap: mciData.menu },
callback
);
}, },
function populateAreas(callback) { function populateAreas(callback) {
self.availAreas = [ { name : '-ALL-' } ].concat(getSortedAvailableFileAreas(self.client) || []); self.availAreas = [{ name: '-ALL-' }].concat(
getSortedAvailableFileAreas(self.client) || []
);
const areasView = vc.getView(MciViewIds.editor.area); const areasView = vc.getView(MciViewIds.editor.area);
if (areasView) { if (areasView) {
@ -189,7 +207,7 @@ exports.getModule = class FileAreaFilterEdit extends MenuModule {
self.loadDataForFilter(self.currentFilterIndex); self.loadDataForFilter(self.currentFilterIndex);
self.viewControllers.editor.resetInitialFocus(); self.viewControllers.editor.resetInitialFocus();
return callback(null); return callback(null);
} },
], ],
err => { err => {
return cb(err); return cb(err);
@ -213,7 +231,10 @@ exports.getModule = class FileAreaFilterEdit extends MenuModule {
const activeFilter = FileBaseFilters.getActiveFilter(this.client); const activeFilter = FileBaseFilters.getActiveFilter(this.client);
if (activeFilter) { if (activeFilter) {
const activeFormat = this.menuConfig.config.activeFormat || '{name}'; const activeFormat = this.menuConfig.config.activeFormat || '{name}';
this.setText(MciViewIds.editor.activeFilterInfo, stringFormat(activeFormat, activeFilter)); this.setText(
MciViewIds.editor.activeFilterInfo,
stringFormat(activeFormat, activeFilter)
);
} }
} }
@ -225,13 +246,19 @@ exports.getModule = class FileAreaFilterEdit extends MenuModule {
} }
clearForm(newFocusId) { clearForm(newFocusId) {
[ MciViewIds.editor.searchTerms, MciViewIds.editor.tags, MciViewIds.editor.filterName ].forEach(mciId => { [
MciViewIds.editor.searchTerms,
MciViewIds.editor.tags,
MciViewIds.editor.filterName,
].forEach(mciId => {
this.setText(mciId, ''); this.setText(mciId, '');
}); });
[ MciViewIds.editor.area, MciViewIds.editor.order, MciViewIds.editor.sort ].forEach(mciId => { [MciViewIds.editor.area, MciViewIds.editor.order, MciViewIds.editor.sort].forEach(
mciId => {
this.setFocusItemIndex(mciId, 0); this.setFocusItemIndex(mciId, 0);
}); }
);
if (newFocusId) { if (newFocusId) {
this.viewControllers.editor.switchFocus(newFocusId); this.viewControllers.editor.switchFocus(newFocusId);
@ -260,7 +287,10 @@ exports.getModule = class FileAreaFilterEdit extends MenuModule {
const filter = this.getCurrentFilter(); const filter = this.getCurrentFilter();
if (filter) { if (filter) {
// special treatment: areaTag saved as blank ("") if -ALL- // special treatment: areaTag saved as blank ("") if -ALL-
index = (filter.areaTag && this.availAreas.findIndex(area => filter.areaTag === area.areaTag)) || 0; index =
(filter.areaTag &&
this.availAreas.findIndex(area => filter.areaTag === area.areaTag)) ||
0;
} else { } else {
index = 0; index = 0;
} }
@ -271,7 +301,8 @@ exports.getModule = class FileAreaFilterEdit extends MenuModule {
let index; let index;
const filter = this.getCurrentFilter(); const filter = this.getCurrentFilter();
if (filter) { if (filter) {
index = FileBaseFilters.OrderByValues.findIndex( ob => filter.order === ob ) || 0; index =
FileBaseFilters.OrderByValues.findIndex(ob => filter.order === ob) || 0;
} else { } else {
index = 0; index = 0;
} }

View File

@ -70,7 +70,6 @@ const MciViewIds = {
}; };
exports.getModule = class FileAreaList extends MenuModule { exports.getModule = class FileAreaList extends MenuModule {
constructor(options) { constructor(options) {
super(options); super(options);
@ -180,12 +179,20 @@ exports.getModule = class FileAreaList extends MenuModule {
if (_.isNumber(this.lastMenuResultValue.rating)) { if (_.isNumber(this.lastMenuResultValue.rating)) {
const fileId = this.fileList[this.fileListPosition]; const fileId = this.fileList[this.fileListPosition];
FileEntry.persistUserRating(fileId, this.client.user.userId, this.lastMenuResultValue.rating, err => { FileEntry.persistUserRating(
fileId,
this.client.user.userId,
this.lastMenuResultValue.rating,
err => {
if (err) { if (err) {
this.client.log.warn( { error : err.message, fileId : fileId }, 'Failed to persist file rating' ); this.client.log.warn(
{ error: err.message, fileId: fileId },
'Failed to persist file rating'
);
} }
return cb(null); return cb(null);
}); }
);
} else { } else {
return cb(null); return cb(null);
} }
@ -205,11 +212,14 @@ exports.getModule = class FileAreaList extends MenuModule {
function display(callback) { function display(callback) {
return self.displayBrowsePage(false, err => { return self.displayBrowsePage(false, err => {
if (err) { if (err) {
self.gotoMenu(self.menuConfig.config.noResultsMenu || 'fileBaseListEntriesNoResults'); self.gotoMenu(
self.menuConfig.config.noResultsMenu ||
'fileBaseListEntriesNoResults'
);
} }
return callback(err); return callback(err);
}); });
} },
], ],
() => { () => {
self.finishedLoading(); self.finishedLoading();
@ -221,13 +231,15 @@ exports.getModule = class FileAreaList extends MenuModule {
const config = this.menuConfig.config; const config = this.menuConfig.config;
const currEntry = this.currentFileEntry; const currEntry = this.currentFileEntry;
const uploadTimestampFormat = config.uploadTimestampFormat || this.client.currentTheme.helpers.getDateFormat('short'); const uploadTimestampFormat =
config.uploadTimestampFormat ||
this.client.currentTheme.helpers.getDateFormat('short');
const area = FileArea.getFileAreaByTag(currEntry.areaTag); const area = FileArea.getFileAreaByTag(currEntry.areaTag);
const hashTagsSep = config.hashTagsSep || ', '; const hashTagsSep = config.hashTagsSep || ', ';
const isQueuedIndicator = config.isQueuedIndicator || 'Y'; const isQueuedIndicator = config.isQueuedIndicator || 'Y';
const isNotQueuedIndicator = config.isNotQueuedIndicator || 'N'; const isNotQueuedIndicator = config.isNotQueuedIndicator || 'N';
const entryInfo = currEntry.entryInfo = { const entryInfo = (currEntry.entryInfo = {
fileId: currEntry.fileId, fileId: currEntry.fileId,
areaTag: currEntry.areaTag, areaTag: currEntry.areaTag,
areaName: _.get(area, 'name') || 'N/A', areaName: _.get(area, 'name') || 'N/A',
@ -237,12 +249,16 @@ exports.getModule = class FileAreaList extends MenuModule {
desc: currEntry.desc || '', desc: currEntry.desc || '',
descLong: currEntry.descLong || '', descLong: currEntry.descLong || '',
userRating: currEntry.userRating, userRating: currEntry.userRating,
uploadTimestamp : moment(currEntry.uploadTimestamp).format(uploadTimestampFormat), uploadTimestamp: moment(currEntry.uploadTimestamp).format(
uploadTimestampFormat
),
hashTags: Array.from(currEntry.hashTags).join(hashTagsSep), hashTags: Array.from(currEntry.hashTags).join(hashTagsSep),
isQueued : this.dlQueue.isQueued(currEntry) ? isQueuedIndicator : isNotQueuedIndicator, isQueued: this.dlQueue.isQueued(currEntry)
? isQueuedIndicator
: isNotQueuedIndicator,
webDlLink: '', // :TODO: fetch web any existing web d/l link webDlLink: '', // :TODO: fetch web any existing web d/l link
webDlExpire: '', // :TODO: fetch web d/l link expire time webDlExpire: '', // :TODO: fetch web d/l link expire time
}; });
// //
// We need the entry object to contain meta keys even if they are empty as // We need the entry object to contain meta keys even if they are empty as
@ -250,7 +266,9 @@ exports.getModule = class FileAreaList extends MenuModule {
// //
const metaValues = FileEntry.WellKnownMetaValues; const metaValues = FileEntry.WellKnownMetaValues;
metaValues.forEach(name => { metaValues.forEach(name => {
const value = !_.isUndefined(currEntry.meta[name]) ? currEntry.meta[name] : 'N/A'; const value = !_.isUndefined(currEntry.meta[name])
? currEntry.meta[name]
: 'N/A';
entryInfo[_.camelCase(name)] = value; entryInfo[_.camelCase(name)] = value;
}); });
@ -262,7 +280,9 @@ exports.getModule = class FileAreaList extends MenuModule {
if (Array.isArray(fileType)) { if (Array.isArray(fileType)) {
// further refine by extention // further refine by extention
fileType = fileType.find(ft => paths.extname(currEntry.fileName) === ft.ext); fileType = fileType.find(
ft => paths.extname(currEntry.fileName) === ft.ext
);
} }
desc = fileType && fileType.desc; desc = fileType && fileType.desc;
} }
@ -271,7 +291,8 @@ exports.getModule = class FileAreaList extends MenuModule {
entryInfo.archiveTypeDesc = 'N/A'; entryInfo.archiveTypeDesc = 'N/A';
} }
entryInfo.uploadByUsername = entryInfo.uploadByUserName = entryInfo.uploadByUsername || 'N/A'; // may be imported entryInfo.uploadByUsername = entryInfo.uploadByUserName =
entryInfo.uploadByUsername || 'N/A'; // may be imported
entryInfo.hashTags = entryInfo.hashTags || '(none)'; entryInfo.hashTags = entryInfo.hashTags || '(none)';
// create a rating string, e.g. "**---" // create a rating string, e.g. "**---"
@ -280,30 +301,47 @@ exports.getModule = class FileAreaList extends MenuModule {
entryInfo.userRating = ~~Math.round(entryInfo.userRating) || 0; // be safe! entryInfo.userRating = ~~Math.round(entryInfo.userRating) || 0; // be safe!
entryInfo.userRatingString = userRatingTicked.repeat(entryInfo.userRating); entryInfo.userRatingString = userRatingTicked.repeat(entryInfo.userRating);
if (entryInfo.userRating < 5) { if (entryInfo.userRating < 5) {
entryInfo.userRatingString += userRatingUnticked.repeat( (5 - entryInfo.userRating) ); entryInfo.userRatingString += userRatingUnticked.repeat(
5 - entryInfo.userRating
);
} }
FileAreaWeb.getExistingTempDownloadServeItem(this.client, this.currentFileEntry, (err, serveItem) => { FileAreaWeb.getExistingTempDownloadServeItem(
this.client,
this.currentFileEntry,
(err, serveItem) => {
if (err) { if (err) {
entryInfo.webDlExpire = ''; entryInfo.webDlExpire = '';
if (ErrNotEnabled === err.reasonCode) { if (ErrNotEnabled === err.reasonCode) {
entryInfo.webDlExpire = config.webDlLinkNoWebserver || 'Web server is not enabled'; entryInfo.webDlExpire =
config.webDlLinkNoWebserver || 'Web server is not enabled';
} else { } else {
entryInfo.webDlLink = config.webDlLinkNeedsGenerated || 'Not yet generated'; entryInfo.webDlLink =
config.webDlLinkNeedsGenerated || 'Not yet generated';
} }
} else { } else {
const webDlExpireTimeFormat = config.webDlExpireTimeFormat || this.client.currentTheme.helpers.getDateTimeFormat('short'); const webDlExpireTimeFormat =
config.webDlExpireTimeFormat ||
this.client.currentTheme.helpers.getDateTimeFormat('short');
entryInfo.webDlLink = ansi.vtxHyperlink(this.client, serveItem.url) + serveItem.url; entryInfo.webDlLink =
entryInfo.webDlExpire = moment(serveItem.expireTimestamp).format(webDlExpireTimeFormat); ansi.vtxHyperlink(this.client, serveItem.url) + serveItem.url;
entryInfo.webDlExpire = moment(serveItem.expireTimestamp).format(
webDlExpireTimeFormat
);
} }
return cb(null); return cb(null);
}); }
);
} }
populateCustomLabels(category, startId) { populateCustomLabels(category, startId) {
return this.updateCustomViewTextsWithFilter(category, startId, this.currentFileEntry.entryInfo); return this.updateCustomViewTextsWithFilter(
category,
startId,
this.currentFileEntry.entryInfo
);
} }
displayArtAndPrepViewController(name, options, cb) { displayArtAndPrepViewController(name, options, cb) {
@ -337,7 +375,10 @@ exports.getModule = class FileAreaList extends MenuModule {
vcOpts.noInput = options.noInput; vcOpts.noInput = options.noInput;
} }
const vc = self.addViewController(name, new ViewController(vcOpts)); const vc = self.addViewController(
name,
new ViewController(vcOpts)
);
if ('details' === name) { if ('details' === name) {
try { try {
@ -346,7 +387,11 @@ exports.getModule = class FileAreaList extends MenuModule {
bottom: artData.mciMap.XY3.position, bottom: artData.mciMap.XY3.position,
}; };
} catch (e) { } catch (e) {
return callback(Errors.DoesNotExist('Missing XY2 and XY3 position indicators!')); return callback(
Errors.DoesNotExist(
'Missing XY2 and XY3 position indicators!'
)
);
} }
} }
@ -361,7 +406,6 @@ exports.getModule = class FileAreaList extends MenuModule {
self.viewControllers[name].setFocus(true); self.viewControllers[name].setFocus(true);
return callback(null); return callback(null);
}, },
], ],
err => { err => {
@ -383,27 +427,38 @@ exports.getModule = class FileAreaList extends MenuModule {
}, },
function checkEmptyResults(callback) { function checkEmptyResults(callback) {
if (0 === self.fileList.length) { if (0 === self.fileList.length) {
return callback(Errors.General('No results for criteria', 'NORESULTS')); return callback(
Errors.General('No results for criteria', 'NORESULTS')
);
} }
return callback(null); return callback(null);
}, },
function prepArtAndViewController(callback) { function prepArtAndViewController(callback) {
return self.displayArtAndPrepViewController('browse', { clearScreen : clearScreen }, callback); return self.displayArtAndPrepViewController(
'browse',
{ clearScreen: clearScreen },
callback
);
}, },
function loadCurrentFileInfo(callback) { function loadCurrentFileInfo(callback) {
self.currentFileEntry = new FileEntry(); self.currentFileEntry = new FileEntry();
self.currentFileEntry.load( self.fileList[ self.fileListPosition ], err => { self.currentFileEntry.load(
self.fileList[self.fileListPosition],
err => {
if (err) { if (err) {
return callback(err); return callback(err);
} }
return self.populateCurrentEntryInfo(callback); return self.populateCurrentEntryInfo(callback);
}); }
);
}, },
function populateDesc(callback) { function populateDesc(callback) {
if (_.isString(self.currentFileEntry.desc)) { if (_.isString(self.currentFileEntry.desc)) {
const descView = self.viewControllers.browse.getView(MciViewIds.browse.desc); const descView = self.viewControllers.browse.getView(
MciViewIds.browse.desc
);
if (descView) { if (descView) {
// //
// For descriptions we want to support as many color code systems // For descriptions we want to support as many color code systems
@ -415,17 +470,23 @@ exports.getModule = class FileAreaList extends MenuModule {
// it as text. // it as text.
// //
const desc = controlCodesToAnsi(self.currentFileEntry.desc); const desc = controlCodesToAnsi(self.currentFileEntry.desc);
if(desc.length != self.currentFileEntry.desc.length || isAnsi(desc)) { if (
desc.length != self.currentFileEntry.desc.length ||
isAnsi(desc)
) {
const opts = { const opts = {
prepped: false, prepped: false,
forceLineTerm : true forceLineTerm: true,
}; };
// //
// if SAUCE states a term width, honor it else we may see // if SAUCE states a term width, honor it else we may see
// display corruption // display corruption
// //
const sauceTermWidth = _.get(self.currentFileEntry.meta, 'desc_sauce.Character.characterWidth'); const sauceTermWidth = _.get(
self.currentFileEntry.meta,
'desc_sauce.Character.characterWidth'
);
if (_.isNumber(sauceTermWidth)) { if (_.isNumber(sauceTermWidth)) {
opts.termWidth = sauceTermWidth; opts.termWidth = sauceTermWidth;
} }
@ -444,9 +505,12 @@ exports.getModule = class FileAreaList extends MenuModule {
}, },
function populateAdditionalViews(callback) { function populateAdditionalViews(callback) {
self.updateQueueIndicator(); self.updateQueueIndicator();
self.populateCustomLabels('browse', MciViewIds.browse.customRangeStart); self.populateCustomLabels(
'browse',
MciViewIds.browse.customRangeStart
);
return callback(null); return callback(null);
} },
], ],
err => { err => {
if (cb) { if (cb) {
@ -462,17 +526,26 @@ exports.getModule = class FileAreaList extends MenuModule {
async.series( async.series(
[ [
function prepArtAndViewController(callback) { function prepArtAndViewController(callback) {
return self.displayArtAndPrepViewController('details', { clearScreen : true }, callback); return self.displayArtAndPrepViewController(
'details',
{ clearScreen: true },
callback
);
}, },
function populateViews(callback) { function populateViews(callback) {
self.populateCustomLabels('details', MciViewIds.details.customRangeStart); self.populateCustomLabels(
'details',
MciViewIds.details.customRangeStart
);
return callback(null); return callback(null);
}, },
function prepSection(callback) { function prepSection(callback) {
return self.displayDetailsSection('general', false, callback); return self.displayDetailsSection('general', false, callback);
}, },
function listenNavChanges(callback) { function listenNavChanges(callback) {
const navMenu = self.viewControllers.details.getView(MciViewIds.details.navMenu); const navMenu = self.viewControllers.details.getView(
MciViewIds.details.navMenu
);
navMenu.setFocusItemIndex(0); navMenu.setFocusItemIndex(0);
navMenu.on('index update', index => { navMenu.on('index update', index => {
@ -488,7 +561,7 @@ exports.getModule = class FileAreaList extends MenuModule {
}); });
return callback(null); return callback(null);
} },
], ],
err => { err => {
return cb(err); return cb(err);
@ -497,15 +570,11 @@ exports.getModule = class FileAreaList extends MenuModule {
} }
displayHelpPage(cb) { displayHelpPage(cb) {
this.displayAsset( this.displayAsset(this.menuConfig.config.art.help, { clearScreen: true }, () => {
this.menuConfig.config.art.help,
{ clearScreen : true },
() => {
this.client.waitForKeyPress(() => { this.client.waitForKeyPress(() => {
return this.displayBrowsePage(true, cb); return this.displayBrowsePage(true, cb);
}); });
} });
);
} }
_handleMovementKeyPress(keyName, cb) { _handleMovementKeyPress(keyName, cb) {
@ -515,10 +584,18 @@ exports.getModule = class FileAreaList extends MenuModule {
} }
switch (keyName) { switch (keyName) {
case 'down arrow' : descView.scrollDocumentUp(); break; case 'down arrow':
case 'up arrow' : descView.scrollDocumentDown(); break; descView.scrollDocumentUp();
case 'page up' : descView.keyPressPageUp(); break; break;
case 'page down' : descView.keyPressPageDown(); break; case 'up arrow':
descView.scrollDocumentDown();
break;
case 'page up':
descView.keyPressPageUp();
break;
case 'page down':
descView.keyPressPageDown();
break;
} }
this.viewControllers.browse.switchFocus(MciViewIds.browse.navMenu); this.viewControllers.browse.switchFocus(MciViewIds.browse.navMenu);
@ -531,12 +608,14 @@ exports.getModule = class FileAreaList extends MenuModule {
async.series( async.series(
[ [
function generateLinkIfNeeded(callback) { function generateLinkIfNeeded(callback) {
if (self.currentFileEntry.webDlExpireTime < moment()) { if (self.currentFileEntry.webDlExpireTime < moment()) {
return callback(null); return callback(null);
} }
const expireTime = moment().add(Config().fileBase.web.expireMinutes, 'minutes'); const expireTime = moment().add(
Config().fileBase.web.expireMinutes,
'minutes'
);
FileAreaWeb.createAndServeTempDownload( FileAreaWeb.createAndServeTempDownload(
self.client, self.client,
@ -549,10 +628,14 @@ exports.getModule = class FileAreaList extends MenuModule {
self.currentFileEntry.webDlExpireTime = expireTime; self.currentFileEntry.webDlExpireTime = expireTime;
const webDlExpireTimeFormat = self.menuConfig.config.webDlExpireTimeFormat || 'YYYY-MMM-DD @ h:mm'; const webDlExpireTimeFormat =
self.menuConfig.config.webDlExpireTimeFormat ||
'YYYY-MMM-DD @ h:mm';
self.currentFileEntry.entryInfo.webDlLink = ansi.vtxHyperlink(self.client, url) + url; self.currentFileEntry.entryInfo.webDlLink =
self.currentFileEntry.entryInfo.webDlExpire = expireTime.format(webDlExpireTimeFormat); ansi.vtxHyperlink(self.client, url) + url;
self.currentFileEntry.entryInfo.webDlExpire =
expireTime.format(webDlExpireTimeFormat);
return callback(null); return callback(null);
} }
@ -561,11 +644,12 @@ exports.getModule = class FileAreaList extends MenuModule {
function updateActiveViews(callback) { function updateActiveViews(callback) {
self.updateCustomViewTextsWithFilter( self.updateCustomViewTextsWithFilter(
'browse', 'browse',
MciViewIds.browse.customRangeStart, self.currentFileEntry.entryInfo, MciViewIds.browse.customRangeStart,
self.currentFileEntry.entryInfo,
{ filter: ['{webDlLink}', '{webDlExpire}'] } { filter: ['{webDlLink}', '{webDlExpire}'] }
); );
return callback(null); return callback(null);
} },
], ],
err => { err => {
return cb(err); return cb(err);
@ -578,9 +662,9 @@ exports.getModule = class FileAreaList extends MenuModule {
const isNotQueuedIndicator = this.menuConfig.config.isNotQueuedIndicator || 'N'; const isNotQueuedIndicator = this.menuConfig.config.isNotQueuedIndicator || 'N';
this.currentFileEntry.entryInfo.isQueued = stringFormat( this.currentFileEntry.entryInfo.isQueued = stringFormat(
this.dlQueue.isQueued(this.currentFileEntry) ? this.dlQueue.isQueued(this.currentFileEntry)
isQueuedIndicator : ? isQueuedIndicator
isNotQueuedIndicator : isNotQueuedIndicator
); );
this.updateCustomViewTextsWithFilter( this.updateCustomViewTextsWithFilter(
@ -605,19 +689,27 @@ exports.getModule = class FileAreaList extends MenuModule {
const filePath = this.currentFileEntry.filePath; const filePath = this.currentFileEntry.filePath;
const archiveUtil = ArchiveUtil.getInstance(); const archiveUtil = ArchiveUtil.getInstance();
archiveUtil.listEntries(filePath, this.currentFileEntry.entryInfo.archiveType, (err, entries) => { archiveUtil.listEntries(
filePath,
this.currentFileEntry.entryInfo.archiveType,
(err, entries) => {
if (err) { if (err) {
return cb(err); return cb(err);
} }
// assign and add standard "text" member for itemFormat // assign and add standard "text" member for itemFormat
this.currentFileEntry.archiveEntries = entries.map(e => Object.assign(e, { text : `${e.fileName} (${e.byteSize})` } )); this.currentFileEntry.archiveEntries = entries.map(e =>
Object.assign(e, { text: `${e.fileName} (${e.byteSize})` })
);
return cb(null, 're-cached'); return cb(null, 're-cached');
}); }
);
} }
setFileListNoListing(text) { setFileListNoListing(text) {
const fileListView = this.viewControllers.detailsFileList.getView(MciViewIds.detailsFileList.fileList); const fileListView = this.viewControllers.detailsFileList.getView(
MciViewIds.detailsFileList.fileList
);
if (fileListView) { if (fileListView) {
fileListView.complexItems = false; fileListView.complexItems = false;
fileListView.setItems([text]); fileListView.setItems([text]);
@ -626,7 +718,9 @@ exports.getModule = class FileAreaList extends MenuModule {
} }
populateFileListing() { populateFileListing() {
const fileListView = this.viewControllers.detailsFileList.getView(MciViewIds.detailsFileList.fileList); const fileListView = this.viewControllers.detailsFileList.getView(
MciViewIds.detailsFileList.fileList
);
if (this.currentFileEntry.entryInfo.archiveType) { if (this.currentFileEntry.entryInfo.archiveType) {
this.cacheArchiveEntries((err, cacheStatus) => { this.cacheArchiveEntries((err, cacheStatus) => {
@ -640,7 +734,10 @@ exports.getModule = class FileAreaList extends MenuModule {
} }
}); });
} else { } else {
const notAnArchiveFileName = stringFormat(this.menuConfig.config.notAnArchiveFormat || 'Not an archive', { fileName : this.currentFileEntry.fileName } ); const notAnArchiveFileName = stringFormat(
this.menuConfig.config.notAnArchiveFormat || 'Not an archive',
{ fileName: this.currentFileEntry.fileName }
);
this.setFileListNoListing(notAnArchiveFileName); this.setFileListNoListing(notAnArchiveFileName);
} }
} }
@ -658,9 +755,10 @@ exports.getModule = class FileAreaList extends MenuModule {
return callback(null); return callback(null);
}, },
function prepArtAndViewController(callback) { function prepArtAndViewController(callback) {
function gotoTopPos() { function gotoTopPos() {
self.client.term.rawWrite(ansi.goto(self.detailsInfoArea.top[0], 1)); self.client.term.rawWrite(
ansi.goto(self.detailsInfoArea.top[0], 1)
);
} }
gotoTopPos(); gotoTopPos();
@ -678,7 +776,11 @@ exports.getModule = class FileAreaList extends MenuModule {
gotoTopPos(); gotoTopPos();
} }
return self.displayArtAndPrepViewController(name, { clearScreen : false, noInput : true }, callback); return self.displayArtAndPrepViewController(
name,
{ clearScreen: false, noInput: true },
callback
);
}, },
function populateViews(callback) { function populateViews(callback) {
self.lastDetailsViewController = self.viewControllers[name]; self.lastDetailsViewController = self.viewControllers[name];
@ -686,7 +788,9 @@ exports.getModule = class FileAreaList extends MenuModule {
switch (sectionName) { switch (sectionName) {
case 'nfo': case 'nfo':
{ {
const nfoView = self.viewControllers.detailsNfo.getView(MciViewIds.detailsNfo.nfo); const nfoView = self.viewControllers.detailsNfo.getView(
MciViewIds.detailsNfo.nfo
);
if (!nfoView) { if (!nfoView) {
return callback(null); return callback(null);
} }
@ -703,7 +807,9 @@ exports.getModule = class FileAreaList extends MenuModule {
} }
); );
} else { } else {
nfoView.setText(self.currentFileEntry.entryInfo.descLong); nfoView.setText(
self.currentFileEntry.entryInfo.descLong
);
return callback(null); return callback(null);
} }
} }
@ -720,7 +826,7 @@ exports.getModule = class FileAreaList extends MenuModule {
function setLabels(callback) { function setLabels(callback) {
self.populateCustomLabels(name, MciViewIds[name].customRangeStart); self.populateCustomLabels(name, MciViewIds[name].customRangeStart);
return callback(null); return callback(null);
} },
], ],
err => { err => {
if (cb) { if (cb) {
@ -731,7 +837,11 @@ exports.getModule = class FileAreaList extends MenuModule {
} }
loadFileIds(force, cb) { loadFileIds(force, cb) {
if(force || (_.isUndefined(this.fileList) || _.isUndefined(this.fileListPosition))) { if (
force ||
_.isUndefined(this.fileList) ||
_.isUndefined(this.fileListPosition)
) {
this.fileListPosition = 0; this.fileListPosition = 0;
const filterCriteria = Object.assign({}, this.filterCriteria); const filterCriteria = Object.assign({}, this.filterCriteria);

View File

@ -48,7 +48,11 @@ class FileAreaWebAccess {
function addWebRoute(callback) { function addWebRoute(callback) {
self.webServer = getServer(webServerPackageName); self.webServer = getServer(webServerPackageName);
if (!self.webServer) { if (!self.webServer) {
return callback(Errors.DoesNotExist(`Server with package name "${webServerPackageName}" does not exist`)); return callback(
Errors.DoesNotExist(
`Server with package name "${webServerPackageName}" does not exist`
)
);
} }
if (self.isEnabled()) { if (self.isEnabled()) {
@ -57,11 +61,13 @@ class FileAreaWebAccess {
path: Config().fileBase.web.routePath, path: Config().fileBase.web.routePath,
handler: self.routeWebRequest.bind(self), handler: self.routeWebRequest.bind(self),
}); });
return callback(routeAdded ? null : Errors.General('Failed adding route')); return callback(
routeAdded ? null : Errors.General('Failed adding route')
);
} else { } else {
return callback(null); // not enabled, but no error return callback(null); // not enabled, but no error
} }
} },
], ],
err => { err => {
return cb(err); return cb(err);
@ -116,7 +122,6 @@ class FileAreaWebAccess {
} }
scheduleExpire(hashId, expireTime) { scheduleExpire(hashId, expireTime) {
// remove any previous entry for this hashId // remove any previous entry for this hashId
const previous = this.expireTimers[hashId]; const previous = this.expireTimers[hashId];
if (previous) { if (previous) {
@ -145,7 +150,9 @@ class FileAreaWebAccess {
[hashId], [hashId],
(err, result) => { (err, result) => {
if (err || !result) { if (err || !result) {
return cb(err ? err : Errors.DoesNotExist('Invalid or missing hash ID')); return cb(
err ? err : Errors.DoesNotExist('Invalid or missing hash ID')
);
} }
const decoded = this.hashids.decode(hashId); const decoded = this.hashids.decode(hashId);
@ -162,7 +169,10 @@ class FileAreaWebAccess {
expireTimestamp: moment(result.expire_timestamp), expireTimestamp: moment(result.expire_timestamp),
}; };
if(FileAreaWebAccess.getHashIdTypes().SingleFile === servedItem.hashIdType) { if (
FileAreaWebAccess.getHashIdTypes().SingleFile ===
servedItem.hashIdType
) {
servedItem.fileIds = decoded.slice(2); servedItem.fileIds = decoded.slice(2);
} }
@ -172,11 +182,17 @@ class FileAreaWebAccess {
} }
getSingleFileHashId(client, fileEntry) { getSingleFileHashId(client, fileEntry) {
return this.getHashId(client, FileAreaWebAccess.getHashIdTypes().SingleFile, [ fileEntry.fileId ] ); return this.getHashId(client, FileAreaWebAccess.getHashIdTypes().SingleFile, [
fileEntry.fileId,
]);
} }
getBatchArchiveHashId(client, batchId) { getBatchArchiveHashId(client, batchId) {
return this.getHashId(client, FileAreaWebAccess.getHashIdTypes().BatchArchive, batchId); return this.getHashId(
client,
FileAreaWebAccess.getHashIdTypes().BatchArchive,
batchId
);
} }
getHashId(client, hashIdType, identifier) { getHashId(client, hashIdType, identifier) {
@ -264,7 +280,9 @@ class FileAreaWebAccess {
}); });
} }
async.eachSeries(fileEntries, (entry, nextEntry) => { async.eachSeries(
fileEntries,
(entry, nextEntry) => {
trans.run( trans.run(
`INSERT INTO file_web_serve_batch (hash_id, file_id) `INSERT INTO file_web_serve_batch (hash_id, file_id)
VALUES (?, ?);`, VALUES (?, ?);`,
@ -273,11 +291,13 @@ class FileAreaWebAccess {
return nextEntry(err); return nextEntry(err);
} }
); );
}, err => { },
err => {
trans[err ? 'rollback' : 'commit'](() => { trans[err ? 'rollback' : 'commit'](() => {
return cb(err, url); return cb(err, url);
}); });
}); }
);
}); });
}); });
} }
@ -292,7 +312,6 @@ class FileAreaWebAccess {
Log.debug({ hashId: hashId, url: req.url }, 'File area web request'); Log.debug({ hashId: hashId, url: req.url }, 'File area web request');
this.loadServedHashId(hashId, (err, servedItem) => { this.loadServedHashId(hashId, (err, servedItem) => {
if (err) { if (err) {
return this.fileNotFound(resp); return this.fileNotFound(resp);
} }
@ -340,11 +359,16 @@ class FileAreaWebAccess {
resp.on('finish', () => { resp.on('finish', () => {
// transfer completed fully // transfer completed fully
this.updateDownloadStatsForUserIdAndSystem(servedItem.userId, stats.size, [ fileEntry ]); this.updateDownloadStatsForUserIdAndSystem(
servedItem.userId,
stats.size,
[fileEntry]
);
}); });
const headers = { const headers = {
'Content-Type' : mimeTypes.contentType(filePath) || mimeTypes.contentType('.bin'), 'Content-Type':
mimeTypes.contentType(filePath) || mimeTypes.contentType('.bin'),
'Content-Length': stats.size, 'Content-Length': stats.size,
'Content-Disposition': `attachment; filename="${fileEntry.fileName}"`, 'Content-Disposition': `attachment; filename="${fileEntry.fileName}"`,
}; };
@ -376,36 +400,61 @@ class FileAreaWebAccess {
WHERE hash_id = ?;`, WHERE hash_id = ?;`,
[servedItem.hashId], [servedItem.hashId],
(err, fileIdRows) => { (err, fileIdRows) => {
if(err || !Array.isArray(fileIdRows) || 0 === fileIdRows.length) { if (
return callback(Errors.DoesNotExist('Could not get file IDs for batch')); err ||
!Array.isArray(fileIdRows) ||
0 === fileIdRows.length
) {
return callback(
Errors.DoesNotExist(
'Could not get file IDs for batch'
)
);
} }
return callback(null, fileIdRows.map(r => r.file_id)); return callback(
null,
fileIdRows.map(r => r.file_id)
);
} }
); );
}, },
function loadFileEntries(fileIds, callback) { function loadFileEntries(fileIds, callback) {
async.map(fileIds, (fileId, nextFileId) => { async.map(
fileIds,
(fileId, nextFileId) => {
const fileEntry = new FileEntry(); const fileEntry = new FileEntry();
fileEntry.load(fileId, err => { fileEntry.load(fileId, err => {
return nextFileId(err, fileEntry); return nextFileId(err, fileEntry);
}); });
}, (err, fileEntries) => { },
(err, fileEntries) => {
if (err) { if (err) {
return callback(Errors.DoesNotExist('Could not load file IDs for batch')); return callback(
Errors.DoesNotExist(
'Could not load file IDs for batch'
)
);
} }
return callback(null, fileEntries); return callback(null, fileEntries);
}); }
);
}, },
function createAndServeStream(fileEntries, callback) { function createAndServeStream(fileEntries, callback) {
const filePaths = fileEntries.map(fe => fe.filePath); const filePaths = fileEntries.map(fe => fe.filePath);
Log.trace( { filePaths : filePaths }, 'Creating zip archive for batch web request'); Log.trace(
{ filePaths: filePaths },
'Creating zip archive for batch web request'
);
const zipFile = new yazl.ZipFile(); const zipFile = new yazl.ZipFile();
zipFile.on('error', err => { zipFile.on('error', err => {
Log.warn( { error : err.message }, 'Error adding file to batch web request archive'); Log.warn(
{ error: err.message },
'Error adding file to batch web request archive'
);
}); });
filePaths.forEach(fp => { filePaths.forEach(fp => {
@ -420,7 +469,9 @@ class FileAreaWebAccess {
zipFile.end(finalZipSize => { zipFile.end(finalZipSize => {
if (-1 === finalZipSize) { if (-1 === finalZipSize) {
return callback(Errors.UnexpectedState('Unable to acquire final zip size')); return callback(
Errors.UnexpectedState('Unable to acquire final zip size')
);
} }
resp.on('close', () => { resp.on('close', () => {
@ -430,13 +481,19 @@ class FileAreaWebAccess {
resp.on('finish', () => { resp.on('finish', () => {
// transfer completed fully // transfer completed fully
self.updateDownloadStatsForUserIdAndSystem(servedItem.userId, finalZipSize, fileEntries); self.updateDownloadStatsForUserIdAndSystem(
servedItem.userId,
finalZipSize,
fileEntries
);
}); });
const batchFileName = `batch_${servedItem.hashId}.zip`; const batchFileName = `batch_${servedItem.hashId}.zip`;
const headers = { const headers = {
'Content-Type' : mimeTypes.contentType(batchFileName) || mimeTypes.contentType('.bin'), 'Content-Type':
mimeTypes.contentType(batchFileName) ||
mimeTypes.contentType('.bin'),
'Content-Length': finalZipSize, 'Content-Length': finalZipSize,
'Content-Disposition': `attachment; filename="${batchFileName}"`, 'Content-Disposition': `attachment; filename="${batchFileName}"`,
}; };
@ -444,7 +501,7 @@ class FileAreaWebAccess {
resp.writeHead(200, headers); resp.writeHead(200, headers);
return zipFile.outputStream.pipe(resp); return zipFile.outputStream.pipe(resp);
}); });
} },
], ],
err => { err => {
if (err) { if (err) {
@ -458,8 +515,7 @@ class FileAreaWebAccess {
} }
updateDownloadStatsForUserIdAndSystem(userId, dlBytes, fileEntries) { updateDownloadStatsForUserIdAndSystem(userId, dlBytes, fileEntries) {
async.waterfall( async.waterfall([
[
function fetchActiveUser(callback) { function fetchActiveUser(callback) {
const clientForUserId = getConnectionByUserId(userId); const clientForUserId = getConnectionByUserId(userId);
if (clientForUserId) { if (clientForUserId) {
@ -481,17 +537,13 @@ class FileAreaWebAccess {
return callback(null, user); return callback(null, user);
}, },
function sendEvent(user, callback) { function sendEvent(user, callback) {
Events.emit( Events.emit(Events.getSystemEvents().UserDownload, {
Events.getSystemEvents().UserDownload,
{
user: user, user: user,
files: fileEntries, files: fileEntries,
} });
);
return callback(null); return callback(null);
} },
] ]);
);
} }
} }

View File

@ -53,27 +53,30 @@ exports.cleanUpTempSessionItems = cleanUpTempSessionItems;
// for scheduler: // for scheduler:
exports.updateAreaStatsScheduledEvent = updateAreaStatsScheduledEvent; exports.updateAreaStatsScheduledEvent = updateAreaStatsScheduledEvent;
const WellKnownAreaTags = exports.WellKnownAreaTags = { const WellKnownAreaTags = (exports.WellKnownAreaTags = {
Invalid: '', Invalid: '',
MessageAreaAttach: 'system_message_attachment', MessageAreaAttach: 'system_message_attachment',
TempDownloads: 'system_temporary_download', TempDownloads: 'system_temporary_download',
}; });
function startup(cb) { function startup(cb) {
async.series( async.series(
[ [
(callback) => { callback => {
return cleanUpTempSessionItems(callback); return cleanUpTempSessionItems(callback);
}, },
(callback) => { callback => {
getAreaStats((err, stats) => { getAreaStats((err, stats) => {
if (!err) { if (!err) {
StatLog.setNonPersistentSystemStat(SysProps.FileBaseAreaStats, stats); StatLog.setNonPersistentSystemStat(
SysProps.FileBaseAreaStats,
stats
);
} }
return callback(null); return callback(null);
}); });
} },
], ],
err => { err => {
return cb(err); return cb(err);
@ -82,14 +85,19 @@ function startup(cb) {
} }
function isInternalArea(areaTag) { function isInternalArea(areaTag) {
return [ WellKnownAreaTags.MessageAreaAttach, WellKnownAreaTags.TempDownloads ].includes(areaTag); return [
WellKnownAreaTags.MessageAreaAttach,
WellKnownAreaTags.TempDownloads,
].includes(areaTag);
} }
function getAvailableFileAreas(client, options) { function getAvailableFileAreas(client, options) {
options = options || {}; options = options || {};
// perform ACS check per conf & omit internal if desired // perform ACS check per conf & omit internal if desired
const allAreas = _.map(Config().fileBase.areas, (areaInfo, areaTag) => Object.assign(areaInfo, { areaTag : areaTag } )); const allAreas = _.map(Config().fileBase.areas, (areaInfo, areaTag) =>
Object.assign(areaInfo, { areaTag: areaTag })
);
return _.omitBy(allAreas, areaInfo => { return _.omitBy(allAreas, areaInfo => {
if (!options.includeSystemInternal && isInternalArea(areaInfo.areaTag)) { if (!options.includeSystemInternal && isInternalArea(areaInfo.areaTag)) {
@ -130,7 +138,10 @@ function getDefaultFileAreaTag(client, disableAcsCheck) {
// just use anything we can // just use anything we can
defaultArea = _.findKey(config.fileBase.areas, (area, areaTag) => { defaultArea = _.findKey(config.fileBase.areas, (area, areaTag) => {
return WellKnownAreaTags.MessageAreaAttach !== areaTag && (true === disableAcsCheck || client.acs.hasFileAreaRead(area)); return (
WellKnownAreaTags.MessageAreaAttach !== areaTag &&
(true === disableAcsCheck || client.acs.hasFileAreaRead(area))
);
}); });
return defaultArea; return defaultArea;
@ -153,8 +164,7 @@ function getFileAreaByTag(areaTag) {
} }
function getFileAreasByTagWildcardRule(rule) { function getFileAreasByTagWildcardRule(rule) {
const areaTags = Object.keys(Config().fileBase.areas) const areaTags = Object.keys(Config().fileBase.areas).filter(areaTag => {
.filter(areaTag => {
return !isInternalArea(areaTag) && wildcardMatch(areaTag, rule); return !isInternalArea(areaTag) && wildcardMatch(areaTag, rule);
}); });
@ -166,7 +176,10 @@ function changeFileAreaWithOptions(client, areaTag, options, cb) {
[ [
function getArea(callback) { function getArea(callback) {
const area = getFileAreaByTag(areaTag); const area = getFileAreaByTag(areaTag);
return callback(area ? null : Errors.Invalid('Invalid file areaTag'), area); return callback(
area ? null : Errors.Invalid('Invalid file areaTag'),
area
);
}, },
function validateAccess(area, callback) { function validateAccess(area, callback) {
if (!client.acs.hasFileAreaRead(area)) { if (!client.acs.hasFileAreaRead(area)) {
@ -182,13 +195,19 @@ function changeFileAreaWithOptions(client, areaTag, options, cb) {
client.user.properties[UserProps.FileAreaTag] = areaTag; client.user.properties[UserProps.FileAreaTag] = areaTag;
return callback(null, area); return callback(null, area);
} }
} },
], ],
(err, area) => { (err, area) => {
if (!err) { if (!err) {
client.log.info( { areaTag : areaTag, area : area }, 'Current file area changed'); client.log.info(
{ areaTag: areaTag, area: area },
'Current file area changed'
);
} else { } else {
client.log.warn( { areaTag : areaTag, area : area, error : err.message }, 'Could not change file area'); client.log.warn(
{ areaTag: areaTag, area: area, error: err.message },
'Could not change file area'
);
} }
return cb(err); return cb(err);
@ -202,7 +221,7 @@ function isValidStorageTag(storageTag) {
function getAreaStorageDirectoryByTag(storageTag) { function getAreaStorageDirectoryByTag(storageTag) {
const config = Config(); const config = Config();
const storageLocation = (storageTag && config.fileBase.storageTags[storageTag]); const storageLocation = storageTag && config.fileBase.storageTags[storageTag];
return paths.resolve(config.fileBase.areaStoragePrefix, storageLocation || ''); return paths.resolve(config.fileBase.areaStoragePrefix, storageLocation || '');
} }
@ -212,21 +231,22 @@ function getAreaDefaultStorageDirectory(areaInfo) {
} }
function getAreaStorageLocations(areaInfo) { function getAreaStorageLocations(areaInfo) {
const storageTags = Array.isArray(areaInfo.storageTags)
const storageTags = Array.isArray(areaInfo.storageTags) ? ? areaInfo.storageTags
areaInfo.storageTags : : [areaInfo.storageTags || ''];
[ areaInfo.storageTags || '' ];
const avail = Config().fileBase.storageTags; const avail = Config().fileBase.storageTags;
return _.compact(storageTags.map(storageTag => { return _.compact(
storageTags.map(storageTag => {
if (avail[storageTag]) { if (avail[storageTag]) {
return { return {
storageTag: storageTag, storageTag: storageTag,
dir: getAreaStorageDirectoryByTag(storageTag), dir: getAreaStorageDirectoryByTag(storageTag),
}; };
} }
})); })
);
} }
function getFileEntryPath(fileEntry) { function getFileEntryPath(fileEntry) {
@ -261,7 +281,7 @@ function getExistingFileEntriesBySha256(sha256, cb) {
// :TODO: This is basically sliceAtEOF() from art.js .... DRY! // :TODO: This is basically sliceAtEOF() from art.js .... DRY!
function sliceAtSauceMarker(data) { function sliceAtSauceMarker(data) {
let eof = data.length; let eof = data.length;
const stopPos = Math.max(data.length - (256), 0); // 256 = 2 * sizeof(SAUCE) const stopPos = Math.max(data.length - 256, 0); // 256 = 2 * sizeof(SAUCE)
for (let i = eof - 1; i > stopPos; i--) { for (let i = eof - 1; i > stopPos; i--) {
if (0x1a === data[i]) { if (0x1a === data[i]) {
@ -341,7 +361,9 @@ function extractAndProcessDescFiles(fileEntry, filePath, archiveEntries, cb) {
const extractList = []; const extractList = [];
const shortDescFile = archiveEntries.find(e => { const shortDescFile = archiveEntries.find(e => {
return config.fileBase.fileNamePatterns.desc.find( pat => new RegExp(pat, 'i').test(e.fileName) ); return config.fileBase.fileNamePatterns.desc.find(pat =>
new RegExp(pat, 'i').test(e.fileName)
);
}); });
if (shortDescFile) { if (shortDescFile) {
@ -349,7 +371,9 @@ function extractAndProcessDescFiles(fileEntry, filePath, archiveEntries, cb) {
} }
const longDescFile = archiveEntries.find(e => { const longDescFile = archiveEntries.find(e => {
return config.fileBase.fileNamePatterns.descLong.find( pat => new RegExp(pat, 'i').test(e.fileName) ); return config.fileBase.fileNamePatterns.descLong.find(pat =>
new RegExp(pat, 'i').test(e.fileName)
);
}); });
if (longDescFile) { if (longDescFile) {
@ -366,23 +390,41 @@ function extractAndProcessDescFiles(fileEntry, filePath, archiveEntries, cb) {
} }
const archiveUtil = ArchiveUtil.getInstance(); const archiveUtil = ArchiveUtil.getInstance();
archiveUtil.extractTo(filePath, tempDir, fileEntry.meta.archive_type, extractList, err => { archiveUtil.extractTo(
filePath,
tempDir,
fileEntry.meta.archive_type,
extractList,
err => {
if (err) { if (err) {
return callback(err); return callback(err);
} }
const descFiles = { const descFiles = {
desc : shortDescFile ? paths.join(tempDir, paths.basename(shortDescFile.fileName)) : null, desc: shortDescFile
descLong : longDescFile ? paths.join(tempDir, paths.basename(longDescFile.fileName)) : null, ? paths.join(
tempDir,
paths.basename(shortDescFile.fileName)
)
: null,
descLong: longDescFile
? paths.join(
tempDir,
paths.basename(longDescFile.fileName)
)
: null,
}; };
return callback(null, descFiles); return callback(null, descFiles);
}); }
);
}); });
}, },
function readDescFiles(descFiles, callback) { function readDescFiles(descFiles, callback) {
const config = Config(); const config = Config();
async.each(Object.keys(descFiles), (descType, next) => { async.each(
Object.keys(descFiles),
(descType, next) => {
const path = descFiles[descType]; const path = descFiles[descType];
if (!path) { if (!path) {
return next(null); return next(null);
@ -394,9 +436,20 @@ function extractAndProcessDescFiles(fileEntry, filePath, archiveEntries, cb) {
} }
// skip entries that are too large // skip entries that are too large
const maxFileSizeKey = `max${_.upperFirst(descType)}FileByteSize`; const maxFileSizeKey = `max${_.upperFirst(
if(config.fileBase[maxFileSizeKey] && stats.size > config.fileBase[maxFileSizeKey]) { descType
logDebug( { byteSize : stats.size, maxByteSize : config.fileBase[maxFileSizeKey] }, `Skipping "${descType}"; Too large` ); )}FileByteSize`;
if (
config.fileBase[maxFileSizeKey] &&
stats.size > config.fileBase[maxFileSizeKey]
) {
logDebug(
{
byteSize: stats.size,
maxByteSize: config.fileBase[maxFileSizeKey],
},
`Skipping "${descType}"; Too large`
);
return next(null); return next(null);
} }
@ -409,7 +462,9 @@ function extractAndProcessDescFiles(fileEntry, filePath, archiveEntries, cb) {
if (sauce) { if (sauce) {
// if we have SAUCE, this information will be kept as well, // if we have SAUCE, this information will be kept as well,
// but separate/pre-parsed. // but separate/pre-parsed.
const metaKey = `desc${'descLong' === descType ? '_long' : ''}_sauce`; const metaKey = `desc${
'descLong' === descType ? '_long' : ''
}_sauce`;
fileEntry.meta[metaKey] = JSON.stringify(sauce); fileEntry.meta[metaKey] = JSON.stringify(sauce);
} }
@ -425,14 +480,19 @@ function extractAndProcessDescFiles(fileEntry, filePath, archiveEntries, cb) {
}); });
}); });
}); });
}, () => { },
() => {
// cleanup but don't wait // cleanup but don't wait
temptmp.cleanup(paths => { temptmp.cleanup(paths => {
// note: don't use client logger here - may not be avail // note: don't use client logger here - may not be avail
logTrace( { paths : paths, sessionId : temptmp.sessionId }, 'Cleaned up temporary files' ); logTrace(
{ paths: paths, sessionId: temptmp.sessionId },
'Cleaned up temporary files'
);
}); });
return callback(null); return callback(null);
}); }
);
}, },
], ],
err => { err => {
@ -442,7 +502,6 @@ function extractAndProcessDescFiles(fileEntry, filePath, archiveEntries, cb) {
} }
function extractAndProcessSingleArchiveEntry(fileEntry, filePath, archiveEntries, cb) { function extractAndProcessSingleArchiveEntry(fileEntry, filePath, archiveEntries, cb) {
async.waterfall( async.waterfall(
[ [
function extractToTemp(callback) { function extractToTemp(callback) {
@ -455,15 +514,23 @@ function extractAndProcessSingleArchiveEntry(fileEntry, filePath, archiveEntries
const archiveUtil = ArchiveUtil.getInstance(); const archiveUtil = ArchiveUtil.getInstance();
// ensure we only extract one - there should only be one anyway -- we also just need the fileName // ensure we only extract one - there should only be one anyway -- we also just need the fileName
const extractList = archiveEntries.slice(0, 1).map(entry => entry.fileName); const extractList = archiveEntries
.slice(0, 1)
.map(entry => entry.fileName);
archiveUtil.extractTo(filePath, tempDir, fileEntry.meta.archive_type, extractList, err => { archiveUtil.extractTo(
filePath,
tempDir,
fileEntry.meta.archive_type,
extractList,
err => {
if (err) { if (err) {
return callback(err); return callback(err);
} }
return callback(null, paths.join(tempDir, extractList[0])); return callback(null, paths.join(tempDir, extractList[0]));
}); }
);
}); });
}, },
function processSingleExtractedFile(extractedFile, callback) { function processSingleExtractedFile(extractedFile, callback) {
@ -474,7 +541,7 @@ function extractAndProcessSingleArchiveEntry(fileEntry, filePath, archiveEntries
} }
return callback(err); return callback(err);
}); });
} },
], ],
err => { err => {
return cb(err); return cb(err);
@ -525,7 +592,10 @@ function populateFileEntryWithArchive(fileEntry, filePath, stepInfo, iterator, c
// Otherwise, try to find particular desc files such as FILE_ID.DIZ // Otherwise, try to find particular desc files such as FILE_ID.DIZ
// and README.1ST // and README.1ST
// //
const archDescHandler = (1 === entries.length) ? extractAndProcessSingleArchiveEntry : extractAndProcessDescFiles; const archDescHandler =
1 === entries.length
? extractAndProcessSingleArchiveEntry
: extractAndProcessDescFiles;
archDescHandler(fileEntry, filePath, entries, err => { archDescHandler(fileEntry, filePath, entries, err => {
return callback(err); return callback(err);
}); });
@ -577,13 +647,17 @@ function populateFileEntryInfoFromFile(fileEntry, filePath, cb) {
return cb(null); return cb(null);
} }
async.eachSeries( [ 'short', 'long' ], (descType, nextDesc) => { async.eachSeries(
['short', 'long'],
(descType, nextDesc) => {
const util = getInfoExtractUtilForDesc(mimeType, filePath, descType); const util = getInfoExtractUtilForDesc(mimeType, filePath, descType);
if (!util) { if (!util) {
return nextDesc(null); return nextDesc(null);
} }
const args = (util.args || [ '{filePath}'] ).map( arg => stringFormat(arg, { filePath : filePath } ) ); const args = (util.args || ['{filePath}']).map(arg =>
stringFormat(arg, { filePath: filePath })
);
execFile(util.cmd, args, { timeout: 1000 * 30 }, (err, stdout) => { execFile(util.cmd, args, { timeout: 1000 * 30 }, (err, stdout) => {
if (err || !stdout) { if (err || !stdout) {
@ -604,7 +678,9 @@ function populateFileEntryInfoFromFile(fileEntry, filePath, cb) {
// //
// See http://www.textfiles.com/computers/fileid.txt // See http://www.textfiles.com/computers/fileid.txt
// //
stdout = (wordWrapText( stdout, { width : 45 } ).wrapped || []).join('\n'); stdout = (
wordWrapText(stdout, { width: 45 }).wrapped || []
).join('\n');
} }
fileEntry[key] = stdout; fileEntry[key] = stdout;
@ -614,13 +690,14 @@ function populateFileEntryInfoFromFile(fileEntry, filePath, cb) {
return nextDesc(null); return nextDesc(null);
}); });
}, () => { },
() => {
return cb(null); return cb(null);
}); }
);
} }
function populateFileEntryNonArchive(fileEntry, filePath, stepInfo, iterator, cb) { function populateFileEntryNonArchive(fileEntry, filePath, stepInfo, iterator, cb) {
async.series( async.series(
[ [
function processDescFilesStart(callback) { function processDescFilesStart(callback) {
@ -654,7 +731,7 @@ function addNewFileEntry(fileEntry, filePath, cb) {
[ [
function addNewDbRecord(callback) { function addNewDbRecord(callback) {
return fileEntry.persist(callback); return fileEntry.persist(callback);
} },
], ],
err => { err => {
return cb(err); return cb(err);
@ -665,7 +742,6 @@ function addNewFileEntry(fileEntry, filePath, cb) {
const HASH_NAMES = ['sha1', 'sha256', 'md5', 'crc32']; const HASH_NAMES = ['sha1', 'sha256', 'md5', 'crc32'];
function scanFile(filePath, options, iterator, cb) { function scanFile(filePath, options, iterator, cb) {
if (3 === arguments.length && _.isFunction(iterator)) { if (3 === arguments.length && _.isFunction(iterator)) {
cb = iterator; cb = iterator;
iterator = null; iterator = null;
@ -689,7 +765,7 @@ function scanFile(filePath, options, iterator, cb) {
fileName: paths.basename(filePath), fileName: paths.basename(filePath),
}; };
const callIter = (next) => { const callIter = next => {
return iterator ? iterator(stepInfo, next) : next(null); return iterator ? iterator(stepInfo, next) : next(null);
}; };
@ -737,13 +813,13 @@ function scanFile(filePath, options, iterator, cb) {
const hashes = {}; const hashes = {};
hashesToCalc.forEach(hashName => { hashesToCalc.forEach(hashName => {
if ('crc32' === hashName) { if ('crc32' === hashName) {
hashes.crc32 = new CRC32; hashes.crc32 = new CRC32();
} else { } else {
hashes[hashName] = crypto.createHash(hashName); hashes[hashName] = crypto.createHash(hashName);
} }
}); });
const updateHashes = (data) => { const updateHashes = data => {
for (let i = 0; i < hashesToCalc.length; ++i) { for (let i = 0; i < hashesToCalc.length; ++i) {
hashes[hashesToCalc[i]].update(data); hashes[hashesToCalc[i]].update(data);
} }
@ -767,7 +843,10 @@ function scanFile(filePath, options, iterator, cb) {
if (err) { if (err) {
return fs.close(fd, closeErr => { return fs.close(fd, closeErr => {
if (closeErr) { if (closeErr) {
logError( { filePath, error : err.message }, 'Failed to close file'); logError(
{ filePath, error: err.message },
'Failed to close file'
);
} }
return readErrorCallIter(err, callback); return readErrorCallIter(err, callback);
}); });
@ -780,31 +859,49 @@ function scanFile(filePath, options, iterator, cb) {
for (let i = 0; i < hashesToCalc.length; ++i) { for (let i = 0; i < hashesToCalc.length; ++i) {
const hashName = hashesToCalc[i]; const hashName = hashesToCalc[i];
if ('sha256' === hashName) { if ('sha256' === hashName) {
stepInfo.sha256 = fileEntry.fileSha256 = hashes.sha256.digest('hex'); stepInfo.sha256 = fileEntry.fileSha256 =
} else if('sha1' === hashName || 'md5' === hashName) { hashes.sha256.digest('hex');
stepInfo[hashName] = fileEntry.meta[`file_${hashName}`] = hashes[hashName].digest('hex'); } else if (
'sha1' === hashName ||
'md5' === hashName
) {
stepInfo[hashName] = fileEntry.meta[
`file_${hashName}`
] = hashes[hashName].digest('hex');
} else if ('crc32' === hashName) { } else if ('crc32' === hashName) {
stepInfo.crc32 = fileEntry.meta.file_crc32 = hashes.crc32.finalize().toString(16); stepInfo.crc32 = fileEntry.meta.file_crc32 =
hashes.crc32.finalize().toString(16);
} }
} }
stepInfo.step = 'hash_finish'; stepInfo.step = 'hash_finish';
return fs.close(fd, closeErr => { return fs.close(fd, closeErr => {
if (closeErr) { if (closeErr) {
logError( { filePath, error : err.message }, 'Failed to close file'); logError(
{ filePath, error: err.message },
'Failed to close file'
);
} }
return callIter(callback); return callIter(callback);
}); });
} }
stepInfo.bytesProcessed += bytesRead; stepInfo.bytesProcessed += bytesRead;
stepInfo.calcHashPercent = Math.round(((stepInfo.bytesProcessed / stepInfo.byteSize) * 100)); stepInfo.calcHashPercent = Math.round(
(stepInfo.bytesProcessed / stepInfo.byteSize) * 100
);
// //
// Only send 'hash_update' step update if we have a noticeable percentage change in progress // Only send 'hash_update' step update if we have a noticeable percentage change in progress
// //
const data = bytesRead < chunkSize ? buffer.slice(0, bytesRead) : buffer; const data =
if(!iterator || stepInfo.calcHashPercent === lastCalcHashPercent) { bytesRead < chunkSize
? buffer.slice(0, bytesRead)
: buffer;
if (
!iterator ||
stepInfo.calcHashPercent === lastCalcHashPercent
) {
updateHashes(data); updateHashes(data);
return nextChunk(); return nextChunk();
} else { } else {
@ -834,39 +931,66 @@ function scanFile(filePath, options, iterator, cb) {
// save this off // save this off
fileEntry.meta.archive_type = archiveType; fileEntry.meta.archive_type = archiveType;
populateFileEntryWithArchive(fileEntry, filePath, stepInfo, callIter, err => { populateFileEntryWithArchive(
fileEntry,
filePath,
stepInfo,
callIter,
err => {
if (err) { if (err) {
populateFileEntryNonArchive(fileEntry, filePath, stepInfo, callIter, err => { populateFileEntryNonArchive(
fileEntry,
filePath,
stepInfo,
callIter,
err => {
if (err) { if (err) {
logDebug( { error : err.message }, 'Non-archive file entry population failed'); logDebug(
{ error: err.message },
'Non-archive file entry population failed'
);
} }
return callback(null); // ignore err return callback(null); // ignore err
}); }
);
} else { } else {
return callback(null); return callback(null);
} }
}); }
);
} else { } else {
populateFileEntryNonArchive(fileEntry, filePath, stepInfo, callIter, err => { populateFileEntryNonArchive(
fileEntry,
filePath,
stepInfo,
callIter,
err => {
if (err) { if (err) {
logDebug( { error : err.message }, 'Non-archive file entry population failed'); logDebug(
{ error: err.message },
'Non-archive file entry population failed'
);
} }
return callback(null); // ignore err return callback(null); // ignore err
}); }
);
} }
}); });
}, },
function fetchExistingEntry(callback) { function fetchExistingEntry(callback) {
getExistingFileEntriesBySha256(fileEntry.fileSha256, (err, dupeEntries) => { getExistingFileEntriesBySha256(
fileEntry.fileSha256,
(err, dupeEntries) => {
return callback(err, dupeEntries); return callback(err, dupeEntries);
}); }
);
}, },
function finished(dupeEntries, callback) { function finished(dupeEntries, callback) {
stepInfo.step = 'finished'; stepInfo.step = 'finished';
callIter(() => { callIter(() => {
return callback(null, dupeEntries); return callback(null, dupeEntries);
}); });
} },
], ],
(err, dupeEntries) => { (err, dupeEntries) => {
if (err) { if (err) {
@ -982,9 +1106,10 @@ function getDescFromFileName(fileName) {
const ext = paths.extname(fileName); const ext = paths.extname(fileName);
const name = paths.basename(fileName, ext); const name = paths.basename(fileName, ext);
const asIsRe = /([vV]?(?:[0-9]{1,4})(?:\.[0-9]{1,4})+[-+]?(?:[a-z]{1,4})?)|(Incl\.)|(READ\.NFO)/g; const asIsRe =
/([vV]?(?:[0-9]{1,4})(?:\.[0-9]{1,4})+[-+]?(?:[a-z]{1,4})?)|(Incl\.)|(READ\.NFO)/g;
const normalize = (s) => { const normalize = s => {
return _.upperFirst(s.replace(/[-_.+]/g, ' ').replace(/\s+/g, ' ')); return _.upperFirst(s.replace(/[-_.+]/g, ' ').replace(/\s+/g, ' '));
}; };
@ -1076,9 +1201,9 @@ function cleanUpTempSessionItems(cb) {
metaPairs: [ metaPairs: [
{ {
name: 'session_temp_dl', name: 'session_temp_dl',
value : 1 value: 1,
} },
] ],
}; };
FileEntry.findFiles(filter, (err, fileIds) => { FileEntry.findFiles(filter, (err, fileIds) => {
@ -1086,23 +1211,36 @@ function cleanUpTempSessionItems(cb) {
return cb(err); return cb(err);
} }
async.each(fileIds, (fileId, nextFileId) => { async.each(
fileIds,
(fileId, nextFileId) => {
const fileEntry = new FileEntry(); const fileEntry = new FileEntry();
fileEntry.load(fileId, err => { fileEntry.load(fileId, err => {
if (err) { if (err) {
Log.warn( { fileId }, 'Failed loading temporary session download item for cleanup'); Log.warn(
{ fileId },
'Failed loading temporary session download item for cleanup'
);
return nextFileId(null); return nextFileId(null);
} }
FileEntry.removeEntry(fileEntry, { removePhysFile: true }, err => { FileEntry.removeEntry(fileEntry, { removePhysFile: true }, err => {
if (err) { if (err) {
Log.warn( { fileId : fileEntry.fileId, filePath : fileEntry.filePath }, 'Failed to clean up temporary session download item'); Log.warn(
{
fileId: fileEntry.fileId,
filePath: fileEntry.filePath,
},
'Failed to clean up temporary session download item'
);
} }
return nextFileId(null); return nextFileId(null);
}); });
}); });
}, () => { },
() => {
return cb(null); return cb(null);
}); }
);
}); });
} }

View File

@ -37,8 +37,13 @@ exports.getModule = class FileAreaSelectModule extends MenuModule {
menuFlags: ['popParent', 'mergeFlags'], menuFlags: ['popParent', 'mergeFlags'],
}; };
return this.gotoMenu(this.menuConfig.config.fileBaseListEntriesMenu || 'fileBaseListEntries', menuOpts, cb); return this.gotoMenu(
} this.menuConfig.config.fileBaseListEntriesMenu ||
'fileBaseListEntries',
menuOpts,
cb
);
},
}; };
} }
@ -53,7 +58,9 @@ exports.getModule = class FileAreaSelectModule extends MenuModule {
async.waterfall( async.waterfall(
[ [
function mergeAreaStats(callback) { function mergeAreaStats(callback) {
const areaStats = StatLog.getSystemStat(SysProps.FileBaseAreaStats) || { areas : {} }; const areaStats = StatLog.getSystemStat(
SysProps.FileBaseAreaStats
) || { areas: {} };
// we could use 'sort' alone, but area/conf sorting has some special properties; user can still override // we could use 'sort' alone, but area/conf sorting has some special properties; user can still override
const availAreas = getSortedAvailableFileAreas(self.client); const availAreas = getSortedAvailableFileAreas(self.client);
@ -66,18 +73,30 @@ exports.getModule = class FileAreaSelectModule extends MenuModule {
return callback(null, availAreas); return callback(null, availAreas);
}, },
function prepView(availAreas, callback) { function prepView(availAreas, callback) {
self.prepViewController('allViews', 0, mciData.menu, (err, vc) => { self.prepViewController(
'allViews',
0,
mciData.menu,
(err, vc) => {
if (err) { if (err) {
return callback(err); return callback(err);
} }
const areaListView = vc.getView(MciViewIds.areaList); const areaListView = vc.getView(MciViewIds.areaList);
areaListView.setItems(availAreas.map(area => Object.assign(area, { text : area.name, data : area.areaTag } ))); areaListView.setItems(
availAreas.map(area =>
Object.assign(area, {
text: area.name,
data: area.areaTag,
})
)
);
areaListView.redraw(); areaListView.redraw();
return callback(null); return callback(null);
});
} }
);
},
], ],
err => { err => {
return cb(err); return cb(err);

View File

@ -35,7 +35,6 @@ const MciViewIds = {
}; };
exports.getModule = class FileBaseDownloadQueueManager extends MenuModule { exports.getModule = class FileBaseDownloadQueueManager extends MenuModule {
constructor(options) { constructor(options) {
super(options); super(options);
@ -53,10 +52,15 @@ exports.getModule = class FileBaseDownloadQueueManager extends MenuModule {
extraArgs: { extraArgs: {
sendQueue: this.dlQueue.items, sendQueue: this.dlQueue.items,
direction: 'send', direction: 'send',
} },
}; };
return this.gotoMenu(this.menuConfig.config.fileTransferProtocolSelection || 'fileTransferProtocolSelection', modOpts, cb); return this.gotoMenu(
this.menuConfig.config.fileTransferProtocolSelection ||
'fileTransferProtocolSelection',
modOpts,
cb
);
}, },
removeItem: (formData, extraArgs, cb) => { removeItem: (formData, extraArgs, cb) => {
const selectedItem = this.dlQueue.items[formData.value.queueItem]; const selectedItem = this.dlQueue.items[formData.value.queueItem];
@ -67,14 +71,17 @@ exports.getModule = class FileBaseDownloadQueueManager extends MenuModule {
this.dlQueue.removeItems(selectedItem.fileId); this.dlQueue.removeItems(selectedItem.fileId);
// :TODO: broken: does not redraw menu properly - needs fixed! // :TODO: broken: does not redraw menu properly - needs fixed!
return this.removeItemsFromDownloadQueueView(formData.value.queueItem, cb); return this.removeItemsFromDownloadQueueView(
formData.value.queueItem,
cb
);
}, },
clearQueue: (formData, extraArgs, cb) => { clearQueue: (formData, extraArgs, cb) => {
this.dlQueue.clear(); this.dlQueue.clear();
// :TODO: broken: does not redraw menu properly - needs fixed! // :TODO: broken: does not redraw menu properly - needs fixed!
return this.removeItemsFromDownloadQueueView('all', cb); return this.removeItemsFromDownloadQueueView('all', cb);
} },
}; };
} }
@ -86,7 +93,10 @@ exports.getModule = class FileBaseDownloadQueueManager extends MenuModule {
} }
// Simply an empty D/L queue: Present a specialized "empty queue" page // Simply an empty D/L queue: Present a specialized "empty queue" page
return this.gotoMenu(this.menuConfig.config.emptyQueueMenu || 'fileBaseDownloadManagerEmptyQueue'); return this.gotoMenu(
this.menuConfig.config.emptyQueueMenu ||
'fileBaseDownloadManagerEmptyQueue'
);
} }
const self = this; const self = this;
@ -98,7 +108,7 @@ exports.getModule = class FileBaseDownloadQueueManager extends MenuModule {
}, },
function display(callback) { function display(callback) {
return self.displayQueueManagerPage(false, callback); return self.displayQueueManagerPage(false, callback);
} },
], ],
() => { () => {
return self.finishedLoading(); return self.finishedLoading();
@ -107,7 +117,9 @@ exports.getModule = class FileBaseDownloadQueueManager extends MenuModule {
} }
removeItemsFromDownloadQueueView(itemIndex, cb) { removeItemsFromDownloadQueueView(itemIndex, cb) {
const queueView = this.viewControllers.queueManager.getView(MciViewIds.queueManager.queue); const queueView = this.viewControllers.queueManager.getView(
MciViewIds.queueManager.queue
);
if (!queueView) { if (!queueView) {
return cb(Errors.DoesNotExist('Queue view does not exist')); return cb(Errors.DoesNotExist('Queue view does not exist'));
} }
@ -124,12 +136,20 @@ exports.getModule = class FileBaseDownloadQueueManager extends MenuModule {
} }
displayWebDownloadLinkForFileEntry(fileEntry) { displayWebDownloadLinkForFileEntry(fileEntry) {
FileAreaWeb.getExistingTempDownloadServeItem(this.client, fileEntry, (err, serveItem) => { FileAreaWeb.getExistingTempDownloadServeItem(
this.client,
fileEntry,
(err, serveItem) => {
if (serveItem && serveItem.url) { if (serveItem && serveItem.url) {
const webDlExpireTimeFormat = this.menuConfig.config.webDlExpireTimeFormat || 'YYYY-MMM-DD @ h:mm'; const webDlExpireTimeFormat =
this.menuConfig.config.webDlExpireTimeFormat ||
'YYYY-MMM-DD @ h:mm';
fileEntry.webDlLink = ansi.vtxHyperlink(this.client, serveItem.url) + serveItem.url; fileEntry.webDlLink =
fileEntry.webDlExpire = moment(serveItem.expireTimestamp).format(webDlExpireTimeFormat); ansi.vtxHyperlink(this.client, serveItem.url) + serveItem.url;
fileEntry.webDlExpire = moment(serveItem.expireTimestamp).format(
webDlExpireTimeFormat
);
} else { } else {
fileEntry.webDlLink = ''; fileEntry.webDlLink = '';
fileEntry.webDlExpire = ''; fileEntry.webDlExpire = '';
@ -137,14 +157,18 @@ exports.getModule = class FileBaseDownloadQueueManager extends MenuModule {
this.updateCustomViewTextsWithFilter( this.updateCustomViewTextsWithFilter(
'queueManager', 'queueManager',
MciViewIds.queueManager.customRangeStart, fileEntry, MciViewIds.queueManager.customRangeStart,
fileEntry,
{ filter: ['{webDlLink}', '{webDlExpire}'] } { filter: ['{webDlLink}', '{webDlExpire}'] }
); );
}); }
);
} }
updateDownloadQueueView(cb) { updateDownloadQueueView(cb) {
const queueView = this.viewControllers.queueManager.getView(MciViewIds.queueManager.queue); const queueView = this.viewControllers.queueManager.getView(
MciViewIds.queueManager.queue
);
if (!queueView) { if (!queueView) {
return cb(Errors.DoesNotExist('Queue view does not exist')); return cb(Errors.DoesNotExist('Queue view does not exist'));
} }
@ -168,11 +192,15 @@ exports.getModule = class FileBaseDownloadQueueManager extends MenuModule {
async.series( async.series(
[ [
function prepArtAndViewController(callback) { function prepArtAndViewController(callback) {
return self.displayArtAndPrepViewController('queueManager', { clearScreen : clearScreen }, callback); return self.displayArtAndPrepViewController(
'queueManager',
{ clearScreen: clearScreen },
callback
);
}, },
function populateViews(callback) { function populateViews(callback) {
return self.updateDownloadQueueView(callback); return self.updateDownloadQueueView(callback);
} },
], ],
err => { err => {
if (cb) { if (cb) {
@ -213,7 +241,10 @@ exports.getModule = class FileBaseDownloadQueueManager extends MenuModule {
vcOpts.noInput = options.noInput; vcOpts.noInput = options.noInput;
} }
const vc = self.addViewController(name, new ViewController(vcOpts)); const vc = self.addViewController(
name,
new ViewController(vcOpts)
);
const loadOpts = { const loadOpts = {
callingMenu: self, callingMenu: self,
@ -226,7 +257,6 @@ exports.getModule = class FileBaseDownloadQueueManager extends MenuModule {
self.viewControllers[name].setFocus(true); self.viewControllers[name].setFocus(true);
return callback(null); return callback(null);
}, },
], ],
err => { err => {

View File

@ -78,7 +78,10 @@ module.exports = class FileBaseFilters {
} catch (e) { } catch (e) {
this.filters = FileBaseFilters.getBuiltInSystemFilters(); // something bad happened; reset everything back to defaults :( this.filters = FileBaseFilters.getBuiltInSystemFilters(); // something bad happened; reset everything back to defaults :(
defaulted = true; defaulted = true;
this.client.log.error( { error : e.message, property : filtersProperty }, 'Failed parsing file base filters property' ); this.client.log.error(
{ error: e.message, property: filtersProperty },
'Failed parsing file base filters property'
);
} }
if (defaulted) { if (defaulted) {
@ -92,11 +95,18 @@ module.exports = class FileBaseFilters {
} }
persist(cb) { persist(cb) {
return this.client.user.persistProperty(UserProps.FileBaseFilters, JSON.stringify(this.filters), cb); return this.client.user.persistProperty(
UserProps.FileBaseFilters,
JSON.stringify(this.filters),
cb
);
} }
cleanTags(tags) { cleanTags(tags) {
return tags.toLowerCase().replace(/,?\s+|,/g, ' ').trim(); return tags
.toLowerCase()
.replace(/,?\s+|,/g, ' ')
.trim();
} }
setActive(filterUuid) { setActive(filterUuid) {
@ -104,7 +114,10 @@ module.exports = class FileBaseFilters {
if (activeFilter) { if (activeFilter) {
this.activeFilter = activeFilter; this.activeFilter = activeFilter;
this.client.user.persistProperty(UserProps.FileBaseFilterActiveUuid, filterUuid); this.client.user.persistProperty(
UserProps.FileBaseFilterActiveUuid,
filterUuid
);
return true; return true;
} }
@ -124,18 +137,20 @@ module.exports = class FileBaseFilters {
sort: 'upload_timestamp', sort: 'upload_timestamp',
uuid: U_LATEST, uuid: U_LATEST,
system: true, system: true,
} },
}; };
return filters; return filters;
} }
static getActiveFilter(client) { static getActiveFilter(client) {
return new FileBaseFilters(client).get(client.user.properties[UserProps.FileBaseFilterActiveUuid]); return new FileBaseFilters(client).get(
client.user.properties[UserProps.FileBaseFilterActiveUuid]
);
} }
static getFileBaseLastViewedFileIdByUser(user) { static getFileBaseLastViewedFileIdByUser(user) {
return parseInt((user.properties[UserProps.FileBaseLastViewedId] || 0)); return parseInt(user.properties[UserProps.FileBaseLastViewedId] || 0);
} }
static setFileBaseLastViewedFileIdForUser(user, fileId, allowOlder, cb) { static setFileBaseLastViewedFileIdForUser(user, fileId, allowOlder, cb) {

View File

@ -7,10 +7,7 @@ const FileEntry = require('./file_entry.js');
const FileArea = require('./file_base_area.js'); const FileArea = require('./file_base_area.js');
const Config = require('./config.js').get; const Config = require('./config.js').get;
const { Errors } = require('./enig_error.js'); const { Errors } = require('./enig_error.js');
const { const { splitTextAtTerms, isAnsi } = require('./string_util.js');
splitTextAtTerms,
isAnsi,
} = require('./string_util.js');
const AnsiPrep = require('./ansi_prep.js'); const AnsiPrep = require('./ansi_prep.js');
const Log = require('./logger.js').log; const Log = require('./logger.js').log;
@ -27,7 +24,8 @@ exports.updateFileBaseDescFilesScheduledEvent = updateFileBaseDescFilesSchedul
function exportFileList(filterCriteria, options, cb) { function exportFileList(filterCriteria, options, cb) {
options.templateEncoding = options.templateEncoding || 'utf8'; options.templateEncoding = options.templateEncoding || 'utf8';
options.entryTemplate = options.entryTemplate || 'descript_ion_export_entry_template.asc'; options.entryTemplate =
options.entryTemplate || 'descript_ion_export_entry_template.asc';
options.tsFormat = options.tsFormat || 'YYYY-MM-DD'; options.tsFormat = options.tsFormat || 'YYYY-MM-DD';
options.descWidth = options.descWidth || 45; // FILE_ID.DIZ spec options.descWidth = options.descWidth || 45; // FILE_ID.DIZ spec
options.escapeDesc = _.get(options, 'escapeDesc', false); // escape \r and \n in desc? options.escapeDesc = _.get(options, 'escapeDesc', false); // escape \r and \n in desc?
@ -43,15 +41,13 @@ function exportFileList(filterCriteria, options, cb) {
status: 'Preparing', status: 'Preparing',
}; };
const updateProgress = _.isFunction(options.progress) ? const updateProgress = _.isFunction(options.progress)
progCb => { ? progCb => {
return options.progress(state, progCb); return options.progress(state, progCb);
} :
progCb => {
return progCb(null);
} }
; : progCb => {
return progCb(null);
};
async.waterfall( async.waterfall(
[ [
function readTemplateFiles(callback) { function readTemplateFiles(callback) {
@ -62,26 +58,35 @@ function exportFileList(filterCriteria, options, cb) {
const templateFiles = [ const templateFiles = [
{ name: options.headerTemplate, req: false }, { name: options.headerTemplate, req: false },
{ name : options.entryTemplate, req : true } { name: options.entryTemplate, req: true },
]; ];
const config = Config(); const config = Config();
async.map(templateFiles, (template, nextTemplate) => { async.map(
templateFiles,
(template, nextTemplate) => {
if (!template.name && !template.req) { if (!template.name && !template.req) {
return nextTemplate(null, Buffer.from([])); return nextTemplate(null, Buffer.from([]));
} }
template.name = paths.isAbsolute(template.name) ? template.name : paths.join(config.paths.misc, template.name); template.name = paths.isAbsolute(template.name)
? template.name
: paths.join(config.paths.misc, template.name);
fs.readFile(template.name, (err, data) => { fs.readFile(template.name, (err, data) => {
return nextTemplate(err, data); return nextTemplate(err, data);
}); });
}, (err, templates) => { },
(err, templates) => {
if (err) { if (err) {
return callback(Errors.General(err.message)); return callback(Errors.General(err.message));
} }
// decode + ensure DOS style CRLF // decode + ensure DOS style CRLF
templates = templates.map(tmp => iconv.decode(tmp, options.templateEncoding).replace(/\r?\n/g, '\r\n') ); templates = templates.map(tmp =>
iconv
.decode(tmp, options.templateEncoding)
.replace(/\r?\n/g, '\r\n')
);
// Look for the first {fileDesc} (if any) in 'entry' template & find indentation requirements // Look for the first {fileDesc} (if any) in 'entry' template & find indentation requirements
let descIndent = 0; let descIndent = 0;
@ -97,7 +102,8 @@ function exportFileList(filterCriteria, options, cb) {
} }
return callback(null, templates[0], templates[1], descIndent); return callback(null, templates[0], templates[1], descIndent);
}); }
);
}); });
}, },
function findFiles(headerTemplate, entryTemplate, descIndent, callback) { function findFiles(headerTemplate, entryTemplate, descIndent, callback) {
@ -110,14 +116,28 @@ function exportFileList(filterCriteria, options, cb) {
FileEntry.findFiles(filterCriteria, (err, fileIds) => { FileEntry.findFiles(filterCriteria, (err, fileIds) => {
if (0 === fileIds.length) { if (0 === fileIds.length) {
return callback(Errors.General('No results for criteria', 'NORESULTS')); return callback(
Errors.General('No results for criteria', 'NORESULTS')
);
} }
return callback(err, headerTemplate, entryTemplate, descIndent, fileIds); return callback(
err,
headerTemplate,
entryTemplate,
descIndent,
fileIds
);
}); });
}); });
}, },
function buildListEntries(headerTemplate, entryTemplate, descIndent, fileIds, callback) { function buildListEntries(
headerTemplate,
entryTemplate,
descIndent,
fileIds,
callback
) {
const formatObj = { const formatObj = {
totalFileCount: fileIds.length, totalFileCount: fileIds.length,
}; };
@ -129,7 +149,9 @@ function exportFileList(filterCriteria, options, cb) {
state.step = 'file'; state.step = 'file';
async.eachSeries(fileIds, (fileId, nextFileId) => { async.eachSeries(
fileIds,
(fileId, nextFileId) => {
const fileInfo = new FileEntry(); const fileInfo = new FileEntry();
current += 1; current += 1;
@ -142,11 +164,17 @@ function exportFileList(filterCriteria, options, cb) {
const appendFileInfo = () => { const appendFileInfo = () => {
if (options.escapeDesc) { if (options.escapeDesc) {
formatObj.fileDesc = formatObj.fileDesc.replace(/\r?\n/g, options.escapeDesc); formatObj.fileDesc = formatObj.fileDesc.replace(
/\r?\n/g,
options.escapeDesc
);
} }
if (options.maxDescLen) { if (options.maxDescLen) {
formatObj.fileDesc = formatObj.fileDesc.slice(0, options.maxDescLen); formatObj.fileDesc = formatObj.fileDesc.slice(
0,
options.maxDescLen
);
} }
listBody += stringFormat(entryTemplate, formatObj); listBody += stringFormat(entryTemplate, formatObj);
@ -169,22 +197,36 @@ function exportFileList(filterCriteria, options, cb) {
formatObj.fileName = fileInfo.fileName; formatObj.fileName = fileInfo.fileName;
formatObj.fileSize = fileInfo.meta.byte_size; formatObj.fileSize = fileInfo.meta.byte_size;
formatObj.fileDesc = fileInfo.desc || ''; formatObj.fileDesc = fileInfo.desc || '';
formatObj.fileDescShort = formatObj.fileDesc.slice(0, options.descWidth); formatObj.fileDescShort = formatObj.fileDesc.slice(
0,
options.descWidth
);
formatObj.fileSha256 = fileInfo.fileSha256; formatObj.fileSha256 = fileInfo.fileSha256;
formatObj.fileCrc32 = fileInfo.meta.file_crc32; formatObj.fileCrc32 = fileInfo.meta.file_crc32;
formatObj.fileMd5 = fileInfo.meta.file_md5; formatObj.fileMd5 = fileInfo.meta.file_md5;
formatObj.fileSha1 = fileInfo.meta.file_sha1; formatObj.fileSha1 = fileInfo.meta.file_sha1;
formatObj.uploadBy = fileInfo.meta.upload_by_username || 'N/A'; formatObj.uploadBy =
formatObj.fileUploadTs = moment(fileInfo.uploadTimestamp).format(options.tsFormat); fileInfo.meta.upload_by_username || 'N/A';
formatObj.fileHashTags = fileInfo.hashTags.size > 0 ? Array.from(fileInfo.hashTags).join(', ') : 'N/A'; formatObj.fileUploadTs = moment(
fileInfo.uploadTimestamp
).format(options.tsFormat);
formatObj.fileHashTags =
fileInfo.hashTags.size > 0
? Array.from(fileInfo.hashTags).join(', ')
: 'N/A';
formatObj.currentFile = current; formatObj.currentFile = current;
formatObj.progress = Math.floor( (current / fileIds.length) * 100 ); formatObj.progress = Math.floor(
(current / fileIds.length) * 100
);
if (isAnsi(fileInfo.desc)) { if (isAnsi(fileInfo.desc)) {
AnsiPrep( AnsiPrep(
fileInfo.desc, fileInfo.desc,
{ {
cols : Math.min(options.descWidth, 79 - descIndent), cols: Math.min(
options.descWidth,
79 - descIndent
),
forceLineTerm: true, // ensure each line is term'd forceLineTerm: true, // ensure each line is term'd
asciiMode: true, // export to ASCII asciiMode: true, // export to ASCII
fillLines: false, // don't fill up to |cols| fillLines: false, // don't fill up to |cols|
@ -198,14 +240,20 @@ function exportFileList(filterCriteria, options, cb) {
} }
); );
} else { } else {
const indentSpc = descIndent > 0 ? ' '.repeat(descIndent) : ''; const indentSpc =
formatObj.fileDesc = splitTextAtTerms(formatObj.fileDesc).join(`\r\n${indentSpc}`) + '\r\n'; descIndent > 0 ? ' '.repeat(descIndent) : '';
formatObj.fileDesc =
splitTextAtTerms(formatObj.fileDesc).join(
`\r\n${indentSpc}`
) + '\r\n';
return appendFileInfo(); return appendFileInfo();
} }
}); });
}, err => { },
err => {
return callback(err, listBody, headerTemplate, totals); return callback(err, listBody, headerTemplate, totals);
}); }
);
}, },
function buildHeader(listBody, headerTemplate, totals, callback) { function buildHeader(listBody, headerTemplate, totals, callback) {
// header is built last such that we can have totals/etc. // header is built last such that we can have totals/etc.
@ -243,8 +291,9 @@ function exportFileList(filterCriteria, options, cb) {
updateProgress(() => { updateProgress(() => {
return callback(null, listBody); return callback(null, listBody);
}); });
} },
], (err, listBody) => { ],
(err, listBody) => {
return cb(err, listBody); return cb(err, listBody);
} }
); );
@ -264,10 +313,14 @@ function updateFileBaseDescFilesScheduledEvent(args, cb) {
const headerTemplate = args[1]; const headerTemplate = args[1];
const areas = FileArea.getAvailableFileAreas(null, { skipAcsCheck: true }); const areas = FileArea.getAvailableFileAreas(null, { skipAcsCheck: true });
async.each(areas, (area, nextArea) => { async.each(
areas,
(area, nextArea) => {
const storageLocations = FileArea.getAreaStorageLocations(area); const storageLocations = FileArea.getAreaStorageLocations(area);
async.each(storageLocations, (storageLoc, nextStorageLoc) => { async.each(
storageLocations,
(storageLoc, nextStorageLoc) => {
const filterCriteria = { const filterCriteria = {
areaTag: area.areaTag, areaTag: area.areaTag,
storageTag: storageLoc.storageTag, storageTag: storageLoc.storageTag,
@ -281,21 +334,34 @@ function updateFileBaseDescFilesScheduledEvent(args, cb) {
}; };
exportFileList(filterCriteria, exportOpts, (err, listBody) => { exportFileList(filterCriteria, exportOpts, (err, listBody) => {
const descIonPath = paths.join(storageLoc.dir, 'DESCRIPT.ION'); const descIonPath = paths.join(storageLoc.dir, 'DESCRIPT.ION');
fs.writeFile(descIonPath, iconv.encode(listBody, 'cp437'), err => { fs.writeFile(
descIonPath,
iconv.encode(listBody, 'cp437'),
err => {
if (err) { if (err) {
Log.warn( { error : err.message, path : descIonPath }, 'Failed (re)creating DESCRIPT.ION'); Log.warn(
{ error: err.message, path: descIonPath },
'Failed (re)creating DESCRIPT.ION'
);
} else { } else {
Log.debug( { path : descIonPath }, '(Re)generated DESCRIPT.ION'); Log.debug(
{ path: descIonPath },
'(Re)generated DESCRIPT.ION'
);
} }
return nextStorageLoc(null); return nextStorageLoc(null);
}); }
}); );
}, () => { });
return nextArea(null); },
}); () => {
}, () => { return nextArea(null);
return cb(null); }
}); );
},
() => {
return cb(null);
}
);
} }

View File

@ -4,7 +4,8 @@
// ENiGMA½ // ENiGMA½
const MenuModule = require('./menu_module.js').MenuModule; const MenuModule = require('./menu_module.js').MenuModule;
const ViewController = require('./view_controller.js').ViewController; const ViewController = require('./view_controller.js').ViewController;
const getSortedAvailableFileAreas = require('./file_base_area.js').getSortedAvailableFileAreas; const getSortedAvailableFileAreas =
require('./file_base_area.js').getSortedAvailableFileAreas;
const FileBaseFilters = require('./file_base_filter.js'); const FileBaseFilters = require('./file_base_filter.js');
// deps // deps
@ -25,7 +26,7 @@ const MciViewIds = {
orderBy: 5, orderBy: 5,
sort: 6, sort: 6,
advSearch: 7, advSearch: 7,
} },
}; };
exports.getModule = class FileBaseSearch extends MenuModule { exports.getModule = class FileBaseSearch extends MenuModule {
@ -47,15 +48,23 @@ exports.getModule = class FileBaseSearch extends MenuModule {
} }
const self = this; const self = this;
const vc = self.addViewController( 'search', new ViewController( { client : this.client } ) ); const vc = self.addViewController(
'search',
new ViewController({ client: this.client })
);
async.series( async.series(
[ [
function loadFromConfig(callback) { function loadFromConfig(callback) {
return vc.loadFromMenuConfig( { callingMenu : self, mciMap : mciData.menu }, callback); return vc.loadFromMenuConfig(
{ callingMenu: self, mciMap: mciData.menu },
callback
);
}, },
function populateAreas(callback) { function populateAreas(callback) {
self.availAreas = [ { name : '-ALL-' } ].concat(getSortedAvailableFileAreas(self.client) || []); self.availAreas = [{ name: '-ALL-' }].concat(
getSortedAvailableFileAreas(self.client) || []
);
const areasView = vc.getView(MciViewIds.search.area); const areasView = vc.getView(MciViewIds.search.area);
areasView.setItems(self.availAreas.map(a => a.name)); areasView.setItems(self.availAreas.map(a => a.name));
@ -63,7 +72,7 @@ exports.getModule = class FileBaseSearch extends MenuModule {
vc.switchFocus(MciViewIds.search.searchTerms); vc.switchFocus(MciViewIds.search.searchTerms);
return callback(null); return callback(null);
} },
], ],
err => { err => {
return cb(err); return cb(err);
@ -115,6 +124,10 @@ exports.getModule = class FileBaseSearch extends MenuModule {
menuFlags: ['popParent'], menuFlags: ['popParent'],
}; };
return this.gotoMenu(this.menuConfig.config.fileBaseListEntriesMenu || 'fileBaseListEntries', menuOpts, cb); return this.gotoMenu(
this.menuConfig.config.fileBaseListEntriesMenu || 'fileBaseListEntries',
menuOpts,
cb
);
} }
}; };

View File

@ -59,20 +59,25 @@ const MciViewIds = {
progressBar: 2, progressBar: 2,
customRangeStart: 10, customRangeStart: 10,
} },
}; };
exports.getModule = class FileBaseListExport extends MenuModule { exports.getModule = class FileBaseListExport extends MenuModule {
constructor(options) { constructor(options) {
super(options); super(options);
this.config = Object.assign({}, _.get(options, 'menuConfig.config'), options.extraArgs); this.config = Object.assign(
{},
_.get(options, 'menuConfig.config'),
options.extraArgs
);
this.config.templateEncoding = this.config.templateEncoding || 'utf8'; this.config.templateEncoding = this.config.templateEncoding || 'utf8';
this.config.tsFormat = this.config.tsFormat || this.client.currentTheme.helpers.getDateTimeFormat('short'); this.config.tsFormat =
this.config.tsFormat ||
this.client.currentTheme.helpers.getDateTimeFormat('short');
this.config.descWidth = this.config.descWidth || 45; // ie FILE_ID.DIZ this.config.descWidth = this.config.descWidth || 45; // ie FILE_ID.DIZ
this.config.progBarChar = renderSubstr( (this.config.progBarChar || '▒'), 0, 1); this.config.progBarChar = renderSubstr(this.config.progBarChar || '▒', 0, 1);
this.config.compressThreshold = this.config.compressThreshold || (1440000); // >= 1.44M by default :) this.config.compressThreshold = this.config.compressThreshold || 1440000; // >= 1.44M by default :)
} }
mciReady(mciData, cb) { mciReady(mciData, cb) {
@ -83,13 +88,22 @@ exports.getModule = class FileBaseListExport extends MenuModule {
async.series( async.series(
[ [
(callback) => this.prepViewController('main', FormIds.main, mciData.menu, callback), callback =>
(callback) => this.prepareList(callback), this.prepViewController(
'main',
FormIds.main,
mciData.menu,
callback
),
callback => this.prepareList(callback),
], ],
err => { err => {
if (err) { if (err) {
if ('NORESULTS' === err.reasonCode) { if ('NORESULTS' === err.reasonCode) {
return this.gotoMenu(this.menuConfig.config.noResultsMenu || 'fileBaseExportListNoResults'); return this.gotoMenu(
this.menuConfig.config.noResultsMenu ||
'fileBaseExportListNoResults'
);
} }
return this.prevMenu(); return this.prevMenu();
@ -108,13 +122,15 @@ exports.getModule = class FileBaseListExport extends MenuModule {
const self = this; const self = this;
const statusView = self.viewControllers.main.getView(MciViewIds.main.status); const statusView = self.viewControllers.main.getView(MciViewIds.main.status);
const updateStatus = (status) => { const updateStatus = status => {
if (statusView) { if (statusView) {
statusView.setText(status); statusView.setText(status);
} }
}; };
const progBarView = self.viewControllers.main.getView(MciViewIds.main.progressBar); const progBarView = self.viewControllers.main.getView(
MciViewIds.main.progressBar
);
const updateProgressBar = (curr, total) => { const updateProgressBar = (curr, total) => {
if (progBarView) { if (progBarView) {
const prog = Math.floor((curr / total) * progBarView.dimens.width); const prog = Math.floor((curr / total) * progBarView.dimens.width);
@ -133,7 +149,11 @@ exports.getModule = class FileBaseListExport extends MenuModule {
case 'file': case 'file':
updateStatus(state.status); updateStatus(state.status);
updateProgressBar(state.current, state.total); updateProgressBar(state.current, state.total);
self.updateCustomViewTextsWithFilter('main', MciViewIds.main.customRangeStart, state.fileInfo); self.updateCustomViewTextsWithFilter(
'main',
MciViewIds.main.customRangeStart,
state.fileInfo
);
break; break;
default: default:
break; break;
@ -159,13 +179,23 @@ exports.getModule = class FileBaseListExport extends MenuModule {
const filterCriteria = Object.assign({}, self.config.filterCriteria); const filterCriteria = Object.assign({}, self.config.filterCriteria);
if (!filterCriteria.areaTag) { if (!filterCriteria.areaTag) {
filterCriteria.areaTag = FileArea.getAvailableFileAreaTags(self.client); filterCriteria.areaTag = FileArea.getAvailableFileAreaTags(
self.client
);
} }
const opts = { const opts = {
templateEncoding: self.config.templateEncoding, templateEncoding: self.config.templateEncoding,
headerTemplate : _.get(self.config, 'templates.header', 'file_list_header.asc'), headerTemplate: _.get(
entryTemplate : _.get(self.config, 'templates.entry', 'file_list_entry.asc'), self.config,
'templates.header',
'file_list_header.asc'
),
entryTemplate: _.get(
self.config,
'templates.entry',
'file_list_entry.asc'
),
tsFormat: self.config.tsFormat, tsFormat: self.config.tsFormat,
descWidth: self.config.descWidth, descWidth: self.config.descWidth,
progress: exportListProgress, progress: exportListProgress,
@ -178,8 +208,11 @@ exports.getModule = class FileBaseListExport extends MenuModule {
function persistList(listBody, callback) { function persistList(listBody, callback) {
updateStatus('Persisting list'); updateStatus('Persisting list');
const sysTempDownloadArea = FileArea.getFileAreaByTag(FileArea.WellKnownAreaTags.TempDownloads); const sysTempDownloadArea = FileArea.getFileAreaByTag(
const sysTempDownloadDir = FileArea.getAreaDefaultStorageDirectory(sysTempDownloadArea); FileArea.WellKnownAreaTags.TempDownloads
);
const sysTempDownloadDir =
FileArea.getAreaDefaultStorageDirectory(sysTempDownloadArea);
fse.mkdirs(sysTempDownloadDir, err => { fse.mkdirs(sysTempDownloadDir, err => {
if (err) { if (err) {
@ -188,7 +221,9 @@ exports.getModule = class FileBaseListExport extends MenuModule {
const outputFileName = paths.join( const outputFileName = paths.join(
sysTempDownloadDir, sysTempDownloadDir,
`file_list_${UUIDv4().substr(-8)}_${moment().format('YYYY-MM-DD')}.txt` `file_list_${UUIDv4().substr(-8)}_${moment().format(
'YYYY-MM-DD'
)}.txt`
); );
fs.writeFile(outputFileName, listBody, 'utf8', err => { fs.writeFile(outputFileName, listBody, 'utf8', err => {
@ -196,13 +231,26 @@ exports.getModule = class FileBaseListExport extends MenuModule {
return callback(err); return callback(err);
} }
self.getSizeAndCompressIfMeetsSizeThreshold(outputFileName, (err, finalOutputFileName, fileSize) => { self.getSizeAndCompressIfMeetsSizeThreshold(
return callback(err, finalOutputFileName, fileSize, sysTempDownloadArea); outputFileName,
}); (err, finalOutputFileName, fileSize) => {
return callback(
err,
finalOutputFileName,
fileSize,
sysTempDownloadArea
);
}
);
}); });
}); });
}, },
function persistFileEntry(outputFileName, fileSize, sysTempDownloadArea, callback) { function persistFileEntry(
outputFileName,
fileSize,
sysTempDownloadArea,
callback
) {
const newEntry = new FileEntry({ const newEntry = new FileEntry({
areaTag: sysTempDownloadArea.areaTag, areaTag: sysTempDownloadArea.areaTag,
fileName: paths.basename(outputFileName), fileName: paths.basename(outputFileName),
@ -212,7 +260,7 @@ exports.getModule = class FileBaseListExport extends MenuModule {
upload_by_user_id: self.client.user.userId, upload_by_user_id: self.client.user.userId,
byte_size: fileSize, byte_size: fileSize,
session_temp_dl: 1, // download is valid until session is over session_temp_dl: 1, // download is valid until session is over
} },
}); });
newEntry.desc = 'File List Export'; newEntry.desc = 'File List Export';
@ -232,7 +280,7 @@ exports.getModule = class FileBaseListExport extends MenuModule {
updateStatus('Exported list has been added to your download queue'); updateStatus('Exported list has been added to your download queue');
return callback(null); return callback(null);
} },
], ],
err => { err => {
self.client.removeListener('key press', keyPressHandler); self.client.removeListener('key press', keyPressHandler);

View File

@ -24,7 +24,7 @@ exports.moduleInfo = {
}; };
const FormIds = { const FormIds = {
queueManager : 0 queueManager: 0,
}; };
const MciViewIds = { const MciViewIds = {
@ -33,11 +33,10 @@ const MciViewIds = {
navMenu: 2, navMenu: 2,
customRangeStart: 10, customRangeStart: 10,
} },
}; };
exports.getModule = class FileBaseWebDownloadQueueManager extends MenuModule { exports.getModule = class FileBaseWebDownloadQueueManager extends MenuModule {
constructor(options) { constructor(options) {
super(options); super(options);
@ -53,7 +52,10 @@ exports.getModule = class FileBaseWebDownloadQueueManager extends MenuModule {
this.dlQueue.removeItems(selectedItem.fileId); this.dlQueue.removeItems(selectedItem.fileId);
// :TODO: broken: does not redraw menu properly - needs fixed! // :TODO: broken: does not redraw menu properly - needs fixed!
return this.removeItemsFromDownloadQueueView(formData.value.queueItem, cb); return this.removeItemsFromDownloadQueueView(
formData.value.queueItem,
cb
);
}, },
clearQueue: (formData, extraArgs, cb) => { clearQueue: (formData, extraArgs, cb) => {
this.dlQueue.clear(); this.dlQueue.clear();
@ -63,13 +65,16 @@ exports.getModule = class FileBaseWebDownloadQueueManager extends MenuModule {
}, },
getBatchLink: (formData, extraArgs, cb) => { getBatchLink: (formData, extraArgs, cb) => {
return this.generateAndDisplayBatchLink(cb); return this.generateAndDisplayBatchLink(cb);
} },
}; };
} }
initSequence() { initSequence() {
if (0 === this.dlQueue.items.length) { if (0 === this.dlQueue.items.length) {
return this.gotoMenu(this.menuConfig.config.emptyQueueMenu || 'fileBaseDownloadManagerEmptyQueue'); return this.gotoMenu(
this.menuConfig.config.emptyQueueMenu ||
'fileBaseDownloadManagerEmptyQueue'
);
} }
const self = this; const self = this;
@ -81,7 +86,7 @@ exports.getModule = class FileBaseWebDownloadQueueManager extends MenuModule {
}, },
function display(callback) { function display(callback) {
return self.displayQueueManagerPage(false, callback); return self.displayQueueManagerPage(false, callback);
} },
], ],
() => { () => {
return self.finishedLoading(); return self.finishedLoading();
@ -90,7 +95,9 @@ exports.getModule = class FileBaseWebDownloadQueueManager extends MenuModule {
} }
removeItemsFromDownloadQueueView(itemIndex, cb) { removeItemsFromDownloadQueueView(itemIndex, cb) {
const queueView = this.viewControllers.queueManager.getView(MciViewIds.queueManager.queue); const queueView = this.viewControllers.queueManager.getView(
MciViewIds.queueManager.queue
);
if (!queueView) { if (!queueView) {
return cb(Errors.DoesNotExist('Queue view does not exist')); return cb(Errors.DoesNotExist('Queue view does not exist'));
} }
@ -109,13 +116,16 @@ exports.getModule = class FileBaseWebDownloadQueueManager extends MenuModule {
displayFileInfoForFileEntry(fileEntry) { displayFileInfoForFileEntry(fileEntry) {
this.updateCustomViewTextsWithFilter( this.updateCustomViewTextsWithFilter(
'queueManager', 'queueManager',
MciViewIds.queueManager.customRangeStart, fileEntry, MciViewIds.queueManager.customRangeStart,
fileEntry,
{ filter: ['{webDlLink}', '{webDlExpire}', '{fileName}'] } // :TODO: Others.... { filter: ['{webDlLink}', '{webDlExpire}', '{fileName}'] } // :TODO: Others....
); );
} }
updateDownloadQueueView(cb) { updateDownloadQueueView(cb) {
const queueView = this.viewControllers.queueManager.getView(MciViewIds.queueManager.queue); const queueView = this.viewControllers.queueManager.getView(
MciViewIds.queueManager.queue
);
if (!queueView) { if (!queueView) {
return cb(Errors.DoesNotExist('Queue view does not exist')); return cb(Errors.DoesNotExist('Queue view does not exist'));
} }
@ -140,7 +150,7 @@ exports.getModule = class FileBaseWebDownloadQueueManager extends MenuModule {
this.client, this.client,
this.dlQueue.items, this.dlQueue.items,
{ {
expireTime : expireTime expireTime: expireTime,
}, },
(err, webBatchDlLink) => { (err, webBatchDlLink) => {
// :TODO: handle not enabled -> display such // :TODO: handle not enabled -> display such
@ -148,10 +158,12 @@ exports.getModule = class FileBaseWebDownloadQueueManager extends MenuModule {
return cb(err); return cb(err);
} }
const webDlExpireTimeFormat = this.menuConfig.config.webDlExpireTimeFormat || 'YYYY-MMM-DD @ h:mm'; const webDlExpireTimeFormat =
this.menuConfig.config.webDlExpireTimeFormat || 'YYYY-MMM-DD @ h:mm';
const formatObj = { const formatObj = {
webBatchDlLink : ansi.vtxHyperlink(this.client, webBatchDlLink) + webBatchDlLink, webBatchDlLink:
ansi.vtxHyperlink(this.client, webBatchDlLink) + webBatchDlLink,
webBatchDlExpire: expireTime.format(webDlExpireTimeFormat), webBatchDlExpire: expireTime.format(webDlExpireTimeFormat),
}; };
@ -173,20 +185,34 @@ exports.getModule = class FileBaseWebDownloadQueueManager extends MenuModule {
async.series( async.series(
[ [
function prepArtAndViewController(callback) { function prepArtAndViewController(callback) {
return self.displayArtAndPrepViewController('queueManager', { clearScreen : clearScreen }, callback); return self.displayArtAndPrepViewController(
'queueManager',
{ clearScreen: clearScreen },
callback
);
}, },
function prepareQueueDownloadLinks(callback) { function prepareQueueDownloadLinks(callback) {
const webDlExpireTimeFormat = self.menuConfig.config.webDlExpireTimeFormat || 'YYYY-MMM-DD @ h:mm'; const webDlExpireTimeFormat =
self.menuConfig.config.webDlExpireTimeFormat ||
'YYYY-MMM-DD @ h:mm';
const config = Config(); const config = Config();
async.each(self.dlQueue.items, (fileEntry, nextFileEntry) => { async.each(
FileAreaWeb.getExistingTempDownloadServeItem(self.client, fileEntry, (err, serveItem) => { self.dlQueue.items,
(fileEntry, nextFileEntry) => {
FileAreaWeb.getExistingTempDownloadServeItem(
self.client,
fileEntry,
(err, serveItem) => {
if (err) { if (err) {
if (ErrNotEnabled === err.reasonCode) { if (ErrNotEnabled === err.reasonCode) {
return nextFileEntry(err); // we should have caught this prior return nextFileEntry(err); // we should have caught this prior
} }
const expireTime = moment().add(config.fileBase.web.expireMinutes, 'minutes'); const expireTime = moment().add(
config.fileBase.web.expireMinutes,
'minutes'
);
FileAreaWeb.createAndServeTempDownload( FileAreaWeb.createAndServeTempDownload(
self.client, self.client,
@ -198,26 +224,40 @@ exports.getModule = class FileBaseWebDownloadQueueManager extends MenuModule {
} }
fileEntry.webDlLinkRaw = url; fileEntry.webDlLinkRaw = url;
fileEntry.webDlLink = ansi.vtxHyperlink(self.client, url) + url; fileEntry.webDlLink =
fileEntry.webDlExpire = expireTime.format(webDlExpireTimeFormat); ansi.vtxHyperlink(self.client, url) +
url;
fileEntry.webDlExpire =
expireTime.format(
webDlExpireTimeFormat
);
return nextFileEntry(null); return nextFileEntry(null);
} }
); );
} else { } else {
fileEntry.webDlLinkRaw = serveItem.url; fileEntry.webDlLinkRaw = serveItem.url;
fileEntry.webDlLink = ansi.vtxHyperlink(self.client, serveItem.url) + serveItem.url; fileEntry.webDlLink =
fileEntry.webDlExpire = moment(serveItem.expireTimestamp).format(webDlExpireTimeFormat); ansi.vtxHyperlink(
self.client,
serveItem.url
) + serveItem.url;
fileEntry.webDlExpire = moment(
serveItem.expireTimestamp
).format(webDlExpireTimeFormat);
return nextFileEntry(null); return nextFileEntry(null);
} }
}); }
}, err => { );
},
err => {
return callback(err); return callback(err);
}); }
);
}, },
function populateViews(callback) { function populateViews(callback) {
return self.updateDownloadQueueView(callback); return self.updateDownloadQueueView(callback);
} },
], ],
err => { err => {
if (cb) { if (cb) {
@ -258,7 +298,10 @@ exports.getModule = class FileBaseWebDownloadQueueManager extends MenuModule {
vcOpts.noInput = options.noInput; vcOpts.noInput = options.noInput;
} }
const vc = self.addViewController(name, new ViewController(vcOpts)); const vc = self.addViewController(
name,
new ViewController(vcOpts)
);
const loadOpts = { const loadOpts = {
callingMenu: self, callingMenu: self,
@ -271,7 +314,6 @@ exports.getModule = class FileBaseWebDownloadQueueManager extends MenuModule {
self.viewControllers[name].setFocus(true); self.viewControllers[name].setFocus(true);
return callback(null); return callback(null);
}, },
], ],
err => { err => {

View File

@ -3,10 +3,7 @@
const fileDb = require('./database.js').dbs.file; const fileDb = require('./database.js').dbs.file;
const Errors = require('./enig_error.js').Errors; const Errors = require('./enig_error.js').Errors;
const { const { getISOTimestampString, sanitizeString } = require('./database.js');
getISOTimestampString,
sanitizeString
} = require('./database.js');
const Config = require('./config.js').get; const Config = require('./config.js').get;
// deps // deps
@ -19,28 +16,34 @@ const crypto = require('crypto');
const moment = require('moment'); const moment = require('moment');
const FILE_TABLE_MEMBERS = [ const FILE_TABLE_MEMBERS = [
'file_id', 'area_tag', 'file_sha256', 'file_name', 'storage_tag', 'file_id',
'desc', 'desc_long', 'upload_timestamp' 'area_tag',
'file_sha256',
'file_name',
'storage_tag',
'desc',
'desc_long',
'upload_timestamp',
]; ];
const FILE_WELL_KNOWN_META = { const FILE_WELL_KNOWN_META = {
// name -> *read* converter, if any // name -> *read* converter, if any
upload_by_username: null, upload_by_username: null,
upload_by_user_id : (u) => parseInt(u) || 0, upload_by_user_id: u => parseInt(u) || 0,
file_md5: null, file_md5: null,
file_sha1: null, file_sha1: null,
file_crc32: null, file_crc32: null,
est_release_year : (y) => parseInt(y) || new Date().getFullYear(), est_release_year: y => parseInt(y) || new Date().getFullYear(),
dl_count : (d) => parseInt(d) || 0, dl_count: d => parseInt(d) || 0,
byte_size : (b) => parseInt(b) || 0, byte_size: b => parseInt(b) || 0,
archive_type: null, archive_type: null,
short_file_name: null, // e.g. DOS 8.3 filename, avail in some scenarios such as TIC import short_file_name: null, // e.g. DOS 8.3 filename, avail in some scenarios such as TIC import
tic_origin: null, // TIC "Origin" tic_origin: null, // TIC "Origin"
tic_desc: null, // TIC "Desc" tic_desc: null, // TIC "Desc"
tic_ldesc: null, // TIC "Ldesc" joined by '\n' tic_ldesc: null, // TIC "Ldesc" joined by '\n'
session_temp_dl : (v) => parseInt(v) ? true : false, session_temp_dl: v => (parseInt(v) ? true : false),
desc_sauce : (s) => JSON.parse(s) || {}, desc_sauce: s => JSON.parse(s) || {},
desc_long_sauce : (s) => JSON.parse(s) || {}, desc_long_sauce: s => JSON.parse(s) || {},
}; };
module.exports = class FileEntry { module.exports = class FileEntry {
@ -100,7 +103,7 @@ module.exports = class FileEntry {
}, },
function loadUserRating(callback) { function loadUserRating(callback) {
return self.loadRating(callback); return self.loadRating(callback);
} },
], ],
err => { err => {
return cb(err); return cb(err);
@ -120,7 +123,11 @@ module.exports = class FileEntry {
[ [
function check(callback) { function check(callback) {
if (isUpdate && !self.fileId) { if (isUpdate && !self.fileId) {
return callback(Errors.Invalid('Cannot update file entry without an existing "fileId" member')); return callback(
Errors.Invalid(
'Cannot update file entry without an existing "fileId" member'
)
);
} }
return callback(null); return callback(null);
}, },
@ -130,7 +137,11 @@ module.exports = class FileEntry {
} }
if (isUpdate) { if (isUpdate) {
return callback(Errors.MissingParam('fileSha256 property must be set for updates!')); return callback(
Errors.MissingParam(
'fileSha256 property must be set for updates!'
)
);
} }
readFile(self.filePath, (err, data) => { readFile(self.filePath, (err, data) => {
@ -152,7 +163,16 @@ module.exports = class FileEntry {
trans.run( trans.run(
`REPLACE INTO file (file_id, area_tag, file_sha256, file_name, storage_tag, desc, desc_long, upload_timestamp) `REPLACE INTO file (file_id, area_tag, file_sha256, file_name, storage_tag, desc, desc_long, upload_timestamp)
VALUES(?, ?, ?, ?, ?, ?, ?, ?);`, VALUES(?, ?, ?, ?, ?, ?, ?, ?);`,
[ self.fileId, self.areaTag, self.fileSha256, self.fileName, self.storageTag, self.desc, self.descLong, getISOTimestampString() ], [
self.fileId,
self.areaTag,
self.fileSha256,
self.fileName,
self.storageTag,
self.desc,
self.descLong,
getISOTimestampString(),
],
err => { err => {
return callback(err, trans); return callback(err, trans);
} }
@ -161,8 +181,17 @@ module.exports = class FileEntry {
trans.run( trans.run(
`REPLACE INTO file (area_tag, file_sha256, file_name, storage_tag, desc, desc_long, upload_timestamp) `REPLACE INTO file (area_tag, file_sha256, file_name, storage_tag, desc, desc_long, upload_timestamp)
VALUES(?, ?, ?, ?, ?, ?, ?);`, VALUES(?, ?, ?, ?, ?, ?, ?);`,
[ self.areaTag, self.fileSha256, self.fileName, self.storageTag, self.desc, self.descLong, getISOTimestampString() ], [
function inserted(err) { // use non-arrow func for 'this' scope / lastID self.areaTag,
self.fileSha256,
self.fileName,
self.storageTag,
self.desc,
self.descLong,
getISOTimestampString(),
],
function inserted(err) {
// use non-arrow func for 'this' scope / lastID
if (!err) { if (!err) {
self.fileId = this.lastID; self.fileId = this.lastID;
} }
@ -172,23 +201,40 @@ module.exports = class FileEntry {
} }
}, },
function storeMeta(trans, callback) { function storeMeta(trans, callback) {
async.each(Object.keys(self.meta), (n, next) => { async.each(
Object.keys(self.meta),
(n, next) => {
const v = self.meta[n]; const v = self.meta[n];
return FileEntry.persistMetaValue(self.fileId, n, v, trans, next); return FileEntry.persistMetaValue(
self.fileId,
n,
v,
trans,
next
);
}, },
err => { err => {
return callback(err, trans); return callback(err, trans);
}); }
);
}, },
function storeHashTags(trans, callback) { function storeHashTags(trans, callback) {
const hashTagsArray = Array.from(self.hashTags); const hashTagsArray = Array.from(self.hashTags);
async.each(hashTagsArray, (hashTag, next) => { async.each(
return FileEntry.persistHashTag(self.fileId, hashTag, trans, next); hashTagsArray,
(hashTag, next) => {
return FileEntry.persistHashTag(
self.fileId,
hashTag,
trans,
next
);
}, },
err => { err => {
return callback(err, trans); return callback(err, trans);
});
} }
);
},
], ],
(err, trans) => { (err, trans) => {
// :TODO: Log orig err // :TODO: Log orig err
@ -205,7 +251,7 @@ module.exports = class FileEntry {
static getAreaStorageDirectoryByTag(storageTag) { static getAreaStorageDirectoryByTag(storageTag) {
const config = Config(); const config = Config();
const storageLocation = (storageTag && config.fileBase.storageTags[storageTag]); const storageLocation = storageTag && config.fileBase.storageTags[storageTag];
// absolute paths as-is // absolute paths as-is
if (storageLocation && '/' === storageLocation.charAt(0)) { if (storageLocation && '/' === storageLocation.charAt(0)) {
@ -290,7 +336,9 @@ module.exports = class FileEntry {
(err, meta) => { (err, meta) => {
if (meta) { if (meta) {
const conv = FILE_WELL_KNOWN_META[meta.meta_name]; const conv = FILE_WELL_KNOWN_META[meta.meta_name];
this.meta[meta.meta_name] = conv ? conv(meta.meta_value) : meta.meta_value; this.meta[meta.meta_name] = conv
? conv(meta.meta_value)
: meta.meta_value;
} }
}, },
err => { err => {
@ -450,7 +498,9 @@ module.exports = class FileEntry {
} }
const entries = []; const entries = [];
async.each(fileIdRows, (row, nextRow) => { async.each(
fileIdRows,
(row, nextRow) => {
const fileEntry = new FileEntry(); const fileEntry = new FileEntry();
fileEntry.load(row.file_id, err => { fileEntry.load(row.file_id, err => {
if (!err) { if (!err) {
@ -461,7 +511,8 @@ module.exports = class FileEntry {
}, },
err => { err => {
return cb(err, entries); return cb(err, entries);
}); }
);
} }
); );
} }
@ -506,19 +557,20 @@ module.exports = class FileEntry {
} }
if (filter.sort && filter.sort.length > 0) { if (filter.sort && filter.sort.length > 0) {
if(Object.keys(FILE_WELL_KNOWN_META).indexOf(filter.sort) > -1) { // sorting via a meta value? if (Object.keys(FILE_WELL_KNOWN_META).indexOf(filter.sort) > -1) {
sql = // sorting via a meta value?
`SELECT DISTINCT f.file_id sql = `SELECT DISTINCT f.file_id
FROM file f, file_meta m`; FROM file f, file_meta m`;
appendWhereClause(`f.file_id = m.file_id AND m.meta_name = "${filter.sort}"`); appendWhereClause(
`f.file_id = m.file_id AND m.meta_name = "${filter.sort}"`
);
sqlOrderBy = `${getOrderByWithCast('m.meta_value')} ${sqlOrderDir}`; sqlOrderBy = `${getOrderByWithCast('m.meta_value')} ${sqlOrderDir}`;
} else { } else {
// additional special treatment for user ratings: we need to average them // additional special treatment for user ratings: we need to average them
if ('user_rating' === filter.sort) { if ('user_rating' === filter.sort) {
sql = sql = `SELECT DISTINCT f.file_id,
`SELECT DISTINCT f.file_id,
(SELECT IFNULL(AVG(rating), 0) rating (SELECT IFNULL(AVG(rating), 0) rating
FROM file_user_rating FROM file_user_rating
WHERE file_id = f.file_id) WHERE file_id = f.file_id)
@ -527,16 +579,15 @@ module.exports = class FileEntry {
sqlOrderBy = `ORDER BY avg_rating ${sqlOrderDir}`; sqlOrderBy = `ORDER BY avg_rating ${sqlOrderDir}`;
} else { } else {
sql = sql = `SELECT DISTINCT f.file_id
`SELECT DISTINCT f.file_id
FROM file f`; FROM file f`;
sqlOrderBy = getOrderByWithCast(`f.${filter.sort}`) + ' ' + sqlOrderDir; sqlOrderBy =
getOrderByWithCast(`f.${filter.sort}`) + ' ' + sqlOrderDir;
} }
} }
} else { } else {
sql = sql = `SELECT DISTINCT f.file_id
`SELECT DISTINCT f.file_id
FROM file f`; FROM file f`;
sqlOrderBy = `${getOrderByWithCast('f.file_id')} ${sqlOrderDir}`; sqlOrderBy = `${getOrderByWithCast('f.file_id')} ${sqlOrderDir}`;
@ -552,7 +603,6 @@ module.exports = class FileEntry {
} }
if (filter.metaPairs && filter.metaPairs.length > 0) { if (filter.metaPairs && filter.metaPairs.length > 0) {
filter.metaPairs.forEach(mp => { filter.metaPairs.forEach(mp => {
if (mp.wildcards) { if (mp.wildcards) {
// convert any * -> % and ? -> _ for SQLite syntax - see https://www.sqlite.org/lang_expr.html // convert any * -> % and ? -> _ for SQLite syntax - see https://www.sqlite.org/lang_expr.html
@ -608,7 +658,12 @@ module.exports = class FileEntry {
if (filter.tags && filter.tags.length > 0) { if (filter.tags && filter.tags.length > 0) {
// build list of quoted tags; filter.tags comes in as a space and/or comma separated values // build list of quoted tags; filter.tags comes in as a space and/or comma separated values
const tags = filter.tags.replace(/,/g, ' ').replace(/\s{2,}/g, ' ').split(' ').map( tag => `"${sanitizeString(tag)}"` ).join(','); const tags = filter.tags
.replace(/,/g, ' ')
.replace(/\s{2,}/g, ' ')
.split(' ')
.map(tag => `"${sanitizeString(tag)}"`)
.join(',');
appendWhereClause( appendWhereClause(
`f.file_id IN ( `f.file_id IN (
@ -623,8 +678,13 @@ module.exports = class FileEntry {
); );
} }
if(_.isString(filter.newerThanTimestamp) && filter.newerThanTimestamp.length > 0) { if (
appendWhereClause(`DATETIME(f.upload_timestamp) > DATETIME("${filter.newerThanTimestamp}", "+1 seconds")`); _.isString(filter.newerThanTimestamp) &&
filter.newerThanTimestamp.length > 0
) {
appendWhereClause(
`DATETIME(f.upload_timestamp) > DATETIME("${filter.newerThanTimestamp}", "+1 seconds")`
);
} }
if (_.isNumber(filter.newerThanFileId)) { if (_.isNumber(filter.newerThanFileId)) {
@ -646,7 +706,10 @@ module.exports = class FileEntry {
if (!rows || 0 === rows.length) { if (!rows || 0 === rows.length) {
return cb(null, []); // no matches return cb(null, []); // no matches
} }
return cb(null, rows.map(r => r.file_id)); return cb(
null,
rows.map(r => r.file_id)
);
}); });
} }
@ -676,7 +739,7 @@ module.exports = class FileEntry {
unlink(srcFileEntry.filePath, err => { unlink(srcFileEntry.filePath, err => {
return callback(err); return callback(err);
}); });
} },
], ],
err => { err => {
return cb(err); return cb(err);
@ -720,7 +783,7 @@ module.exports = class FileEntry {
return callback(err); return callback(err);
} }
); );
} },
], ],
err => { err => {
return cb(err); return cb(err);

View File

@ -61,7 +61,8 @@ exports.getModule = class TransferFileModule extends MenuModule {
const config = Config(); const config = Config();
if (options.extraArgs) { if (options.extraArgs) {
if (options.extraArgs.protocol) { if (options.extraArgs.protocol) {
this.protocolConfig = config.fileTransferProtocols[options.extraArgs.protocol]; this.protocolConfig =
config.fileTransferProtocols[options.extraArgs.protocol];
} }
if (options.extraArgs.direction) { if (options.extraArgs.direction) {
@ -101,7 +102,8 @@ exports.getModule = class TransferFileModule extends MenuModule {
} }
} }
this.protocolConfig = this.protocolConfig || config.fileTransferProtocols.zmodem8kSz; // try for *something* this.protocolConfig =
this.protocolConfig || config.fileTransferProtocols.zmodem8kSz; // try for *something*
this.direction = this.direction || 'send'; this.direction = this.direction || 'send';
this.sendQueue = this.sendQueue || []; this.sendQueue = this.sendQueue || [];
@ -118,7 +120,7 @@ exports.getModule = class TransferFileModule extends MenuModule {
} }
isSending() { isSending() {
return ('send' === this.direction); return 'send' === this.direction;
} }
restorePipeAfterExternalProc() { restorePipeAfterExternalProc() {
@ -135,16 +137,21 @@ exports.getModule = class TransferFileModule extends MenuModule {
const allFiles = this.sendQueue.map(f => f.path); const allFiles = this.sendQueue.map(f => f.path);
this.executeExternalProtocolHandlerForSend(allFiles, err => { this.executeExternalProtocolHandlerForSend(allFiles, err => {
if (err) { if (err) {
this.client.log.warn( { files : allFiles, error : err.message }, 'Error sending file(s)' ); this.client.log.warn(
{ files: allFiles, error: err.message },
'Error sending file(s)'
);
} else { } else {
const sentFiles = []; const sentFiles = [];
this.sendQueue.forEach(f => { this.sendQueue.forEach(f => {
f.sent = true; f.sent = true;
sentFiles.push(f.path); sentFiles.push(f.path);
}); });
this.client.log.info( { sentFiles : sentFiles }, `Successfully sent ${sentFiles.length} file(s)` ); this.client.log.info(
{ sentFiles: sentFiles },
`Successfully sent ${sentFiles.length} file(s)`
);
} }
return cb(err); return cb(err);
}); });
@ -205,13 +212,16 @@ exports.getModule = class TransferFileModule extends MenuModule {
let tryDstPath; let tryDstPath;
async.until( async.until(
(callback) => callback(null, movedOk), // until moved OK callback => callback(null, movedOk), // until moved OK
(cb) => { cb => {
if (0 === renameIndex) { if (0 === renameIndex) {
// try originally supplied path first // try originally supplied path first
tryDstPath = dst; tryDstPath = dst;
} else { } else {
tryDstPath = paths.join(dstPath, `${dstFileSuffix}(${renameIndex})${dstFileExt}`); tryDstPath = paths.join(
dstPath,
`${dstFileSuffix}(${renameIndex})${dstFileExt}`
);
} }
fse.move(src, tryDstPath, err => { fse.move(src, tryDstPath, err => {
@ -254,7 +264,9 @@ exports.getModule = class TransferFileModule extends MenuModule {
} }
if (!stats.isFile()) { if (!stats.isFile()) {
return cb(Errors.Invalid('Expected file entry in recv directory')); return cb(
Errors.Invalid('Expected file entry in recv directory')
);
} }
this.recvFilePaths.push(recvFullPath); this.recvFilePaths.push(recvFullPath);
@ -270,12 +282,16 @@ exports.getModule = class TransferFileModule extends MenuModule {
} }
// stat each to grab files only // stat each to grab files only
async.each(files, (fileName, nextFile) => { async.each(
files,
(fileName, nextFile) => {
const recvFullPath = paths.join(this.recvDirectory, fileName); const recvFullPath = paths.join(this.recvDirectory, fileName);
fs.stat(recvFullPath, (err, stats) => { fs.stat(recvFullPath, (err, stats) => {
if (err) { if (err) {
this.client.log.warn('Failed to stat file', { path : recvFullPath } ); this.client.log.warn('Failed to stat file', {
path: recvFullPath,
});
return nextFile(null); // just try the next one return nextFile(null); // just try the next one
} }
@ -285,9 +301,11 @@ exports.getModule = class TransferFileModule extends MenuModule {
return nextFile(null); return nextFile(null);
}); });
}, () => { },
() => {
return cb(null); return cb(null);
}); }
);
}); });
} }
}); });
@ -306,12 +324,16 @@ exports.getModule = class TransferFileModule extends MenuModule {
async.waterfall( async.waterfall(
[ [
function getTempFileListPath(callback) { function getTempFileListPath(callback) {
const hasFileList = externalArgs.find(ea => (ea.indexOf('{fileListPath}') > -1) ); const hasFileList = externalArgs.find(
ea => ea.indexOf('{fileListPath}') > -1
);
if (!hasFileList) { if (!hasFileList) {
return callback(null, null); return callback(null, null);
} }
temptmp.open( { prefix : TEMP_SUFFIX, suffix : '.txt' }, (err, tempFileInfo) => { temptmp.open(
{ prefix: TEMP_SUFFIX, suffix: '.txt' },
(err, tempFileInfo) => {
if (err) { if (err) {
return callback(err); // failed to create it return callback(err); // failed to create it
} }
@ -324,12 +346,15 @@ exports.getModule = class TransferFileModule extends MenuModule {
return callback(err, tempFileInfo.path); return callback(err, tempFileInfo.path);
}); });
}); });
}); }
);
}, },
function createArgs(tempFileListPath, callback) { function createArgs(tempFileListPath, callback) {
// initial args: ignore {filePaths} as we must break that into it's own sep array items // initial args: ignore {filePaths} as we must break that into it's own sep array items
const args = externalArgs.map(arg => { const args = externalArgs.map(arg => {
return '{filePaths}' === arg ? arg : stringFormat(arg, { return '{filePaths}' === arg
? arg
: stringFormat(arg, {
fileListPath: tempFileListPath || '', fileListPath: tempFileListPath || '',
}); });
}); });
@ -341,7 +366,7 @@ exports.getModule = class TransferFileModule extends MenuModule {
} }
return callback(null, args); return callback(null, args);
} },
], ],
(err, args) => { (err, args) => {
return cb(err, args); return cb(err, args);
@ -352,10 +377,12 @@ exports.getModule = class TransferFileModule extends MenuModule {
prepAndBuildRecvArgs(cb) { prepAndBuildRecvArgs(cb) {
const argsKey = this.recvFileName ? 'recvArgsNonBatch' : 'recvArgs'; const argsKey = this.recvFileName ? 'recvArgsNonBatch' : 'recvArgs';
const externalArgs = this.protocolConfig.external[argsKey]; const externalArgs = this.protocolConfig.external[argsKey];
const args = externalArgs.map(arg => stringFormat(arg, { const args = externalArgs.map(arg =>
stringFormat(arg, {
uploadDir: this.recvDirectory, uploadDir: this.recvDirectory,
fileName: this.recvFileName || '', fileName: this.recvFileName || '',
})); })
);
return cb(null, args); return cb(null, args);
} }
@ -365,9 +392,7 @@ exports.getModule = class TransferFileModule extends MenuModule {
const cmd = external[`${this.direction}Cmd`]; const cmd = external[`${this.direction}Cmd`];
// support for handlers that need IACs taken care of over Telnet/etc. // support for handlers that need IACs taken care of over Telnet/etc.
const processIACs = const processIACs = external.processIACs || external.escapeTelnet; // deprecated name
external.processIACs ||
external.escapeTelnet; // deprecated name
// :TODO: we should only do this when over Telnet (or derived, such as WebSockets)? // :TODO: we should only do this when over Telnet (or derived, such as WebSockets)?
@ -375,7 +400,12 @@ exports.getModule = class TransferFileModule extends MenuModule {
const EscapedIAC = Buffer.from([255, 255]); const EscapedIAC = Buffer.from([255, 255]);
this.client.log.debug( this.client.log.debug(
{ cmd : cmd, args : args, tempDir : this.recvDirectory, direction : this.direction }, {
cmd: cmd,
args: args,
tempDir: this.recvDirectory,
direction: this.direction,
},
'Executing external protocol' 'Executing external protocol'
); );
@ -390,7 +420,7 @@ exports.getModule = class TransferFileModule extends MenuModule {
let dataHits = 0; let dataHits = 0;
const updateActivity = () => { const updateActivity = () => {
if (0 === (dataHits++ % 4)) { if (0 === dataHits++ % 4) {
this.client.explicitActivityTimeUpdate(); this.client.explicitActivityTimeUpdate();
} }
}; };
@ -459,13 +489,23 @@ exports.getModule = class TransferFileModule extends MenuModule {
return this.restorePipeAfterExternalProc(); return this.restorePipeAfterExternalProc();
}); });
externalProc.once('exit', (exitCode) => { externalProc.once('exit', exitCode => {
this.client.log.debug( { cmd : cmd, args : args, exitCode : exitCode }, 'Process exited' ); this.client.log.debug(
{ cmd: cmd, args: args, exitCode: exitCode },
'Process exited'
);
this.restorePipeAfterExternalProc(); this.restorePipeAfterExternalProc();
externalProc.removeAllListeners(); externalProc.removeAllListeners();
return cb(exitCode ? Errors.ExternalProcess(`Process exited with exit code ${exitCode}`, 'EBADEXIT') : null); return cb(
exitCode
? Errors.ExternalProcess(
`Process exited with exit code ${exitCode}`,
'EBADEXIT'
)
: null
);
}); });
} }
@ -510,7 +550,9 @@ exports.getModule = class TransferFileModule extends MenuModule {
let downloadCount = 0; let downloadCount = 0;
let fileIds = []; let fileIds = [];
async.each(this.sendQueue, (queueItem, next) => { async.each(
this.sendQueue,
(queueItem, next) => {
if (!queueItem.sent) { if (!queueItem.sent) {
return next(null); return next(null);
} }
@ -528,7 +570,10 @@ exports.getModule = class TransferFileModule extends MenuModule {
// we just have a path - figure it out // we just have a path - figure it out
fs.stat(queueItem.path, (err, stats) => { fs.stat(queueItem.path, (err, stats) => {
if (err) { if (err) {
this.client.log.warn( { error : err.message, path : queueItem.path }, 'File stat failed' ); this.client.log.warn(
{ error: err.message, path: queueItem.path },
'File stat failed'
);
} else { } else {
downloadCount += 1; downloadCount += 1;
downloadBytes += stats.size; downloadBytes += stats.size;
@ -536,10 +581,19 @@ exports.getModule = class TransferFileModule extends MenuModule {
return next(null); return next(null);
}); });
}, () => { },
() => {
// All stats/meta currently updated via fire & forget - if this is ever a issue, we can wait for callbacks // All stats/meta currently updated via fire & forget - if this is ever a issue, we can wait for callbacks
StatLog.incrementUserStat(this.client.user, UserProps.FileDlTotalCount, downloadCount); StatLog.incrementUserStat(
StatLog.incrementUserStat(this.client.user, UserProps.FileDlTotalBytes, downloadBytes); this.client.user,
UserProps.FileDlTotalCount,
downloadCount
);
StatLog.incrementUserStat(
this.client.user,
UserProps.FileDlTotalBytes,
downloadBytes
);
StatLog.incrementSystemStat(SysProps.FileDlTotalCount, downloadCount); StatLog.incrementSystemStat(SysProps.FileDlTotalCount, downloadCount);
StatLog.incrementSystemStat(SysProps.FileDlTotalBytes, downloadBytes); StatLog.incrementSystemStat(SysProps.FileDlTotalBytes, downloadBytes);
@ -549,18 +603,24 @@ exports.getModule = class TransferFileModule extends MenuModule {
}); });
return cb(null); return cb(null);
}); }
);
} }
updateRecvStats(cb) { updateRecvStats(cb) {
let uploadBytes = 0; let uploadBytes = 0;
let uploadCount = 0; let uploadCount = 0;
async.each(this.recvFilePaths, (filePath, next) => { async.each(
this.recvFilePaths,
(filePath, next) => {
// we just have a path - figure it out // we just have a path - figure it out
fs.stat(filePath, (err, stats) => { fs.stat(filePath, (err, stats) => {
if (err) { if (err) {
this.client.log.warn( { error : err.message, path : filePath }, 'File stat failed' ); this.client.log.warn(
{ error: err.message, path: filePath },
'File stat failed'
);
} else { } else {
uploadCount += 1; uploadCount += 1;
uploadBytes += stats.size; uploadBytes += stats.size;
@ -568,15 +628,25 @@ exports.getModule = class TransferFileModule extends MenuModule {
return next(null); return next(null);
}); });
}, () => { },
StatLog.incrementUserStat(this.client.user, UserProps.FileUlTotalCount, uploadCount); () => {
StatLog.incrementUserStat(this.client.user, UserProps.FileUlTotalBytes, uploadBytes); StatLog.incrementUserStat(
this.client.user,
UserProps.FileUlTotalCount,
uploadCount
);
StatLog.incrementUserStat(
this.client.user,
UserProps.FileUlTotalBytes,
uploadBytes
);
StatLog.incrementSystemStat(SysProps.FileUlTotalCount, uploadCount); StatLog.incrementSystemStat(SysProps.FileUlTotalCount, uploadCount);
StatLog.incrementSystemStat(SysProps.FileUlTotalBytes, uploadBytes); StatLog.incrementSystemStat(SysProps.FileUlTotalBytes, uploadBytes);
return cb(null); return cb(null);
}); }
);
} }
initSequence() { initSequence() {
@ -615,13 +685,10 @@ exports.getModule = class TransferFileModule extends MenuModule {
const dlFileEntries = dlQueue.removeItems(sentFileIds); const dlFileEntries = dlQueue.removeItems(sentFileIds);
// fire event for downloaded entries // fire event for downloaded entries
Events.emit( Events.emit(Events.getSystemEvents().UserDownload, {
Events.getSystemEvents().UserDownload,
{
user: self.client.user, user: self.client.user,
files : dlFileEntries files: dlFileEntries,
} });
);
self.sentFileIds = sentFileIds; self.sentFileIds = sentFileIds;
} }
@ -636,7 +703,10 @@ exports.getModule = class TransferFileModule extends MenuModule {
}, },
function cleanupTempFiles(callback) { function cleanupTempFiles(callback) {
temptmp.cleanup(paths => { temptmp.cleanup(paths => {
Log.debug( { paths : paths, sessionId : temptmp.sessionId }, 'Temporary files cleaned up' ); Log.debug(
{ paths: paths, sessionId: temptmp.sessionId },
'Temporary files cleaned up'
);
}); });
return callback(null); return callback(null);
@ -647,7 +717,7 @@ exports.getModule = class TransferFileModule extends MenuModule {
} else { } else {
return self.updateRecvStats(callback); return self.updateRecvStats(callback);
} }
} },
], ],
err => { err => {
if (err) { if (err) {

View File

@ -21,7 +21,6 @@ const MciViewIds = {
}; };
exports.getModule = class FileTransferProtocolSelectModule extends MenuModule { exports.getModule = class FileTransferProtocolSelectModule extends MenuModule {
constructor(options) { constructor(options) {
super(options); super(options);
@ -53,16 +52,28 @@ exports.getModule = class FileTransferProtocolSelectModule extends MenuModule {
selectProtocol: (formData, extraArgs, cb) => { selectProtocol: (formData, extraArgs, cb) => {
const protocol = this.protocols[formData.value.protocol]; const protocol = this.protocols[formData.value.protocol];
const finalExtraArgs = this.extraArgs || {}; const finalExtraArgs = this.extraArgs || {};
Object.assign(finalExtraArgs, { protocol : protocol.protocol, direction : this.config.direction }, extraArgs ); Object.assign(
finalExtraArgs,
{ protocol: protocol.protocol, direction: this.config.direction },
extraArgs
);
const modOpts = { const modOpts = {
extraArgs: finalExtraArgs, extraArgs: finalExtraArgs,
}; };
if ('send' === this.config.direction) { if ('send' === this.config.direction) {
return this.gotoMenu(this.config.downloadFilesMenu || 'sendFilesToUser', modOpts, cb); return this.gotoMenu(
this.config.downloadFilesMenu || 'sendFilesToUser',
modOpts,
cb
);
} else { } else {
return this.gotoMenu(this.config.uploadFilesMenu || 'recvFilesFromUser', modOpts, cb); return this.gotoMenu(
this.config.uploadFilesMenu || 'recvFilesFromUser',
modOpts,
cb
);
} }
}, },
}; };
@ -94,14 +105,16 @@ exports.getModule = class FileTransferProtocolSelectModule extends MenuModule {
} }
const self = this; const self = this;
const vc = self.viewControllers.allViews = new ViewController( { client : self.client } ); const vc = (self.viewControllers.allViews = new ViewController({
client: self.client,
}));
async.series( async.series(
[ [
function loadFromConfig(callback) { function loadFromConfig(callback) {
const loadOpts = { const loadOpts = {
callingMenu: self, callingMenu: self,
mciMap : mciData.menu mciMap: mciData.menu,
}; };
return vc.loadFromMenuConfig(loadOpts, callback); return vc.loadFromMenuConfig(loadOpts, callback);
@ -113,7 +126,7 @@ exports.getModule = class FileTransferProtocolSelectModule extends MenuModule {
protListView.redraw(); protListView.redraw();
return callback(null); return callback(null);
} },
], ],
err => { err => {
return cb(err); return cb(err);
@ -135,7 +148,8 @@ exports.getModule = class FileTransferProtocolSelectModule extends MenuModule {
}); });
// Filter out batch vs non-batch only protocols // Filter out batch vs non-batch only protocols
if(this.extraArgs.recvFileName) { // non-batch aka non-blind if (this.extraArgs.recvFileName) {
// non-batch aka non-blind
this.protocols = this.protocols.filter(prot => prot.hasNonBatch); this.protocols = this.protocols.filter(prot => prot.hasNonBatch);
} else { } else {
this.protocols = this.protocols.filter(prot => prot.hasBatch); this.protocols = this.protocols.filter(prot => prot.hasBatch);
@ -146,7 +160,10 @@ exports.getModule = class FileTransferProtocolSelectModule extends MenuModule {
if (_.isNumber(a.sort) && _.isNumber(b.sort)) { if (_.isNumber(a.sort) && _.isNumber(b.sort)) {
return a.sort - b.sort; return a.sort - b.sort;
} else { } else {
return a.name.localeCompare(b.name, { sensitivity : false, numeric : true } ); return a.name.localeCompare(b.name, {
sensitivity: false,
numeric: true,
});
} }
}); });
} }

View File

@ -38,13 +38,16 @@ function moveOrCopyFileWithCollisionHandling(src, dst, operation, cb) {
} }
async.until( async.until(
(callback) => callback(null, opOk), // until moved OK callback => callback(null, opOk), // until moved OK
(cb) => { cb => {
if (0 === renameIndex) { if (0 === renameIndex) {
// try originally supplied path first // try originally supplied path first
tryDstPath = dst; tryDstPath = dst;
} else { } else {
tryDstPath = paths.join(dstPath, `${dstFileSuffix}(${renameIndex})${dstFileExt}`); tryDstPath = paths.join(
dstPath,
`${dstFileSuffix}(${renameIndex})${dstFileExt}`
);
} }
tryOperation(src, tryDstPath, err => { tryOperation(src, tryDstPath, err => {

View File

@ -40,7 +40,7 @@ module.exports = class FilesBBSFile {
const lines = iconv.decode(descData, 'cp437').split(/\r?\n/g); const lines = iconv.decode(descData, 'cp437').split(/\r?\n/g);
const filesBbs = new FilesBBSFile(); const filesBbs = new FilesBBSFile();
const isBadDescription = (desc) => { const isBadDescription = desc => {
return IgnoredDescriptions.find(d => desc.startsWith(d)) ? true : false; return IgnoredDescriptions.find(d => desc.startsWith(d)) ? true : false;
}; };
@ -59,9 +59,7 @@ module.exports = class FilesBBSFile {
const detectDecoder = () => { const detectDecoder = () => {
// helpers // helpers
const regExpTestUpTo = (n, re) => { const regExpTestUpTo = (n, re) => {
return lines return lines.slice(0, n).some(l => re.test(l));
.slice(0, n)
.some(l => re.test(l));
}; };
// //
@ -70,7 +68,8 @@ module.exports = class FilesBBSFile {
const decoders = [ const decoders = [
{ {
// I've been told this is what Syncrhonet uses // I've been told this is what Syncrhonet uses
lineRegExp : /^([^ ]{1,12})\s{1,11}([0-3][0-9]\/[0-3][0-9]\/[1789][0-9]) ([^\r\n]+)$/, lineRegExp:
/^([^ ]{1,12})\s{1,11}([0-3][0-9]\/[0-3][0-9]\/[1789][0-9]) ([^\r\n]+)$/,
detect: function () { detect: function () {
return regExpTestUpTo(10, this.lineRegExp); return regExpTestUpTo(10, this.lineRegExp);
}, },
@ -99,7 +98,7 @@ module.exports = class FilesBBSFile {
} }
filesBbs.entries.set(fileName, { timestamp, desc }); filesBbs.entries.set(fileName, { timestamp, desc });
} }
} },
}, },
{ {
@ -122,7 +121,11 @@ module.exports = class FilesBBSFile {
for (let j = i + 1; j < lines.length; ++j) { for (let j = i + 1; j < lines.length; ++j) {
line = lines[j]; line = lines[j];
// -------------------------------------------------v 32 // -------------------------------------------------v 32
if(!line.startsWith(' | ')) { if (
!line.startsWith(
' | '
)
) {
break; break;
} }
long.push(line.substr(33)); long.push(line.substr(33));
@ -137,7 +140,7 @@ module.exports = class FilesBBSFile {
filesBbs.entries.set(fileName, { desc }); filesBbs.entries.set(fileName, { desc });
} }
} },
}, },
{ {
@ -177,7 +180,7 @@ module.exports = class FilesBBSFile {
filesBbs.entries.set(fileName, { desc }); filesBbs.entries.set(fileName, { desc });
} }
} },
}, },
{ {
@ -187,7 +190,8 @@ module.exports = class FilesBBSFile {
// Examples: // Examples:
// - Expanding Your BBS CD by David Wolfe, 1995 // - Expanding Your BBS CD by David Wolfe, 1995
// //
lineRegExp : /^([^ ]{1,12})\s{1,20}([0-9]+)\s\s([0-3][0-9]-[0-3][0-9]-[1789][0-9])\s\s([^\r\n]+)$/, lineRegExp:
/^([^ ]{1,12})\s{1,20}([0-9]+)\s\s([0-3][0-9]-[0-3][0-9]-[1789][0-9])\s\s([^\r\n]+)$/,
detect: function () { detect: function () {
return regExpTestUpTo(10, this.lineRegExp); return regExpTestUpTo(10, this.lineRegExp);
}, },
@ -215,13 +219,17 @@ module.exports = class FilesBBSFile {
const size = parseInt(hdr[2]); const size = parseInt(hdr[2]);
const timestamp = moment(hdr[3], 'MM-DD-YY'); const timestamp = moment(hdr[3], 'MM-DD-YY');
if(isBadDescription(desc) || isNaN(size) || !timestamp.isValid()) { if (
isBadDescription(desc) ||
isNaN(size) ||
!timestamp.isValid()
) {
continue; continue;
} }
filesBbs.entries.set(fileName, { desc, size, timestamp }); filesBbs.entries.set(fileName, { desc, size, timestamp });
} }
} },
}, },
{ {
@ -253,7 +261,7 @@ module.exports = class FilesBBSFile {
filesBbs.entries.set(fileName, { desc }); filesBbs.entries.set(fileName, { desc });
} }
}); });
} },
}, },
{ {
@ -281,11 +289,12 @@ module.exports = class FilesBBSFile {
} }
size *= 1024; // K->bytes. size *= 1024; // K->bytes.
if(desc) { // omit empty entries if (desc) {
// omit empty entries
filesBbs.entries.set(fileName, { size, desc }); filesBbs.entries.set(fileName, { size, desc });
} }
}); });
} },
}, },
]; ];
@ -301,11 +310,11 @@ module.exports = class FilesBBSFile {
decoder.extract(decoder); decoder.extract(decoder);
return cb( return cb(
filesBbs.entries.size > 0 ? null : Errors.Invalid('Invalid or unrecognized FILES.BBS format'), filesBbs.entries.size > 0
? null
: Errors.Invalid('Invalid or unrecognized FILES.BBS format'),
filesBbs filesBbs
); );
}); });
} }
}; };

View File

@ -31,8 +31,11 @@ module.exports = class FNV1a {
for (let b of data) { for (let b of data) {
this.hash = this.hash ^ b; this.hash = this.hash ^ b;
this.hash += this.hash +=
(this.hash << 24) + (this.hash << 8) + (this.hash << 7) + (this.hash << 24) +
(this.hash << 4) + (this.hash << 1); (this.hash << 8) +
(this.hash << 7) +
(this.hash << 4) +
(this.hash << 1);
} }
return this; return this;
@ -49,4 +52,3 @@ module.exports = class FNV1a {
return this.hash & 0xffffffff; return this.hash & 0xffffffff;
} }
}; };

View File

@ -7,24 +7,14 @@ const { ViewController } = require('./view_controller.js');
const ansi = require('./ansi_term.js'); const ansi = require('./ansi_term.js');
const theme = require('./theme.js'); const theme = require('./theme.js');
const Message = require('./message.js'); const Message = require('./message.js');
const { const { updateMessageAreaLastReadId } = require('./message_area.js');
updateMessageAreaLastReadId
} = require('./message_area.js');
const { getMessageAreaByTag } = require('./message_area.js'); const { getMessageAreaByTag } = require('./message_area.js');
const User = require('./user.js'); const User = require('./user.js');
const StatLog = require('./stat_log.js'); const StatLog = require('./stat_log.js');
const stringFormat = require('./string_format.js'); const stringFormat = require('./string_format.js');
const { const { MessageAreaConfTempSwitcher } = require('./mod_mixins.js');
MessageAreaConfTempSwitcher const { isAnsi, stripAnsiControlCodes, insert } = require('./string_util.js');
} = require('./mod_mixins.js'); const { stripMciColorCodes, controlCodesToAnsi } = require('./color_codes.js');
const {
isAnsi, stripAnsiControlCodes,
insert
} = require('./string_util.js');
const {
stripMciColorCodes,
controlCodesToAnsi,
} = require('./color_codes.js');
const Config = require('./config.js').get; const Config = require('./config.js').get;
const { getAddressedToInfo } = require('./mail_util.js'); const { getAddressedToInfo } = require('./mail_util.js');
const Events = require('./events.js'); const Events = require('./events.js');
@ -80,7 +70,7 @@ const MciViewIds = {
quotedMsg: 1, quotedMsg: 1,
// 2 NYI // 2 NYI
quoteLines: 3, quoteLines: 3,
} },
}; };
/* /*
@ -103,8 +93,10 @@ const MciViewIds = {
// :TODO: convert code in this class to newer styles, conventions, etc. There is a lot of experimental stuff here that has better (DRY) alternatives // :TODO: convert code in this class to newer styles, conventions, etc. There is a lot of experimental stuff here that has better (DRY) alternatives
exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModule extends MessageAreaConfTempSwitcher(MenuModule) { exports.FullScreenEditorModule =
exports.getModule = class FullScreenEditorModule extends (
MessageAreaConfTempSwitcher(MenuModule)
) {
constructor(options) { constructor(options) {
super(options); super(options);
@ -149,7 +141,12 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
} }
} }
this.noUpdateLastReadId = _.get(options, 'extraArgs.noUpdateLastReadId', config.noUpdateLastReadId) || false; this.noUpdateLastReadId =
_.get(
options,
'extraArgs.noUpdateLastReadId',
config.noUpdateLastReadId
) || false;
this.isReady = false; this.isReady = false;
@ -164,7 +161,9 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
// Validation stuff // Validation stuff
// //
viewValidationListener: function (err, cb) { viewValidationListener: function (err, cb) {
var errMsgView = self.viewControllers.header.getView(MciViewIds.header.errorMsg); var errMsgView = self.viewControllers.header.getView(
MciViewIds.header.errorMsg
);
var newFocusViewId; var newFocusViewId;
if (errMsgView) { if (errMsgView) {
if (err) { if (err) {
@ -184,7 +183,8 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
return cb(null); return cb(null);
}, },
editModeEscPressed: function (formData, extraArgs, cb) { editModeEscPressed: function (formData, extraArgs, cb) {
self.footerMode = 'editor' === self.footerMode ? 'editorMenu' : 'editor'; self.footerMode =
'editor' === self.footerMode ? 'editorMenu' : 'editor';
self.switchFooter(function next(err) { self.switchFooter(function next(err) {
if (err) { if (err) {
@ -193,7 +193,9 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
switch (self.footerMode) { switch (self.footerMode) {
case 'editor': case 'editor':
if(!_.isUndefined(self.viewControllers.footerEditorMenu)) { if (
!_.isUndefined(self.viewControllers.footerEditorMenu)
) {
self.viewControllers.footerEditorMenu.detachClientEvents(); self.viewControllers.footerEditorMenu.detachClientEvents();
} }
self.viewControllers.body.switchFocus(1); self.viewControllers.body.switchFocus(1);
@ -205,7 +207,8 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
self.viewControllers.footerEditorMenu.switchFocus(1); self.viewControllers.footerEditorMenu.switchFocus(1);
break; break;
default : throw new Error('Unexpected mode'); default:
throw new Error('Unexpected mode');
} }
return cb(null); return cb(null);
@ -217,7 +220,9 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
return cb(null); return cb(null);
}, },
appendQuoteEntry: function (formData, extraArgs, cb) { appendQuoteEntry: function (formData, extraArgs, cb) {
const quoteMsgView = self.viewControllers.quoteBuilder.getView(MciViewIds.quoteBuilder.quotedMsg); const quoteMsgView = self.viewControllers.quoteBuilder.getView(
MciViewIds.quoteBuilder.quotedMsg
);
if (self.newQuoteBlock) { if (self.newQuoteBlock) {
self.newQuoteBlock = false; self.newQuoteBlock = false;
@ -227,7 +232,9 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
quoteMsgView.addText(self.getQuoteByHeader()); quoteMsgView.addText(self.getQuoteByHeader());
} }
const quoteListView = self.viewControllers.quoteBuilder.getView(MciViewIds.quoteBuilder.quoteLines); const quoteListView = self.viewControllers.quoteBuilder.getView(
MciViewIds.quoteBuilder.quoteLines
);
const quoteText = quoteListView.getItem(formData.value.quote); const quoteText = quoteListView.getItem(formData.value.quote);
quoteMsgView.addText(quoteText); quoteMsgView.addText(quoteText);
@ -310,8 +317,11 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
getHeaderFormatObj() { getHeaderFormatObj() {
const remoteUserNotAvail = this.menuConfig.config.remoteUserNotAvail || 'N/A'; const remoteUserNotAvail = this.menuConfig.config.remoteUserNotAvail || 'N/A';
const localUserIdNotAvail = this.menuConfig.config.localUserIdNotAvail || 'N/A'; const localUserIdNotAvail =
const modTimestampFormat = this.menuConfig.config.modTimestampFormat || this.client.currentTheme.helpers.getDateTimeFormat(); this.menuConfig.config.localUserIdNotAvail || 'N/A';
const modTimestampFormat =
this.menuConfig.config.modTimestampFormat ||
this.client.currentTheme.helpers.getDateTimeFormat();
return { return {
// :TODO: ensure we show real names for form/to if they are enforced in the area // :TODO: ensure we show real names for form/to if they are enforced in the area
@ -320,10 +330,26 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
// :TODO: // :TODO:
//fromRealName //fromRealName
//toRealName //toRealName
fromUserId : _.get(this.message, 'meta.System.local_from_user_id', localUserIdNotAvail), fromUserId: _.get(
toUserId : _.get(this.message, 'meta.System.local_to_user_id', localUserIdNotAvail), this.message,
fromRemoteUser : _.get(this.message, 'meta.System.remote_from_user', remoteUserNotAvail), 'meta.System.local_from_user_id',
toRemoteUser : _.get(this.message, 'meta.System.remote_to_user', remoteUserNotAvail), localUserIdNotAvail
),
toUserId: _.get(
this.message,
'meta.System.local_to_user_id',
localUserIdNotAvail
),
fromRemoteUser: _.get(
this.message,
'meta.System.remote_from_user',
remoteUserNotAvail
),
toRemoteUser: _.get(
this.message,
'meta.System.remote_to_user',
remoteUserNotAvail
),
subject: this.message.subject, subject: this.message.subject,
modTimestamp: this.message.modTimestamp.format(modTimestampFormat), modTimestamp: this.message.modTimestamp.format(modTimestampFormat),
msgNum: this.messageIndex + 1, msgNum: this.messageIndex + 1,
@ -334,8 +360,12 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
setInitialFooterMode() { setInitialFooterMode() {
switch (this.editorMode) { switch (this.editorMode) {
case 'edit' : this.footerMode = 'editor'; break; case 'edit':
case 'view' : this.footerMode = 'view'; break; this.footerMode = 'editor';
break;
case 'view':
this.footerMode = 'view';
break;
} }
} }
@ -344,12 +374,15 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
const area = getMessageAreaByTag(this.messageAreaTag); const area = getMessageAreaByTag(this.messageAreaTag);
const getFromUserName = () => { const getFromUserName = () => {
return (area && area.realNames) ? return area && area.realNames
this.client.user.getProperty(UserProps.RealName) || this.client.user.username : ? this.client.user.getProperty(UserProps.RealName) ||
this.client.user.username; this.client.user.username
: this.client.user.username;
}; };
let messageBody = this.viewControllers.body.getView(MciViewIds.body.message).getData( { forceLineTerms : this.replyIsAnsi } ); let messageBody = this.viewControllers.body
.getView(MciViewIds.body.message)
.getData({ forceLineTerms: this.replyIsAnsi });
const msgOpts = { const msgOpts = {
areaTag: this.messageAreaTag, areaTag: this.messageAreaTag,
@ -368,8 +401,19 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
// to packetAnsiMsgEncoding (generally cp437) as various boards // to packetAnsiMsgEncoding (generally cp437) as various boards
// really don't like ANSI messages in UTF-8 encoding (they should!) // really don't like ANSI messages in UTF-8 encoding (they should!)
// //
msgOpts.meta = { System : { 'explicit_encoding' : _.get(Config(), 'scannerTossers.ftn_bso.packetAnsiMsgEncoding', 'cp437') } }; msgOpts.meta = {
messageBody = `${ansi.reset()}${ansi.eraseData(2)}${ansi.goto(1,1)}\r\n${ansi.up()}${messageBody}`; System: {
explicit_encoding: _.get(
Config(),
'scannerTossers.ftn_bso.packetAnsiMsgEncoding',
'cp437'
),
},
};
messageBody = `${ansi.reset()}${ansi.eraseData(2)}${ansi.goto(
1,
1
)}\r\n${ansi.up()}${messageBody}`;
} }
} }
@ -396,7 +440,10 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
} }
return updateMessageAreaLastReadId( return updateMessageAreaLastReadId(
this.client.user.userId, this.messageAreaTag, this.message.messageId, cb this.client.user.userId,
this.messageAreaTag,
this.message.messageId,
cb
); );
} }
@ -408,7 +455,9 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
this.initHeaderViewMode(); this.initHeaderViewMode();
this.initFooterViewMode(); this.initFooterViewMode();
const bodyMessageView = this.viewControllers.body.getView(MciViewIds.body.message); const bodyMessageView = this.viewControllers.body.getView(
MciViewIds.body.message
);
let msg = this.message.message; let msg = this.message.message;
if (bodyMessageView && _.has(this, 'message.message')) { if (bodyMessageView && _.has(this, 'message.message')) {
@ -424,7 +473,11 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
const tearLinePos = Message.getTearLinePosition(msg); const tearLinePos = Message.getTearLinePosition(msg);
if (tearLinePos > -1) { if (tearLinePos > -1) {
msg = insert(msg, tearLinePos, bodyMessageView.getSGRFor('text')); msg = insert(
msg,
tearLinePos,
bodyMessageView.getSGRFor('text')
);
} }
bodyMessageView.setAnsi( bodyMessageView.setAnsi(
@ -456,17 +509,27 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
if (this.menuConfig.config.quoteStyleLevel1) { if (this.menuConfig.config.quoteStyleLevel1) {
// can be a single style to cover 'XX> TEXT' or an array to cover 'XX', '>', and TEXT // can be a single style to cover 'XX> TEXT' or an array to cover 'XX', '>', and TEXT
// Non-standard (as for BBSes) single > TEXT, omitting space before XX, etc. are allowed // Non-standard (as for BBSes) single > TEXT, omitting space before XX, etc. are allowed
const styleL1 = styleToArray(this.menuConfig.config.quoteStyleLevel1, 3); const styleL1 = styleToArray(
this.menuConfig.config.quoteStyleLevel1,
3
);
const QuoteRegex = /^([ ]?)([!-~]{0,2})>([ ]*)([^\r\n]*\r?\n)/gm; const QuoteRegex =
msg = msg.replace(QuoteRegex, (m, spc1, initials, spc2, text) => { /^([ ]?)([!-~]{0,2})>([ ]*)([^\r\n]*\r?\n)/gm;
msg = msg.replace(
QuoteRegex,
(m, spc1, initials, spc2, text) => {
return `${spc1}${styleL1[0]}${initials}${styleL1[1]}>${spc2}${styleL1[2]}${text}${bodyMessageView.styleSGR1}`; return `${spc1}${styleL1[0]}${initials}${styleL1[1]}>${spc2}${styleL1[2]}${text}${bodyMessageView.styleSGR1}`;
}); }
);
} }
if (this.menuConfig.config.tearLineStyle) { if (this.menuConfig.config.tearLineStyle) {
// '---' and TEXT // '---' and TEXT
const style = styleToArray(this.menuConfig.config.tearLineStyle, 2); const style = styleToArray(
this.menuConfig.config.tearLineStyle,
2
);
const TearLineRegex = /^--- (.+)$(?![\s\S]*^--- .+$)/m; const TearLineRegex = /^--- (.+)$(?![\s\S]*^--- .+$)/m;
msg = msg.replace(TearLineRegex, (m, text) => { msg = msg.replace(TearLineRegex, (m, text) => {
@ -475,7 +538,10 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
} }
if (this.menuConfig.config.originStyle) { if (this.menuConfig.config.originStyle) {
const style = styleToArray(this.menuConfig.config.originStyle, 3); const style = styleToArray(
this.menuConfig.config.originStyle,
3
);
const OriginRegex = /^([ ]{1,2})\* Origin: (.+)$/m; const OriginRegex = /^([ ]{1,2})\* Origin: (.+)$/m;
msg = msg.replace(OriginRegex, (m, spc, text) => { msg = msg.replace(OriginRegex, (m, spc, text) => {
@ -519,9 +585,20 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
// don't try to look up the local user ID. Instead, mark the mail // don't try to look up the local user ID. Instead, mark the mail
// for export with the remote to address. // for export with the remote to address.
// //
if(self.replyToMessage && self.replyToMessage.isFromRemoteUser()) { if (
self.message.setRemoteToUser(self.replyToMessage.meta.System[Message.SystemMetaNames.RemoteFromUser]); self.replyToMessage &&
self.message.setExternalFlavor(self.replyToMessage.meta.System[Message.SystemMetaNames.ExternalFlavor]); self.replyToMessage.isFromRemoteUser()
) {
self.message.setRemoteToUser(
self.replyToMessage.meta.System[
Message.SystemMetaNames.RemoteFromUser
]
);
self.message.setExternalFlavor(
self.replyToMessage.meta.System[
Message.SystemMetaNames.ExternalFlavor
]
);
return callback(null); return callback(null);
} }
@ -529,8 +606,13 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
// Detect if the user is attempting to send to a remote mail type that we support // Detect if the user is attempting to send to a remote mail type that we support
// //
// :TODO: how to plug in support without tying to various types here? isSupportedExteranlType() or such // :TODO: how to plug in support without tying to various types here? isSupportedExteranlType() or such
const addressedToInfo = getAddressedToInfo(self.message.toUserName); const addressedToInfo = getAddressedToInfo(
if(addressedToInfo.name && Message.AddressFlavor.FTN === addressedToInfo.flavor) { self.message.toUserName
);
if (
addressedToInfo.name &&
Message.AddressFlavor.FTN === addressedToInfo.flavor
) {
self.message.setRemoteToUser(addressedToInfo.remote); self.message.setRemoteToUser(addressedToInfo.remote);
self.message.setExternalFlavor(addressedToInfo.flavor); self.message.setExternalFlavor(addressedToInfo.flavor);
self.message.toUserName = addressedToInfo.name; self.message.toUserName = addressedToInfo.name;
@ -538,15 +620,18 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
} }
// we need to look it up // we need to look it up
User.getUserIdAndNameByLookup(self.message.toUserName, (err, toUserId) => { User.getUserIdAndNameByLookup(
self.message.toUserName,
(err, toUserId) => {
if (err) { if (err) {
return callback(err); return callback(err);
} }
self.message.setLocalToUserId(toUserId); self.message.setLocalToUserId(toUserId);
return callback(null); return callback(null);
});
} }
);
},
], ],
err => { err => {
return cb(err, self.message); return cb(err, self.message);
@ -556,18 +641,28 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
updateUserAndSystemStats(cb) { updateUserAndSystemStats(cb) {
if (Message.isPrivateAreaTag(this.message.areaTag)) { if (Message.isPrivateAreaTag(this.message.areaTag)) {
Events.emit(Events.getSystemEvents().UserSendMail, { user : this.client.user }); Events.emit(Events.getSystemEvents().UserSendMail, {
user: this.client.user,
});
if (cb) { if (cb) {
cb(null); cb(null);
} }
return; // don't inc stats for private messages return; // don't inc stats for private messages
} }
Events.emit(Events.getSystemEvents().UserPostMessage, { user : this.client.user, areaTag : this.message.areaTag }); Events.emit(Events.getSystemEvents().UserPostMessage, {
user: this.client.user,
areaTag: this.message.areaTag,
});
StatLog.incrementNonPersistentSystemStat(SysProps.MessageTotalCount, 1); StatLog.incrementNonPersistentSystemStat(SysProps.MessageTotalCount, 1);
StatLog.incrementNonPersistentSystemStat(SysProps.MessagesToday, 1); StatLog.incrementNonPersistentSystemStat(SysProps.MessagesToday, 1);
return StatLog.incrementUserStat(this.client.user, UserProps.MessagePostCount, 1, cb); return StatLog.incrementUserStat(
this.client.user,
UserProps.MessagePostCount,
1,
cb
);
} }
redrawFooter(options, cb) { redrawFooter(options, cb) {
@ -603,12 +698,15 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
theme.displayThemedAsset( theme.displayThemedAsset(
footerArt, footerArt,
self.client, self.client,
{ font : self.menuConfig.font, startRow: self.header.height + self.body.height }, {
font: self.menuConfig.font,
startRow: self.header.height + self.body.height,
},
function displayed(err, artData) { function displayed(err, artData) {
callback(err, artData); callback(err, artData);
} }
); );
} },
], ],
function complete(err, artData) { function complete(err, artData) {
cb(err, artData); cb(err, artData);
@ -642,12 +740,15 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
theme.displayThemedAsset( theme.displayThemedAsset(
art['header'], art['header'],
self.client, self.client,
{ font : self.menuConfig.font, startRow: artInfo.height + 1 }, {
font: self.menuConfig.font,
startRow: artInfo.height + 1,
},
function displayed(err, artInfo) { function displayed(err, artInfo) {
return callback(err, artInfo); return callback(err, artInfo);
} }
); );
} },
], ],
function complete(err) { function complete(err) {
//self.body.height = self.client.term.termHeight - self.header.height - 1; //self.body.height = self.client.term.termHeight - self.header.height - 1;
@ -657,9 +758,12 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
}, },
function displayFooter(callback) { function displayFooter(callback) {
// we have to treat the footer special // we have to treat the footer special
self.redrawFooter( { clear : false, footerName : self.getFooterName() }, function footerDisplayed(err) { self.redrawFooter(
{ clear: false, footerName: self.getFooterName() },
function footerDisplayed(err) {
callback(err); callback(err);
}); }
);
}, },
function refreshViews(callback) { function refreshViews(callback) {
comps.push(self.getFooterName()); comps.push(self.getFooterName());
@ -669,7 +773,7 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
}); });
callback(null); callback(null);
} },
], ],
function complete(err) { function complete(err) {
cb(err); cb(err);
@ -692,7 +796,7 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
var menuLoadOpts = { var menuLoadOpts = {
callingMenu: this, callingMenu: this,
formId: formId, formId: formId,
mciMap : artData.mciMap mciMap: artData.mciMap,
}; };
this.addViewController( this.addViewController(
@ -742,24 +846,30 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
function displayed(err, artInfo) { function displayed(err, artInfo) {
if (artInfo) { if (artInfo) {
mciData['body'] = artInfo; mciData['body'] = artInfo;
self.body = {height: artInfo.height - self.header.height}; self.body = {
height: artInfo.height - self.header.height,
};
} }
return callback(err, artInfo); return callback(err, artInfo);
}); }
);
}, },
function displayFooter(artInfo, callback) { function displayFooter(artInfo, callback) {
self.setInitialFooterMode(); self.setInitialFooterMode();
var footerName = self.getFooterName(); var footerName = self.getFooterName();
self.redrawFooter( { footerName : footerName }, function artDisplayed(err, artData) { self.redrawFooter(
{ footerName: footerName },
function artDisplayed(err, artData) {
mciData[footerName] = artData; mciData[footerName] = artData;
callback(err); callback(err);
}); }
);
}, },
function afterArtDisplayed(callback) { function afterArtDisplayed(callback) {
self.mciReady(mciData, callback); self.mciReady(mciData, callback);
} },
], ],
function complete(err) { function complete(err) {
if (err) { if (err) {
@ -784,7 +894,10 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
self.addViewController( self.addViewController(
'header', 'header',
new ViewController( { client : self.client, formId : menuLoadOpts.formId } ) new ViewController({
client: self.client,
formId: menuLoadOpts.formId,
})
).loadFromMenuConfig(menuLoadOpts, function headerReady(err) { ).loadFromMenuConfig(menuLoadOpts, function headerReady(err) {
callback(err); callback(err);
}); });
@ -795,7 +908,10 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
self.addViewController( self.addViewController(
'body', 'body',
new ViewController( { client : self.client, formId : menuLoadOpts.formId } ) new ViewController({
client: self.client,
formId: menuLoadOpts.formId,
})
).loadFromMenuConfig(menuLoadOpts, function bodyReady(err) { ).loadFromMenuConfig(menuLoadOpts, function bodyReady(err) {
callback(err); callback(err);
}); });
@ -808,19 +924,26 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
self.addViewController( self.addViewController(
footerName, footerName,
new ViewController( { client : self.client, formId : menuLoadOpts.formId } ) new ViewController({
client: self.client,
formId: menuLoadOpts.formId,
})
).loadFromMenuConfig(menuLoadOpts, function footerReady(err) { ).loadFromMenuConfig(menuLoadOpts, function footerReady(err) {
callback(err); callback(err);
}); });
}, },
function prepareViewStates(callback) { function prepareViewStates(callback) {
let from = self.viewControllers.header.getView(MciViewIds.header.from); let from = self.viewControllers.header.getView(
MciViewIds.header.from
);
if (from) { if (from) {
from.acceptsFocus = false; from.acceptsFocus = false;
} }
// :TODO: make this a method // :TODO: make this a method
var body = self.viewControllers.body.getView(MciViewIds.body.message); var body = self.viewControllers.body.getView(
MciViewIds.body.message
);
self.updateTextEditMode(body.getTextEditMode()); self.updateTextEditMode(body.getTextEditMode());
self.updateEditModePosition(body.getEditPosition()); self.updateEditModePosition(body.getEditPosition());
@ -829,28 +952,41 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
callback(null); callback(null);
}, },
function setInitialData(callback) { function setInitialData(callback) {
switch (self.editorMode) { switch (self.editorMode) {
case 'view': case 'view':
if (self.message) { if (self.message) {
self.initHeaderViewMode(); self.initHeaderViewMode();
self.initFooterViewMode(); self.initFooterViewMode();
var bodyMessageView = self.viewControllers.body.getView(MciViewIds.body.message); var bodyMessageView =
if(bodyMessageView && _.has(self, 'message.message')) { self.viewControllers.body.getView(
MciViewIds.body.message
);
if (
bodyMessageView &&
_.has(self, 'message.message')
) {
//self.setBodyMessageViewText(); //self.setBodyMessageViewText();
bodyMessageView.setText(stripAnsiControlCodes(self.message.message)); bodyMessageView.setText(
stripAnsiControlCodes(self.message.message)
);
} }
} }
break; break;
case 'edit': case 'edit':
{ {
const fromView = self.viewControllers.header.getView(MciViewIds.header.from); const fromView = self.viewControllers.header.getView(
MciViewIds.header.from
);
const area = getMessageAreaByTag(self.messageAreaTag); const area = getMessageAreaByTag(self.messageAreaTag);
if (fromView !== undefined) { if (fromView !== undefined) {
if (area && area.realNames) { if (area && area.realNames) {
fromView.setText(self.client.user.properties[UserProps.RealName] || self.client.user.username); fromView.setText(
self.client.user.properties[
UserProps.RealName
] || self.client.user.username
);
} else { } else {
fromView.setText(self.client.user.username); fromView.setText(self.client.user.username);
} }
@ -866,7 +1002,6 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
callback(null); callback(null);
}, },
function setInitialFocus(callback) { function setInitialFocus(callback) {
switch (self.editorMode) { switch (self.editorMode) {
case 'edit': case 'edit':
self.switchToHeader(); self.switchToHeader();
@ -879,7 +1014,7 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
} }
callback(null); callback(null);
} },
], ],
function complete(err) { function complete(err) {
return cb(err); return cb(err);
@ -888,7 +1023,6 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
} }
mciReadyHandler(mciData, cb) { mciReadyHandler(mciData, cb) {
this.createInitialViews(mciData, err => { this.createInitialViews(mciData, err => {
// :TODO: Can probably be replaced with @systemMethod:validateUserNameExists when the framework is in // :TODO: Can probably be replaced with @systemMethod:validateUserNameExists when the framework is in
// place - if this is for existing usernames else validate spec // place - if this is for existing usernames else validate spec
@ -917,7 +1051,11 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
if (posView) { if (posView) {
this.client.term.rawWrite(ansi.savePos()); this.client.term.rawWrite(ansi.savePos());
// :TODO: Use new formatting techniques here, e.g. state.cursorPositionRow, cursorPositionCol and cursorPositionFormat // :TODO: Use new formatting techniques here, e.g. state.cursorPositionRow, cursorPositionCol and cursorPositionFormat
posView.setText(_.padStart(String(pos.row + 1), 2, '0') + ',' + _.padEnd(String(pos.col + 1), 2, '0')); posView.setText(
_.padStart(String(pos.row + 1), 2, '0') +
',' +
_.padEnd(String(pos.col + 1), 2, '0')
);
this.client.term.rawWrite(ansi.restorePos()); this.client.term.rawWrite(ansi.restorePos());
} }
} }
@ -940,20 +1078,33 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
initHeaderViewMode() { initHeaderViewMode() {
// Only set header text for from view if it is on the form // Only set header text for from view if it is on the form
if (this.viewControllers.header.getView(MciViewIds.header.from) !== undefined) { if (
this.viewControllers.header.getView(MciViewIds.header.from) !== undefined
) {
this.setHeaderText(MciViewIds.header.from, this.message.fromUserName); this.setHeaderText(MciViewIds.header.from, this.message.fromUserName);
} }
this.setHeaderText(MciViewIds.header.to, this.message.toUserName); this.setHeaderText(MciViewIds.header.to, this.message.toUserName);
this.setHeaderText(MciViewIds.header.subject, this.message.subject); this.setHeaderText(MciViewIds.header.subject, this.message.subject);
this.setHeaderText(MciViewIds.header.modTimestamp, moment(this.message.modTimestamp).format( this.setHeaderText(
this.menuConfig.config.modTimestampFormat || this.client.currentTheme.helpers.getDateTimeFormat()) MciViewIds.header.modTimestamp,
moment(this.message.modTimestamp).format(
this.menuConfig.config.modTimestampFormat ||
this.client.currentTheme.helpers.getDateTimeFormat()
)
); );
this.setHeaderText(MciViewIds.header.msgNum, (this.messageIndex + 1).toString()); this.setHeaderText(
MciViewIds.header.msgNum,
(this.messageIndex + 1).toString()
);
this.setHeaderText(MciViewIds.header.msgTotal, this.messageTotal.toString()); this.setHeaderText(MciViewIds.header.msgTotal, this.messageTotal.toString());
this.updateCustomViewTextsWithFilter('header', MciViewIds.header.customRangeStart, this.getHeaderFormatObj()); this.updateCustomViewTextsWithFilter(
'header',
MciViewIds.header.customRangeStart,
this.getHeaderFormatObj()
);
// if we changed conf/area we need to update any related standard MCI view // if we changed conf/area we need to update any related standard MCI view
this.refreshPredefinedMciViewsByCode('header', ['MA', 'MC', 'ML', 'CM']); this.refreshPredefinedMciViewsByCode('header', ['MA', 'MC', 'ML', 'CM']);
@ -977,8 +1128,16 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
} }
initFooterViewMode() { initFooterViewMode() {
this.setViewText('footerView', MciViewIds.ViewModeFooter.msgNum, (this.messageIndex + 1).toString() ); this.setViewText(
this.setViewText('footerView', MciViewIds.ViewModeFooter.msgTotal, this.messageTotal.toString() ); 'footerView',
MciViewIds.ViewModeFooter.msgNum,
(this.messageIndex + 1).toString()
);
this.setViewText(
'footerView',
MciViewIds.ViewModeFooter.msgTotal,
this.messageTotal.toString()
);
} }
displayHelp(cb) { displayHelp(cb) {
@ -998,25 +1157,32 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
} }
addToDownloadQueue(cb) { addToDownloadQueue(cb) {
const sysTempDownloadArea = FileArea.getFileAreaByTag(FileArea.WellKnownAreaTags.TempDownloads); const sysTempDownloadArea = FileArea.getFileAreaByTag(
const sysTempDownloadDir = FileArea.getAreaDefaultStorageDirectory(sysTempDownloadArea); FileArea.WellKnownAreaTags.TempDownloads
);
const sysTempDownloadDir =
FileArea.getAreaDefaultStorageDirectory(sysTempDownloadArea);
const msgInfo = this.getHeaderFormatObj(); const msgInfo = this.getHeaderFormatObj();
const outputFileName = paths.join( const outputFileName = paths.join(
sysTempDownloadDir, sysTempDownloadDir,
sanatizeFilename( sanatizeFilename(
`(${msgInfo.messageId}) ${msgInfo.subject}_(${this.message.modTimestamp.format('YYYY-MM-DD')}).txt`) `(${msgInfo.messageId}) ${
msgInfo.subject
}_(${this.message.modTimestamp.format('YYYY-MM-DD')}).txt`
)
); );
async.waterfall( async.waterfall(
[ [
(callback) => { callback => {
const header = const header = `+${'-'.repeat(79)}
`+${'-'.repeat(79)}
| To : ${msgInfo.toUserName} | To : ${msgInfo.toUserName}
| From : ${msgInfo.fromUserName} | From : ${msgInfo.fromUserName}
| When : ${moment(this.message.modTimestamp).format('dddd, MMMM Do YYYY, h:mm:ss a (UTCZ)')} | When : ${moment(this.message.modTimestamp).format(
'dddd, MMMM Do YYYY, h:mm:ss a (UTCZ)'
)}
| Subject : ${msgInfo.subject} | Subject : ${msgInfo.subject}
| ID : ${this.message.messageUuid} (${msgInfo.messageId}) | ID : ${this.message.messageUuid} (${msgInfo.messageId})
+${'-'.repeat(79)} +${'-'.repeat(79)}
@ -1036,9 +1202,14 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
}); });
}, },
(exportedMessage, callback) => { (exportedMessage, callback) => {
return fs.writeFile(outputFileName, exportedMessage, 'utf8', callback); return fs.writeFile(
outputFileName,
exportedMessage,
'utf8',
callback
);
}, },
(callback) => { callback => {
fs.stat(outputFileName, (err, stats) => { fs.stat(outputFileName, (err, stats) => {
return callback(err, stats.size); return callback(err, stats.size);
}); });
@ -1053,7 +1224,7 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
upload_by_user_id: this.client.user.userId, upload_by_user_id: this.client.user.userId,
byte_size: fileSize, byte_size: fileSize,
session_temp_dl: 1, // download is valid until session is over session_temp_dl: 1, // download is valid until session is over
} },
}); });
newEntry.desc = `${msgInfo.messageId} - ${msgInfo.subject}`; newEntry.desc = `${msgInfo.messageId} - ${msgInfo.subject}`;
@ -1061,27 +1232,30 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
newEntry.persist(err => { newEntry.persist(err => {
if (!err) { if (!err) {
// queue it! // queue it!
DownloadQueue.get(this.client).addTemporaryDownload(newEntry); DownloadQueue.get(this.client).addTemporaryDownload(
newEntry
);
} }
return callback(err); return callback(err);
}); });
}, },
(callback) => { callback => {
const artSpec = this.menuConfig.config.art.expToDlQueue || const artSpec =
Buffer.from('Exported message has been added to your download queue!'); this.menuConfig.config.art.expToDlQueue ||
this.displayAsset( Buffer.from(
artSpec, 'Exported message has been added to your download queue!'
{ clearScreen : true }, );
() => { this.displayAsset(artSpec, { clearScreen: true }, () => {
this.client.waitForKeyPress(() => { this.client.waitForKeyPress(() => {
this.redrawScreen(() => { this.redrawScreen(() => {
this.viewControllers[this.getFooterName()].setFocus(true); this.viewControllers[this.getFooterName()].setFocus(
true
);
return callback(null); return callback(null);
}); });
}); });
} });
); },
}
], ],
err => { err => {
return cb(err); return cb(err);
@ -1102,11 +1276,20 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
// :TODO: NetRunner does NOT support delete line, so this does not work: // :TODO: NetRunner does NOT support delete line, so this does not work:
self.client.term.rawWrite( self.client.term.rawWrite(
ansi.goto(self.header.height + 1, 1) + ansi.goto(self.header.height + 1, 1) +
ansi.deleteLine((self.client.term.termHeight - self.header.height) - 1)); ansi.deleteLine(
self.client.term.termHeight - self.header.height - 1
)
);
theme.displayThemeArt( { name : self.menuConfig.config.art.quote, client : self.client }, function displayed(err, artData) { theme.displayThemeArt(
{
name: self.menuConfig.config.art.quote,
client: self.client,
},
function displayed(err, artData) {
callback(err, artData); callback(err, artData);
}); }
);
}, },
function createViewsIfNecessary(artData, callback) { function createViewsIfNecessary(artData, callback) {
var formId = self.getFormId('quoteBuilder'); var formId = self.getFormId('quoteBuilder');
@ -1120,18 +1303,28 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
self.addViewController( self.addViewController(
'quoteBuilder', 'quoteBuilder',
new ViewController( { client : self.client, formId : formId } ) new ViewController({
).loadFromMenuConfig(menuLoadOpts, function quoteViewsReady(err) { client: self.client,
formId: formId,
})
).loadFromMenuConfig(
menuLoadOpts,
function quoteViewsReady(err) {
callback(err); callback(err);
}); }
);
} else { } else {
self.viewControllers.quoteBuilder.redrawAll(); self.viewControllers.quoteBuilder.redrawAll();
callback(null); callback(null);
} }
}, },
function loadQuoteLines(callback) { function loadQuoteLines(callback) {
const quoteView = self.viewControllers.quoteBuilder.getView(MciViewIds.quoteBuilder.quoteLines); const quoteView = self.viewControllers.quoteBuilder.getView(
const bodyView = self.viewControllers.body.getView(MciViewIds.body.message); MciViewIds.quoteBuilder.quoteLines
);
const bodyView = self.viewControllers.body.getView(
MciViewIds.body.message
);
self.replyToMessage.getQuoteLines( self.replyToMessage.getQuoteLines(
{ {
@ -1152,8 +1345,12 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
quoteView.setItems(quoteLines); quoteView.setItems(quoteLines);
quoteView.setFocusItems(focusQuoteLines); quoteView.setFocusItems(focusQuoteLines);
self.viewControllers.quoteBuilder.getView(MciViewIds.quoteBuilder.quotedMsg).setFocus(false); self.viewControllers.quoteBuilder
self.viewControllers.quoteBuilder.switchFocus(MciViewIds.quoteBuilder.quoteLines); .getView(MciViewIds.quoteBuilder.quotedMsg)
.setFocus(false);
self.viewControllers.quoteBuilder.switchFocus(
MciViewIds.quoteBuilder.quoteLines
);
return callback(null); return callback(null);
} }
@ -1162,7 +1359,10 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
], ],
function complete(err) { function complete(err) {
if (err) { if (err) {
self.client.log.warn( { error : err.message }, 'Error displaying quote builder'); self.client.log.warn(
{ error: err.message },
'Error displaying quote builder'
);
} }
} }
); );
@ -1223,14 +1423,18 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
quoteBuilderFinalize() { quoteBuilderFinalize() {
// :TODO: fix magic #'s // :TODO: fix magic #'s
const quoteMsgView = this.viewControllers.quoteBuilder.getView(MciViewIds.quoteBuilder.quotedMsg); const quoteMsgView = this.viewControllers.quoteBuilder.getView(
MciViewIds.quoteBuilder.quotedMsg
);
const msgView = this.viewControllers.body.getView(MciViewIds.body.message); const msgView = this.viewControllers.body.getView(MciViewIds.body.message);
let quoteLines = quoteMsgView.getData().trim(); let quoteLines = quoteMsgView.getData().trim();
if (quoteLines.length > 0) { if (quoteLines.length > 0) {
if (this.replyIsAnsi) { if (this.replyIsAnsi) {
const bodyMessageView = this.viewControllers.body.getView(MciViewIds.body.message); const bodyMessageView = this.viewControllers.body.getView(
MciViewIds.body.message
);
quoteLines += `${ansi.normal()}${bodyMessageView.getSGRFor('text')}`; quoteLines += `${ansi.normal()}${bodyMessageView.getSGRFor('text')}`;
} }
msgView.addText(`${quoteLines}\n\n`); msgView.addText(`${quoteLines}\n\n`);
@ -1254,7 +1458,9 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
quoteFormat = 'On {dateTime} {userName} said...'; quoteFormat = 'On {dateTime} {userName} said...';
} }
const dtFormat = this.menuConfig.config.quoteDateTimeFormat || this.client.currentTheme.helpers.getDateTimeFormat(); const dtFormat =
this.menuConfig.config.quoteDateTimeFormat ||
this.client.currentTheme.helpers.getDateTimeFormat();
return stringFormat(quoteFormat, { return stringFormat(quoteFormat, {
dateTime: moment(this.replyToMessage.modTimestamp).format(dtFormat), dateTime: moment(this.replyToMessage.modTimestamp).format(dtFormat),
userName: this.replyToMessage.fromUserName, userName: this.replyToMessage.fromUserName,

View File

@ -4,7 +4,8 @@
const _ = require('lodash'); const _ = require('lodash');
const FTN_ADDRESS_REGEXP = /^([0-9]+:)?([0-9]+)(\/[0-9]+)?(\.[0-9]+)?(@[a-z0-9\-.]+)?$/i; const FTN_ADDRESS_REGEXP = /^([0-9]+:)?([0-9]+)(\/[0-9]+)?(\.[0-9]+)?(@[a-z0-9\-.]+)?$/i;
const FTN_PATTERN_REGEXP = /^([0-9*]+:)?([0-9*]+)(\/[0-9*]+)?(\.[0-9*]+)?(@[a-z0-9\-.*]+)?$/i; const FTN_PATTERN_REGEXP =
/^([0-9*]+:)?([0-9*]+)(\/[0-9*]+)?(\.[0-9*]+)?(@[a-z0-9\-.*]+)?$/i;
module.exports = class Address { module.exports = class Address {
constructor(addr) { constructor(addr) {

View File

@ -53,7 +53,8 @@ class PacketHeader {
this.prodData = 0x47694e45; // "ENiG" this.prodData = 0x47694e45; // "ENiG"
this.capWord = 0x0001; this.capWord = 0x0001;
this.capWordValidate = ((this.capWord & 0xff) << 8) | ((this.capWord >> 8) & 0xff); // swap this.capWordValidate =
((this.capWord & 0xff) << 8) | ((this.capWord >> 8) & 0xff); // swap
this.prodCodeHi = 0xfe; // see above this.prodCodeHi = 0xfe; // see above
this.prodRevHi = 0; this.prodRevHi = 0;
@ -133,7 +134,7 @@ class PacketHeader {
date: this.day, date: this.day,
hour: this.hour, hour: this.hour,
minute: this.minute, minute: this.minute,
second : this.second second: this.second,
}); });
} }
@ -249,10 +250,17 @@ function Packet(options) {
} }
// Convert password from NULL padded array to string // Convert password from NULL padded array to string
packetHeader.password = strUtil.stringFromNullTermBuffer(packetHeader.password, 'CP437'); packetHeader.password = strUtil.stringFromNullTermBuffer(
packetHeader.password,
'CP437'
);
if (FTN_PACKET_HEADER_TYPE !== packetHeader.packetType) { if (FTN_PACKET_HEADER_TYPE !== packetHeader.packetType) {
return cb(Errors.Invalid(`Unsupported FTN packet header type: ${packetHeader.packetType}`)); return cb(
Errors.Invalid(
`Unsupported FTN packet header type: ${packetHeader.packetType}`
)
);
} }
// //
@ -276,10 +284,11 @@ function Packet(options) {
((packetHeader.capWordValidate & 0xff) << 8) | ((packetHeader.capWordValidate & 0xff) << 8) |
((packetHeader.capWordValidate >> 8) & 0xff); ((packetHeader.capWordValidate >> 8) & 0xff);
if(capWordValidateSwapped === packetHeader.capWord && if (
capWordValidateSwapped === packetHeader.capWord &&
0 != packetHeader.capWord && 0 != packetHeader.capWord &&
packetHeader.capWord & 0x0001) packetHeader.capWord & 0x0001
{ ) {
packetHeader.version = '2+'; packetHeader.version = '2+';
// See FSC-0048 // See FSC-0048
@ -299,7 +308,7 @@ function Packet(options) {
date: packetHeader.day, date: packetHeader.day,
hour: packetHeader.hour, hour: packetHeader.hour,
minute: packetHeader.minute, minute: packetHeader.minute,
second : packetHeader.second second: packetHeader.second,
}); });
const ph = new PacketHeader(); const ph = new PacketHeader();
@ -322,7 +331,10 @@ function Packet(options) {
buffer.writeUInt16LE(packetHeader.baud, 16); buffer.writeUInt16LE(packetHeader.baud, 16);
buffer.writeUInt16LE(FTN_PACKET_HEADER_TYPE, 18); buffer.writeUInt16LE(FTN_PACKET_HEADER_TYPE, 18);
buffer.writeUInt16LE(-1 === packetHeader.origNet ? 0xffff : packetHeader.origNet, 20); buffer.writeUInt16LE(
-1 === packetHeader.origNet ? 0xffff : packetHeader.origNet,
20
);
buffer.writeUInt16LE(packetHeader.destNet, 22); buffer.writeUInt16LE(packetHeader.destNet, 22);
buffer.writeUInt8(packetHeader.prodCodeLo, 24); buffer.writeUInt8(packetHeader.prodCodeLo, 24);
buffer.writeUInt8(packetHeader.prodRevHi, 25); buffer.writeUInt8(packetHeader.prodRevHi, 25);
@ -360,7 +372,10 @@ function Packet(options) {
buffer.writeUInt16LE(packetHeader.baud, 16); buffer.writeUInt16LE(packetHeader.baud, 16);
buffer.writeUInt16LE(FTN_PACKET_HEADER_TYPE, 18); buffer.writeUInt16LE(FTN_PACKET_HEADER_TYPE, 18);
buffer.writeUInt16LE(-1 === packetHeader.origNet ? 0xffff : packetHeader.origNet, 20); buffer.writeUInt16LE(
-1 === packetHeader.origNet ? 0xffff : packetHeader.origNet,
20
);
buffer.writeUInt16LE(packetHeader.destNet, 22); buffer.writeUInt16LE(packetHeader.destNet, 22);
buffer.writeUInt8(packetHeader.prodCodeLo, 24); buffer.writeUInt8(packetHeader.prodCodeLo, 24);
buffer.writeUInt8(packetHeader.prodRevHi, 25); buffer.writeUInt8(packetHeader.prodRevHi, 25);
@ -451,19 +466,34 @@ function Packet(options) {
// :TODO: This is wrong: SAUCE may not have EOF marker for one, also if it's // :TODO: This is wrong: SAUCE may not have EOF marker for one, also if it's
// present, we need to extract it but keep the rest of hte message intact as it likely // present, we need to extract it but keep the rest of hte message intact as it likely
// has SEEN-BY, PATH, and other kludge information *appended* // has SEEN-BY, PATH, and other kludge information *appended*
const sauceHeaderPosition = messageBodyBuffer.indexOf(FTN_MESSAGE_SAUCE_HEADER); const sauceHeaderPosition = messageBodyBuffer.indexOf(
FTN_MESSAGE_SAUCE_HEADER
);
if (sauceHeaderPosition > -1) { if (sauceHeaderPosition > -1) {
sauce.readSAUCE(messageBodyBuffer.slice(sauceHeaderPosition, sauceHeaderPosition + sauce.SAUCE_SIZE), (err, theSauce) => { sauce.readSAUCE(
messageBodyBuffer.slice(
sauceHeaderPosition,
sauceHeaderPosition + sauce.SAUCE_SIZE
),
(err, theSauce) => {
if (!err) { if (!err) {
// we read some SAUCE - don't re-process that portion into the body // we read some SAUCE - don't re-process that portion into the body
messageBodyBuffer = messageBodyBuffer.slice(0, sauceHeaderPosition) + messageBodyBuffer.slice(sauceHeaderPosition + sauce.SAUCE_SIZE); messageBodyBuffer =
messageBodyBuffer.slice(0, sauceHeaderPosition) +
messageBodyBuffer.slice(
sauceHeaderPosition + sauce.SAUCE_SIZE
);
// messageBodyBuffer = messageBodyBuffer.slice(0, sauceHeaderPosition); // messageBodyBuffer = messageBodyBuffer.slice(0, sauceHeaderPosition);
messageBodyData.sauce = theSauce; messageBodyData.sauce = theSauce;
} else { } else {
Log.warn( { error : err.message }, 'Found what looks like to be a SAUCE record, but failed to read'); Log.warn(
{ error: err.message },
'Found what looks like to be a SAUCE record, but failed to read'
);
} }
return callback(null); // failure to read SAUCE is OK return callback(null); // failure to read SAUCE is OK
}); }
);
} else { } else {
callback(null); callback(null);
} }
@ -483,7 +513,9 @@ function Packet(options) {
// Also according to the spec, the deprecated "CHARSET" value may be used // Also according to the spec, the deprecated "CHARSET" value may be used
// :TODO: Look into CHARSET more - should we bother supporting it? // :TODO: Look into CHARSET more - should we bother supporting it?
// :TODO: See encodingFromHeader() for CHRS/CHARSET support @ https://github.com/Mithgol/node-fidonet-jam // :TODO: See encodingFromHeader() for CHRS/CHARSET support @ https://github.com/Mithgol/node-fidonet-jam
const FTN_CHRS_PREFIX = Buffer.from( [ 0x01, 0x43, 0x48, 0x52, 0x53, 0x3a, 0x20 ] ); // "\x01CHRS:" const FTN_CHRS_PREFIX = Buffer.from([
0x01, 0x43, 0x48, 0x52, 0x53, 0x3a, 0x20,
]); // "\x01CHRS:"
const FTN_CHRS_SUFFIX = Buffer.from([0x0d]); const FTN_CHRS_SUFFIX = Buffer.from([0x0d]);
let chrsPrefixIndex = messageBodyBuffer.indexOf(FTN_CHRS_PREFIX); let chrsPrefixIndex = messageBodyBuffer.indexOf(FTN_CHRS_PREFIX);
@ -493,18 +525,25 @@ function Packet(options) {
chrsPrefixIndex += FTN_CHRS_PREFIX.length; chrsPrefixIndex += FTN_CHRS_PREFIX.length;
const chrsEndIndex = messageBodyBuffer.indexOf(FTN_CHRS_SUFFIX, chrsPrefixIndex); const chrsEndIndex = messageBodyBuffer.indexOf(
FTN_CHRS_SUFFIX,
chrsPrefixIndex
);
if (chrsEndIndex < 0) { if (chrsEndIndex < 0) {
return callback(null); return callback(null);
} }
let chrsContent = messageBodyBuffer.slice(chrsPrefixIndex, chrsEndIndex); let chrsContent = messageBodyBuffer.slice(
chrsPrefixIndex,
chrsEndIndex
);
if (0 === chrsContent.length) { if (0 === chrsContent.length) {
return callback(null); return callback(null);
} }
chrsContent = iconv.decode(chrsContent, 'CP437'); chrsContent = iconv.decode(chrsContent, 'CP437');
const chrsEncoding = ftn.getEncodingFromCharacterSetIdentifier(chrsContent); const chrsEncoding =
ftn.getEncodingFromCharacterSetIdentifier(chrsContent);
if (chrsEncoding) { if (chrsEncoding) {
encoding = chrsEncoding; encoding = chrsEncoding;
} }
@ -519,11 +558,16 @@ function Packet(options) {
try { try {
decoded = iconv.decode(messageBodyBuffer, encoding); decoded = iconv.decode(messageBodyBuffer, encoding);
} catch (e) { } catch (e) {
Log.debug( { encoding : encoding, error : e.toString() }, 'Error decoding. Falling back to ASCII'); Log.debug(
{ encoding: encoding, error: e.toString() },
'Error decoding. Falling back to ASCII'
);
decoded = iconv.decode(messageBodyBuffer, 'ascii'); decoded = iconv.decode(messageBodyBuffer, 'ascii');
} }
const messageLines = strUtil.splitTextAtTerms(decoded.replace(/\xec/g, '')); const messageLines = strUtil.splitTextAtTerms(
decoded.replace(/\xec/g, '')
);
let endOfMessage = false; let endOfMessage = false;
messageLines.forEach(line => { messageLines.forEach(line => {
@ -533,16 +577,21 @@ function Packet(options) {
} }
if (line.startsWith('AREA:')) { if (line.startsWith('AREA:')) {
messageBodyData.area = line.substring(line.indexOf(':') + 1).trim(); messageBodyData.area = line
.substring(line.indexOf(':') + 1)
.trim();
} else if (line.startsWith('--- ')) { } else if (line.startsWith('--- ')) {
// Tear Lines are tracked allowing for specialized display/etc. // Tear Lines are tracked allowing for specialized display/etc.
messageBodyData.tearLine = line; messageBodyData.tearLine = line;
} else if(/^[ ]{1,2}\* Origin: /.test(line)) { // To spec is " * Origin: ..." } else if (/^[ ]{1,2}\* Origin: /.test(line)) {
// To spec is " * Origin: ..."
messageBodyData.originLine = line; messageBodyData.originLine = line;
endOfMessage = true; // Anything past origin is not part of the message body endOfMessage = true; // Anything past origin is not part of the message body
} else if (line.startsWith('SEEN-BY:')) { } else if (line.startsWith('SEEN-BY:')) {
endOfMessage = true; // Anything past the first SEEN-BY is not part of the message body endOfMessage = true; // Anything past the first SEEN-BY is not part of the message body
messageBodyData.seenBy.push(line.substring(line.indexOf(':') + 1).trim()); messageBodyData.seenBy.push(
line.substring(line.indexOf(':') + 1).trim()
);
} else if (FTN_MESSAGE_KLUDGE_PREFIX === line.charAt(0)) { } else if (FTN_MESSAGE_KLUDGE_PREFIX === line.charAt(0)) {
if ('PATH:' === line.slice(1, 6)) { if ('PATH:' === line.slice(1, 6)) {
endOfMessage = true; // Anything pats the first PATH is not part of the message body endOfMessage = true; // Anything pats the first PATH is not part of the message body
@ -555,7 +604,7 @@ function Packet(options) {
}); });
return callback(null); return callback(null);
} },
], ],
() => { () => {
messageBodyData.message = messageBodyData.message.join('\n'); messageBodyData.message = messageBodyData.message.join('\n');
@ -571,7 +620,10 @@ function Packet(options) {
// //
if (packetBuffer.length < 3) { if (packetBuffer.length < 3) {
const peek = packetBuffer.slice(0, 2); const peek = packetBuffer.slice(0, 2);
if(peek.equals(Buffer.from([ 0x00 ])) || peek.equals(Buffer.from( [ 0x00, 0x00 ]))) { if (
peek.equals(Buffer.from([0x00])) ||
peek.equals(Buffer.from([0x00, 0x00]))
) {
// end marker - no more messages // end marker - no more messages
return cb(null); return cb(null);
} }
@ -586,7 +638,9 @@ function Packet(options) {
} }
if (FTN_PACKET_MESSAGE_TYPE != msgData.messageType) { if (FTN_PACKET_MESSAGE_TYPE != msgData.messageType) {
return cb(Errors.Invalid(`Unsupported FTN message type: ${msgData.messageType}`)); return cb(
Errors.Invalid(`Unsupported FTN message type: ${msgData.messageType}`)
);
} }
// //
@ -604,16 +658,32 @@ function Packet(options) {
// later on. // later on.
// //
if (msgData.modDateTime.length != 20) { if (msgData.modDateTime.length != 20) {
return cb(Errors.Invalid(`FTN packet DateTime field must be 20 bytes (got ${msgData.modDateTime.length})`)); return cb(
Errors.Invalid(
`FTN packet DateTime field must be 20 bytes (got ${msgData.modDateTime.length})`
)
);
} }
if (msgData.toUserName.length > 36) { if (msgData.toUserName.length > 36) {
return cb(Errors.Invalid(`FTN packet toUserName field must be 36 bytes max (got ${msgData.toUserName.length})`)); return cb(
Errors.Invalid(
`FTN packet toUserName field must be 36 bytes max (got ${msgData.toUserName.length})`
)
);
} }
if (msgData.fromUserName.length > 36) { if (msgData.fromUserName.length > 36) {
return cb(Errors.Invalid(`FTN packet fromUserName field must be 36 bytes max (got ${msgData.fromUserName.length})`)); return cb(
Errors.Invalid(
`FTN packet fromUserName field must be 36 bytes max (got ${msgData.fromUserName.length})`
)
);
} }
if (msgData.subject.length > 72) { if (msgData.subject.length > 72) {
return cb(Errors.Invalid(`FTN packet subject field must be 72 bytes max (got ${msgData.subject.length})`)); return cb(
Errors.Invalid(
`FTN packet subject field must be 72 bytes max (got ${msgData.subject.length})`
)
);
} }
// Arrays of CP437 bytes -> String // Arrays of CP437 bytes -> String
@ -705,10 +775,14 @@ function Packet(options) {
// :TODO: Parser should give is this info: // :TODO: Parser should give is this info:
const bytesRead = const bytesRead =
14 + // fixed header size 14 + // fixed header size
msgData.modDateTime.length + 1 + // +1 = NULL msgData.modDateTime.length +
msgData.toUserName.length + 1 + // +1 = NULL 1 + // +1 = NULL
msgData.fromUserName.length + 1 + // +1 = NULL msgData.toUserName.length +
msgData.subject.length + 1 + // +1 = NULL 1 + // +1 = NULL
msgData.fromUserName.length +
1 + // +1 = NULL
msgData.subject.length +
1 + // +1 = NULL
msgData.message.length; // includes NULL msgData.message.length; // includes NULL
const nextBuf = packetBuffer.slice(bytesRead); const nextBuf = packetBuffer.slice(bytesRead);
@ -747,7 +821,8 @@ function Packet(options) {
Message.FtnPropertyNames.FtnMsgDestNet, Message.FtnPropertyNames.FtnMsgDestNet,
].forEach(propName => { ].forEach(propName => {
if (message.meta.FtnProperty[propName]) { if (message.meta.FtnProperty[propName]) {
message.meta.FtnProperty[propName] = parseInt(message.meta.FtnProperty[propName]) || 0; message.meta.FtnProperty[propName] =
parseInt(message.meta.FtnProperty[propName]) || 0;
} }
}); });
}; };
@ -756,8 +831,12 @@ function Packet(options) {
// ensure address FtnProperties are numbers // ensure address FtnProperties are numbers
self.sanatizeFtnProperties(message); self.sanatizeFtnProperties(message);
const destNode = message.meta.FtnProperty.ftn_msg_dest_node || message.meta.FtnProperty.ftn_dest_node; const destNode =
const destNet = message.meta.FtnProperty.ftn_msg_dest_net || message.meta.FtnProperty.ftn_dest_network; message.meta.FtnProperty.ftn_msg_dest_node ||
message.meta.FtnProperty.ftn_dest_node;
const destNet =
message.meta.FtnProperty.ftn_msg_dest_net ||
message.meta.FtnProperty.ftn_dest_network;
buf.writeUInt16LE(FTN_PACKET_MESSAGE_TYPE, 0); buf.writeUInt16LE(FTN_PACKET_MESSAGE_TYPE, 0);
buf.writeUInt16LE(message.meta.FtnProperty.ftn_orig_node, 2); buf.writeUInt16LE(message.meta.FtnProperty.ftn_orig_node, 2);
@ -767,12 +846,13 @@ function Packet(options) {
buf.writeUInt16LE(message.meta.FtnProperty.ftn_attr_flags, 10); buf.writeUInt16LE(message.meta.FtnProperty.ftn_attr_flags, 10);
buf.writeUInt16LE(message.meta.FtnProperty.ftn_cost, 12); buf.writeUInt16LE(message.meta.FtnProperty.ftn_cost, 12);
const dateTimeBuffer = Buffer.from(ftn.getDateTimeString(message.modTimestamp) + '\0'); const dateTimeBuffer = Buffer.from(
ftn.getDateTimeString(message.modTimestamp) + '\0'
);
dateTimeBuffer.copy(buf, 14); dateTimeBuffer.copy(buf, 14);
}; };
this.getMessageEntryBuffer = function (message, options, cb) { this.getMessageEntryBuffer = function (message, options, cb) {
function getAppendMeta(k, m, sepChar = ':') { function getAppendMeta(k, m, sepChar = ':') {
let append = ''; let append = '';
if (m) { if (m) {
@ -796,9 +876,18 @@ function Packet(options) {
// //
// To, from, and subject must be NULL term'd and have max lengths as per spec. // To, from, and subject must be NULL term'd and have max lengths as per spec.
// //
const toUserNameBuf = strUtil.stringToNullTermBuffer(message.toUserName, { encoding : 'cp437', maxBufLen : 36 } ); const toUserNameBuf = strUtil.stringToNullTermBuffer(
const fromUserNameBuf = strUtil.stringToNullTermBuffer(message.fromUserName, { encoding : 'cp437', maxBufLen : 36 } ); message.toUserName,
const subjectBuf = strUtil.stringToNullTermBuffer(message.subject, { encoding : 'cp437', maxBufLen : 72 } ); { encoding: 'cp437', maxBufLen: 36 }
);
const fromUserNameBuf = strUtil.stringToNullTermBuffer(
message.fromUserName,
{ encoding: 'cp437', maxBufLen: 36 }
);
const subjectBuf = strUtil.stringToNullTermBuffer(message.subject, {
encoding: 'cp437',
maxBufLen: 72,
});
// //
// message: unbound length, NULL term'd // message: unbound length, NULL term'd
@ -827,20 +916,49 @@ function Packet(options) {
case 'FMPT': case 'FMPT':
case 'TOPT': case 'TOPT':
case 'INTL': case 'INTL':
msgBody += getAppendMeta(`\x01${k}`, message.meta.FtnKludge[k], ''); // no sepChar msgBody += getAppendMeta(
`\x01${k}`,
message.meta.FtnKludge[k],
''
); // no sepChar
break; break;
default: default:
msgBody += getAppendMeta(`\x01${k}`, message.meta.FtnKludge[k]); msgBody += getAppendMeta(
`\x01${k}`,
message.meta.FtnKludge[k]
);
break; break;
} }
}); });
return callback(null, basicHeader, toUserNameBuf, fromUserNameBuf, subjectBuf, msgBody); return callback(
null,
basicHeader,
toUserNameBuf,
fromUserNameBuf,
subjectBuf,
msgBody
);
}, },
function prepareAnsiMessageBody(basicHeader, toUserNameBuf, fromUserNameBuf, subjectBuf, msgBody, callback) { function prepareAnsiMessageBody(
basicHeader,
toUserNameBuf,
fromUserNameBuf,
subjectBuf,
msgBody,
callback
) {
if (!strUtil.isAnsi(message.message)) { if (!strUtil.isAnsi(message.message)) {
return callback(null, basicHeader, toUserNameBuf, fromUserNameBuf, subjectBuf, msgBody, message.message); return callback(
null,
basicHeader,
toUserNameBuf,
fromUserNameBuf,
subjectBuf,
msgBody,
message.message
);
} }
ansiPrep( ansiPrep(
@ -852,11 +970,27 @@ function Packet(options) {
exportMode: true, exportMode: true,
}, },
(err, preppedMsg) => { (err, preppedMsg) => {
return callback(null, basicHeader, toUserNameBuf, fromUserNameBuf, subjectBuf, msgBody, preppedMsg || message.message); return callback(
null,
basicHeader,
toUserNameBuf,
fromUserNameBuf,
subjectBuf,
msgBody,
preppedMsg || message.message
);
} }
); );
}, },
function addMessageBody(basicHeader, toUserNameBuf, fromUserNameBuf, subjectBuf, msgBody, preppedMsg, callback) { function addMessageBody(
basicHeader,
toUserNameBuf,
fromUserNameBuf,
subjectBuf,
msgBody,
preppedMsg,
callback
) {
msgBody += preppedMsg + '\r'; msgBody += preppedMsg + '\r';
// //
@ -878,7 +1012,10 @@ function Packet(options) {
// FTN-0004.001 @ http://ftsc.org/docs/fts-0004.001 // FTN-0004.001 @ http://ftsc.org/docs/fts-0004.001
// SEEN-BY and PATH should be the last lines of a message // SEEN-BY and PATH should be the last lines of a message
// //
msgBody += getAppendMeta('SEEN-BY', message.meta.FtnProperty.ftn_seen_by); // note: no ^A (0x01) msgBody += getAppendMeta(
'SEEN-BY',
message.meta.FtnProperty.ftn_seen_by
); // note: no ^A (0x01)
msgBody += getAppendMeta('\x01PATH', message.meta.FtnKludge['PATH']); msgBody += getAppendMeta('\x01PATH', message.meta.FtnKludge['PATH']);
let msgBodyEncoded; let msgBodyEncoded;
@ -895,10 +1032,10 @@ function Packet(options) {
toUserNameBuf, toUserNameBuf,
fromUserNameBuf, fromUserNameBuf,
subjectBuf, subjectBuf,
msgBodyEncoded msgBodyEncoded,
]) ])
); );
} },
], ],
(err, msgEntryBuffer) => { (err, msgEntryBuffer) => {
return cb(err, msgEntryBuffer); return cb(err, msgEntryBuffer);
@ -959,14 +1096,19 @@ function Packet(options) {
Object.keys(message.meta.FtnKludge).forEach(k => { Object.keys(message.meta.FtnKludge).forEach(k => {
switch (k) { switch (k) {
case 'PATH' : break; // skip & save for last case 'PATH':
break; // skip & save for last
case 'Via': case 'Via':
case 'FMPT': case 'FMPT':
case 'TOPT': case 'TOPT':
case 'INTL' : appendMeta(`\x01${k}`, message.meta.FtnKludge[k], ''); break; // no sepChar case 'INTL':
appendMeta(`\x01${k}`, message.meta.FtnKludge[k], '');
break; // no sepChar
default : appendMeta(`\x01${k}`, message.meta.FtnKludge[k]); break; default:
appendMeta(`\x01${k}`, message.meta.FtnKludge[k]);
break;
} }
}); });
@ -1021,8 +1163,9 @@ function Packet(options) {
header, header,
packetBuffer.slice(FTN_PACKET_HEADER_SIZE), packetBuffer.slice(FTN_PACKET_HEADER_SIZE),
iterator, iterator,
callback); callback
} );
},
], ],
cb // complete cb // complete
); );
@ -1075,7 +1218,7 @@ Packet.prototype.read = function(pathOrBuffer, iterator, cb) {
self.parsePacketBuffer(pathOrBuffer, iterator, err => { self.parsePacketBuffer(pathOrBuffer, iterator, err => {
callback(err); callback(err);
}); });
} },
], ],
err => { err => {
cb(err); cb(err);

View File

@ -93,7 +93,7 @@ function getDateTimeString(m) {
} }
function getMessageSerialNumber(messageId) { function getMessageSerialNumber(messageId) {
const msSinceEnigmaEpoc = (Date.now() - Date.UTC(2016, 1, 1)); const msSinceEnigmaEpoc = Date.now() - Date.UTC(2016, 1, 1);
const hash = Math.abs(new FNV1a(msSinceEnigmaEpoc + messageId).value).toString(16); const hash = Math.abs(new FNV1a(msSinceEnigmaEpoc + messageId).value).toString(16);
return `00000000${hash}`.substr(-8); return `00000000${hash}`.substr(-8);
} }
@ -143,10 +143,13 @@ function getMessageSerialNumber(messageId) {
// //
function getMessageIdentifier(message, address, isNetMail = false) { function getMessageIdentifier(message, address, isNetMail = false) {
const addrStr = new Address(address).toString('5D'); const addrStr = new Address(address).toString('5D');
return isNetMail ? return isNetMail
`${addrStr} ${getMessageSerialNumber(message.messageId)}` : ? `${addrStr} ${getMessageSerialNumber(message.messageId)}`
`${message.messageId}.${message.areaTag.toLowerCase()}@${addrStr} ${getMessageSerialNumber(message.messageId)}` : `${
; message.messageId
}.${message.areaTag.toLowerCase()}@${addrStr} ${getMessageSerialNumber(
message.messageId
)}`;
} }
// //
@ -183,7 +186,10 @@ function getQuotePrefix(name) {
const parts = name.split(' '); const parts = name.split(' ');
if (parts.length > 1) { if (parts.length > 1) {
// First & Last initials - (Bryan Ashby -> BA) // First & Last initials - (Bryan Ashby -> BA)
initials = `${parts[0].slice(0, 1)}${parts[parts.length - 1].slice(0, 1)}`.toUpperCase(); initials = `${parts[0].slice(0, 1)}${parts[parts.length - 1].slice(
0,
1
)}`.toUpperCase();
} else { } else {
// Just use the first two - (NuSkooler -> Nu) // Just use the first two - (NuSkooler -> Nu)
initials = _.capitalize(name.slice(0, 2)); initials = _.capitalize(name.slice(0, 2));
@ -198,9 +204,9 @@ function getQuotePrefix(name) {
// //
function getOrigin(address) { function getOrigin(address) {
const config = Config(); const config = Config();
const origin = _.has(config, 'messageNetworks.originLine') ? const origin = _.has(config, 'messageNetworks.originLine')
config.messageNetworks.originLine : ? config.messageNetworks.originLine
config.general.boardName; : config.general.boardName;
const addrStr = new Address(address).toString('5D'); const addrStr = new Address(address).toString('5D');
return ` * Origin: ${origin} (${addrStr})`; return ` * Origin: ${origin} (${addrStr})`;
@ -208,7 +214,9 @@ function getOrigin(address) {
function getTearLine() { function getTearLine() {
const nodeVer = process.version.substr(1); // remove 'v' prefix const nodeVer = process.version.substr(1); // remove 'v' prefix
return `--- ENiGMA 1/2 v${packageJson.version} (${os.platform()}; ${os.arch()}; ${nodeVer})`; return `--- ENiGMA 1/2 v${
packageJson.version
} (${os.platform()}; ${os.arch()}; ${nodeVer})`;
} }
// //
@ -342,8 +350,9 @@ function getUpdatedPathEntries(existingEntries, localAddress) {
existingEntries = [existingEntries]; existingEntries = [existingEntries];
} }
existingEntries.push(getAbbreviatedNetNodeList( existingEntries.push(
parseAbbreviatedNetNodeList(localAddress))); getAbbreviatedNetNodeList(parseAbbreviatedNetNodeList(localAddress))
);
return existingEntries; return existingEntries;
} }
@ -368,7 +377,6 @@ const ENCODING_TO_FTS_5003_001_CHARS = {
'utf-8': ['UTF-8', 4], 'utf-8': ['UTF-8', 4],
}; };
function getCharacterSetIdentifierByEncoding(encodingName) { function getCharacterSetIdentifierByEncoding(encodingName) {
const value = ENCODING_TO_FTS_5003_001_CHARS[encodingName.toLowerCase()]; const value = ENCODING_TO_FTS_5003_001_CHARS[encodingName.toLowerCase()];
return value ? `${value[0]} ${value[1]}` : encodingName.toUpperCase(); return value ? `${value[0]} ${value[1]}` : encodingName.toUpperCase();
@ -376,31 +384,31 @@ function getCharacterSetIdentifierByEncoding(encodingName) {
const CHRSToEncodingTable = { const CHRSToEncodingTable = {
Level1: { Level1: {
'ASCII' : 'ascii', // ISO-646-1 ASCII: 'ascii', // ISO-646-1
'DUTCH' : 'ascii', // ISO-646 DUTCH: 'ascii', // ISO-646
'FINNISH' : 'ascii', // ISO-646-10 FINNISH: 'ascii', // ISO-646-10
'FRENCH' : 'ascii', // ISO-646 FRENCH: 'ascii', // ISO-646
'CANADIAN' : 'ascii', // ISO-646 CANADIAN: 'ascii', // ISO-646
'GERMAN' : 'ascii', // ISO-646 GERMAN: 'ascii', // ISO-646
'ITALIAN' : 'ascii', // ISO-646 ITALIAN: 'ascii', // ISO-646
'NORWEIG' : 'ascii', // ISO-646 NORWEIG: 'ascii', // ISO-646
'PORTU' : 'ascii', // ISO-646 PORTU: 'ascii', // ISO-646
'SPANISH' : 'iso-656', SPANISH: 'iso-656',
'SWEDISH' : 'ascii', // ISO-646-10 SWEDISH: 'ascii', // ISO-646-10
'SWISS' : 'ascii', // ISO-646 SWISS: 'ascii', // ISO-646
'UK' : 'ascii', // ISO-646 UK: 'ascii', // ISO-646
'ISO-10': 'ascii', // ISO-646-10 'ISO-10': 'ascii', // ISO-646-10
}, },
Level2: { Level2: {
'CP437' : 'cp437', CP437: 'cp437',
'CP850' : 'cp850', CP850: 'cp850',
'CP852' : 'cp852', CP852: 'cp852',
'CP866' : 'cp866', CP866: 'cp866',
'CP848' : 'cp848', CP848: 'cp848',
'CP1250' : 'cp1250', CP1250: 'cp1250',
'CP1251' : 'cp1251', CP1251: 'cp1251',
'CP1252' : 'cp1252', CP1252: 'cp1252',
'CP10000' : 'macroman', CP10000: 'macroman',
'LATIN-1': 'iso-8859-1', 'LATIN-1': 'iso-8859-1',
'LATIN-2': 'iso-8859-2', 'LATIN-2': 'iso-8859-2',
'LATIN-5': 'iso-8859-9', 'LATIN-5': 'iso-8859-9',
@ -412,11 +420,11 @@ const CHRSToEncodingTable = {
}, },
DeprecatedMisc: { DeprecatedMisc: {
'IBMPC' : 'cp1250', // :TODO: validate IBMPC: 'cp1250', // :TODO: validate
'+7_FIDO': 'cp866', '+7_FIDO': 'cp866',
'+7': 'cp866', '+7': 'cp866',
'MAC' : 'macroman', // :TODO: validate MAC: 'macroman', // :TODO: validate
} },
}; };
// Given 1:N CHRS kludge IDs, try to pick the best encoding we can // Given 1:N CHRS kludge IDs, try to pick the best encoding we can

View File

@ -18,10 +18,8 @@ function FullMenuView(options) {
options.cursor = options.cursor || 'hide'; options.cursor = options.cursor || 'hide';
options.justify = options.justify || 'left'; options.justify = options.justify || 'left';
MenuView.call(this, options); MenuView.call(this, options);
// Initialize paging // Initialize paging
this.pages = []; this.pages = [];
this.currentPage = 0; this.currentPage = 0;
@ -38,8 +36,12 @@ function FullMenuView(options) {
this.autoAdjustHeightIfEnabled = () => { this.autoAdjustHeightIfEnabled = () => {
if (this.autoAdjustHeight) { if (this.autoAdjustHeight) {
this.dimens.height = (this.items.length * (this.itemSpacing + 1)) - (this.itemSpacing); this.dimens.height =
this.dimens.height = Math.min(this.dimens.height, this.client.term.termHeight - this.position.row); this.items.length * (this.itemSpacing + 1) - this.itemSpacing;
this.dimens.height = Math.min(
this.dimens.height,
this.client.term.termHeight - this.position.row
);
} }
this.positionCacheExpired = true; this.positionCacheExpired = true;
@ -58,16 +60,20 @@ function FullMenuView(options) {
for (let i = 0; i < this.dimens.height; i++) { for (let i = 0; i < this.dimens.height; i++) {
const text = `${strUtil.pad(this.fillChar, width, this.fillChar, 'left')}`; const text = `${strUtil.pad(this.fillChar, width, this.fillChar, 'left')}`;
this.client.term.write(`${ansi.goto(this.position.row + i, this.position.col)}${this.getSGR()}${text}`); this.client.term.write(
} `${ansi.goto(
this.position.row + i,
this.position.col
)}${this.getSGR()}${text}`
);
} }
};
this.cachePositions = () => { this.cachePositions = () => {
if (this.positionCacheExpired) { if (this.positionCacheExpired) {
// first, clear the page // first, clear the page
this.clearPage(); this.clearPage();
this.autoAdjustHeightIfEnabled(); this.autoAdjustHeightIfEnabled();
this.pages = []; // reset this.pages = []; // reset
@ -75,7 +81,7 @@ function FullMenuView(options) {
// Calculate number of items visible per column // Calculate number of items visible per column
this.itemsPerRow = Math.floor(this.dimens.height / (this.itemSpacing + 1)); this.itemsPerRow = Math.floor(this.dimens.height / (this.itemSpacing + 1));
// handle case where one can fit at the end // handle case where one can fit at the end
if (this.dimens.height > (this.itemsPerRow * (this.itemSpacing + 1))) { if (this.dimens.height > this.itemsPerRow * (this.itemSpacing + 1)) {
this.itemsPerRow++; this.itemsPerRow++;
} }
@ -122,10 +128,9 @@ function FullMenuView(options) {
this.items[i - j].fixedLength = maxLength; this.items[i - j].fixedLength = maxLength;
} }
// Check if we have room for this column // Check if we have room for this column
// skip for column 0, we need at least one // skip for column 0, we need at least one
if (itemInCol != 0 && (col + maxLength > this.dimens.width)) { if (itemInCol != 0 && col + maxLength > this.dimens.width) {
// save previous page // save previous page
this.pages.push({ start: pageStart, end: i - itemInRow }); this.pages.push({ start: pageStart, end: i - itemInRow });
@ -137,12 +142,10 @@ function FullMenuView(options) {
this.items[i - j].col = this.position.col; this.items[i - j].col = this.position.col;
pageStart = i - j; pageStart = i - j;
} }
} }
// Since this is the last page, save the current page as well // Since this is the last page, save the current page as well
this.pages.push({ start: pageStart, end: i }); this.pages.push({ start: pageStart, end: i });
} }
// also handle going to next column // also handle going to next column
else if (itemInRow == this.itemsPerRow) { else if (itemInRow == this.itemsPerRow) {
@ -166,7 +169,7 @@ function FullMenuView(options) {
// Check if we have room for this column in the current page // Check if we have room for this column in the current page
// skip for first column, we need at least one // skip for first column, we need at least one
if (itemInCol != 0 && (col + maxLength > this.dimens.width)) { if (itemInCol != 0 && col + maxLength > this.dimens.width) {
// save previous page // save previous page
this.pages.push({ start: pageStart, end: i - this.itemsPerRow }); this.pages.push({ start: pageStart, end: i - this.itemsPerRow });
@ -181,7 +184,6 @@ function FullMenuView(options) {
for (let j = 0; j < this.itemsPerRow; j++) { for (let j = 0; j < this.itemsPerRow; j++) {
this.items[i - j].col = col; this.items[i - j].col = col;
} }
} }
// increment the column // increment the column
@ -189,7 +191,6 @@ function FullMenuView(options) {
itemInCol++; itemInCol++;
} }
// Set the current page if the current item is focused. // Set the current page if the current item is focused.
if (this.focusedItemIndex === i) { if (this.focusedItemIndex === i) {
this.currentPage = this.pages.length; this.currentPage = this.pages.length;
@ -200,7 +201,7 @@ function FullMenuView(options) {
this.positionCacheExpired = false; this.positionCacheExpired = false;
}; };
this.drawItem = (index) => { this.drawItem = index => {
const item = this.items[index]; const item = this.items[index];
if (!item) { if (!item) {
return; return;
@ -218,21 +219,45 @@ function FullMenuView(options) {
text = focusItem ? focusItem.text : item.text; text = focusItem ? focusItem.text : item.text;
sgr = ''; sgr = '';
} else if (this.complexItems) { } else if (this.complexItems) {
text = pipeToAnsi(formatString(item.focused && this.focusItemFormat ? this.focusItemFormat : this.itemFormat, item)); text = pipeToAnsi(
sgr = this.focusItemFormat ? '' : (index === this.focusedItemIndex ? this.getFocusSGR() : this.getSGR()); formatString(
item.focused && this.focusItemFormat
? this.focusItemFormat
: this.itemFormat,
item
)
);
sgr = this.focusItemFormat
? ''
: index === this.focusedItemIndex
? this.getFocusSGR()
: this.getSGR();
} else { } else {
text = strUtil.stylizeString(item.text, item.focused ? this.focusTextStyle : this.textStyle); text = strUtil.stylizeString(
sgr = (index === this.focusedItemIndex ? this.getFocusSGR() : this.getSGR()); item.text,
item.focused ? this.focusTextStyle : this.textStyle
);
sgr = index === this.focusedItemIndex ? this.getFocusSGR() : this.getSGR();
} }
let renderLength = strUtil.renderStringLength(text); let renderLength = strUtil.renderStringLength(text);
if (this.hasTextOverflow() && (item.col + renderLength) > this.dimens.width) { if (this.hasTextOverflow() && item.col + renderLength > this.dimens.width) {
text = strUtil.renderSubstr(text, 0, this.dimens.width - (item.col + this.textOverflow.length)) + this.textOverflow; text =
strUtil.renderSubstr(
text,
0,
this.dimens.width - (item.col + this.textOverflow.length)
) + this.textOverflow;
} }
let padLength = Math.min(item.fixedLength + 1, this.dimens.width); let padLength = Math.min(item.fixedLength + 1, this.dimens.width);
text = `${sgr}${strUtil.pad(text, padLength, this.fillChar, this.justify)}${this.getSGR()}`; text = `${sgr}${strUtil.pad(
text,
padLength,
this.fillChar,
this.justify
)}${this.getSGR()}`;
this.client.term.write(`${ansi.goto(item.row, item.col)}${text}`); this.client.term.write(`${ansi.goto(item.row, item.col)}${text}`);
this.setRenderCacheItem(index, text, item.focused); this.setRenderCacheItem(index, text, item.focused);
}; };
@ -246,7 +271,11 @@ FullMenuView.prototype.redraw = function() {
this.cachePositions(); this.cachePositions();
if (this.items.length) { if (this.items.length) {
for (let i = this.pages[this.currentPage].start; i <= this.pages[this.currentPage].end; ++i) { for (
let i = this.pages[this.currentPage].start;
i <= this.pages[this.currentPage].end;
++i
) {
this.items[i].focused = this.focusedItemIndex === i; this.items[i].focused = this.focusedItemIndex === i;
this.drawItem(i); this.drawItem(i);
} }
@ -274,8 +303,7 @@ FullMenuView.prototype.setTextOverflow = function(overflow) {
FullMenuView.super_.prototype.setTextOverflow.call(this, overflow); FullMenuView.super_.prototype.setTextOverflow.call(this, overflow);
this.positionCacheExpired = true; this.positionCacheExpired = true;
};
}
FullMenuView.prototype.setPosition = function (pos) { FullMenuView.prototype.setPosition = function (pos) {
FullMenuView.super_.prototype.setPosition.call(this, pos); FullMenuView.super_.prototype.setPosition.call(this, pos);
@ -349,8 +377,7 @@ FullMenuView.prototype.focusNext = function() {
this.clearPage(); this.clearPage();
this.focusedItemIndex = 0; this.focusedItemIndex = 0;
this.currentPage = 0; this.currentPage = 0;
} } else {
else {
this.focusedItemIndex++; this.focusedItemIndex++;
if (this.focusedItemIndex > this.pages[this.currentPage].end) { if (this.focusedItemIndex > this.pages[this.currentPage].end) {
this.clearPage(); this.clearPage();
@ -368,8 +395,7 @@ FullMenuView.prototype.focusPrevious = function() {
this.clearPage(); this.clearPage();
this.focusedItemIndex = this.items.length - 1; this.focusedItemIndex = this.items.length - 1;
this.currentPage = this.pages.length - 1; this.currentPage = this.pages.length - 1;
} } else {
else {
this.focusedItemIndex--; this.focusedItemIndex--;
if (this.focusedItemIndex < this.pages[this.currentPage].start) { if (this.focusedItemIndex < this.pages[this.currentPage].start) {
this.clearPage(); this.clearPage();
@ -383,7 +409,6 @@ FullMenuView.prototype.focusPrevious = function() {
}; };
FullMenuView.prototype.focusPreviousColumn = function () { FullMenuView.prototype.focusPreviousColumn = function () {
const currentRow = this.items[this.focusedItemIndex].itemInRow; const currentRow = this.items[this.focusedItemIndex].itemInRow;
this.focusedItemIndex = this.focusedItemIndex - this.itemsPerRow; this.focusedItemIndex = this.focusedItemIndex - this.itemsPerRow;
if (this.focusedItemIndex < 0) { if (this.focusedItemIndex < 0) {
@ -391,15 +416,13 @@ FullMenuView.prototype.focusPreviousColumn = function() {
const lastItemRow = this.items[this.items.length - 1].itemInRow; const lastItemRow = this.items[this.items.length - 1].itemInRow;
if (lastItemRow > currentRow) { if (lastItemRow > currentRow) {
this.focusedItemIndex = this.items.length - (lastItemRow - currentRow) - 1; this.focusedItemIndex = this.items.length - (lastItemRow - currentRow) - 1;
} } else {
else {
// can't go to same column, so go to last item // can't go to same column, so go to last item
this.focusedItemIndex = this.items.length - 1; this.focusedItemIndex = this.items.length - 1;
} }
// set to last page // set to last page
this.currentPage = this.pages.length - 1; this.currentPage = this.pages.length - 1;
} } else {
else {
if (this.focusedItemIndex < this.pages[this.currentPage].start) { if (this.focusedItemIndex < this.pages[this.currentPage].start) {
this.clearPage(); this.clearPage();
this.currentPage--; this.currentPage--;
@ -413,15 +436,13 @@ FullMenuView.prototype.focusPreviousColumn = function() {
}; };
FullMenuView.prototype.focusNextColumn = function () { FullMenuView.prototype.focusNextColumn = function () {
const currentRow = this.items[this.focusedItemIndex].itemInRow; const currentRow = this.items[this.focusedItemIndex].itemInRow;
this.focusedItemIndex = this.focusedItemIndex + this.itemsPerRow; this.focusedItemIndex = this.focusedItemIndex + this.itemsPerRow;
if (this.focusedItemIndex > this.items.length - 1) { if (this.focusedItemIndex > this.items.length - 1) {
this.focusedItemIndex = currentRow - 1; this.focusedItemIndex = currentRow - 1;
this.currentPage = 0; this.currentPage = 0;
this.clearPage(); this.clearPage();
} } else if (this.focusedItemIndex > this.pages[this.currentPage].end) {
else if (this.focusedItemIndex > this.pages[this.currentPage].end) {
this.clearPage(); this.clearPage();
this.currentPage++; this.currentPage++;
} }
@ -433,7 +454,6 @@ FullMenuView.prototype.focusNextColumn = function() {
}; };
FullMenuView.prototype.focusPreviousPageItem = function () { FullMenuView.prototype.focusPreviousPageItem = function () {
// handle first page // handle first page
if (this.currentPage == 0) { if (this.currentPage == 0) {
// Do nothing, page up shouldn't go down on last page // Do nothing, page up shouldn't go down on last page
@ -450,7 +470,6 @@ FullMenuView.prototype.focusPreviousPageItem = function() {
}; };
FullMenuView.prototype.focusNextPageItem = function () { FullMenuView.prototype.focusNextPageItem = function () {
// handle last page // handle last page
if (this.currentPage == this.pages.length - 1) { if (this.currentPage == this.pages.length - 1) {
// Do nothing, page up shouldn't go down on last page // Do nothing, page up shouldn't go down on last page
@ -467,7 +486,6 @@ FullMenuView.prototype.focusNextPageItem = function() {
}; };
FullMenuView.prototype.focusFirst = function () { FullMenuView.prototype.focusFirst = function () {
this.currentPage = 0; this.currentPage = 0;
this.focusedItemIndex = 0; this.focusedItemIndex = 0;
this.clearPage(); this.clearPage();
@ -477,7 +495,6 @@ FullMenuView.prototype.focusFirst = function() {
}; };
FullMenuView.prototype.focusLast = function () { FullMenuView.prototype.focusLast = function () {
this.currentPage = this.pages.length - 1; this.currentPage = this.pages.length - 1;
this.focusedItemIndex = this.pages[this.currentPage].end; this.focusedItemIndex = this.pages[this.currentPage].end;
this.clearPage(); this.clearPage();
@ -503,7 +520,6 @@ FullMenuView.prototype.setJustify = function(justify) {
this.positionCacheExpired = true; this.positionCacheExpired = true;
}; };
FullMenuView.prototype.setItemHorizSpacing = function (itemHorizSpacing) { FullMenuView.prototype.setItemHorizSpacing = function (itemHorizSpacing) {
FullMenuView.super_.prototype.setItemHorizSpacing.call(this, itemHorizSpacing); FullMenuView.super_.prototype.setItemHorizSpacing.call(this, itemHorizSpacing);

View File

@ -60,17 +60,36 @@ function HorizontalMenuView(options) {
text = focusItem ? focusItem.text : item.text; text = focusItem ? focusItem.text : item.text;
sgr = ''; sgr = '';
} else if (this.complexItems) { } else if (this.complexItems) {
text = pipeToAnsi(formatString(item.focused && this.focusItemFormat ? this.focusItemFormat : this.itemFormat, item)); text = pipeToAnsi(
sgr = this.focusItemFormat ? '' : (index === self.focusedItemIndex ? self.getFocusSGR() : self.getSGR()); formatString(
item.focused && this.focusItemFormat
? this.focusItemFormat
: this.itemFormat,
item
)
);
sgr = this.focusItemFormat
? ''
: index === self.focusedItemIndex
? self.getFocusSGR()
: self.getSGR();
} else { } else {
text = strUtil.stylizeString(item.text, item.focused ? self.focusTextStyle : self.textStyle); text = strUtil.stylizeString(
sgr = (index === self.focusedItemIndex ? self.getFocusSGR() : self.getSGR()); item.text,
item.focused ? self.focusTextStyle : self.textStyle
);
sgr = index === self.focusedItemIndex ? self.getFocusSGR() : self.getSGR();
} }
const drawWidth = strUtil.renderStringLength(text) + (self.getSpacer().length * 2); const drawWidth = strUtil.renderStringLength(text) + self.getSpacer().length * 2;
self.client.term.write( self.client.term.write(
`${goto(self.position.row, item.col)}${sgr}${strUtil.pad(text, drawWidth, self.fillChar, 'center')}` `${goto(self.position.row, item.col)}${sgr}${strUtil.pad(
text,
drawWidth,
self.fillChar,
'center'
)}`
); );
}; };
} }
@ -126,7 +145,6 @@ HorizontalMenuView.prototype.focusNext = function() {
}; };
HorizontalMenuView.prototype.focusPrevious = function () { HorizontalMenuView.prototype.focusPrevious = function () {
if (0 === this.focusedItemIndex) { if (0 === this.focusedItemIndex) {
this.focusedItemIndex = this.items.length - 1; this.focusedItemIndex = this.items.length - 1;
} else { } else {

View File

@ -34,7 +34,11 @@ module.exports = class KeyEntryView extends View {
ch = ch.toUpperCase(); ch = ch.toUpperCase();
} }
if(drawKey && isPrintable(drawKey) && (!this.keys || this.keys.indexOf(ch) > -1)) { if (
drawKey &&
isPrintable(drawKey) &&
(!this.keys || this.keys.indexOf(ch) > -1)
) {
this.redraw(); // sets position this.redraw(); // sets position
this.client.term.write(stylizeString(ch, this.textStyle)); this.client.term.write(stylizeString(ch, this.textStyle));
} }
@ -73,5 +77,7 @@ module.exports = class KeyEntryView extends View {
super.setPropertyValue(propName, propValue); super.setPropertyValue(propName, propValue);
} }
getData() { return this.keyEntered; } getData() {
return this.keyEntered;
}
}; };

View File

@ -19,7 +19,7 @@ exports.moduleInfo = {
name: 'Last Callers', name: 'Last Callers',
desc: 'Last callers to the system', desc: 'Last callers to the system',
author: 'NuSkooler', author: 'NuSkooler',
packageName : 'codes.l33t.enigma.lastcallers' packageName: 'codes.l33t.enigma.lastcallers',
}; };
const MciViewIds = { const MciViewIds = {
@ -31,7 +31,11 @@ exports.getModule = class LastCallersModule extends MenuModule {
super(options); super(options);
this.actionIndicators = _.get(options, 'menuConfig.config.actionIndicators', {}); this.actionIndicators = _.get(options, 'menuConfig.config.actionIndicators', {});
this.actionIndicatorDefault = _.get(options, 'menuConfig.config.actionIndicatorDefault', '-'); this.actionIndicatorDefault = _.get(
options,
'menuConfig.config.actionIndicatorDefault',
'-'
);
} }
mciReady(mciData, cb) { mciReady(mciData, cb) {
@ -42,34 +46,46 @@ exports.getModule = class LastCallersModule extends MenuModule {
async.waterfall( async.waterfall(
[ [
(callback) => { callback => {
this.prepViewController('callers', 0, mciData.menu, err => { this.prepViewController('callers', 0, mciData.menu, err => {
return callback(err); return callback(err);
}); });
}, },
(callback) => { callback => {
this.fetchHistory((err, loginHistory) => { this.fetchHistory((err, loginHistory) => {
return callback(err, loginHistory); return callback(err, loginHistory);
}); });
}, },
(loginHistory, callback) => { (loginHistory, callback) => {
this.loadUserForHistoryItems(loginHistory, (err, updatedHistory) => { this.loadUserForHistoryItems(
loginHistory,
(err, updatedHistory) => {
return callback(err, updatedHistory); return callback(err, updatedHistory);
}); }
);
}, },
(loginHistory, callback) => { (loginHistory, callback) => {
const callersView = this.viewControllers.callers.getView(MciViewIds.callerList); const callersView = this.viewControllers.callers.getView(
MciViewIds.callerList
);
if (!callersView) { if (!callersView) {
return cb(Errors.MissingMci(`Missing caller list MCI ${MciViewIds.callerList}`)); return cb(
Errors.MissingMci(
`Missing caller list MCI ${MciViewIds.callerList}`
)
);
} }
callersView.setItems(loginHistory); callersView.setItems(loginHistory);
callersView.redraw(); callersView.redraw();
return callback(null); return callback(null);
} },
], ],
err => { err => {
if (err) { if (err) {
this.client.log.warn( { error : err.message }, 'Error loading last callers'); this.client.log.warn(
{ error: err.message },
'Error loading last callers'
);
} }
return cb(err); return cb(err);
} }
@ -79,7 +95,9 @@ exports.getModule = class LastCallersModule extends MenuModule {
getCollapse(conf) { getCollapse(conf) {
let collapse = _.get(this, conf); let collapse = _.get(this, conf);
collapse = collapse && collapse.match(/^([0-9]+)\s*(minutes?|seconds?|hours?|days?|months?)$/); collapse =
collapse &&
collapse.match(/^([0-9]+)\s*(minutes?|seconds?|hours?|days?|months?)$/);
if (collapse) { if (collapse) {
return moment.duration(parseInt(collapse[1]), collapse[2]); return moment.duration(parseInt(collapse[1]), collapse[2]);
} }
@ -101,7 +119,10 @@ exports.getModule = class LastCallersModule extends MenuModule {
} }
const dateTimeFormat = _.get( const dateTimeFormat = _.get(
this, 'menuConfig.config.dateTimeFormat', this.client.currentTheme.helpers.getDateFormat('short')); this,
'menuConfig.config.dateTimeFormat',
this.client.currentTheme.helpers.getDateFormat('short')
);
loginHistory = loginHistory.map(item => { loginHistory = loginHistory.map(item => {
try { try {
@ -119,25 +140,29 @@ exports.getModule = class LastCallersModule extends MenuModule {
item.timestamp = moment(item.timestamp); item.timestamp = moment(item.timestamp);
return Object.assign( return Object.assign(item, {
item, ts: moment(item.timestamp).format(dateTimeFormat),
{ });
ts : moment(item.timestamp).format(dateTimeFormat)
}
);
}); });
const hideSysOp = _.get(this, 'menuConfig.config.sysop.hide'); const hideSysOp = _.get(this, 'menuConfig.config.sysop.hide');
const sysOpCollapse = this.getCollapse('menuConfig.config.sysop.collapse'); const sysOpCollapse = this.getCollapse(
'menuConfig.config.sysop.collapse'
);
const collapseList = (withUserId, minAge) => { const collapseList = (withUserId, minAge) => {
let lastUserId; let lastUserId;
let lastTimestamp; let lastTimestamp;
loginHistory = loginHistory.filter(item => { loginHistory = loginHistory.filter(item => {
const secApart = lastTimestamp ? moment.duration(lastTimestamp.diff(item.timestamp)).asSeconds() : 0; const secApart = lastTimestamp
const collapse = (null === withUserId ? true : withUserId === item.userId) && ? moment
(lastUserId === item.userId) && .duration(lastTimestamp.diff(item.timestamp))
(secApart < minAge); .asSeconds()
: 0;
const collapse =
(null === withUserId ? true : withUserId === item.userId) &&
lastUserId === item.userId &&
secApart < minAge;
lastUserId = item.userId; lastUserId = item.userId;
lastTimestamp = item.timestamp; lastTimestamp = item.timestamp;
@ -147,7 +172,9 @@ exports.getModule = class LastCallersModule extends MenuModule {
}; };
if (hideSysOp) { if (hideSysOp) {
loginHistory = loginHistory.filter(item => false === User.isRootUserId(item.userId)); loginHistory = loginHistory.filter(
item => false === User.isRootUserId(item.userId)
);
} else if (sysOpCollapse) { } else if (sysOpCollapse) {
collapseList(User.RootUserID, sysOpCollapse.asSeconds()); collapseList(User.RootUserID, sysOpCollapse.asSeconds());
} }
@ -167,18 +194,22 @@ exports.getModule = class LastCallersModule extends MenuModule {
loadUserForHistoryItems(loginHistory, cb) { loadUserForHistoryItems(loginHistory, cb) {
const getPropOpts = { const getPropOpts = {
names : [ UserProps.RealName, UserProps.Location, UserProps.Affiliations ] names: [UserProps.RealName, UserProps.Location, UserProps.Affiliations],
}; };
const actionIndicatorNames = _.map(this.actionIndicators, (v, k) => k); const actionIndicatorNames = _.map(this.actionIndicators, (v, k) => k);
let indicatorSumsSql; let indicatorSumsSql;
if (actionIndicatorNames.length > 0) { if (actionIndicatorNames.length > 0) {
indicatorSumsSql = actionIndicatorNames.map(i => { indicatorSumsSql = actionIndicatorNames.map(i => {
return `SUM(CASE WHEN log_name='${_.snakeCase(i)}' THEN 1 ELSE 0 END) AS ${i}`; return `SUM(CASE WHEN log_name='${_.snakeCase(
i
)}' THEN 1 ELSE 0 END) AS ${i}`;
}); });
} }
async.map(loginHistory, (item, nextHistoryItem) => { async.map(
loginHistory,
(item, nextHistoryItem) => {
User.getUserName(item.userId, (err, userName) => { User.getUserName(item.userId, (err, userName) => {
if (err) { if (err) {
return nextHistoryItem(null, null); return nextHistoryItem(null, null);
@ -188,7 +219,8 @@ exports.getModule = class LastCallersModule extends MenuModule {
User.loadProperties(item.userId, getPropOpts, (err, props) => { User.loadProperties(item.userId, getPropOpts, (err, props) => {
item.location = (props && props[UserProps.Location]) || ''; item.location = (props && props[UserProps.Location]) || '';
item.affiliation = item.affils = (props && props[UserProps.Affiliations]) || ''; item.affiliation = item.affils =
(props && props[UserProps.Affiliations]) || '';
item.realName = (props && props[UserProps.RealName]) || ''; item.realName = (props && props[UserProps.RealName]) || '';
if (!indicatorSumsSql) { if (!indicatorSumsSql) {
@ -205,7 +237,11 @@ exports.getModule = class LastCallersModule extends MenuModule {
if (_.isObject(results)) { if (_.isObject(results)) {
item.actions = ''; item.actions = '';
Object.keys(results).forEach(n => { Object.keys(results).forEach(n => {
const indicator = results[n] > 0 ? this.actionIndicators[n] || this.actionIndicatorDefault : this.actionIndicatorDefault; const indicator =
results[n] > 0
? this.actionIndicators[n] ||
this.actionIndicatorDefault
: this.actionIndicatorDefault;
item[n] = indicator; item[n] = indicator;
item.actions += indicator; item.actions += indicator;
}); });
@ -217,7 +253,11 @@ exports.getModule = class LastCallersModule extends MenuModule {
}); });
}, },
(err, mapped) => { (err, mapped) => {
return cb(err, mapped.filter(item => item)); // remove deleted return cb(
}); err,
mapped.filter(item => item)
); // remove deleted
}
);
} }
}; };

View File

@ -28,8 +28,12 @@ function getServer(packageName) {
function startListening(cb) { function startListening(cb) {
const moduleUtil = require('./module_util.js'); // late load so we get Config const moduleUtil = require('./module_util.js'); // late load so we get Config
async.each( [ 'login', 'content', 'chat' ], (category, next) => { async.each(
moduleUtil.loadModulesForCategory(`${category}Servers`, (module, nextModule) => { ['login', 'content', 'chat'],
(category, next) => {
moduleUtil.loadModulesForCategory(
`${category}Servers`,
(module, nextModule) => {
const moduleInst = new module.getModule(); const moduleInst = new module.getModule();
try { try {
moduleInst.createServer(err => { moduleInst.createServer(err => {
@ -54,10 +58,14 @@ function startListening(cb) {
logger.log.error(e, 'Exception caught creating server!'); logger.log.error(e, 'Exception caught creating server!');
return nextModule(e); return nextModule(e);
} }
}, err => { },
err => {
return next(err); return next(err);
}); }
}, err => { );
return cb(err); },
}); err => {
return cb(err);
}
);
} }

View File

@ -8,7 +8,6 @@ const fs = require('graceful-fs');
const _ = require('lodash'); const _ = require('lodash');
module.exports = class Log { module.exports = class Log {
static init() { static init() {
const Config = require('./config.js').get(); const Config = require('./config.js').get();
const logPath = Config.paths.logs; const logPath = Config.paths.logs;
@ -21,7 +20,10 @@ module.exports = class Log {
const logStreams = []; const logStreams = [];
if (_.isObject(Config.logging.rotatingFile)) { if (_.isObject(Config.logging.rotatingFile)) {
Config.logging.rotatingFile.path = paths.join(logPath, Config.logging.rotatingFile.fileName); Config.logging.rotatingFile.path = paths.join(
logPath,
Config.logging.rotatingFile.fileName
);
logStreams.push(Config.logging.rotatingFile); logStreams.push(Config.logging.rotatingFile);
} }
@ -31,7 +33,7 @@ module.exports = class Log {
// try to remove sensitive info by default, e.g. 'password' fields // try to remove sensitive info by default, e.g. 'password' fields
['formData', 'formValue'].forEach(keyName => { ['formData', 'formValue'].forEach(keyName => {
serializers[keyName] = (fd) => Log.hideSensitive(fd); serializers[keyName] = fd => Log.hideSensitive(fd);
}); });
this.log = bunyan.createLogger({ this.log = bunyan.createLogger({
@ -62,9 +64,12 @@ module.exports = class Log {
// Use a regexp -- we don't know how nested fields we want to seek and destroy may be // Use a regexp -- we don't know how nested fields we want to seek and destroy may be
// //
return JSON.parse( return JSON.parse(
JSON.stringify(obj).replace(/"(password|passwordConfirm|key|authCode)"\s?:\s?"([^"]+)"/, (match, valueName) => { JSON.stringify(obj).replace(
/"(password|passwordConfirm|key|authCode)"\s?:\s?"([^"]+)"/,
(match, valueName) => {
return `"${valueName}":"********"`; return `"${valueName}":"********"`;
}) }
)
); );
} catch (e) { } catch (e) {
// be safe and return empty obj! // be safe and return empty obj!

View File

@ -53,12 +53,13 @@ module.exports = class LoginServerModule extends ServerModule {
} }
client.session.serverName = modInfo.name; client.session.serverName = modInfo.name;
client.session.isSecure = _.isBoolean(client.isSecure) ? client.isSecure : (modInfo.isSecure || false); client.session.isSecure = _.isBoolean(client.isSecure)
? client.isSecure
: modInfo.isSecure || false;
clientConns.addNewClient(client, clientSock); clientConns.addNewClient(client, clientSock);
client.on('ready', readyOptions => { client.on('ready', readyOptions => {
client.startIdleMonitor(); client.startIdleMonitor();
// Go to module -- use default error handler // Go to module -- use default error handler
@ -72,7 +73,10 @@ module.exports = class LoginServerModule extends ServerModule {
}); });
client.on('error', err => { client.on('error', err => {
logger.log.info({ nodeId : client.node, error : err.message }, 'Connection error'); logger.log.info(
{ nodeId: client.node, error: err.message },
'Connection error'
);
}); });
client.on('close', err => { client.on('close', err => {

View File

@ -6,7 +6,8 @@ const Message = require('./message.js');
exports.getAddressedToInfo = getAddressedToInfo; exports.getAddressedToInfo = getAddressedToInfo;
const EMAIL_REGEX = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; const EMAIL_REGEX =
/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
/* /*
Input Output Input Output
@ -44,7 +45,11 @@ function getAddressedToInfo(input) {
addr = Address.fromString(input.slice(lessThanPos + 1, greaterThanPos)); addr = Address.fromString(input.slice(lessThanPos + 1, greaterThanPos));
if (Address.isValidAddress(addr)) { if (Address.isValidAddress(addr)) {
return { name : input.slice(0, lessThanPos).trim(), flavor : Message.AddressFlavor.FTN, remote : addr.toString() }; return {
name: input.slice(0, lessThanPos).trim(),
flavor: Message.AddressFlavor.FTN,
remote: addr.toString(),
};
} }
return { name: input, flavor: Message.AddressFlavor.Local }; return { name: input, flavor: Message.AddressFlavor.Local };
@ -56,7 +61,11 @@ function getAddressedToInfo(input) {
const addr = input.slice(lessThanPos + 1, greaterThanPos); const addr = input.slice(lessThanPos + 1, greaterThanPos);
const m = addr.match(EMAIL_REGEX); const m = addr.match(EMAIL_REGEX);
if (m) { if (m) {
return { name : input.slice(0, lessThanPos).trim(), flavor : Message.AddressFlavor.Email, remote : addr }; return {
name: input.slice(0, lessThanPos).trim(),
flavor: Message.AddressFlavor.Email,
remote: addr,
};
} }
return { name: input, flavor: Message.AddressFlavor.Local }; return { name: input, flavor: Message.AddressFlavor.Local };
@ -64,7 +73,11 @@ function getAddressedToInfo(input) {
let m = input.match(EMAIL_REGEX); let m = input.match(EMAIL_REGEX);
if (m) { if (m) {
return { name : input.slice(0, firstAtPos), flavor : Message.AddressFlavor.Email, remote : input }; return {
name: input.slice(0, firstAtPos),
flavor: Message.AddressFlavor.Email,
remote: input,
};
} }
let addr = Address.fromString(input); // 5D? let addr = Address.fromString(input); // 5D?
@ -74,7 +87,11 @@ function getAddressedToInfo(input) {
addr = Address.fromString(input.slice(firstAtPos + 1).trim()); addr = Address.fromString(input.slice(firstAtPos + 1).trim());
if (Address.isValidAddress(addr)) { if (Address.isValidAddress(addr)) {
return { name : input.slice(0, firstAtPos).trim(), flavor : Message.AddressFlavor.FTN, remote : addr.toString() }; return {
name: input.slice(0, firstAtPos).trim(),
flavor: Message.AddressFlavor.FTN,
remote: addr.toString(),
};
} }
return { name: input, flavor: Message.AddressFlavor.Local }; return { name: input, flavor: Message.AddressFlavor.Local };

View File

@ -47,11 +47,16 @@ function MaskEditTextView(options) {
this.clientBackspace = function () { this.clientBackspace = function () {
var fillCharSGR = this.getStyleSGR(3) || this.getSGR(); var fillCharSGR = this.getStyleSGR(3) || this.getSGR();
this.client.term.write('\b' + fillCharSGR + this.fillChar + '\b' + this.getFocusSGR()); this.client.term.write(
'\b' + fillCharSGR + this.fillChar + '\b' + this.getFocusSGR()
);
}; };
this.drawText = function (s) { this.drawText = function (s) {
var textToDraw = strUtil.stylizeString(s, this.hasFocus ? this.focusTextStyle : this.textStyle); var textToDraw = strUtil.stylizeString(
s,
this.hasFocus ? this.focusTextStyle : this.textStyle
);
assert(textToDraw.length <= self.patternArray.length); assert(textToDraw.length <= self.patternArray.length);
@ -61,13 +66,18 @@ function MaskEditTextView(options) {
while (i < self.patternArray.length) { while (i < self.patternArray.length) {
if (_.isRegExp(self.patternArray[i])) { if (_.isRegExp(self.patternArray[i])) {
if (t < textToDraw.length) { if (t < textToDraw.length) {
self.client.term.write((self.hasFocus ? self.getFocusSGR() : self.getSGR()) + textToDraw[t]); self.client.term.write(
(self.hasFocus ? self.getFocusSGR() : self.getSGR()) +
textToDraw[t]
);
t++; t++;
} else { } else {
self.client.term.write((self.getStyleSGR(3) || '') + self.fillChar); self.client.term.write((self.getStyleSGR(3) || '') + self.fillChar);
} }
} else { } else {
var styleSgr = this.hasFocus ? (self.getStyleSGR(2) || '') : (self.getStyleSGR(1) || ''); var styleSgr = this.hasFocus
? self.getStyleSGR(2) || ''
: self.getStyleSGR(1) || '';
self.client.term.write(styleSgr + self.maskPattern[i]); self.client.term.write(styleSgr + self.maskPattern[i]);
} }
i++; i++;
@ -81,7 +91,9 @@ function MaskEditTextView(options) {
for (var i = 0; i < self.maskPattern.length; i++) { for (var i = 0; i < self.maskPattern.length; i++) {
// :TODO: support escaped characters, e.g. \#. Also allow \\ for a '\' mark! // :TODO: support escaped characters, e.g. \#. Also allow \\ for a '\' mark!
if (self.maskPattern[i] in MaskEditTextView.maskPatternCharacterRegEx) { if (self.maskPattern[i] in MaskEditTextView.maskPatternCharacterRegEx) {
self.patternArray.push(MaskEditTextView.maskPatternCharacterRegEx[self.maskPattern[i]]); self.patternArray.push(
MaskEditTextView.maskPatternCharacterRegEx[self.maskPattern[i]]
);
++self.maxLength; ++self.maxLength;
} else { } else {
self.patternArray.push(self.maskPattern[i]); self.patternArray.push(self.maskPattern[i]);
@ -94,14 +106,13 @@ function MaskEditTextView(options) {
}; };
this.buildPattern(); this.buildPattern();
} }
require('util').inherits(MaskEditTextView, TextView); require('util').inherits(MaskEditTextView, TextView);
MaskEditTextView.maskPatternCharacterRegEx = { MaskEditTextView.maskPatternCharacterRegEx = {
'#': /[0-9]/, // Numeric '#': /[0-9]/, // Numeric
'A' : /[a-zA-Z]/, // Alpha A: /[a-zA-Z]/, // Alpha
'@': /[0-9a-zA-Z]/, // Alphanumeric '@': /[0-9a-zA-Z]/, // Alphanumeric
'&': /[\w\d\s]/, // Any "printable" 32-126, 128-255 '&': /[\w\d\s]/, // Any "printable" 32-126, 128-255
}; };
@ -109,7 +120,8 @@ MaskEditTextView.maskPatternCharacterRegEx = {
MaskEditTextView.prototype.setText = function (text) { MaskEditTextView.prototype.setText = function (text) {
MaskEditTextView.super_.prototype.setText.call(this, text); MaskEditTextView.super_.prototype.setText.call(this, text);
if(this.patternArray) { // :TODO: This is a hack - see TextView ctor note about setText() if (this.patternArray) {
// :TODO: This is a hack - see TextView ctor note about setText()
this.patternArrayPos = this.patternArray.length; this.patternArrayPos = this.patternArray.length;
} }
}; };
@ -135,7 +147,12 @@ MaskEditTextView.prototype.onKeyPress = function(ch, key) {
while (this.patternArrayPos >= 0) { while (this.patternArrayPos >= 0) {
if (_.isRegExp(this.patternArray[this.patternArrayPos])) { if (_.isRegExp(this.patternArray[this.patternArrayPos])) {
this.text = this.text.substr(0, this.text.length - 1); this.text = this.text.substr(0, this.text.length - 1);
this.client.term.write(ansi.goto(this.position.row, this.getEndOfTextColumn() + 1)); this.client.term.write(
ansi.goto(
this.position.row,
this.getEndOfTextColumn() + 1
)
);
this.clientBackspace(); this.clientBackspace();
break; break;
} }
@ -165,14 +182,17 @@ MaskEditTextView.prototype.onKeyPress = function(ch, key) {
this.text += ch; this.text += ch;
this.patternArrayPos++; this.patternArrayPos++;
while(this.patternArrayPos < this.patternArray.length && while (
!_.isRegExp(this.patternArray[this.patternArrayPos])) this.patternArrayPos < this.patternArray.length &&
{ !_.isRegExp(this.patternArray[this.patternArrayPos])
) {
this.patternArrayPos++; this.patternArrayPos++;
} }
this.redraw(); this.redraw();
this.client.term.write(ansi.goto(this.position.row, this.getEndOfTextColumn())); this.client.term.write(
ansi.goto(this.position.row, this.getEndOfTextColumn())
);
} }
} }
@ -181,7 +201,9 @@ MaskEditTextView.prototype.onKeyPress = function(ch, key) {
MaskEditTextView.prototype.setPropertyValue = function (propName, value) { MaskEditTextView.prototype.setPropertyValue = function (propName, value) {
switch (propName) { switch (propName) {
case 'maskPattern' : this.setMaskPattern(value); break; case 'maskPattern':
this.setMaskPattern(value);
break;
} }
MaskEditTextView.super_.prototype.setPropertyValue.call(this, propName, value); MaskEditTextView.super_.prototype.setPropertyValue.call(this, propName, value);

View File

@ -9,7 +9,7 @@ const { Errors } = require('./enig_error');
// //
// Number to 32bit MBF // Number to 32bit MBF
const numToMbf32 = (v) => { const numToMbf32 = v => {
const mbf = Buffer.alloc(4); const mbf = Buffer.alloc(4);
if (0 === v) { if (0 === v) {
@ -36,7 +36,7 @@ const numToMbf32 = (v) => {
return mbf; return mbf;
}; };
const mbf32ToNum = (mbf) => { const mbf32ToNum = mbf => {
if (0 === mbf[3]) { if (0 === mbf[3]) {
return 0.0; return 0.0;
} }

View File

@ -13,7 +13,8 @@ const SpinnerMenuView = require('./spinner_menu_view.js').SpinnerMenuView;
const ToggleMenuView = require('./toggle_menu_view.js').ToggleMenuView; const ToggleMenuView = require('./toggle_menu_view.js').ToggleMenuView;
const MaskEditTextView = require('./mask_edit_text_view.js').MaskEditTextView; const MaskEditTextView = require('./mask_edit_text_view.js').MaskEditTextView;
const KeyEntryView = require('./key_entry_view.js'); const KeyEntryView = require('./key_entry_view.js');
const MultiLineEditTextView = require('./multi_line_edit_text_view.js').MultiLineEditTextView; const MultiLineEditTextView =
require('./multi_line_edit_text_view.js').MultiLineEditTextView;
const getPredefinedMCIValue = require('./predefined_mci.js').getPredefinedMCIValue; const getPredefinedMCIValue = require('./predefined_mci.js').getPredefinedMCIValue;
const ansi = require('./ansi_term.js'); const ansi = require('./ansi_term.js');
@ -28,7 +29,18 @@ function MCIViewFactory(client) {
} }
MCIViewFactory.UserViewCodes = [ MCIViewFactory.UserViewCodes = [
'TL', 'ET', 'ME', 'MT', 'PL', 'BT', 'VM', 'HM', 'FM', 'SM', 'TM', 'KE', 'TL',
'ET',
'ME',
'MT',
'PL',
'BT',
'VM',
'HM',
'FM',
'SM',
'TM',
'KE',
// //
// XY is a special MCI code that allows finding positions // XY is a special MCI code that allows finding positions
@ -38,9 +50,7 @@ MCIViewFactory.UserViewCodes = [
'XY', 'XY',
]; ];
MCIViewFactory.MovementCodes = [ MCIViewFactory.MovementCodes = ['CF', 'CB', 'CU', 'CD'];
'CF', 'CB', 'CU', 'CD',
];
MCIViewFactory.prototype.createFromMCI = function (mci) { MCIViewFactory.prototype.createFromMCI = function (mci) {
assert(mci.code); assert(mci.code);
@ -73,7 +83,11 @@ MCIViewFactory.prototype.createFromMCI = function(mci) {
} }
function setFocusOption(pos, name) { function setFocusOption(pos, name) {
if(mci.focusArgs && mci.focusArgs.length > pos && mci.focusArgs[pos].length > 0) { if (
mci.focusArgs &&
mci.focusArgs.length > pos &&
mci.focusArgs[pos].length > 0
) {
options[name] = mci.focusArgs[pos]; options[name] = mci.focusArgs[pos];
} }
} }

View File

@ -8,7 +8,8 @@ const ViewController = require('./view_controller.js').ViewController
const menuUtil = require('./menu_util.js'); const menuUtil = require('./menu_util.js');
const Config = require('./config.js').get; const Config = require('./config.js').get;
const stringFormat = require('../core/string_format.js'); const stringFormat = require('../core/string_format.js');
const MultiLineEditTextView = require('../core/multi_line_edit_text_view.js').MultiLineEditTextView; const MultiLineEditTextView =
require('../core/multi_line_edit_text_view.js').MultiLineEditTextView;
const Errors = require('../core/enig_error.js').Errors; const Errors = require('../core/enig_error.js').Errors;
const { getPredefinedMCIValue } = require('../core/predefined_mci.js'); const { getPredefinedMCIValue } = require('../core/predefined_mci.js');
@ -19,7 +20,6 @@ const _ = require('lodash');
const iconvDecode = require('iconv-lite').decode; const iconvDecode = require('iconv-lite').decode;
exports.MenuModule = class MenuModule extends PluginModule { exports.MenuModule = class MenuModule extends PluginModule {
constructor(options) { constructor(options) {
super(options); super(options);
@ -30,7 +30,11 @@ exports.MenuModule = class MenuModule extends PluginModule {
this.menuConfig.config = this.menuConfig.config || {}; this.menuConfig.config = this.menuConfig.config || {};
this.cls = _.get(this.menuConfig.config, 'cls', Config().menus.cls); this.cls = _.get(this.menuConfig.config, 'cls', Config().menus.cls);
this.viewControllers = {}; this.viewControllers = {};
this.interrupt = (_.get(this.menuConfig.config, 'interrupt', MenuModule.InterruptTypes.Queued)).toLowerCase(); this.interrupt = _.get(
this.menuConfig.config,
'interrupt',
MenuModule.InterruptTypes.Queued
).toLowerCase();
if (MenuModule.InterruptTypes.Realtime === this.interrupt) { if (MenuModule.InterruptTypes.Realtime === this.interrupt) {
this.realTimeInterrupt = 'blocked'; this.realTimeInterrupt = 'blocked';
@ -59,8 +63,11 @@ exports.MenuModule = class MenuModule extends PluginModule {
let pausePosition = { row: 0, column: 0 }; let pausePosition = { row: 0, column: 0 };
const hasArt = () => { const hasArt = () => {
return _.isString(self.menuConfig.art) || return (
(Array.isArray(self.menuConfig.art) && _.has(self.menuConfig.art[0], 'acs')); _.isString(self.menuConfig.art) ||
(Array.isArray(self.menuConfig.art) &&
_.has(self.menuConfig.art[0], 'acs'))
);
}; };
async.waterfall( async.waterfall(
@ -81,7 +88,10 @@ exports.MenuModule = class MenuModule extends PluginModule {
self.menuConfig.config, self.menuConfig.config,
(err, artData) => { (err, artData) => {
if (err) { if (err) {
self.client.log.trace('Could not display art', { art : self.menuConfig.art, reason : err.message } ); self.client.log.trace('Could not display art', {
art: self.menuConfig.art,
reason: err.message,
});
} else { } else {
mciData.menu = artData.mciMap; mciData.menu = artData.mciMap;
} }
@ -100,7 +110,11 @@ exports.MenuModule = class MenuModule extends PluginModule {
} }
if (!_.isObject(self.menuConfig.promptConfig)) { if (!_.isObject(self.menuConfig.promptConfig)) {
return callback(Errors.MissingConfig('Prompt specified but no "promptConfig" block found')); return callback(
Errors.MissingConfig(
'Prompt specified but no "promptConfig" block found'
)
);
} }
const options = Object.assign({}, self.menuConfig.config); const options = Object.assign({}, self.menuConfig.config);
@ -130,7 +144,10 @@ exports.MenuModule = class MenuModule extends PluginModule {
return callback(null, null); return callback(null, null);
} }
if(self.client.term.termHeight > 0 && pausePosition.row > self.client.termHeight) { if (
self.client.term.termHeight > 0 &&
pausePosition.row > self.client.termHeight
) {
// If this scrolled, the prompt will go to the bottom of the screen // If this scrolled, the prompt will go to the bottom of the screen
pausePosition.row = self.client.termHeight; pausePosition.row = self.client.termHeight;
} }
@ -141,13 +158,17 @@ exports.MenuModule = class MenuModule extends PluginModule {
self.finishedLoading(); self.finishedLoading();
self.realTimeInterrupt = 'allowed'; self.realTimeInterrupt = 'allowed';
return self.autoNextMenu(callback); return self.autoNextMenu(callback);
} },
], ],
err => { err => {
if (err) { if (err) {
self.client.log.warn('Error during init sequence', { error : err.message } ); self.client.log.warn('Error during init sequence', {
error: err.message,
});
return self.prevMenu( () => { /* dummy */ } ); return self.prevMenu(() => {
/* dummy */
});
} }
} }
); );
@ -156,7 +177,9 @@ exports.MenuModule = class MenuModule extends PluginModule {
beforeArt(cb) { beforeArt(cb) {
if (_.isNumber(this.menuConfig.config.baudRate)) { if (_.isNumber(this.menuConfig.config.baudRate)) {
// :TODO: some terminals not supporting cterm style emulated baud rate end up displaying a broken ESC sequence or a single "r" here // :TODO: some terminals not supporting cterm style emulated baud rate end up displaying a broken ESC sequence or a single "r" here
this.client.term.rawWrite(ansi.setEmulatedBaudRate(this.menuConfig.config.baudRate)); this.client.term.rawWrite(
ansi.setEmulatedBaudRate(this.menuConfig.config.baudRate)
);
} }
if (this.cls) { if (this.cls) {
@ -183,7 +206,7 @@ exports.MenuModule = class MenuModule extends PluginModule {
let opts = { cls: true }; // clear screen for first message let opts = { cls: true }; // clear screen for first message
async.whilst( async.whilst(
(callback) => callback(null, this.client.interruptQueue.hasItems()), callback => callback(null, this.client.interruptQueue.hasItems()),
next => { next => {
this.client.interruptQueue.displayNext(opts, err => { this.client.interruptQueue.displayNext(opts, err => {
opts = {}; opts = {};
@ -197,7 +220,10 @@ exports.MenuModule = class MenuModule extends PluginModule {
} }
attemptInterruptNow(interruptItem, cb) { attemptInterruptNow(interruptItem, cb) {
if(this.realTimeInterrupt !== 'allowed' || MenuModule.InterruptTypes.Realtime !== this.interrupt) { if (
this.realTimeInterrupt !== 'allowed' ||
MenuModule.InterruptTypes.Realtime !== this.interrupt
) {
return cb(null, false); // don't eat up the item; queue for later return cb(null, false); // don't eat up the item; queue for later
} }
@ -220,7 +246,8 @@ exports.MenuModule = class MenuModule extends PluginModule {
this.reload(err => { this.reload(err => {
return done(err, err ? false : true); return done(err, err ? false : true);
}); });
}); }
);
} }
getSaveState() { getSaveState() {
@ -307,7 +334,10 @@ exports.MenuModule = class MenuModule extends PluginModule {
} }
addViewController(name, vc) { addViewController(name, vc) {
assert(!this.viewControllers[name], `ViewController by the name of "${name}" already exists!`); assert(
!this.viewControllers[name],
`ViewController by the name of "${name}" already exists!`
);
this.viewControllers[name] = vc; this.viewControllers[name] = vc;
return vc; return vc;
@ -327,7 +357,10 @@ exports.MenuModule = class MenuModule extends PluginModule {
} }
shouldPause() { shouldPause() {
return ('end' === this.menuConfig.config.pause || true === this.menuConfig.config.pause); return (
'end' === this.menuConfig.config.pause ||
true === this.menuConfig.config.pause
);
} }
hasNextTimeout() { hasNextTimeout() {
@ -335,7 +368,7 @@ exports.MenuModule = class MenuModule extends PluginModule {
} }
haveNext() { haveNext() {
return (_.isString(this.menuConfig.next) || _.isArray(this.menuConfig.next)); return _.isString(this.menuConfig.next) || _.isArray(this.menuConfig.next);
} }
autoNextMenu(cb) { autoNextMenu(cb) {
@ -349,7 +382,10 @@ exports.MenuModule = class MenuModule extends PluginModule {
} }
}; };
if(_.has(this.menuConfig, 'runtime.autoNext') && true === this.menuConfig.runtime.autoNext) { if (
_.has(this.menuConfig, 'runtime.autoNext') &&
true === this.menuConfig.runtime.autoNext
) {
if (this.hasNextTimeout()) { if (this.hasNextTimeout()) {
setTimeout(() => { setTimeout(() => {
return gotoNextMenu(); return gotoNextMenu();
@ -374,7 +410,10 @@ exports.MenuModule = class MenuModule extends PluginModule {
function addViewControllers(callback) { function addViewControllers(callback) {
_.forEach(mciData, (mciMap, name) => { _.forEach(mciData, (mciMap, name) => {
assert('menu' === name || 'prompt' === name); assert('menu' === name || 'prompt' === name);
self.addViewController(name, new ViewController( { client : self.client } ) ); self.addViewController(
name,
new ViewController({ client: self.client })
);
}); });
return callback(null); return callback(null);
@ -404,10 +443,13 @@ exports.MenuModule = class MenuModule extends PluginModule {
mciMap: mciData.prompt, mciMap: mciData.prompt,
}; };
self.viewControllers.prompt.loadFromPromptConfig(promptLoadOpts, err => { self.viewControllers.prompt.loadFromPromptConfig(
promptLoadOpts,
err => {
return callback(err); return callback(err);
});
} }
);
},
], ],
err => { err => {
return cb(err); return cb(err);
@ -425,19 +467,18 @@ exports.MenuModule = class MenuModule extends PluginModule {
this.client.term.rawWrite(ansi.resetScreen()); this.client.term.rawWrite(ansi.resetScreen());
} }
options = Object.assign( { client : this.client, font : this.menuConfig.config.font }, options ); options = Object.assign(
{ client: this.client, font: this.menuConfig.config.font },
options
);
if (Buffer.isBuffer(nameOrData)) { if (Buffer.isBuffer(nameOrData)) {
const data = iconvDecode(nameOrData, options.encoding || 'cp437'); const data = iconvDecode(nameOrData, options.encoding || 'cp437');
return theme.displayPreparedArt( return theme.displayPreparedArt(options, { data }, (err, artData) => {
options,
{ data },
(err, artData) => {
if (cb) { if (cb) {
return cb(err, artData); return cb(err, artData);
} }
} });
);
} }
return theme.displayThemedAsset( return theme.displayThemedAsset(
@ -479,17 +520,13 @@ exports.MenuModule = class MenuModule extends PluginModule {
} }
prepViewControllerWithArt(name, formId, options, cb) { prepViewControllerWithArt(name, formId, options, cb) {
this.displayAsset( this.displayAsset(this.menuConfig.config.art[name], options, (err, artData) => {
this.menuConfig.config.art[name],
options,
(err, artData) => {
if (err) { if (err) {
return cb(err); return cb(err);
} }
return this.prepViewController(name, formId, artData.mciMap, cb); return this.prepViewController(name, formId, artData.mciMap, cb);
} });
);
} }
optionalMoveToPosition(position) { optionalMoveToPosition(position) {
@ -512,7 +549,11 @@ exports.MenuModule = class MenuModule extends PluginModule {
return theme.displayThemedPause(this.client, { position }, cb); return theme.displayThemedPause(this.client, { position }, cb);
} }
promptForInput( { formName, formId, promptName, prevFormName, position } = {}, options, cb) { promptForInput(
{ formName, formId, promptName, prevFormName, position } = {},
options,
cb
) {
if (!cb && _.isFunction(options)) { if (!cb && _.isFunction(options)) {
cb = options; cb = options;
options = {}; options = {};
@ -542,7 +583,9 @@ exports.MenuModule = class MenuModule extends PluginModule {
if (options.clearAtSubmit) { if (options.clearAtSubmit) {
this.optionalMoveToPosition(position); this.optionalMoveToPosition(position);
if (options.clearWidth) { if (options.clearWidth) {
this.client.term.rawWrite(`${ansi.reset()}${' '.repeat(options.clearWidth)}`); this.client.term.rawWrite(
`${ansi.reset()}${' '.repeat(options.clearWidth)}`
);
} else { } else {
// :TODO: handle multi-rows via artHeight // :TODO: handle multi-rows via artHeight
this.client.term.rawWrite(ansi.eraseLine()); this.client.term.rawWrite(ansi.eraseLine());
@ -569,7 +612,7 @@ exports.MenuModule = class MenuModule extends PluginModule {
return; return;
} }
if(appendMultiLine && (view instanceof MultiLineEditTextView)) { if (appendMultiLine && view instanceof MultiLineEditTextView) {
view.addText(text); view.addText(text);
} else { } else {
view.setText(text); view.setText(text);
@ -589,14 +632,23 @@ exports.MenuModule = class MenuModule extends PluginModule {
const config = this.menuConfig.config; const config = this.menuConfig.config;
const endId = options.endId || 99; // we'll fail to get a view before 99 const endId = options.endId || 99; // we'll fail to get a view before 99
while(customMciId <= endId && (textView = this.viewControllers[formName].getView(customMciId)) ) { while (
customMciId <= endId &&
(textView = this.viewControllers[formName].getView(customMciId))
) {
const key = `${formName}InfoFormat${customMciId}`; // e.g. "mainInfoFormat10" const key = `${formName}InfoFormat${customMciId}`; // e.g. "mainInfoFormat10"
const format = config[key]; const format = config[key];
if(format && (!options.filter || options.filter.find(f => format.indexOf(f) > - 1))) { if (
format &&
(!options.filter || options.filter.find(f => format.indexOf(f) > -1))
) {
const text = stringFormat(format, fmtObj); const text = stringFormat(format, fmtObj);
if(options.appendMultiLine && (textView instanceof MultiLineEditTextView)) { if (
options.appendMultiLine &&
textView instanceof MultiLineEditTextView
) {
textView.addText(text); textView.addText(text);
} else { } else {
textView.setText(text); textView.setText(text);
@ -665,10 +717,18 @@ exports.MenuModule = class MenuModule extends PluginModule {
badReason = `Missing "${key}", expected ${type}`; badReason = `Missing "${key}", expected ${type}`;
} else { } else {
switch (type) { switch (type) {
case 'string' : typeOk = _.isString(c); break; case 'string':
case 'object' : typeOk = _.isObject(c); break; typeOk = _.isString(c);
case 'array' : typeOk = Array.isArray(c); break; break;
case 'number' : typeOk = !isNaN(parseInt(c)); break; case 'object':
typeOk = _.isObject(c);
break;
case 'array':
typeOk = Array.isArray(c);
break;
case 'number':
typeOk = !isNaN(parseInt(c));
break;
default: default:
typeOk = false; typeOk = false;
badReason = `Don't know how to validate ${type}`; badReason = `Don't know how to validate ${type}`;
@ -684,6 +744,12 @@ exports.MenuModule = class MenuModule extends PluginModule {
return typeOk; return typeOk;
}); });
return cb(good ? null : Errors.Invalid(`Invalid or missing config option "${firstBadKey}" (${badReason})`)); return cb(
good
? null
: Errors.Invalid(
`Invalid or missing config option "${firstBadKey}" (${badReason})`
)
);
} }
}; };

View File

@ -3,13 +3,8 @@
// ENiGMA½ // ENiGMA½
const loadMenu = require('./menu_util.js').loadMenu; const loadMenu = require('./menu_util.js').loadMenu;
const { const { Errors, ErrorReasons } = require('./enig_error.js');
Errors, const { getResolvedSpec } = require('./menu_util.js');
ErrorReasons
} = require('./enig_error.js');
const {
getResolvedSpec
} = require('./menu_util.js');
// deps // deps
const _ = require('lodash'); const _ = require('lodash');
@ -58,14 +53,26 @@ module.exports = class MenuStack {
const menuConfig = currentModuleInfo.instance.menuConfig; const menuConfig = currentModuleInfo.instance.menuConfig;
const nextMenu = getResolvedSpec(this.client, menuConfig.next, 'next'); const nextMenu = getResolvedSpec(this.client, menuConfig.next, 'next');
if (!nextMenu) { if (!nextMenu) {
return cb(Array.isArray(menuConfig.next) ? return cb(
Errors.MenuStack('No matching condition for "next"', ErrorReasons.NoConditionMatch) : Array.isArray(menuConfig.next)
Errors.MenuStack('Invalid or missing "next" member in menu config', ErrorReasons.InvalidNextMenu) ? Errors.MenuStack(
'No matching condition for "next"',
ErrorReasons.NoConditionMatch
)
: Errors.MenuStack(
'Invalid or missing "next" member in menu config',
ErrorReasons.InvalidNextMenu
)
); );
} }
if (nextMenu === currentModuleInfo.name) { if (nextMenu === currentModuleInfo.name) {
return cb(Errors.MenuStack('Menu config "next" specifies current menu', ErrorReasons.AlreadyThere)); return cb(
Errors.MenuStack(
'Menu config "next" specifies current menu',
ErrorReasons.AlreadyThere
)
);
} }
this.goto(nextMenu, {}, cb); this.goto(nextMenu, {}, cb);
@ -89,7 +96,9 @@ module.exports = class MenuStack {
return this.goto(previousModuleInfo.name, opts, cb); return this.goto(previousModuleInfo.name, opts, cb);
} }
return cb(Errors.MenuStack('No previous menu available', ErrorReasons.NoPreviousMenu)); return cb(
Errors.MenuStack('No previous menu available', ErrorReasons.NoPreviousMenu)
);
} }
goto(name, options, cb) { goto(name, options, cb) {
@ -105,7 +114,12 @@ module.exports = class MenuStack {
if (currentModuleInfo && name === currentModuleInfo.name) { if (currentModuleInfo && name === currentModuleInfo.name) {
if (cb) { if (cb) {
cb(Errors.MenuStack('Already at supplied menu', ErrorReasons.AlreadyThere)); cb(
Errors.MenuStack(
'Already at supplied menu',
ErrorReasons.AlreadyThere
)
);
} }
return; return;
} }
@ -146,7 +160,10 @@ module.exports = class MenuStack {
{ options: modInst.menuConfig.options }, { options: modInst.menuConfig.options },
'Use of "options" is deprecated. Move relevant members to "config" block! Support will be fully removed in future versions' 'Use of "options" is deprecated. Move relevant members to "config" block! Support will be fully removed in future versions'
); );
Object.assign(modInst.menuConfig.config || {}, modInst.menuConfig.options); Object.assign(
modInst.menuConfig.config || {},
modInst.menuConfig.options
);
delete modInst.menuConfig.options; delete modInst.menuConfig.options;
} }
@ -161,14 +178,18 @@ module.exports = class MenuStack {
menuFlags = modInst.menuConfig.config.menuFlags; menuFlags = modInst.menuConfig.config.menuFlags;
// in code we can ask to merge in // in code we can ask to merge in
if(Array.isArray(options.menuFlags) && options.menuFlags.includes('mergeFlags')) { if (
Array.isArray(options.menuFlags) &&
options.menuFlags.includes('mergeFlags')
) {
menuFlags = _.uniq(menuFlags.concat(options.menuFlags)); menuFlags = _.uniq(menuFlags.concat(options.menuFlags));
} }
} }
if (currentModuleInfo) { if (currentModuleInfo) {
// save stack state // save stack state
currentModuleInfo.savedState = currentModuleInfo.instance.getSaveState(); currentModuleInfo.savedState =
currentModuleInfo.instance.getSaveState();
currentModuleInfo.instance.leave(); currentModuleInfo.instance.leave();
@ -196,7 +217,9 @@ module.exports = class MenuStack {
const stackEntries = self.stack.map(stackEntry => { const stackEntries = self.stack.map(stackEntry => {
let name = stackEntry.name; let name = stackEntry.name;
if (stackEntry.instance.menuConfig.config.menuFlags.length > 0) { if (stackEntry.instance.menuConfig.config.menuFlags.length > 0) {
name += ` (${stackEntry.instance.menuConfig.config.menuFlags.join(', ')})`; name += ` (${stackEntry.instance.menuConfig.config.menuFlags.join(
', '
)})`;
} }
return name; return name;
}); });

View File

@ -34,13 +34,16 @@ function getMenuConfig(client, name, cb) {
function locatePromptConfig(menuConfig, callback) { function locatePromptConfig(menuConfig, callback) {
if (_.isString(menuConfig.prompt)) { if (_.isString(menuConfig.prompt)) {
if (_.has(client.currentTheme, ['prompts', menuConfig.prompt])) { if (_.has(client.currentTheme, ['prompts', menuConfig.prompt])) {
menuConfig.promptConfig = client.currentTheme.prompts[menuConfig.prompt]; menuConfig.promptConfig =
client.currentTheme.prompts[menuConfig.prompt];
return callback(null, menuConfig); return callback(null, menuConfig);
} }
return callback(Errors.DoesNotExist(`No prompt entry for "${menuConfig.prompt}"`)); return callback(
Errors.DoesNotExist(`No prompt entry for "${menuConfig.prompt}"`)
);
} }
return callback(null, menuConfig); return callback(null, menuConfig);
} },
], ],
(err, menuConfig) => { (err, menuConfig) => {
return cb(err, menuConfig); return cb(err, menuConfig);
@ -62,7 +65,6 @@ function loadMenu(options, cb) {
}); });
}, },
function loadMenuModule(menuConfig, callback) { function loadMenuModule(menuConfig, callback) {
menuConfig.config = menuConfig.config || {}; menuConfig.config = menuConfig.config || {};
menuConfig.config.menuFlags = menuConfig.config.menuFlags || []; menuConfig.config.menuFlags = menuConfig.config.menuFlags || [];
if (!Array.isArray(menuConfig.config.menuFlags)) { if (!Array.isArray(menuConfig.config.menuFlags)) {
@ -74,8 +76,12 @@ function loadMenu(options, cb) {
const modLoadOpts = { const modLoadOpts = {
name: modSupplied ? modAsset.asset : 'standard_menu', name: modSupplied ? modAsset.asset : 'standard_menu',
path : (!modSupplied || 'systemModule' === modAsset.type) ? __dirname : Config().paths.mods, path:
category : (!modSupplied || 'systemModule' === modAsset.type) ? null : 'mods', !modSupplied || 'systemModule' === modAsset.type
? __dirname
: Config().paths.mods,
category:
!modSupplied || 'systemModule' === modAsset.type ? null : 'mods',
}; };
moduleUtil.loadModuleEx(modLoadOpts, (err, mod) => { moduleUtil.loadModuleEx(modLoadOpts, (err, mod) => {
@ -90,8 +96,14 @@ function loadMenu(options, cb) {
}, },
function createModuleInstance(modData, callback) { function createModuleInstance(modData, callback) {
Log.trace( Log.trace(
{ moduleName : modData.name, extraArgs : options.extraArgs, config : modData.config, info : modData.mod.modInfo }, {
'Creating menu module instance'); moduleName: modData.name,
extraArgs: options.extraArgs,
config: modData.config,
info: modData.mod.modInfo,
},
'Creating menu module instance'
);
let moduleInstance; let moduleInstance;
try { try {
@ -107,7 +119,7 @@ function loadMenu(options, cb) {
} }
return callback(null, moduleInstance); return callback(null, moduleInstance);
} },
], ],
(err, modInst) => { (err, modInst) => {
return cb(err, modInst); return cb(err, modInst);
@ -125,7 +137,7 @@ function getFormConfigByIDAndMap(menuConfig, formId, mciMap, cb) {
} }
const formForId = menuConfig.form[formId]; const formForId = menuConfig.form[formId];
const mciReqKey = _.filter(_.map(_.sortBy(mciMap, 'code'), 'code'), (mci) => { const mciReqKey = _.filter(_.map(_.sortBy(mciMap, 'code'), 'code'), mci => {
return MCIViewFactory.UserViewCodes.indexOf(mci) > -1; return MCIViewFactory.UserViewCodes.indexOf(mci) > -1;
}).join(''); }).join('');
@ -147,7 +159,9 @@ function getFormConfigByIDAndMap(menuConfig, formId, mciMap, cb) {
return cb(null, formForId); return cb(null, formForId);
} }
return cb(Errors.DoesNotExist(`No matching form configuration found for key "${mciReqKey}"`)); return cb(
Errors.DoesNotExist(`No matching form configuration found for key "${mciReqKey}"`)
);
} }
// :TODO: Most of this should be moved elsewhere .... DRY... // :TODO: Most of this should be moved elsewhere .... DRY...
@ -158,13 +172,27 @@ function callModuleMenuMethod(client, asset, path, formData, extraArgs, cb) {
try { try {
client.log.trace( client.log.trace(
{ path : path, methodName : asset.asset, formData : formData, extraArgs : extraArgs }, {
'Calling menu method'); path: path,
methodName: asset.asset,
formData: formData,
extraArgs: extraArgs,
},
'Calling menu method'
);
const methodMod = require(path); const methodMod = require(path);
return methodMod[asset.asset](client.currentMenuModule, formData || { }, extraArgs, cb); return methodMod[asset.asset](
client.currentMenuModule,
formData || {},
extraArgs,
cb
);
} catch (e) { } catch (e) {
client.log.error( { error : e.toString(), methodName : asset.asset }, 'Failed to execute asset method'); client.log.error(
{ error: e.toString(), methodName: asset.asset },
'Failed to execute asset method'
);
return cb(e); return cb(e);
} }
} }
@ -190,7 +218,8 @@ function handleAction(client, formData, conf, cb) {
paths.join(Config().paths.mods, actionAsset.location), paths.join(Config().paths.mods, actionAsset.location),
formData, formData,
conf.extraArgs, conf.extraArgs,
cb); cb
);
} else if ('systemMethod' === actionAsset.type) { } else if ('systemMethod' === actionAsset.type) {
// :TODO: Need to pass optional args here -- conf.extraArgs and args between e.g. () // :TODO: Need to pass optional args here -- conf.extraArgs and args between e.g. ()
// :TODO: Probably better as system_method.js // :TODO: Probably better as system_method.js
@ -200,12 +229,17 @@ function handleAction(client, formData, conf, cb) {
paths.join(__dirname, 'system_menu_method.js'), paths.join(__dirname, 'system_menu_method.js'),
formData, formData,
conf.extraArgs, conf.extraArgs,
cb); cb
);
} else { } else {
// local to current module // local to current module
const currentModule = client.currentMenuModule; const currentModule = client.currentMenuModule;
if (_.isFunction(currentModule.menuMethods[actionAsset.asset])) { if (_.isFunction(currentModule.menuMethods[actionAsset.asset])) {
return currentModule.menuMethods[actionAsset.asset](formData, conf.extraArgs, cb); return currentModule.menuMethods[actionAsset.asset](
formData,
conf.extraArgs,
cb
);
} }
const err = Errors.DoesNotExist('Method does not exist'); const err = Errors.DoesNotExist('Method does not exist');
@ -214,7 +248,11 @@ function handleAction(client, formData, conf, cb) {
} }
case 'menu': case 'menu':
return client.currentMenuModule.gotoMenu(actionAsset.asset, { formData : formData, extraArgs : conf.extraArgs }, cb ); return client.currentMenuModule.gotoMenu(
actionAsset.asset,
{ formData: formData, extraArgs: conf.extraArgs },
cb
);
} }
} }
@ -261,16 +299,34 @@ function handleNext(client, nextSpec, conf, cb) {
case 'method': case 'method':
case 'systemMethod': case 'systemMethod':
if (_.isString(nextAsset.location)) { if (_.isString(nextAsset.location)) {
return callModuleMenuMethod(client, nextAsset, paths.join(Config().paths.mods, nextAsset.location), {}, extraArgs, cb); return callModuleMenuMethod(
client,
nextAsset,
paths.join(Config().paths.mods, nextAsset.location),
{},
extraArgs,
cb
);
} else if ('systemMethod' === nextAsset.type) { } else if ('systemMethod' === nextAsset.type) {
// :TODO: see other notes about system_menu_method.js here // :TODO: see other notes about system_menu_method.js here
return callModuleMenuMethod(client, nextAsset, paths.join(__dirname, 'system_menu_method.js'), {}, extraArgs, cb); return callModuleMenuMethod(
client,
nextAsset,
paths.join(__dirname, 'system_menu_method.js'),
{},
extraArgs,
cb
);
} else { } else {
// local to current module // local to current module
const currentModule = client.currentMenuModule; const currentModule = client.currentMenuModule;
if (_.isFunction(currentModule.menuMethods[nextAsset.asset])) { if (_.isFunction(currentModule.menuMethods[nextAsset.asset])) {
const formData = {}; // we don't have any const formData = {}; // we don't have any
return currentModule.menuMethods[nextAsset.asset]( formData, extraArgs, cb ); return currentModule.menuMethods[nextAsset.asset](
formData,
extraArgs,
cb
);
} }
const err = Errors.DoesNotExist('Method does not exist'); const err = Errors.DoesNotExist('Method does not exist');
@ -279,7 +335,11 @@ function handleNext(client, nextSpec, conf, cb) {
} }
case 'menu': case 'menu':
return client.currentMenuModule.gotoMenu(nextAsset.asset, { extraArgs : extraArgs }, cb ); return client.currentMenuModule.gotoMenu(
nextAsset.asset,
{ extraArgs: extraArgs },
cb
);
} }
const err = Errors.Invalid('Invalid asset type for "next"'); const err = Errors.Invalid('Invalid asset type for "next"');

View File

@ -31,15 +31,21 @@ function MenuView(options) {
this.renderCache = {}; this.renderCache = {};
this.caseInsensitiveHotKeys = miscUtil.valueWithDefault(options.caseInsensitiveHotKeys, true); this.caseInsensitiveHotKeys = miscUtil.valueWithDefault(
options.caseInsensitiveHotKeys,
true
);
this.setHotKeys(options.hotKeys); this.setHotKeys(options.hotKeys);
this.focusedItemIndex = options.focusedItemIndex || 0; this.focusedItemIndex = options.focusedItemIndex || 0;
this.focusedItemIndex = this.items.length >= this.focusedItemIndex ? this.focusedItemIndex : 0; this.focusedItemIndex =
this.items.length >= this.focusedItemIndex ? this.focusedItemIndex : 0;
this.itemSpacing = _.isNumber(options.itemSpacing) ? options.itemSpacing : 0; this.itemSpacing = _.isNumber(options.itemSpacing) ? options.itemSpacing : 0;
this.itemHorizSpacing = _.isNumber(options.itemHorizSpacing) ? options.itemHorizSpacing : 0; this.itemHorizSpacing = _.isNumber(options.itemHorizSpacing)
? options.itemHorizSpacing
: 0;
// :TODO: probably just replace this with owner draw / pipe codes / etc. more control, less specialization // :TODO: probably just replace this with owner draw / pipe codes / etc. more control, less specialization
this.focusPrefix = options.focusPrefix || ''; this.focusPrefix = options.focusPrefix || '';
@ -53,7 +59,8 @@ function MenuView(options) {
this.getHotKeyItemIndex = function (ch) { this.getHotKeyItemIndex = function (ch) {
if (ch && self.hotKeys) { if (ch && self.hotKeys) {
const keyIndex = self.hotKeys[self.caseInsensitiveHotKeys ? ch.toLowerCase() : ch]; const keyIndex =
self.hotKeys[self.caseInsensitiveHotKeys ? ch.toLowerCase() : ch];
if (_.isNumber(keyIndex)) { if (_.isNumber(keyIndex)) {
return keyIndex; return keyIndex;
} }
@ -71,11 +78,11 @@ util.inherits(MenuView, View);
MenuView.prototype.setTextOverflow = function (overflow) { MenuView.prototype.setTextOverflow = function (overflow) {
this.textOverflow = overflow; this.textOverflow = overflow;
this.invalidateRenderCache(); this.invalidateRenderCache();
} };
MenuView.prototype.hasTextOverflow = function () { MenuView.prototype.hasTextOverflow = function () {
return this.textOverflow != undefined; return this.textOverflow != undefined;
} };
MenuView.prototype.setItems = function (items) { MenuView.prototype.setItems = function (items) {
if (Array.isArray(items)) { if (Array.isArray(items)) {
@ -245,11 +252,9 @@ MenuView.prototype.setFocusItems = function(items) {
if (items) { if (items) {
this.focusItems = []; this.focusItems = [];
items.forEach(itemText => { items.forEach(itemText => {
this.focusItems.push( this.focusItems.push({
{ text: self.disablePipe ? itemText : pipeToAnsi(itemText, self.client),
text : self.disablePipe ? itemText : pipeToAnsi(itemText, self.client) });
}
);
}); });
} }
}; };
@ -272,16 +277,36 @@ MenuView.prototype.setItemHorizSpacing = function(itemHorizSpacing) {
MenuView.prototype.setPropertyValue = function (propName, value) { MenuView.prototype.setPropertyValue = function (propName, value) {
switch (propName) { switch (propName) {
case 'itemSpacing' : this.setItemSpacing(value); break; case 'itemSpacing':
case 'itemHorizSpacing' : this.setItemHorizSpacing(value); break; this.setItemSpacing(value);
case 'items' : this.setItems(value); break; break;
case 'focusItems' : this.setFocusItems(value); break; case 'itemHorizSpacing':
case 'hotKeys' : this.setHotKeys(value); break; this.setItemHorizSpacing(value);
case 'textOverflow' : this.setTextOverflow(value); break; break;
case 'hotKeySubmit' : this.hotKeySubmit = value; break; case 'items':
case 'justify' : this.setJustify(value); break; this.setItems(value);
case 'fillChar' : this.setFillChar(value); break; break;
case 'focusItemIndex' : this.focusedItemIndex = value; break; case 'focusItems':
this.setFocusItems(value);
break;
case 'hotKeys':
this.setHotKeys(value);
break;
case 'textOverflow':
this.setTextOverflow(value);
break;
case 'hotKeySubmit':
this.hotKeySubmit = value;
break;
case 'justify':
this.setJustify(value);
break;
case 'fillChar':
this.setFillChar(value);
break;
case 'focusItemIndex':
this.focusedItemIndex = value;
break;
case 'itemFormat': case 'itemFormat':
case 'focusItemFormat': case 'focusItemFormat':
@ -290,7 +315,9 @@ MenuView.prototype.setPropertyValue = function(propName, value) {
this.invalidateRenderCache(); this.invalidateRenderCache();
break; break;
case 'sort' : this.setSort(value); break; case 'sort':
this.setSort(value);
break;
} }
MenuView.super_.prototype.setPropertyValue.call(this, propName, value); MenuView.super_.prototype.setPropertyValue.call(this, propName, value);
@ -299,13 +326,13 @@ MenuView.prototype.setPropertyValue = function(propName, value) {
MenuView.prototype.setFillChar = function (fillChar) { MenuView.prototype.setFillChar = function (fillChar) {
this.fillChar = miscUtil.valueWithDefault(fillChar, ' ').substr(0, 1); this.fillChar = miscUtil.valueWithDefault(fillChar, ' ').substr(0, 1);
this.invalidateRenderCache(); this.invalidateRenderCache();
} };
MenuView.prototype.setJustify = function (justify) { MenuView.prototype.setJustify = function (justify) {
this.justify = justify; this.justify = justify;
this.invalidateRenderCache(); this.invalidateRenderCache();
this.positionCacheExpired = true; this.positionCacheExpired = true;
} };
MenuView.prototype.setHotKeys = function (hotKeys) { MenuView.prototype.setHotKeys = function (hotKeys) {
if (_.isObject(hotKeys)) { if (_.isObject(hotKeys)) {
@ -319,4 +346,3 @@ MenuView.prototype.setHotKeys = function(hotKeys) {
} }
} }
}; };

View File

@ -7,17 +7,16 @@ const ftnUtil = require('./ftn_util.js');
const createNamedUUID = require('./uuid_util.js').createNamedUUID; const createNamedUUID = require('./uuid_util.js').createNamedUUID;
const Errors = require('./enig_error.js').Errors; const Errors = require('./enig_error.js').Errors;
const ANSI = require('./ansi_term.js'); const ANSI = require('./ansi_term.js');
const { const { sanitizeString, getISOTimestampString } = require('./database.js');
sanitizeString,
getISOTimestampString } = require('./database.js');
const { isCP437Encodable } = require('./cp437util'); const { isCP437Encodable } = require('./cp437util');
const { containsNonLatinCodepoints } = require('./string_util'); const { containsNonLatinCodepoints } = require('./string_util');
const { const {
isAnsi, isFormattedLine, isAnsi,
isFormattedLine,
splitTextAtTerms, splitTextAtTerms,
renderSubstr renderSubstr,
} = require('./string_util.js'); } = require('./string_util.js');
const ansiPrep = require('./ansi_prep.js'); const ansiPrep = require('./ansi_prep.js');
@ -30,7 +29,9 @@ const assert = require('assert');
const moment = require('moment'); const moment = require('moment');
const iconvEncode = require('iconv-lite').encode; const iconvEncode = require('iconv-lite').encode;
const ENIGMA_MESSAGE_UUID_NAMESPACE = uuidParse.parse('154506df-1df8-46b9-98f8-ebb5815baaf8'); const ENIGMA_MESSAGE_UUID_NAMESPACE = uuidParse.parse(
'154506df-1df8-46b9-98f8-ebb5815baaf8'
);
const WELL_KNOWN_AREA_TAGS = { const WELL_KNOWN_AREA_TAGS = {
Invalid: '', Invalid: '',
@ -101,12 +102,11 @@ const QWKPropertyNames = {
// :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',
modified_timestamp : 'modTimestamp' modified_timestamp: 'modTimestamp',
}; };
module.exports = class Message { module.exports = class Message {
constructor( constructor({
{
messageId = 0, messageId = 0,
areaTag = Message.WellKnownAreaTags.Invalid, areaTag = Message.WellKnownAreaTags.Invalid,
uuid, uuid,
@ -118,9 +118,7 @@ module.exports = class Message {
modTimestamp = moment(), modTimestamp = moment(),
meta, meta,
hashTags = [], hashTags = [],
} = { } } = {}) {
)
{
this.messageId = messageId; this.messageId = messageId;
this.areaTag = areaTag; this.areaTag = areaTag;
this.messageUuid = uuid; this.messageUuid = uuid;
@ -142,11 +140,14 @@ module.exports = class Message {
this.hashTags = hashTags; this.hashTags = hashTags;
} }
get uuid() { // deprecated, will be removed in the near future get uuid() {
// deprecated, will be removed in the near future
return this.messageUuid; return this.messageUuid;
} }
isValid() { return true; } // :TODO: obviously useless; look into this or remove it isValid() {
return true;
} // :TODO: obviously useless; look into this or remove it
static isPrivateAreaTag(areaTag) { static isPrivateAreaTag(areaTag) {
return areaTag.toLowerCase() === Message.WellKnownAreaTags.Private; return areaTag.toLowerCase() === Message.WellKnownAreaTags.Private;
@ -161,17 +162,21 @@ module.exports = class Message {
} }
isCP437Encodable() { isCP437Encodable() {
return isCP437Encodable(this.toUserName) && return (
isCP437Encodable(this.toUserName) &&
isCP437Encodable(this.fromUserName) && isCP437Encodable(this.fromUserName) &&
isCP437Encodable(this.subject) && isCP437Encodable(this.subject) &&
isCP437Encodable(this.message); isCP437Encodable(this.message)
);
} }
containsNonLatinCodepoints() { containsNonLatinCodepoints() {
return containsNonLatinCodepoints(this.toUserName) || return (
containsNonLatinCodepoints(this.toUserName) ||
containsNonLatinCodepoints(this.fromUserName) || containsNonLatinCodepoints(this.fromUserName) ||
containsNonLatinCodepoints(this.subject) || containsNonLatinCodepoints(this.subject) ||
containsNonLatinCodepoints(this.message); containsNonLatinCodepoints(this.message)
);
} }
/* /*
@ -196,7 +201,9 @@ module.exports = class Message {
*/ */
userHasDeleteRights(user) { userHasDeleteRights(user) {
const messageLocalUserId = parseInt(this.meta.System[Message.SystemMetaNames.LocalToUserID]); const messageLocalUserId = parseInt(
this.meta.System[Message.SystemMetaNames.LocalToUserID]
);
return (this.isPrivate() && user.userId === messageLocalUserId) || user.isSysOp(); return (this.isPrivate() && user.userId === messageLocalUserId) || user.isSysOp();
} }
@ -257,9 +264,17 @@ module.exports = class Message {
areaTag = iconvEncode(areaTag.toUpperCase(), 'CP437'); areaTag = iconvEncode(areaTag.toUpperCase(), 'CP437');
modTimestamp = iconvEncode(modTimestamp.format('DD MMM YY HH:mm:ss'), 'CP437'); modTimestamp = iconvEncode(modTimestamp.format('DD MMM YY HH:mm:ss'), 'CP437');
subject = iconvEncode(subject.toUpperCase().trim(), 'CP437'); subject = iconvEncode(subject.toUpperCase().trim(), 'CP437');
body = iconvEncode(body.replace(/\r\n|[\n\v\f\r\x85\u2028\u2029]/g, '').trim(), 'CP437'); body = iconvEncode(
body.replace(/\r\n|[\n\v\f\r\x85\u2028\u2029]/g, '').trim(),
'CP437'
);
return uuidParse.unparse(createNamedUUID(ENIGMA_MESSAGE_UUID_NAMESPACE, Buffer.concat( [ areaTag, modTimestamp, subject, body ] ))); return uuidParse.unparse(
createNamedUUID(
ENIGMA_MESSAGE_UUID_NAMESPACE,
Buffer.concat([areaTag, modTimestamp, subject, body])
)
);
} }
static getMessageFromRow(row) { static getMessageFromRow(row) {
@ -313,9 +328,17 @@ module.exports = class Message {
filter.operator = filter.operator || 'AND'; filter.operator = filter.operator || 'AND';
if ('messageList' === filter.resultType) { if ('messageList' === filter.resultType) {
filter.extraFields = _.uniq(filter.extraFields.concat( filter.extraFields = _.uniq(
[ 'area_tag', 'message_uuid', 'reply_to_message_id', 'to_user_name', 'from_user_name', 'subject', 'modified_timestamp' ] filter.extraFields.concat([
)); 'area_tag',
'message_uuid',
'reply_to_message_id',
'to_user_name',
'from_user_name',
'subject',
'modified_timestamp',
])
);
} }
const field = 'uuid' === filter.resultType ? 'message_uuid' : 'message_id'; const field = 'uuid' === filter.resultType ? 'message_uuid' : 'message_id';
@ -326,13 +349,14 @@ module.exports = class Message {
let sql; let sql;
if ('count' === filter.resultType) { if ('count' === filter.resultType) {
sql = sql = `SELECT COUNT() AS count
`SELECT COUNT() AS count
FROM message m`; FROM message m`;
} else { } else {
sql = sql = `SELECT DISTINCT m.${field}${
`SELECT DISTINCT m.${field}${filter.extraFields.length > 0 ? ', ' + filter.extraFields.map(f => `m.${f}`).join(', ') : ''} filter.extraFields.length > 0
? ', ' + filter.extraFields.map(f => `m.${f}`).join(', ')
: ''
}
FROM message m`; FROM message m`;
} }
@ -365,7 +389,6 @@ module.exports = class Message {
appendWhereClause(`m.message_id IN (${uuidList})`); appendWhereClause(`m.message_id IN (${uuidList})`);
} }
if (_.isNumber(filter.privateTagUserId)) { if (_.isNumber(filter.privateTagUserId)) {
appendWhereClause(`m.area_tag = "${Message.WellKnownAreaTags.Private}"`); appendWhereClause(`m.area_tag = "${Message.WellKnownAreaTags.Private}"`);
appendWhereClause( appendWhereClause(
@ -373,7 +396,8 @@ module.exports = class Message {
SELECT message_id SELECT message_id
FROM message_meta FROM message_meta
WHERE meta_category = "System" AND meta_name = "${Message.SystemMetaNames.LocalToUserID}" AND meta_value = ${filter.privateTagUserId} WHERE meta_category = "System" AND meta_name = "${Message.SystemMetaNames.LocalToUserID}" AND meta_value = ${filter.privateTagUserId}
)`); )`
);
} else { } else {
if (filter.areaTag && filter.areaTag.length > 0) { if (filter.areaTag && filter.areaTag.length > 0) {
if (!Array.isArray(filter.areaTag)) { if (!Array.isArray(filter.areaTag)) {
@ -382,7 +406,8 @@ module.exports = class Message {
const areaList = filter.areaTag const areaList = filter.areaTag
.filter(t => t !== Message.WellKnownAreaTags.Private) .filter(t => t !== Message.WellKnownAreaTags.Private)
.map(t => `"${t}"`).join(', '); .map(t => `"${t}"`)
.join(', ');
if (areaList.length > 0) { if (areaList.length > 0) {
appendWhereClause(`m.area_tag IN(${areaList})`); appendWhereClause(`m.area_tag IN(${areaList})`);
} else { } else {
@ -391,7 +416,10 @@ module.exports = class Message {
} }
} else { } else {
// explicit exclude of Private // explicit exclude of Private
appendWhereClause(`m.area_tag != "${Message.WellKnownAreaTags.Private}"`, 'AND'); appendWhereClause(
`m.area_tag != "${Message.WellKnownAreaTags.Private}"`,
'AND'
);
} }
} }
@ -408,18 +436,32 @@ module.exports = class Message {
val = [val]; val = [val];
} }
if (Array.isArray(val)) { if (Array.isArray(val)) {
val = '(' + val.map(v => { val =
'(' +
val
.map(v => {
return `m.${_.snakeCase(field)} LIKE "${sanitizeString(v)}"`; return `m.${_.snakeCase(field)} LIKE "${sanitizeString(v)}"`;
}).join(' OR ') + ')'; })
.join(' OR ') +
')';
appendWhereClause(val); appendWhereClause(val);
} }
}); });
if(_.isString(filter.newerThanTimestamp) && filter.newerThanTimestamp.length > 0) { if (
_.isString(filter.newerThanTimestamp) &&
filter.newerThanTimestamp.length > 0
) {
// :TODO: should be using "localtime" here? // :TODO: should be using "localtime" here?
appendWhereClause(`DATETIME(m.modified_timestamp) > DATETIME("${filter.newerThanTimestamp}", "+1 seconds")`); appendWhereClause(
`DATETIME(m.modified_timestamp) > DATETIME("${filter.newerThanTimestamp}", "+1 seconds")`
);
} else if (moment.isMoment(filter.date)) { } else if (moment.isMoment(filter.date)) {
appendWhereClause(`DATE(m.modified_timestamp, "localtime") = DATE("${filter.date.format('YYYY-MM-DD')}")`); appendWhereClause(
`DATE(m.modified_timestamp, "localtime") = DATE("${filter.date.format(
'YYYY-MM-DD'
)}")`
);
} }
if (_.isNumber(filter.newerThanMessageId)) { if (_.isNumber(filter.newerThanMessageId)) {
@ -440,7 +482,11 @@ module.exports = class Message {
if (Array.isArray(filter.metaTuples)) { if (Array.isArray(filter.metaTuples)) {
let sub = []; let sub = [];
filter.metaTuples.forEach(mt => { filter.metaTuples.forEach(mt => {
sub.push(`(meta_category = "${mt.category}" AND meta_name = "${mt.name}" AND meta_value = "${sanitizeString(mt.value)}")`); sub.push(
`(meta_category = "${mt.category}" AND meta_name = "${
mt.name
}" AND meta_value = "${sanitizeString(mt.value)}")`
);
}); });
sub = sub.join(` ${filter.operator} `); sub = sub.join(` ${filter.operator} `);
appendWhereClause( appendWhereClause(
@ -468,15 +514,22 @@ module.exports = class Message {
const matches = []; const matches = [];
const extra = filter.extraFields.length > 0; const extra = filter.extraFields.length > 0;
const rowConv = 'messageList' === filter.resultType ? Message.getMessageFromRow : row => row; const rowConv =
'messageList' === filter.resultType
? Message.getMessageFromRow
: row => row;
msgDb.each(sql, (err, row) => { msgDb.each(
sql,
(err, row) => {
if (_.isObject(row)) { if (_.isObject(row)) {
matches.push(extra ? rowConv(row) : row[field]); matches.push(extra ? rowConv(row) : row[field]);
} }
}, err => { },
err => {
return cb(err, matches); return cb(err, matches);
}); }
);
} }
} }
@ -493,7 +546,7 @@ module.exports = class Message {
return cb(err); return cb(err);
} }
const success = (row && row.message_id); const success = row && row.message_id;
return cb( return cb(
success ? null : Errors.DoesNotExist(`No message for UUID ${uuid}`), success ? null : Errors.DoesNotExist(`No message for UUID ${uuid}`),
success ? row.message_id : null success ? row.message_id : null
@ -513,14 +566,16 @@ module.exports = class Message {
if (err) { if (err) {
return cb(err); return cb(err);
} }
return cb(null, rows.map(r => parseInt(r.message_id))); // return array of ID(s) return cb(
null,
rows.map(r => parseInt(r.message_id))
); // return array of ID(s)
} }
); );
} }
static getMetaValuesByMessageId(messageId, category, name, cb) { static getMetaValuesByMessageId(messageId, category, name, cb) {
const sql = const sql = `SELECT meta_value
`SELECT meta_value
FROM message_meta FROM message_meta
WHERE message_id = ? AND meta_category = ? AND meta_name = ?;`; WHERE message_id = ? AND meta_category = ? AND meta_name = ?;`;
@ -538,7 +593,10 @@ module.exports = class Message {
return cb(null, rows[0].meta_value); return cb(null, rows[0].meta_value);
} }
return cb(null, rows.map(r => r.meta_value)); // map to array of values only return cb(
null,
rows.map(r => r.meta_value)
); // map to array of values only
}); });
} }
@ -551,10 +609,15 @@ module.exports = class Message {
}); });
}, },
function getMetaValues(messageId, callback) { function getMetaValues(messageId, callback) {
Message.getMetaValuesByMessageId(messageId, category, name, (err, values) => { Message.getMetaValuesByMessageId(
messageId,
category,
name,
(err, values) => {
return callback(err, values); return callback(err, values);
});
} }
);
},
], ],
(err, values) => { (err, values) => {
return cb(err, values); return cb(err, values);
@ -575,13 +638,15 @@ module.exports = class Message {
} }
} }
*/ */
const sql = const sql = `SELECT meta_category, meta_name, meta_value
`SELECT meta_category, meta_name, meta_value
FROM message_meta FROM message_meta
WHERE message_id = ?;`; WHERE message_id = ?;`;
const self = this; // :TODO: not required - arrow functions below: const self = this; // :TODO: not required - arrow functions below:
msgDb.each(sql, [ this.messageId ], (err, row) => { msgDb.each(
sql,
[this.messageId],
(err, row) => {
if (!(row.meta_category in self.meta)) { if (!(row.meta_category in self.meta)) {
self.meta[row.meta_category] = {}; self.meta[row.meta_category] = {};
self.meta[row.meta_category][row.meta_name] = row.meta_value; self.meta[row.meta_category][row.meta_name] = row.meta_value;
@ -590,15 +655,19 @@ module.exports = class Message {
self.meta[row.meta_category][row.meta_name] = row.meta_value; self.meta[row.meta_category][row.meta_name] = row.meta_value;
} else { } else {
if (_.isString(self.meta[row.meta_category][row.meta_name])) { if (_.isString(self.meta[row.meta_category][row.meta_name])) {
self.meta[row.meta_category][row.meta_name] = [ self.meta[row.meta_category][row.meta_name] ]; self.meta[row.meta_category][row.meta_name] = [
self.meta[row.meta_category][row.meta_name],
];
} }
self.meta[row.meta_category][row.meta_name].push(row.meta_value); self.meta[row.meta_category][row.meta_name].push(row.meta_value);
} }
} }
}, err => { },
err => {
return cb(err); return cb(err);
}); }
);
} }
load(loadWith, cb) { load(loadWith, cb) {
@ -623,7 +692,9 @@ module.exports = class Message {
} }
if (!msgRow) { if (!msgRow) {
return callback(Errors.DoesNotExist('Message (no longer) available')); return callback(
Errors.DoesNotExist('Message (no longer) available')
);
} }
self.messageId = msgRow.message_id; self.messageId = msgRow.message_id;
@ -636,7 +707,9 @@ module.exports = class Message {
self.message = msgRow.message; self.message = msgRow.message;
// We use parseZone() to *preserve* the time zone information // We use parseZone() to *preserve* the time zone information
self.modTimestamp = moment.parseZone(msgRow.modified_timestamp); self.modTimestamp = moment.parseZone(
msgRow.modified_timestamp
);
return callback(err); return callback(err);
} }
@ -650,7 +723,7 @@ module.exports = class Message {
function loadHashTags(callback) { function loadHashTags(callback) {
// :TODO: // :TODO:
return callback(null); return callback(null);
} },
], ],
err => { err => {
return cb(err); return cb(err);
@ -666,7 +739,8 @@ module.exports = class Message {
const metaStmt = transOrDb.prepare( const metaStmt = transOrDb.prepare(
`INSERT INTO message_meta (message_id, meta_category, meta_name, meta_value) `INSERT INTO message_meta (message_id, meta_category, meta_name, meta_value)
VALUES (?, ?, ?, ?);`); VALUES (?, ?, ?, ?);`
);
if (!_.isArray(value)) { if (!_.isArray(value)) {
value = [value]; value = [value];
@ -674,13 +748,17 @@ module.exports = class Message {
const self = this; const self = this;
async.each(value, (v, next) => { async.each(
value,
(v, next) => {
metaStmt.run(self.messageId, category, name, v, err => { metaStmt.run(self.messageId, category, name, v, err => {
return next(err); return next(err);
}); });
}, err => { },
err => {
return cb(err); return cb(err);
}); }
);
} }
persist(cb) { persist(cb) {
@ -710,10 +788,17 @@ module.exports = class Message {
`INSERT INTO message (area_tag, message_uuid, reply_to_message_id, to_user_name, from_user_name, subject, message, modified_timestamp) `INSERT INTO message (area_tag, message_uuid, reply_to_message_id, to_user_name, from_user_name, subject, message, modified_timestamp)
VALUES (?, ?, ?, ?, ?, ?, ?, ?);`, VALUES (?, ?, ?, ?, ?, ?, ?, ?);`,
[ [
self.areaTag, self.messageUuid, self.replyToMsgId, self.toUserName, self.areaTag,
self.fromUserName, self.subject, self.message, getISOTimestampString(self.modTimestamp) self.messageUuid,
self.replyToMsgId,
self.toUserName,
self.fromUserName,
self.subject,
self.message,
getISOTimestampString(self.modTimestamp),
], ],
function inserted(err) { // use non-arrow function for 'this' scope function inserted(err) {
// use non-arrow function for 'this' scope
if (!err) { if (!err) {
self.messageId = this.lastID; self.messageId = this.lastID;
} }
@ -738,23 +823,36 @@ module.exports = class Message {
} }
} }
*/ */
async.each(Object.keys(self.meta), (category, nextCat) => { async.each(
async.each(Object.keys(self.meta[category]), (name, nextName) => { Object.keys(self.meta),
self.persistMetaValue(category, name, self.meta[category][name], trans, err => { (category, nextCat) => {
async.each(
Object.keys(self.meta[category]),
(name, nextName) => {
self.persistMetaValue(
category,
name,
self.meta[category][name],
trans,
err => {
return nextName(err); return nextName(err);
}); }
}, err => { );
},
err => {
return nextCat(err); return nextCat(err);
}); }
);
}, err => { },
err => {
return callback(err, trans); return callback(err, trans);
}); }
);
}, },
function storeHashTags(trans, callback) { function storeHashTags(trans, callback) {
// :TODO: hash tag support // :TODO: hash tag support
return callback(null, trans); return callback(null, trans);
} },
], ],
(err, trans) => { (err, trans) => {
if (trans) { if (trans) {
@ -770,7 +868,9 @@ module.exports = class Message {
deleteMessage(requestingUser, cb) { deleteMessage(requestingUser, cb) {
if (!this.userHasDeleteRights(requestingUser)) { if (!this.userHasDeleteRights(requestingUser)) {
return cb(Errors.AccessDenied('User does not have rights to delete this message')); return cb(
Errors.AccessDenied('User does not have rights to delete this message')
);
} }
msgDb.run( msgDb.run(
@ -802,8 +902,12 @@ module.exports = class Message {
options.startCol = options.startCol || 1; options.startCol = options.startCol || 1;
options.includePrefix = _.get(options, 'includePrefix', true); options.includePrefix = _.get(options, 'includePrefix', true);
options.ansiResetSgr = options.ansiResetSgr || ANSI.getSGRFromGraphicRendition( { fg : 39, bg : 49 }, true); options.ansiResetSgr =
options.ansiFocusPrefixSgr = options.ansiFocusPrefixSgr || ANSI.getSGRFromGraphicRendition( { intensity : 'bold', fg : 39, bg : 49 } ); options.ansiResetSgr ||
ANSI.getSGRFromGraphicRendition({ fg: 39, bg: 49 }, true);
options.ansiFocusPrefixSgr =
options.ansiFocusPrefixSgr ||
ANSI.getSGRFromGraphicRendition({ intensity: 'bold', fg: 39, bg: 49 });
options.isAnsi = options.isAnsi || isAnsi(this.message); // :TODO: If this.isAnsi, use that setting options.isAnsi = options.isAnsi || isAnsi(this.message); // :TODO: If this.isAnsi, use that setting
/* /*
@ -817,7 +921,9 @@ module.exports = class Message {
Ot> Nu> right after doing so, don't ya think? yeah I think so Ot> Nu> right after doing so, don't ya think? yeah I think so
*/ */
const quotePrefix = options.includePrefix ? this.getFTNQuotePrefix(options.prefixSource || 'fromUserName') : ''; const quotePrefix = options.includePrefix
? this.getFTNQuotePrefix(options.prefixSource || 'fromUserName')
: '';
function getWrapped(text, extraPrefix) { function getWrapped(text, extraPrefix) {
extraPrefix = extraPrefix ? ` ${extraPrefix}` : ''; extraPrefix = extraPrefix ? ` ${extraPrefix}` : '';
@ -829,7 +935,9 @@ module.exports = class Message {
}; };
return wordWrapText(text, wrapOpts).wrapped.map((w, i) => { return wordWrapText(text, wrapOpts).wrapped.map((w, i) => {
return i === 0 ? `${quotePrefix}${w}` : `${quotePrefix}${extraPrefix}${w}`; return i === 0
? `${quotePrefix}${w}`
: `${quotePrefix}${extraPrefix}${w}`;
}); });
} }
@ -876,8 +984,17 @@ module.exports = class Message {
split.forEach(l => { split.forEach(l => {
quoteLines.push(`${lastSgr}${l}`); quoteLines.push(`${lastSgr}${l}`);
focusQuoteLines.push(`${options.ansiFocusPrefixSgr}>${lastSgr}${renderSubstr(l, 1, l.length - 1)}`); focusQuoteLines.push(
lastSgr = (l.match(/(?:\x1b\x5b)[?=;0-9]*m(?!.*(?:\x1b\x5b)[?=;0-9]*m)/) || [])[0] || ''; // eslint-disable-line no-control-regex `${options.ansiFocusPrefixSgr}>${lastSgr}${renderSubstr(
l,
1,
l.length - 1
)}`
);
lastSgr =
(l.match(
/(?:\x1b\x5b)[?=;0-9]*m(?!.*(?:\x1b\x5b)[?=;0-9]*m)/
) || [])[0] || ''; // eslint-disable-line no-control-regex
}); });
quoteLines[quoteLines.length - 1] += options.ansiResetSgr; quoteLines[quoteLines.length - 1] += options.ansiResetSgr;
@ -894,7 +1011,10 @@ module.exports = class Message {
let tearLinePos = Message.getTearLinePosition(input); let tearLinePos = Message.getTearLinePosition(input);
tearLinePos = -1 === tearLinePos ? input.length : tearLinePos; // we just want the index or the entire string tearLinePos = -1 === tearLinePos ? input.length : tearLinePos; // we just want the index or the entire string
input.slice(0, tearLinePos).split(/\r\n\r\n|\n\n/).forEach(paragraph => { input
.slice(0, tearLinePos)
.split(/\r\n\r\n|\n\n/)
.forEach(paragraph => {
// //
// For each paragraph, a state machine: // For each paragraph, a state machine:
// - New line - line // - New line - line
@ -931,7 +1051,9 @@ module.exports = class Message {
case 'line': case 'line':
if (quoteMatch) { if (quoteMatch) {
if (isFormattedLine(line)) { if (isFormattedLine(line)) {
quoted.push(getFormattedLine(line.replace(/\s/, ''))); quoted.push(
getFormattedLine(line.replace(/\s/, ''))
);
} else { } else {
quoted.push(...getWrapped(buf, quoteMatch[1])); quoted.push(...getWrapped(buf, quoteMatch[1]));
state = 'quote_line'; state = 'quote_line';
@ -963,7 +1085,8 @@ module.exports = class Message {
quoted.push(getFormattedLine(line)); quoted.push(getFormattedLine(line));
} else { } else {
state = quoteMatch ? 'quote_line' : 'line'; state = quoteMatch ? 'quote_line' : 'line';
buf = 'line' === state ? line : line.replace(/\s/, ''); // trim *first* leading space, if any buf =
'line' === state ? line : line.replace(/\s/, ''); // trim *first* leading space, if any
} }
break; break;
} }
@ -972,7 +1095,10 @@ module.exports = class Message {
quoted.push(...getWrapped(buf, quoteMatch ? quoteMatch[1] : null)); quoted.push(...getWrapped(buf, quoteMatch ? quoteMatch[1] : null));
}); });
input.slice(tearLinePos).split(/\r?\n/).forEach(l => { input
.slice(tearLinePos)
.split(/\r?\n/)
.forEach(l => {
quoted.push(...getWrapped(l)); quoted.push(...getWrapped(l));
}); });

View File

@ -51,22 +51,31 @@ function startup(cb) {
// by default, private messages are NOT included // by default, private messages are NOT included
async.series( async.series(
[ [
(callback) => { callback => {
Message.findMessages({ resultType: 'count' }, (err, count) => { Message.findMessages({ resultType: 'count' }, (err, count) => {
if (count) { if (count) {
StatLog.setNonPersistentSystemStat(SysProps.MessageTotalCount, count); StatLog.setNonPersistentSystemStat(
SysProps.MessageTotalCount,
count
);
} }
return callback(err); return callback(err);
}); });
}, },
(callback) => { callback => {
Message.findMessages( { resultType : 'count', date : moment() }, (err, count) => { Message.findMessages(
{ resultType: 'count', date: moment() },
(err, count) => {
if (count) { if (count) {
StatLog.setNonPersistentSystemStat(SysProps.MessagesToday, count); StatLog.setNonPersistentSystemStat(
SysProps.MessagesToday,
count
);
} }
return callback(err); return callback(err);
});
} }
);
},
], ],
err => { err => {
return cb(err); return cb(err);
@ -149,7 +158,9 @@ function getAllAvailableMessageAreaTags(client, options) {
const areaOpts = Object.assign({}, options, { client }); const areaOpts = Object.assign({}, options, { client });
Object.keys(getAvailableMessageConferences(client, confOpts)).forEach(confTag => { Object.keys(getAvailableMessageConferences(client, confOpts)).forEach(confTag => {
areaTags.push(...Object.keys(getAvailableMessageAreasByConfTag(confTag, areaOpts))); areaTags.push(
...Object.keys(getAvailableMessageAreasByConfTag(confTag, areaOpts))
);
}); });
return areaTags; return areaTags;
@ -179,7 +190,10 @@ function getDefaultMessageConferenceTag(client, disableAcsCheck) {
// just use anything we can // just use anything we can
defaultConf = _.findKey(config.messageConferences, (conf, confTag) => { defaultConf = _.findKey(config.messageConferences, (conf, confTag) => {
return 'system_internal' !== confTag && (true === disableAcsCheck || client.acs.hasMessageConfRead(conf)); return (
'system_internal' !== confTag &&
(true === disableAcsCheck || client.acs.hasMessageConfRead(conf))
);
}); });
return defaultConf; return defaultConf;
@ -210,7 +224,7 @@ function getDefaultMessageAreaTagByConfTag(client, confTag, disableAcsCheck) {
if (Message.isPrivateAreaTag(areaTag)) { if (Message.isPrivateAreaTag(areaTag)) {
return false; return false;
} }
return (true === disableAcsCheck || client.acs.hasMessageAreaRead(area)); return true === disableAcsCheck || client.acs.hasMessageAreaRead(area);
}); });
return defaultArea; return defaultArea;
@ -241,7 +255,10 @@ function getSuitableMessageConfAndAreaTags(client) {
return; return;
} }
_.forEach(conf.areas, (area, at) => { _.forEach(conf.areas, (area, at) => {
if(!_.includes(Message.WellKnownAreaTags, at) && client.acs.hasMessageAreaRead(area)) { if (
!_.includes(Message.WellKnownAreaTags, at) &&
client.acs.hasMessageAreaRead(area)
) {
confTag = ct; confTag = ct;
areaTag = at; areaTag = at;
return false; // stop inner iteration return false; // stop inner iteration
@ -262,7 +279,7 @@ function getMessageConferenceByTag(confTag) {
function getMessageConfTagByAreaTag(areaTag) { function getMessageConfTagByAreaTag(areaTag) {
const confs = Config().messageConferences; const confs = Config().messageConferences;
return Object.keys(confs).find( (confTag) => { return Object.keys(confs).find(confTag => {
return _.has(confs, [confTag, 'areas', areaTag]); return _.has(confs, [confTag, 'areas', areaTag]);
}); });
} }
@ -320,8 +337,13 @@ function changeMessageConference(client, confTag, cb) {
} }
}, },
function validateAccess(conf, areaInfo, callback) { function validateAccess(conf, areaInfo, callback) {
if(!client.acs.hasMessageConfRead(conf) || !client.acs.hasMessageAreaRead(areaInfo.area)) { if (
return callback(new Error('Access denied to message area and/or conference')); !client.acs.hasMessageConfRead(conf) ||
!client.acs.hasMessageAreaRead(areaInfo.area)
) {
return callback(
new Error('Access denied to message area and/or conference')
);
} else { } else {
return callback(null, conf, areaInfo); return callback(null, conf, areaInfo);
} }
@ -338,9 +360,15 @@ function changeMessageConference(client, confTag, cb) {
], ],
function complete(err, conf, areaInfo) { function complete(err, conf, areaInfo) {
if (!err) { if (!err) {
client.log.info( { confTag : confTag, confName : conf.name, areaTag : areaInfo.areaTag }, 'Current message conference changed'); client.log.info(
{ confTag: confTag, confName: conf.name, areaTag: areaInfo.areaTag },
'Current message conference changed'
);
} else { } else {
client.log.warn( { confTag : confTag, error : err.message }, 'Could not change message conference'); client.log.warn(
{ confTag: confTag, error: err.message },
'Could not change message conference'
);
} }
cb(err); cb(err);
} }
@ -368,20 +396,30 @@ function changeMessageAreaWithOptions(client, areaTag, options, cb) {
}, },
function changeArea(area, callback) { function changeArea(area, callback) {
if (true === options.persist) { if (true === options.persist) {
client.user.persistProperty(UserProps.MessageAreaTag, areaTag, function persisted(err) { client.user.persistProperty(
UserProps.MessageAreaTag,
areaTag,
function persisted(err) {
return callback(err, area); return callback(err, area);
}); }
);
} else { } else {
client.user.properties[UserProps.MessageAreaTag] = areaTag; client.user.properties[UserProps.MessageAreaTag] = areaTag;
return callback(null, area); return callback(null, area);
} }
} },
], ],
function complete(err, area) { function complete(err, area) {
if (!err) { if (!err) {
client.log.info( { areaTag : areaTag, area : area }, 'Current message area changed'); client.log.info(
{ areaTag: areaTag, area: area },
'Current message area changed'
);
} else { } else {
client.log.warn( { areaTag : areaTag, area : area, error : err.message }, 'Could not change message area'); client.log.warn(
{ areaTag: areaTag, area: area, error: err.message },
'Could not change message area'
);
} }
return cb(err); return cb(err);
@ -424,7 +462,9 @@ function hasMessageConfAndAreaRead(client, areaOrTag) {
areaOrTag = getMessageAreaByTag(areaOrTag) || {}; areaOrTag = getMessageAreaByTag(areaOrTag) || {};
} }
const conf = getMessageConferenceByTag(areaOrTag.confTag); const conf = getMessageConferenceByTag(areaOrTag.confTag);
return client.acs.hasMessageConfRead(conf) && client.acs.hasMessageAreaRead(areaOrTag); return (
client.acs.hasMessageConfRead(conf) && client.acs.hasMessageAreaRead(areaOrTag)
);
} }
function hasMessageConfAndAreaWrite(client, areaOrTag) { function hasMessageConfAndAreaWrite(client, areaOrTag) {
@ -432,7 +472,9 @@ function hasMessageConfAndAreaWrite(client, areaOrTag) {
areaOrTag = getMessageAreaByTag(areaOrTag) || {}; areaOrTag = getMessageAreaByTag(areaOrTag) || {};
} }
const conf = getMessageConferenceByTag(areaOrTag.confTag); const conf = getMessageConferenceByTag(areaOrTag.confTag);
return client.acs.hasMessageConfWrite(conf) && client.acs.hasMessageAreaWrite(areaOrTag); return (
client.acs.hasMessageConfWrite(conf) && client.acs.hasMessageAreaWrite(areaOrTag)
);
} }
function filterMessageAreaTagsByReadACS(client, areaTags) { function filterMessageAreaTagsByReadACS(client, areaTags) {
@ -509,15 +551,14 @@ function getNewMessagesInAreaForUser(userId, areaTag, cb) {
}); });
} }
function getMessageListForArea(client, areaTag, filter, cb) function getMessageListForArea(client, areaTag, filter, cb) {
{
if (!cb && _.isFunction(filter)) { if (!cb && _.isFunction(filter)) {
cb = filter; cb = filter;
filter = { filter = {
areaTag, areaTag,
resultType: 'messageList', resultType: 'messageList',
sort: 'messageId', sort: 'messageId',
order : 'ascending' order: 'ascending',
}; };
} else { } else {
Object.assign(filter, { areaTag }); Object.assign(filter, { areaTag });
@ -594,18 +635,25 @@ function updateMessageAreaLastReadId(userId, areaTag, messageId, allowOlder, cb)
} else { } else {
callback(null); callback(null);
} }
} },
], ],
function complete(err, didUpdate) { function complete(err, didUpdate) {
if (err) { if (err) {
Log.debug( Log.debug(
{ error : err.toString(), userId : userId, areaTag : areaTag, messageId : messageId }, {
'Failed updating area last read ID'); error: err.toString(),
userId: userId,
areaTag: areaTag,
messageId: messageId,
},
'Failed updating area last read ID'
);
} else { } else {
if (true === didUpdate) { if (true === didUpdate) {
Log.trace( Log.trace(
{ userId: userId, areaTag: areaTag, messageId: messageId }, { userId: userId, areaTag: areaTag, messageId: messageId },
'Area last read ID updated'); 'Area last read ID updated'
);
} }
} }
cb(err); cb(err);
@ -621,7 +669,7 @@ function persistMessage(message, cb) {
}, },
function recordToMessageNetworks(callback) { function recordToMessageNetworks(callback) {
return msgNetRecord(message, callback); return msgNetRecord(message, callback);
} },
], ],
cb cb
); );
@ -629,7 +677,6 @@ function persistMessage(message, cb) {
// method exposed for event scheduler // method exposed for event scheduler
function trimMessageAreasScheduledEvent(args, cb) { function trimMessageAreasScheduledEvent(args, cb) {
function trimMessageAreaByMaxMessages(areaInfo, cb) { function trimMessageAreaByMaxMessages(areaInfo, cb) {
if (0 === areaInfo.maxMessages) { if (0 === areaInfo.maxMessages) {
return cb(null); return cb(null);
@ -645,11 +692,18 @@ function trimMessageAreasScheduledEvent(args, cb) {
LIMIT -1 OFFSET ${areaInfo.maxMessages} LIMIT -1 OFFSET ${areaInfo.maxMessages}
);`, );`,
[areaInfo.areaTag.toLowerCase()], [areaInfo.areaTag.toLowerCase()],
function result(err) { // no arrow func; need this function result(err) {
// no arrow func; need this
if (err) { if (err) {
Log.error( { areaInfo : areaInfo, error : err.message, type : 'maxMessages' }, 'Error trimming message area'); Log.error(
{ areaInfo: areaInfo, error: err.message, type: 'maxMessages' },
'Error trimming message area'
);
} else { } else {
Log.debug( { areaInfo : areaInfo, type : 'maxMessages', count : this.changes }, 'Area trimmed successfully'); Log.debug(
{ areaInfo: areaInfo, type: 'maxMessages', count: this.changes },
'Area trimmed successfully'
);
} }
return cb(err); return cb(err);
} }
@ -665,11 +719,18 @@ function trimMessageAreasScheduledEvent(args, cb) {
`DELETE FROM message `DELETE FROM message
WHERE area_tag = ? AND modified_timestamp < date('now', '-${areaInfo.maxAgeDays} days');`, WHERE area_tag = ? AND modified_timestamp < date('now', '-${areaInfo.maxAgeDays} days');`,
[areaInfo.areaTag], [areaInfo.areaTag],
function result(err) { // no arrow func; need this function result(err) {
// no arrow func; need this
if (err) { if (err) {
Log.warn( { areaInfo : areaInfo, error : err.message, type : 'maxAgeDays' }, 'Error trimming message area'); Log.warn(
{ areaInfo: areaInfo, error: err.message, type: 'maxAgeDays' },
'Error trimming message area'
);
} else { } else {
Log.debug( { areaInfo : areaInfo, type : 'maxAgeDays', count : this.changes }, 'Area trimmed successfully'); Log.debug(
{ areaInfo: areaInfo, type: 'maxAgeDays', count: this.changes },
'Area trimmed successfully'
);
} }
return cb(err); return cb(err);
} }
@ -708,7 +769,6 @@ function trimMessageAreasScheduledEvent(args, cb) {
// determine maxMessages & maxAgeDays per area // determine maxMessages & maxAgeDays per area
const config = Config(); const config = Config();
areaTags.forEach(areaTag => { areaTags.forEach(areaTag => {
let maxMessages = config.messageAreaDefaults.maxMessages; let maxMessages = config.messageAreaDefaults.maxMessages;
let maxAgeDays = config.messageAreaDefaults.maxAgeDays; let maxAgeDays = config.messageAreaDefaults.maxAgeDays;
@ -773,17 +833,24 @@ function trimMessageAreasScheduledEvent(args, cb) {
(mmf.meta_category='System' AND mmf.meta_name='${Message.SystemMetaNames.ExternalFlavor}') (mmf.meta_category='System' AND mmf.meta_name='${Message.SystemMetaNames.ExternalFlavor}')
WHERE m.area_tag='${Message.WellKnownAreaTags.Private}' AND DATETIME('now') > DATETIME(m.modified_timestamp, '+${maxExternalSentAgeDays} days') WHERE m.area_tag='${Message.WellKnownAreaTags.Private}' AND DATETIME('now') > DATETIME(m.modified_timestamp, '+${maxExternalSentAgeDays} days')
);`, );`,
function results(err) { // no arrow func; need this function results(err) {
// no arrow func; need this
if (err) { if (err) {
Log.warn( { error : err.message }, 'Error trimming private externally sent messages'); Log.warn(
{ error: err.message },
'Error trimming private externally sent messages'
);
} else { } else {
Log.debug( { count : this.changes }, 'Private externally sent messages trimmed successfully'); Log.debug(
{ count: this.changes },
'Private externally sent messages trimmed successfully'
);
} }
} }
); );
return callback(null); return callback(null);
} },
], ],
err => { err => {
return cb(err); return cb(err);

View File

@ -35,7 +35,7 @@ const MciViewIds = {
progressBar: 2, progressBar: 2,
customRangeStart: 10, customRangeStart: 10,
} },
}; };
const UserProperties = { const UserProperties = {
@ -53,13 +53,20 @@ exports.getModule = class MessageBaseQWKExport extends MenuModule {
constructor(options) { constructor(options) {
super(options); super(options);
this.config = Object.assign({}, _.get(options, 'menuConfig.config'), options.extraArgs); this.config = Object.assign(
{},
_.get(options, 'menuConfig.config'),
options.extraArgs
);
this.config.progBarChar = renderSubstr( (this.config.progBarChar || '▒'), 0, 1); this.config.progBarChar = renderSubstr(this.config.progBarChar || '▒', 0, 1);
this.config.bbsID = this.config.bbsID || _.get(Config(), 'messageNetworks.qwk.bbsID', 'ENIGMA'); this.config.bbsID =
this.config.bbsID || _.get(Config(), 'messageNetworks.qwk.bbsID', 'ENIGMA');
this.tempName = `${UUIDv4().substr(-8).toUpperCase()}.QWK`; this.tempName = `${UUIDv4().substr(-8).toUpperCase()}.QWK`;
this.sysTempDownloadArea = FileArea.getFileAreaByTag(FileArea.WellKnownAreaTags.TempDownloads); this.sysTempDownloadArea = FileArea.getFileAreaByTag(
FileArea.WellKnownAreaTags.TempDownloads
);
} }
mciReady(mciData, cb) { mciReady(mciData, cb) {
@ -70,27 +77,38 @@ exports.getModule = class MessageBaseQWKExport extends MenuModule {
async.waterfall( async.waterfall(
[ [
(callback) => { callback => {
this.prepViewController('main', FormIds.main, mciData.menu, err => { this.prepViewController(
'main',
FormIds.main,
mciData.menu,
err => {
return callback(err); return callback(err);
}); }
);
}, },
(callback) => { callback => {
this.temptmp = temptmp.createTrackedSession('qwkuserexp'); this.temptmp = temptmp.createTrackedSession('qwkuserexp');
this.temptmp.mkdir({ prefix : 'enigqwkwriter-'}, (err, tempDir) => { this.temptmp.mkdir(
{ prefix: 'enigqwkwriter-' },
(err, tempDir) => {
if (err) { if (err) {
return callback(err); return callback(err);
} }
this.tempPacketDir = tempDir; this.tempPacketDir = tempDir;
const sysTempDownloadDir = FileArea.getAreaDefaultStorageDirectory(this.sysTempDownloadArea); const sysTempDownloadDir =
FileArea.getAreaDefaultStorageDirectory(
this.sysTempDownloadArea
);
// ensure dir exists // ensure dir exists
fse.mkdirs(sysTempDownloadDir, err => { fse.mkdirs(sysTempDownloadDir, err => {
return callback(err, sysTempDownloadDir); return callback(err, sysTempDownloadDir);
}); });
}); }
);
}, },
(sysTempDownloadDir, callback) => { (sysTempDownloadDir, callback) => {
this._performExport(sysTempDownloadDir, err => { this._performExport(sysTempDownloadDir, err => {
@ -104,7 +122,10 @@ exports.getModule = class MessageBaseQWKExport extends MenuModule {
if (err) { if (err) {
// :TODO: doesn't do anything currently: // :TODO: doesn't do anything currently:
if ('NORESULTS' === err.reasonCode) { if ('NORESULTS' === err.reasonCode) {
return this.gotoMenu(this.menuConfig.config.noResultsMenu || 'qwkExportNoResults'); return this.gotoMenu(
this.menuConfig.config.noResultsMenu ||
'qwkExportNoResults'
);
} }
return this.prevMenu(); return this.prevMenu();
@ -160,13 +181,15 @@ exports.getModule = class MessageBaseQWKExport extends MenuModule {
_performExport(sysTempDownloadDir, cb) { _performExport(sysTempDownloadDir, cb) {
const statusView = this.viewControllers.main.getView(MciViewIds.main.status); const statusView = this.viewControllers.main.getView(MciViewIds.main.status);
const updateStatus = (status) => { const updateStatus = status => {
if (statusView) { if (statusView) {
statusView.setText(status); statusView.setText(status);
} }
}; };
const progBarView = this.viewControllers.main.getView(MciViewIds.main.progressBar); const progBarView = this.viewControllers.main.getView(
MciViewIds.main.progressBar
);
const updateProgressBar = (curr, total) => { const updateProgressBar = (curr, total) => {
if (progBarView) { if (progBarView) {
const prog = Math.floor((curr / total) * progBarView.dimens.width); const prog = Math.floor((curr / total) * progBarView.dimens.width);
@ -184,13 +207,21 @@ exports.getModule = class MessageBaseQWKExport extends MenuModule {
case 'next_area': case 'next_area':
updateStatus(state.status); updateStatus(state.status);
updateProgressBar(0, 0); updateProgressBar(0, 0);
this.updateCustomViewTextsWithFilter('main', MciViewIds.main.customRangeStart, state); this.updateCustomViewTextsWithFilter(
'main',
MciViewIds.main.customRangeStart,
state
);
break; break;
case 'message': case 'message':
updateStatus(state.status); updateStatus(state.status);
updateProgressBar(state.current, state.total); updateProgressBar(state.current, state.total);
this.updateCustomViewTextsWithFilter('main', MciViewIds.main.customRangeStart, state); this.updateCustomViewTextsWithFilter(
'main',
MciViewIds.main.customRangeStart,
state
);
break; break;
default: default:
@ -217,7 +248,9 @@ exports.getModule = class MessageBaseQWKExport extends MenuModule {
} }
let current = 1; let current = 1;
async.eachSeries(messageIds, (messageId, nextMessageId) => { async.eachSeries(
messageIds,
(messageId, nextMessageId) => {
const message = new Message(); const message = new Message();
message.load({ messageId }, err => { message.load({ messageId }, err => {
if (err) { if (err) {
@ -230,7 +263,9 @@ exports.getModule = class MessageBaseQWKExport extends MenuModule {
total: ++totalExported, total: ++totalExported,
areaCurrent: current, areaCurrent: current,
areaCount: messageIds.length, areaCount: messageIds.length,
status : `${_.truncate(message.subject, { length : 25 })} (${current} / ${messageIds.length})`, status: `${_.truncate(message.subject, {
length: 25,
})} (${current} / ${messageIds.length})`,
}; };
progressHandler(progress, err => { progressHandler(progress, err => {
@ -247,7 +282,8 @@ exports.getModule = class MessageBaseQWKExport extends MenuModule {
}, },
err => { err => {
return cb(err); return cb(err);
}); }
);
}); });
}; };
@ -264,7 +300,7 @@ exports.getModule = class MessageBaseQWKExport extends MenuModule {
async.waterfall( async.waterfall(
[ [
(callback) => { callback => {
// don't count idle monitor while processing // don't count idle monitor while processing
this.client.stopIdleMonitor(); this.client.stopIdleMonitor();
@ -276,31 +312,41 @@ exports.getModule = class MessageBaseQWKExport extends MenuModule {
}); });
packetWriter.once('error', err => { packetWriter.once('error', err => {
this.client.log.error( { error : err.message }, 'QWK packet writer error'); this.client.log.error(
{ error: err.message },
'QWK packet writer error'
);
cancel = true; cancel = true;
}); });
packetWriter.init(); packetWriter.init();
}, },
(callback) => { callback => {
// For each public area -> for each message // For each public area -> for each message
const userExportAreas = this._getUserQWKExportAreas(); const userExportAreas = this._getUserQWKExportAreas();
const publicExportAreas = userExportAreas const publicExportAreas = userExportAreas.filter(exportArea => {
.filter(exportArea => {
return exportArea.areaTag !== Message.WellKnownAreaTags.Private; return exportArea.areaTag !== Message.WellKnownAreaTags.Private;
}); });
async.eachSeries(publicExportAreas, (exportArea, nextExportArea) => { async.eachSeries(
publicExportAreas,
(exportArea, nextExportArea) => {
const area = getMessageAreaByTag(exportArea.areaTag); const area = getMessageAreaByTag(exportArea.areaTag);
const conf = getMessageConferenceByTag(area.confTag); const conf = getMessageConferenceByTag(area.confTag);
if (!area || !conf) { if (!area || !conf) {
// :TODO: remove from user properties - this area does not exist // :TODO: remove from user properties - this area does not exist
this.client.log.warn({ areaTag : exportArea.areaTag }, 'Cannot QWK export area as it does not exist'); this.client.log.warn(
{ areaTag: exportArea.areaTag },
'Cannot QWK export area as it does not exist'
);
return nextExportArea(null); return nextExportArea(null);
} }
if (!hasMessageConfAndAreaRead(this.client, area)) { if (!hasMessageConfAndAreaRead(this.client, area)) {
this.client.log.warn({ areaTag : area.areaTag }, 'Cannot QWK export area due to ACS'); this.client.log.warn(
{ areaTag: area.areaTag },
'Cannot QWK export area due to ACS'
);
return nextExportArea(null); return nextExportArea(null);
} }
@ -319,7 +365,7 @@ exports.getModule = class MessageBaseQWKExport extends MenuModule {
const filter = { const filter = {
resultType: 'id', resultType: 'id',
areaTag: exportArea.areaTag, areaTag: exportArea.areaTag,
newerThanTimestamp : exportArea.newerThanTimestamp newerThanTimestamp: exportArea.newerThanTimestamp,
}; };
processMessagesWithFilter(filter, err => { processMessagesWithFilter(filter, err => {
@ -329,12 +375,16 @@ exports.getModule = class MessageBaseQWKExport extends MenuModule {
}, },
err => { err => {
return callback(err, userExportAreas); return callback(err, userExportAreas);
}); }
);
}, },
(userExportAreas, callback) => { (userExportAreas, callback) => {
// Private messages to current user if the user has // Private messages to current user if the user has
// elected to export private messages // elected to export private messages
const privateExportArea = userExportAreas.find(exportArea => exportArea.areaTag === Message.WellKnownAreaTags.Private); const privateExportArea = userExportAreas.find(
exportArea =>
exportArea.areaTag === Message.WellKnownAreaTags.Private
);
if (!privateExportArea) { if (!privateExportArea) {
return callback(null); return callback(null);
} }
@ -346,7 +396,7 @@ exports.getModule = class MessageBaseQWKExport extends MenuModule {
}; };
return processMessagesWithFilter(filter, callback); return processMessagesWithFilter(filter, callback);
}, },
(callback) => { callback => {
let packetInfo; let packetInfo;
packetWriter.once('packet', info => { packetWriter.once('packet', info => {
packetInfo = info; packetInfo = info;
@ -382,7 +432,7 @@ exports.getModule = class MessageBaseQWKExport extends MenuModule {
// :TODO: something like this: allow to override the displayed/downloaded as filename // :TODO: something like this: allow to override the displayed/downloaded as filename
// separate from the actual on disk filename. E.g. we could always download as "ENIGMA.QWK" // separate from the actual on disk filename. E.g. we could always download as "ENIGMA.QWK"
//visible_filename : paths.basename(packetInfo.path), //visible_filename : paths.basename(packetInfo.path),
} },
}); });
newEntry.desc = 'QWK Export'; newEntry.desc = 'QWK Export';
@ -395,13 +445,15 @@ exports.getModule = class MessageBaseQWKExport extends MenuModule {
return callback(err); return callback(err);
}); });
}, },
(callback) => { callback => {
// update user's export area dates; they can always change/reset them again // update user's export area dates; they can always change/reset them again
const updatedUserExportAreas = this._getUserQWKExportAreas().map(exportArea => { const updatedUserExportAreas = this._getUserQWKExportAreas().map(
exportArea => {
return Object.assign(exportArea, { return Object.assign(exportArea, {
newerThanTimestamp: getISOTimestampString(), newerThanTimestamp: getISOTimestampString(),
}); });
}); }
);
return this.client.user.persistProperty( return this.client.user.persistProperty(
UserProperties.ExportAreas, UserProperties.ExportAreas,

View File

@ -31,7 +31,7 @@ const MciViewIds = {
to: 5, to: 5,
from: 6, from: 6,
advSearch: 7, advSearch: 7,
} },
}; };
exports.getModule = class MessageBaseSearch extends MenuModule { exports.getModule = class MessageBaseSearch extends MenuModule {
@ -41,7 +41,7 @@ exports.getModule = class MessageBaseSearch extends MenuModule {
this.menuMethods = { this.menuMethods = {
search: (formData, extraArgs, cb) => { search: (formData, extraArgs, cb) => {
return this.searchNow(formData, cb); return this.searchNow(formData, cb);
} },
}; };
} }
@ -64,7 +64,9 @@ exports.getModule = class MessageBaseSearch extends MenuModule {
} }
const availConfs = [{ text: '-ALL-', data: '' }].concat( const availConfs = [{ text: '-ALL-', data: '' }].concat(
getSortedAvailMessageConferences(this.client).map(conf => Object.assign(conf, { text : conf.conf.name, data : conf.confTag } )) || [] getSortedAvailMessageConferences(this.client).map(conf =>
Object.assign(conf, { text: conf.conf.name, data: conf.confTag })
) || []
); );
let availAreas = [{ text: '-ALL-', data: '' }]; // note: will populate if conf changes from ALL let availAreas = [{ text: '-ALL-', data: '' }]; // note: will populate if conf changes from ALL
@ -77,8 +79,13 @@ exports.getModule = class MessageBaseSearch extends MenuModule {
confView.on('index update', idx => { confView.on('index update', idx => {
availAreas = [{ text: '-ALL-', data: '' }].concat( availAreas = [{ text: '-ALL-', data: '' }].concat(
getSortedAvailMessageAreasByConfTag(availConfs[idx].confTag, { client : this.client }).map( getSortedAvailMessageAreasByConfTag(availConfs[idx].confTag, {
area => Object.assign(area, { text : area.area.name, data : area.areaTag } ) client: this.client,
}).map(area =>
Object.assign(area, {
text: area.area.name,
data: area.areaTag,
})
) )
); );
areaView.setItems(availAreas); areaView.setItems(availAreas);
@ -119,7 +126,9 @@ exports.getModule = class MessageBaseSearch extends MenuModule {
// areaTag may be a string or array of strings // areaTag may be a string or array of strings
// getAvailableMessageAreasByConfTag() returns a obj - we only need tags // getAvailableMessageAreasByConfTag() returns a obj - we only need tags
filter.areaTag = _.map( filter.areaTag = _.map(
getAvailableMessageAreasByConfTag(value.confTag, { client : this.client } ), getAvailableMessageAreasByConfTag(value.confTag, {
client: this.client,
}),
(area, areaTag) => areaTag (area, areaTag) => areaTag
); );
} else if (value.areaTag) { } else if (value.areaTag) {
@ -149,7 +158,7 @@ exports.getModule = class MessageBaseSearch extends MenuModule {
const menuOpts = { const menuOpts = {
extraArgs: { extraArgs: {
messageList, messageList,
noUpdateLastReadId : true noUpdateLastReadId: true,
}, },
menuFlags: ['popParent'], menuFlags: ['popParent'],
}; };

View File

@ -21,17 +21,22 @@ function isProduction() {
} }
function isDevelopment() { function isDevelopment() {
return (!(isProduction())); return !isProduction();
} }
function valueWithDefault(val, defVal) { function valueWithDefault(val, defVal) {
return (typeof val !== 'undefined' ? val : defVal); return typeof val !== 'undefined' ? val : defVal;
} }
function resolvePath(path) { function resolvePath(path) {
if (path.substr(0, 2) === '~/') { if (path.substr(0, 2) === '~/') {
var mswCombined = process.env.HOMEDRIVE + process.env.HOMEPATH; var mswCombined = process.env.HOMEDRIVE + process.env.HOMEPATH;
path = (process.env.HOME || mswCombined || process.env.HOMEPATH || process.env.HOMEDIR || process.cwd()) + path.substr(1); path =
(process.env.HOME ||
mswCombined ||
process.env.HOMEPATH ||
process.env.HOMEDIR ||
process.cwd()) + path.substr(1);
} }
return paths.resolve(path); return paths.resolve(path);
} }
@ -40,8 +45,7 @@ function getCleanEnigmaVersion() {
return packageJson.version return packageJson.version
.replace(/-/g, '.') .replace(/-/g, '.')
.replace(/alpha/, 'a') .replace(/alpha/, 'a')
.replace(/beta/,'b') .replace(/beta/, 'b');
;
} }
// See also ftn_util.js getTearLine() & getProductIdentifier() // See also ftn_util.js getTearLine() & getProductIdentifier()

View File

@ -7,10 +7,11 @@ const UserProps = require('./user_property.js');
// deps // deps
const { get } = require('lodash'); const { get } = require('lodash');
exports.MessageAreaConfTempSwitcher = Sup => class extends Sup { exports.MessageAreaConfTempSwitcher = Sup =>
class extends Sup {
tempMessageConfAndAreaSwitch(messageAreaTag, recordPrevious = true) { tempMessageConfAndAreaSwitch(messageAreaTag, recordPrevious = true) {
messageAreaTag = messageAreaTag || get(this, 'config.messageAreaTag', this.messageAreaTag); messageAreaTag =
messageAreaTag || get(this, 'config.messageAreaTag', this.messageAreaTag);
if (!messageAreaTag) { if (!messageAreaTag) {
return; // nothing to do! return; // nothing to do!
} }
@ -23,14 +24,19 @@ exports.MessageAreaConfTempSwitcher = Sup => class extends Sup {
} }
if (!messageArea.tempChangeMessageConfAndArea(this.client, messageAreaTag)) { if (!messageArea.tempChangeMessageConfAndArea(this.client, messageAreaTag)) {
this.client.log.warn( { messageAreaTag : messageArea }, 'Failed to perform temporary message area/conf switch'); this.client.log.warn(
{ messageAreaTag: messageArea },
'Failed to perform temporary message area/conf switch'
);
} }
} }
tempMessageConfAndAreaRestore() { tempMessageConfAndAreaRestore() {
if (this.prevMessageConfAndArea) { if (this.prevMessageConfAndArea) {
this.client.user.properties[UserProps.MessageConfTag] = this.prevMessageConfAndArea.confTag; this.client.user.properties[UserProps.MessageConfTag] =
this.client.user.properties[UserProps.MessageAreaTag] = this.prevMessageConfAndArea.areaTag; this.prevMessageConfAndArea.confTag;
this.client.user.properties[UserProps.MessageAreaTag] =
this.prevMessageConfAndArea.areaTag;
} }
} }
}; };

View File

@ -4,10 +4,7 @@
// ENiGMA½ // ENiGMA½
const Config = require('./config.js').get; const Config = require('./config.js').get;
const Log = require('./logger.js').log; const Log = require('./logger.js').log;
const { const { Errors, ErrorReasons } = require('./enig_error.js');
Errors,
ErrorReasons
} = require('./enig_error.js');
// deps // deps
const fs = require('graceful-fs'); const fs = require('graceful-fs');
@ -29,10 +26,17 @@ function loadModuleEx(options, cb) {
assert(_.isString(options.name)); assert(_.isString(options.name));
assert(_.isString(options.path)); assert(_.isString(options.path));
const modConfig = _.isObject(Config[options.category]) ? Config[options.category][options.name] : null; const modConfig = _.isObject(Config[options.category])
? Config[options.category][options.name]
: null;
if (_.isObject(modConfig) && false === modConfig.enabled) { if (_.isObject(modConfig) && false === modConfig.enabled) {
return cb(Errors.AccessDenied(`Module "${options.name}" is disabled`, ErrorReasons.Disabled)); return cb(
Errors.AccessDenied(
`Module "${options.name}" is disabled`,
ErrorReasons.Disabled
)
);
} }
// //
@ -58,11 +62,15 @@ function loadModuleEx(options, cb) {
} }
if (!_.isObject(mod.moduleInfo)) { if (!_.isObject(mod.moduleInfo)) {
return cb(Errors.Invalid(`No exported "moduleInfo" block for module ${modPath}!`)); return cb(
Errors.Invalid(`No exported "moduleInfo" block for module ${modPath}!`)
);
} }
if (!_.isFunction(mod.getModule)) { if (!_.isFunction(mod.getModule)) {
return cb(Errors.Invalid(`No exported "getModule" method for module ${modPath}!`)); return cb(
Errors.Invalid(`No exported "getModule" method for module ${modPath}!`)
);
} }
return cb(null, mod); return cb(null, mod);
@ -72,16 +80,22 @@ function loadModule(name, category, cb) {
const path = Config().paths[category]; const path = Config().paths[category];
if (!_.isString(path)) { if (!_.isString(path)) {
return cb(Errors.DoesNotExist(`Not sure where to look for module "${name}" of category "${category}"`)); return cb(
Errors.DoesNotExist(
`Not sure where to look for module "${name}" of category "${category}"`
)
);
} }
loadModuleEx( { name : name, path : path, category : category }, function loaded(err, mod) { loadModuleEx(
{ name: name, path: path, category: category },
function loaded(err, mod) {
return cb(err, mod); return cb(err, mod);
}); }
);
} }
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);
@ -91,7 +105,9 @@ function loadModulesForCategory(category, iterator, complete) {
return '.js' === paths.extname(file); return '.js' === paths.extname(file);
}); });
async.each(jsModules, (file, next) => { async.each(
jsModules,
(file, next) => {
loadModule(paths.basename(file, '.js'), category, (err, mod) => { loadModule(paths.basename(file, '.js'), category, (err, mod) => {
if (err) { if (err) {
if (ErrorReasons.Disabled === err.reasonCode) { if (ErrorReasons.Disabled === err.reasonCode) {
@ -103,11 +119,13 @@ function loadModulesForCategory(category, iterator, complete) {
} }
return iterator(mod, next); return iterator(mod, next);
}); });
}, err => { },
err => {
if (complete) { if (complete) {
return complete(err); return complete(err);
} }
}); }
);
}); });
} }
@ -127,7 +145,9 @@ function initializeModules(cb) {
const modulePaths = getModulePaths().concat(__dirname); const modulePaths = getModulePaths().concat(__dirname);
async.each(modulePaths, (modulePath, nextPath) => { async.each(
modulePaths,
(modulePath, nextPath) => {
glob('*{.js,/*.js}', { cwd: modulePath }, (err, files) => { glob('*{.js,/*.js}', { cwd: modulePath }, (err, files) => {
if (err) { if (err) {
return nextPath(err); return nextPath(err);
@ -135,7 +155,9 @@ function initializeModules(cb) {
const ourPath = paths.join(__dirname, __filename); const ourPath = paths.join(__dirname, __filename);
async.each(files, (moduleName, nextModule) => { async.each(
files,
(moduleName, nextModule) => {
const fullModulePath = paths.join(modulePath, moduleName); const fullModulePath = paths.join(modulePath, moduleName);
if (ourPath === fullModulePath) { if (ourPath === fullModulePath) {
return nextModule(null); return nextModule(null);
@ -151,7 +173,13 @@ function initializeModules(cb) {
mod.moduleInitialize(initInfo, err => { mod.moduleInitialize(initInfo, err => {
if (err) { if (err) {
Log.warn( { error : err.message, modulePath : fullModulePath }, 'Error during "moduleInitialize"'); Log.warn(
{
error: err.message,
modulePath: fullModulePath,
},
'Error during "moduleInitialize"'
);
} }
return nextModule(null); return nextModule(null);
}); });
@ -159,16 +187,21 @@ function initializeModules(cb) {
return nextModule(null); return nextModule(null);
} }
} catch (e) { } catch (e) {
Log.warn( { error : e.message, fullModulePath }, 'Exception during "moduleInitialize"'); Log.warn(
{ error: e.message, fullModulePath },
'Exception during "moduleInitialize"'
);
return nextModule(null); return nextModule(null);
} }
}, },
err => { err => {
return nextPath(err); return nextPath(err);
}); }
);
}); });
}, },
err => { err => {
return cb(err); return cb(err);
}); }
);
} }

View File

@ -4,10 +4,7 @@
// ENiGMA½ // ENiGMA½
const Log = require('./logger.js').log; const Log = require('./logger.js').log;
const { MenuModule } = require('./menu_module.js'); const { MenuModule } = require('./menu_module.js');
const { const { pipeToAnsi, stripMciColorCodes } = require('./color_codes.js');
pipeToAnsi,
stripMciColorCodes
} = require('./color_codes.js');
const stringFormat = require('./string_format.js'); const stringFormat = require('./string_format.js');
const StringUtil = require('./string_util.js'); const StringUtil = require('./string_util.js');
const Config = require('./config.js').get; const Config = require('./config.js').get;
@ -43,11 +40,9 @@ const MciViewIds = {
mrcBbses: 6, mrcBbses: 6,
customRangeStart: 20, // 20+ = customs customRangeStart: 20, // 20+ = customs
} },
}; };
// TODO: this is a bit shit, could maybe do it with an ansi instead // TODO: this is a bit shit, could maybe do it with an ansi instead
const helpText = ` const helpText = `
|15General Chat|08: |15General Chat|08:
@ -66,13 +61,14 @@ const helpText = `
|03/|11rainbow |03<your message> |08- |07Crazy rainbow text |03/|11rainbow |03<your message> |08- |07Crazy rainbow text
`; `;
exports.getModule = class mrcModule extends MenuModule { exports.getModule = class mrcModule extends MenuModule {
constructor(options) { constructor(options) {
super(options); super(options);
this.log = Log.child({ module: 'MRC' }); this.log = Log.child({ module: 'MRC' });
this.config = Object.assign({}, _.get(options, 'menuConfig.config'), { extraArgs : options.extraArgs }); this.config = Object.assign({}, _.get(options, 'menuConfig.config'), {
extraArgs: options.extraArgs,
});
this.config.maxScrollbackLines = this.config.maxScrollbackLines || 500; this.config.maxScrollbackLines = this.config.maxScrollbackLines || 500;
@ -98,10 +94,10 @@ exports.getModule = class mrcModule extends MenuModule {
}; };
this.menuMethods = { this.menuMethods = {
sendChatMessage: (formData, extraArgs, cb) => { sendChatMessage: (formData, extraArgs, cb) => {
const inputAreaView = this.viewControllers.mrcChat.getView(
const inputAreaView = this.viewControllers.mrcChat.getView(MciViewIds.mrcChat.inputArea); MciViewIds.mrcChat.inputArea
);
const inputData = inputAreaView.getData(); const inputData = inputAreaView.getData();
this.processOutgoingMessage(inputData); this.processOutgoingMessage(inputData);
@ -111,12 +107,22 @@ exports.getModule = class mrcModule extends MenuModule {
}, },
movementKeyPressed: (formData, extraArgs, cb) => { movementKeyPressed: (formData, extraArgs, cb) => {
const bodyView = this.viewControllers.mrcChat.getView(MciViewIds.mrcChat.chatLog); const bodyView = this.viewControllers.mrcChat.getView(
MciViewIds.mrcChat.chatLog
);
switch (formData.key.name) { switch (formData.key.name) {
case 'down arrow' : bodyView.scrollDocumentUp(); break; case 'down arrow':
case 'up arrow' : bodyView.scrollDocumentDown(); break; bodyView.scrollDocumentUp();
case 'page up' : bodyView.keyPressPageUp(); break; break;
case 'page down' : bodyView.keyPressPageDown(); break; case 'up arrow':
bodyView.scrollDocumentDown();
break;
case 'page up':
bodyView.keyPressPageUp();
break;
case 'page down':
bodyView.keyPressPageDown();
break;
} }
this.viewControllers.mrcChat.switchFocus(MciViewIds.mrcChat.inputArea); this.viewControllers.mrcChat.switchFocus(MciViewIds.mrcChat.inputArea);
@ -131,7 +137,7 @@ exports.getModule = class mrcModule extends MenuModule {
clearMessages: (formData, extraArgs, cb) => { clearMessages: (formData, extraArgs, cb) => {
this.clearMessages(); this.clearMessages();
return cb(null); return cb(null);
} },
}; };
} }
@ -143,15 +149,28 @@ exports.getModule = class mrcModule extends MenuModule {
async.series( async.series(
[ [
(callback) => { callback => {
return this.prepViewController('mrcChat', FormIds.mrcChat, mciData.menu, callback); return this.prepViewController(
'mrcChat',
FormIds.mrcChat,
mciData.menu,
callback
);
}, },
(callback) => { callback => {
return this.validateMCIByViewIds('mrcChat', [ MciViewIds.mrcChat.chatLog, MciViewIds.mrcChat.inputArea ], callback); return this.validateMCIByViewIds(
'mrcChat',
[MciViewIds.mrcChat.chatLog, MciViewIds.mrcChat.inputArea],
callback
);
}, },
(callback) => { callback => {
const connectOpts = { const connectOpts = {
port : _.get(Config(), 'chatServers.mrc.multiplexerPort', 5000), port: _.get(
Config(),
'chatServers.mrc.multiplexerPort',
5000
),
host: 'localhost', host: 'localhost',
}; };
@ -173,12 +192,22 @@ exports.getModule = class mrcModule extends MenuModule {
}, 60000); }, 60000);
// override idle logout seconds if configured // override idle logout seconds if configured
const idleLogoutSeconds = parseInt(this.config.idleLogoutSeconds); const idleLogoutSeconds = parseInt(
this.config.idleLogoutSeconds
);
if (0 === idleLogoutSeconds) { if (0 === idleLogoutSeconds) {
this.log.debug('Temporary disable idle monitor due to config'); this.log.debug(
'Temporary disable idle monitor due to config'
);
this.client.stopIdleMonitor(); this.client.stopIdleMonitor();
} else if (!isNaN(idleLogoutSeconds) && idleLogoutSeconds >= 60) { } else if (
this.log.debug( { idleLogoutSeconds }, 'Temporary override idle logout seconds due to config'); !isNaN(idleLogoutSeconds) &&
idleLogoutSeconds >= 60
) {
this.log.debug(
{ idleLogoutSeconds },
'Temporary override idle logout seconds due to config'
);
this.client.overrideIdleLogoutSeconds(idleLogoutSeconds); this.client.overrideIdleLogoutSeconds(idleLogoutSeconds);
} }
}); });
@ -190,7 +219,10 @@ exports.getModule = class mrcModule extends MenuModule {
}); });
this.state.socket.once('error', err => { this.state.socket.once('error', err => {
this.log.warn( { error : err.message }, 'MRC multiplexer socket error' ); this.log.warn(
{ error: err.message },
'MRC multiplexer socket error'
);
this.state.socket.destroy(); this.state.socket.destroy();
delete this.state.socket; delete this.state.socket;
@ -198,8 +230,8 @@ exports.getModule = class mrcModule extends MenuModule {
return callback(err); return callback(err);
}); });
return(callback); return callback;
} },
], ],
err => { err => {
return cb(err); return cb(err);
@ -238,7 +270,9 @@ exports.getModule = class mrcModule extends MenuModule {
} }
message.forEach(msg => { message.forEach(msg => {
const chatLogView = this.viewControllers.mrcChat.getView(MciViewIds.mrcChat.chatLog); const chatLogView = this.viewControllers.mrcChat.getView(
MciViewIds.mrcChat.chatLog
);
const messageLength = stripMciColorCodes(msg).length; const messageLength = stripMciColorCodes(msg).length;
const chatWidth = chatLogView.dimens.width; const chatWidth = chatLogView.dimens.width;
let padAmount = 0; let padAmount = 0;
@ -266,7 +300,6 @@ exports.getModule = class mrcModule extends MenuModule {
*/ */
processReceivedMessage(blob) { processReceivedMessage(blob) {
blob.split('\n').forEach(message => { blob.split('\n').forEach(message => {
try { try {
message = JSON.parse(message); message = JSON.parse(message);
} catch (e) { } catch (e) {
@ -300,22 +333,19 @@ exports.getModule = class mrcModule extends MenuModule {
break; break;
case 'STATS': { case 'STATS': {
const [ const [boardCount, roomCount, userCount, activityLevel] =
params[1].split(' ').map(v => parseInt(v));
const activityLevelIndicator =
this.getActivityLevelIndicator(activityLevel);
Object.assign(this.customFormatObj, {
boardCount, boardCount,
roomCount, roomCount,
userCount, userCount,
activityLevel activityLevel,
] = params[1].split(' ').map(v => parseInt(v)); activityLevelIndicator,
});
const activityLevelIndicator = this.getActivityLevelIndicator(activityLevel);
Object.assign(
this.customFormatObj,
{
boardCount, roomCount, userCount,
activityLevel, activityLevelIndicator
}
);
this.setText(MciViewIds.mrcChat.mrcUsers, userCount); this.setText(MciViewIds.mrcChat.mrcUsers, userCount);
this.setText(MciViewIds.mrcChat.mrcBbses, boardCount); this.setText(MciViewIds.mrcChat.mrcBbses, boardCount);
@ -328,18 +358,22 @@ exports.getModule = class mrcModule extends MenuModule {
this.addMessageToChatLog(message.body); this.addMessageToChatLog(message.body);
break; break;
} }
} else { } else {
if (message.body === this.state.lastSentMsg.msg) { if (message.body === this.state.lastSentMsg.msg) {
this.customFormatObj.latencyMs = this.customFormatObj.latencyMs = moment
moment.duration(moment().diff(this.state.lastSentMsg.time)).asMilliseconds(); .duration(moment().diff(this.state.lastSentMsg.time))
.asMilliseconds();
delete this.state.lastSentMsg.msg; delete this.state.lastSentMsg.msg;
} }
if (message.to_room == this.state.room) { if (message.to_room == this.state.room) {
// if we're here then we want to show it to the user // if we're here then we want to show it to the user
const currentTime = moment().format(this.client.currentTheme.helpers.getTimeFormat()); const currentTime = moment().format(
this.addMessageToChatLog('|08' + currentTime + '|00 ' + message.body + '|00'); this.client.currentTheme.helpers.getTimeFormat()
);
this.addMessageToChatLog(
'|08' + currentTime + '|00 ' + message.body + '|00'
);
} }
} }
@ -384,7 +418,7 @@ exports.getModule = class mrcModule extends MenuModule {
const textFormatObj = { const textFormatObj = {
fromUserName: this.state.alias, fromUserName: this.state.alias,
toUserName: to_user, toUserName: to_user,
message : message message: message,
}; };
const messageFormat = const messageFormat =
@ -409,12 +443,16 @@ exports.getModule = class mrcModule extends MenuModule {
msg: formattedMessage, msg: formattedMessage,
time: moment(), time: moment(),
}; };
this.sendMessageToMultiplexer(to_user || '', '', this.state.room, formattedMessage); this.sendMessageToMultiplexer(
to_user || '',
'',
this.state.room,
formattedMessage
);
} catch (e) { } catch (e) {
this.client.log.warn({ error: e.message }, 'MRC error'); this.client.log.warn({ error: e.message }, 'MRC error');
} }
} }
} }
/** /**
@ -432,24 +470,35 @@ exports.getModule = class mrcModule extends MenuModule {
case 'rainbow': { case 'rainbow': {
// this is brutal, but i love it // this is brutal, but i love it
const line = message.replace(/^\/rainbow\s/, '').split(' ').reduce(function (a, c) { const line = message
const cc = Math.floor((Math.random() * 31) + 1).toString().padStart(2, '0'); .replace(/^\/rainbow\s/, '')
.split(' ')
.reduce(function (a, c) {
const cc = Math.floor(Math.random() * 31 + 1)
.toString()
.padStart(2, '0');
a += `|${cc}${c}|00 `; a += `|${cc}${c}|00 `;
return a; return a;
}, '').substr(0, 140).replace(/\\s\|\d*$/, ''); }, '')
.substr(0, 140)
.replace(/\\s\|\d*$/, '');
this.processOutgoingMessage(line); this.processOutgoingMessage(line);
break; break;
} }
case 'l33t': case 'l33t':
this.processOutgoingMessage(StringUtil.stylizeString(message.substr(6), 'l33t')); this.processOutgoingMessage(
StringUtil.stylizeString(message.substr(6), 'l33t')
);
break; break;
case 'kewl': { case 'kewl': {
const text_modes = Array('f', 'v', 'V', 'i', 'M'); const text_modes = Array('f', 'v', 'V', 'i', 'M');
const mode = text_modes[Math.floor(Math.random() * text_modes.length)]; const mode = text_modes[Math.floor(Math.random() * text_modes.length)];
this.processOutgoingMessage(StringUtil.stylizeString(message.substr(6), mode)); this.processOutgoingMessage(
StringUtil.stylizeString(message.substr(6), mode)
);
break; break;
} }
@ -470,7 +519,9 @@ exports.getModule = class mrcModule extends MenuModule {
break; break;
case 'topic': case 'topic':
this.sendServerMessage(`NEWTOPIC:${this.state.room}:${message.substr(7)}`); this.sendServerMessage(
`NEWTOPIC:${this.state.room}:${message.substr(7)}`
);
break; break;
case 'info': case 'info':
@ -501,7 +552,6 @@ exports.getModule = class mrcModule extends MenuModule {
break; break;
default: default:
break; break;
} }
@ -511,7 +561,9 @@ exports.getModule = class mrcModule extends MenuModule {
} }
clearMessages() { clearMessages() {
const chatLogView = this.viewControllers.mrcChat.getView(MciViewIds.mrcChat.chatLog); const chatLogView = this.viewControllers.mrcChat.getView(
MciViewIds.mrcChat.chatLog
);
chatLogView.setText(''); chatLogView.setText('');
} }
@ -519,7 +571,6 @@ exports.getModule = class mrcModule extends MenuModule {
* Creates a json object, stringifies it and sends it to the MRC multiplexer * Creates a json object, stringifies it and sends it to the MRC multiplexer
*/ */
sendMessageToMultiplexer(to_user, to_site, to_room, body) { sendMessageToMultiplexer(to_user, to_site, to_room, body) {
const message = { const message = {
to_user, to_user,
to_site, to_site,
@ -570,7 +621,3 @@ exports.getModule = class mrcModule extends MenuModule {
this.sendHeartbeat(); this.sendHeartbeat();
} }
}; };

View File

@ -38,7 +38,9 @@ exports.getModule = class MessageAreaListModule extends MenuModule {
messageArea.changeMessageArea(this.client, area.areaTag, err => { messageArea.changeMessageArea(this.client, area.areaTag, err => {
if (err) { if (err) {
this.client.term.pipeWrite(`\n|00Cannot change area: ${err.message}\n`); this.client.term.pipeWrite(
`\n|00Cannot change area: ${err.message}\n`
);
return this.prevMenuOnTimeout(1000, cb); return this.prevMenuOnTimeout(1000, cb);
} }
@ -47,10 +49,15 @@ exports.getModule = class MessageAreaListModule extends MenuModule {
extraArgs: { extraArgs: {
areaTag: area.areaTag, areaTag: area.areaTag,
}, },
menuFlags : [ 'popParent', 'noHistory' ] menuFlags: ['popParent', 'noHistory'],
}; };
return this.gotoMenu(this.menuConfig.config.changeAreaPreArtMenu || 'changeMessageAreaPreArt', menuOpts, cb); return this.gotoMenu(
this.menuConfig.config.changeAreaPreArtMenu ||
'changeMessageAreaPreArt',
menuOpts,
cb
);
} }
return this.prevMenu(cb); return this.prevMenu(cb);
@ -58,7 +65,7 @@ exports.getModule = class MessageAreaListModule extends MenuModule {
} else { } else {
return cb(null); return cb(null);
} }
} },
}; };
} }
@ -70,13 +77,19 @@ exports.getModule = class MessageAreaListModule extends MenuModule {
async.series( async.series(
[ [
(next) => { next => {
return this.prepViewController('areaList', 0, mciData.menu, next); return this.prepViewController('areaList', 0, mciData.menu, next);
}, },
(next) => { next => {
const areaListView = this.viewControllers.areaList.getView(MciViewIds.areaList); const areaListView = this.viewControllers.areaList.getView(
MciViewIds.areaList
);
if (!areaListView) { if (!areaListView) {
return cb(Errors.MissingMci(`Missing area list MCI ${MciViewIds.areaList}`)); return cb(
Errors.MissingMci(
`Missing area list MCI ${MciViewIds.areaList}`
)
);
} }
areaListView.on('index update', idx => { areaListView.on('index update', idx => {
@ -87,11 +100,14 @@ exports.getModule = class MessageAreaListModule extends MenuModule {
areaListView.redraw(); areaListView.redraw();
this.selectionIndexUpdate(0); this.selectionIndexUpdate(0);
return next(null); return next(null);
} },
], ],
err => { err => {
if (err) { if (err) {
this.client.log.error( { error : err.message }, 'Failed loading message area list'); this.client.log.error(
{ error: err.message },
'Failed loading message area list'
);
} }
return cb(err); return cb(err);
} }
@ -105,15 +121,21 @@ exports.getModule = class MessageAreaListModule extends MenuModule {
return; return;
} }
this.setViewText('areaList', MciViewIds.areaDesc, area.desc); this.setViewText('areaList', MciViewIds.areaDesc, area.desc);
this.updateCustomViewTextsWithFilter('areaList', MciViewIds.customRangeStart, area); this.updateCustomViewTextsWithFilter(
'areaList',
MciViewIds.customRangeStart,
area
);
} }
initList() { initList() {
let index = 1; let index = 1;
this.messageAreas = messageArea.getSortedAvailMessageAreasByConfTag( this.messageAreas = messageArea
.getSortedAvailMessageAreasByConfTag(
this.client.user.properties[UserProps.MessageConfTag], this.client.user.properties[UserProps.MessageConfTag],
{ client: this.client } { client: this.client }
).map(area => { )
.map(area => {
return { return {
index: index++, index: index++,
areaTag: area.areaTag, areaTag: area.areaTag,

View File

@ -4,9 +4,7 @@
const FullScreenEditorModule = require('./fse.js').FullScreenEditorModule; const FullScreenEditorModule = require('./fse.js').FullScreenEditorModule;
const persistMessage = require('./message_area.js').persistMessage; const persistMessage = require('./message_area.js').persistMessage;
const UserProps = require('./user_property.js'); const UserProps = require('./user_property.js');
const { const { hasMessageConfAndAreaWrite } = require('./message_area.js');
hasMessageConfAndAreaWrite,
} = require('./message_area.js');
const async = require('async'); const async = require('async');
@ -26,7 +24,6 @@ exports.getModule = class AreaPostFSEModule extends FullScreenEditorModule {
this.editorMode = 'edit'; this.editorMode = 'edit';
this.menuMethods.editModeMenuSave = function (formData, extraArgs, cb) { this.menuMethods.editModeMenuSave = function (formData, extraArgs, cb) {
var msg; var msg;
async.series( async.series(
[ [
@ -41,7 +38,7 @@ exports.getModule = class AreaPostFSEModule extends FullScreenEditorModule {
}, },
function updateStats(callback) { function updateStats(callback) {
self.updateUserAndSystemStats(callback); self.updateUserAndSystemStats(callback);
} },
], ],
function complete(err) { function complete(err) {
if (err) { if (err) {
@ -49,7 +46,11 @@ exports.getModule = class AreaPostFSEModule extends FullScreenEditorModule {
} else { } else {
// note: not logging 'from' here as it's part of client.log.xxxx() // note: not logging 'from' here as it's part of client.log.xxxx()
self.client.log.info( self.client.log.info(
{ to : msg.toUserName, subject : msg.subject, uuid : msg.messageUuid }, {
to: msg.toUserName,
subject: msg.subject,
uuid: msg.messageUuid,
},
'Message persisted' 'Message persisted'
); );
} }
@ -62,8 +63,7 @@ exports.getModule = class AreaPostFSEModule extends FullScreenEditorModule {
enter() { enter() {
this.messageAreaTag = this.messageAreaTag =
this.messageAreaTag || this.messageAreaTag || this.client.user.getProperty(UserProps.MessageAreaTag);
this.client.user.getProperty(UserProps.MessageAreaTag);
super.enter(); super.enter();
} }

View File

@ -46,7 +46,10 @@ exports.getModule = class AreaViewFSEModule extends FullScreenEditorModule {
this.messageAreaTag = this.messageList[this.messageIndex].areaTag; this.messageAreaTag = this.messageList[this.messageIndex].areaTag;
this.tempMessageConfAndAreaSwitch(this.messageAreaTag, false); // false=don't record prev; we want what we entered the module with this.tempMessageConfAndAreaSwitch(this.messageAreaTag, false); // false=don't record prev; we want what we entered the module with
return self.loadMessageByUuid(self.messageList[self.messageIndex].messageUuid, cb); return self.loadMessageByUuid(
self.messageList[self.messageIndex].messageUuid,
cb
);
} }
// auto-exit if no more to go? // auto-exit if no more to go?
@ -65,7 +68,10 @@ exports.getModule = class AreaViewFSEModule extends FullScreenEditorModule {
this.messageAreaTag = this.messageList[this.messageIndex].areaTag; this.messageAreaTag = this.messageList[this.messageIndex].areaTag;
this.tempMessageConfAndAreaSwitch(this.messageAreaTag, false); // false=don't record prev; we want what we entered the module with this.tempMessageConfAndAreaSwitch(this.messageAreaTag, false); // false=don't record prev; we want what we entered the module with
return self.loadMessageByUuid(self.messageList[self.messageIndex].messageUuid, cb); return self.loadMessageByUuid(
self.messageList[self.messageIndex].messageUuid,
cb
);
} }
return cb(null); return cb(null);
@ -76,10 +82,18 @@ exports.getModule = class AreaViewFSEModule extends FullScreenEditorModule {
// :TODO: Create methods for up/down vs using keyPressXXXXX // :TODO: Create methods for up/down vs using keyPressXXXXX
switch (formData.key.name) { switch (formData.key.name) {
case 'down arrow' : bodyView.scrollDocumentUp(); break; case 'down arrow':
case 'up arrow' : bodyView.scrollDocumentDown(); break; bodyView.scrollDocumentUp();
case 'page up' : bodyView.keyPressPageUp(); break; break;
case 'page down' : bodyView.keyPressPageDown(); break; case 'up arrow':
bodyView.scrollDocumentDown();
break;
case 'page up':
bodyView.keyPressPageUp();
break;
case 'page down':
bodyView.keyPressPageDown();
break;
} }
// :TODO: need to stop down/page down if doing so would push the last // :TODO: need to stop down/page down if doing so would push the last
@ -94,7 +108,7 @@ exports.getModule = class AreaViewFSEModule extends FullScreenEditorModule {
extraArgs: { extraArgs: {
messageAreaTag: self.messageAreaTag, messageAreaTag: self.messageAreaTag,
replyToMessage: self.message, replyToMessage: self.message,
} },
}; };
return self.gotoMenu(extraArgs.menu, modOpts, cb); return self.gotoMenu(extraArgs.menu, modOpts, cb);

View File

@ -33,9 +33,14 @@ exports.getModule = class MessageConfListModule extends MenuModule {
if (1 === formData.submitId) { if (1 === formData.submitId) {
const conf = this.messageConfs[formData.value.conf]; const conf = this.messageConfs[formData.value.conf];
messageArea.changeMessageConference(this.client, conf.confTag, err => { messageArea.changeMessageConference(
this.client,
conf.confTag,
err => {
if (err) { if (err) {
this.client.term.pipeWrite(`\n|00Cannot change conference: ${err.message}\n`); this.client.term.pipeWrite(
`\n|00Cannot change conference: ${err.message}\n`
);
return this.prevMenuOnTimeout(1000, cb); return this.prevMenuOnTimeout(1000, cb);
} }
@ -44,18 +49,24 @@ exports.getModule = class MessageConfListModule extends MenuModule {
extraArgs: { extraArgs: {
confTag: conf.confTag, confTag: conf.confTag,
}, },
menuFlags : [ 'popParent', 'noHistory' ] menuFlags: ['popParent', 'noHistory'],
}; };
return this.gotoMenu(this.menuConfig.config.changeConfPreArtMenu || 'changeMessageConfPreArt', menuOpts, cb); return this.gotoMenu(
this.menuConfig.config.changeConfPreArtMenu ||
'changeMessageConfPreArt',
menuOpts,
cb
);
} }
return this.prevMenu(cb); return this.prevMenu(cb);
}); }
);
} else { } else {
return cb(null); return cb(null);
} }
} },
}; };
} }
@ -67,13 +78,19 @@ exports.getModule = class MessageConfListModule extends MenuModule {
async.series( async.series(
[ [
(next) => { next => {
return this.prepViewController('confList', 0, mciData.menu, next); return this.prepViewController('confList', 0, mciData.menu, next);
}, },
(next) => { next => {
const confListView = this.viewControllers.confList.getView(MciViewIds.confList); const confListView = this.viewControllers.confList.getView(
MciViewIds.confList
);
if (!confListView) { if (!confListView) {
return next(Errors.MissingMci(`Missing conf list MCI ${MciViewIds.confList}`)); return next(
Errors.MissingMci(
`Missing conf list MCI ${MciViewIds.confList}`
)
);
} }
confListView.on('index update', idx => { confListView.on('index update', idx => {
@ -84,11 +101,14 @@ exports.getModule = class MessageConfListModule extends MenuModule {
confListView.redraw(); confListView.redraw();
this.selectionIndexUpdate(0); this.selectionIndexUpdate(0);
return next(null); return next(null);
} },
], ],
err => { err => {
if (err) { if (err) {
this.client.log.error( { error : err.message }, 'Failed loading message conference list'); this.client.log.error(
{ error: err.message },
'Failed loading message conference list'
);
} }
} }
); );
@ -101,13 +121,18 @@ exports.getModule = class MessageConfListModule extends MenuModule {
return; return;
} }
this.setViewText('confList', MciViewIds.confDesc, conf.desc); this.setViewText('confList', MciViewIds.confDesc, conf.desc);
this.updateCustomViewTextsWithFilter('confList', MciViewIds.customRangeStart, conf); this.updateCustomViewTextsWithFilter(
'confList',
MciViewIds.customRangeStart,
conf
);
} }
initList() initList() {
{
let index = 1; let index = 1;
this.messageConfs = messageArea.getSortedAvailMessageConferences(this.client).map(conf => { this.messageConfs = messageArea
.getSortedAvailMessageConferences(this.client)
.map(conf => {
return { return {
index: index++, index: index++,
confTag: conf.confTag, confTag: conf.confTag,

View File

@ -5,7 +5,8 @@
const MenuModule = require('./menu_module.js').MenuModule; const MenuModule = require('./menu_module.js').MenuModule;
const ViewController = require('./view_controller.js').ViewController; const ViewController = require('./view_controller.js').ViewController;
const messageArea = require('./message_area.js'); const messageArea = require('./message_area.js');
const MessageAreaConfTempSwitcher = require('./mod_mixins.js').MessageAreaConfTempSwitcher; const MessageAreaConfTempSwitcher =
require('./mod_mixins.js').MessageAreaConfTempSwitcher;
const Errors = require('./enig_error.js').Errors; const Errors = require('./enig_error.js').Errors;
const Message = require('./message.js'); const Message = require('./message.js');
const UserProps = require('./user_property.js'); const UserProps = require('./user_property.js');
@ -45,35 +46,52 @@ const MciViewIds = {
delPrompt: { delPrompt: {
prompt: 1, prompt: 1,
} },
}; };
exports.getModule = class MessageListModule extends MessageAreaConfTempSwitcher(MenuModule) { exports.getModule = class MessageListModule extends (
MessageAreaConfTempSwitcher(MenuModule)
) {
constructor(options) { constructor(options) {
super(options); super(options);
// :TODO: consider this pattern in base MenuModule - clean up code all over // :TODO: consider this pattern in base MenuModule - clean up code all over
this.config = Object.assign({}, _.get(options, 'menuConfig.config'), options.extraArgs); this.config = Object.assign(
{},
_.get(options, 'menuConfig.config'),
options.extraArgs
);
this.lastMessageReachedExit = _.get(options, 'lastMenuResult.lastMessageReached', false); this.lastMessageReachedExit = _.get(
options,
'lastMenuResult.lastMessageReached',
false
);
this.menuMethods = { this.menuMethods = {
selectMessage: (formData, extraArgs, cb) => { selectMessage: (formData, extraArgs, cb) => {
if (MciViewIds.allViews.msgList === formData.submitId) { if (MciViewIds.allViews.msgList === formData.submitId) {
// 'messageIndex' or older deprecated 'message' member // 'messageIndex' or older deprecated 'message' member
this.initialFocusIndex = _.get(formData, 'value.messageIndex', formData.value.message); this.initialFocusIndex = _.get(
formData,
'value.messageIndex',
formData.value.message
);
const modOpts = { const modOpts = {
extraArgs: { extraArgs: {
messageAreaTag : this.getSelectedAreaTag(this.initialFocusIndex), messageAreaTag: this.getSelectedAreaTag(
this.initialFocusIndex
),
messageList: this.config.messageList, messageList: this.config.messageList,
messageIndex: this.initialFocusIndex, messageIndex: this.initialFocusIndex,
lastMessageNextExit: true, lastMessageNextExit: true,
} },
}; };
if (_.isBoolean(this.config.noUpdateLastReadId)) { if (_.isBoolean(this.config.noUpdateLastReadId)) {
modOpts.extraArgs.noUpdateLastReadId = this.config.noUpdateLastReadId; modOpts.extraArgs.noUpdateLastReadId =
this.config.noUpdateLastReadId;
} }
// //
@ -82,9 +100,12 @@ exports.getModule = class MessageListModule extends MessageAreaConfTempSwitcher(
// //
const self = this; const self = this;
modOpts.extraArgs.toJSON = function () { modOpts.extraArgs.toJSON = function () {
const logMsgList = (self.config.messageList.length <= 4) ? const logMsgList =
self.config.messageList : self.config.messageList.length <= 4
self.config.messageList.slice(0, 2).concat(self.config.messageList.slice(-2)); ? self.config.messageList
: self.config.messageList
.slice(0, 2)
.concat(self.config.messageList.slice(-2));
return { return {
// note |this| is scope of toJSON()! // note |this| is scope of toJSON()!
@ -95,7 +116,11 @@ exports.getModule = class MessageListModule extends MessageAreaConfTempSwitcher(
}; };
}; };
return this.gotoMenu(this.config.menuViewPost || 'messageAreaViewPost', modOpts, cb); return this.gotoMenu(
this.config.menuViewPost || 'messageAreaViewPost',
modOpts,
cb
);
} else { } else {
return cb(null); return cb(null);
} }
@ -110,25 +135,42 @@ exports.getModule = class MessageListModule extends MessageAreaConfTempSwitcher(
} }
// newer 'messageIndex' or older deprecated value // newer 'messageIndex' or older deprecated value
const messageIndex = _.get(formData, 'value.messageIndex', formData.value.message); const messageIndex = _.get(
formData,
'value.messageIndex',
formData.value.message
);
return this.promptDeleteMessageConfirm(messageIndex, cb); return this.promptDeleteMessageConfirm(messageIndex, cb);
}, },
deleteMessageYes: (formData, extraArgs, cb) => { deleteMessageYes: (formData, extraArgs, cb) => {
const msgListView = this.viewControllers.allViews.getView(MciViewIds.allViews.msgList); const msgListView = this.viewControllers.allViews.getView(
MciViewIds.allViews.msgList
);
this.enableMessageListIndexUpdates(msgListView); this.enableMessageListIndexUpdates(msgListView);
if (this.selectedMessageForDelete) { if (this.selectedMessageForDelete) {
this.selectedMessageForDelete.deleteMessage(this.client.user, err => { this.selectedMessageForDelete.deleteMessage(this.client.user, err => {
if (err) { if (err) {
this.client.log.error(`Failed to delete message: ${this.selectedMessageForDelete.messageUuid}`); this.client.log.error(
`Failed to delete message: ${this.selectedMessageForDelete.messageUuid}`
);
} else { } else {
this.client.log.info(`User deleted message: ${this.selectedMessageForDelete.messageUuid}`); this.client.log.info(
this.config.messageList.splice(msgListView.focusedItemIndex, 1); `User deleted message: ${this.selectedMessageForDelete.messageUuid}`
this.updateMessageNumbersAfterDelete(msgListView.focusedItemIndex); );
this.config.messageList.splice(
msgListView.focusedItemIndex,
1
);
this.updateMessageNumbersAfterDelete(
msgListView.focusedItemIndex
);
msgListView.setItems(this.config.messageList); msgListView.setItems(this.config.messageList);
} }
this.selectedMessageForDelete = null; this.selectedMessageForDelete = null;
msgListView.redraw(); msgListView.redraw();
this.populateCustomLabelsForSelected(msgListView.focusedItemIndex); this.populateCustomLabelsForSelected(
msgListView.focusedItemIndex
);
return cb(null); return cb(null);
}); });
} else { } else {
@ -136,7 +178,9 @@ exports.getModule = class MessageListModule extends MessageAreaConfTempSwitcher(
} }
}, },
deleteMessageNo: (formData, extraArgs, cb) => { deleteMessageNo: (formData, extraArgs, cb) => {
const msgListView = this.viewControllers.allViews.getView(MciViewIds.allViews.msgList); const msgListView = this.viewControllers.allViews.getView(
MciViewIds.allViews.msgList
);
this.enableMessageListIndexUpdates(msgListView); this.enableMessageListIndexUpdates(msgListView);
return cb(null); return cb(null);
}, },
@ -146,7 +190,7 @@ exports.getModule = class MessageListModule extends MessageAreaConfTempSwitcher(
} }
return this.markAllMessagesAsRead(cb); return this.markAllMessagesAsRead(cb);
} },
}; };
} }
@ -171,7 +215,8 @@ exports.getModule = class MessageListModule extends MessageAreaConfTempSwitcher(
if (this.config.messageAreaTag) { if (this.config.messageAreaTag) {
this.tempMessageConfAndAreaSwitch(this.config.messageAreaTag); this.tempMessageConfAndAreaSwitch(this.config.messageAreaTag);
} else { } else {
this.config.messageAreaTag = this.client.user.properties[UserProps.MessageAreaTag]; this.config.messageAreaTag =
this.client.user.properties[UserProps.MessageAreaTag];
} }
} }
} }
@ -184,12 +229,16 @@ exports.getModule = class MessageListModule extends MessageAreaConfTempSwitcher(
populateCustomLabelsForSelected(selectedIndex) { populateCustomLabelsForSelected(selectedIndex) {
const formatObj = Object.assign( const formatObj = Object.assign(
{ {
msgNumSelected : (selectedIndex + 1), msgNumSelected: selectedIndex + 1,
msgNumTotal: this.config.messageList.length, msgNumTotal: this.config.messageList.length,
}, },
this.config.messageList[selectedIndex] // plus, all the selected message props this.config.messageList[selectedIndex] // plus, all the selected message props
); );
return this.updateCustomViewTextsWithFilter('allViews', MciViewIds.allViews.customRangeStart, formatObj); return this.updateCustomViewTextsWithFilter(
'allViews',
MciViewIds.allViews.customRangeStart,
formatObj
);
} }
mciReady(mciData, cb) { mciReady(mciData, cb) {
@ -199,7 +248,9 @@ exports.getModule = class MessageListModule extends MessageAreaConfTempSwitcher(
} }
const self = this; const self = this;
const vc = self.viewControllers.allViews = new ViewController( { client : self.client } ); const vc = (self.viewControllers.allViews = new ViewController({
client: self.client,
}));
let configProvidedMessageList = false; let configProvidedMessageList = false;
async.series( async.series(
@ -207,7 +258,7 @@ exports.getModule = class MessageListModule extends MessageAreaConfTempSwitcher(
function loadFromConfig(callback) { function loadFromConfig(callback) {
const loadOpts = { const loadOpts = {
callingMenu: self, callingMenu: self,
mciMap : mciData.menu mciMap: mciData.menu,
}; };
return vc.loadFromMenuConfig(loadOpts, callback); return vc.loadFromMenuConfig(loadOpts, callback);
@ -218,17 +269,25 @@ exports.getModule = class MessageListModule extends MessageAreaConfTempSwitcher(
// //
if (_.isArray(self.config.messageList)) { if (_.isArray(self.config.messageList)) {
configProvidedMessageList = true; configProvidedMessageList = true;
return callback(0 === self.config.messageList.length ? new Error('No messages in area') : null); return callback(
0 === self.config.messageList.length
? new Error('No messages in area')
: null
);
} }
messageArea.getMessageListForArea(self.client, self.config.messageAreaTag, function msgs(err, msgList) { messageArea.getMessageListForArea(
self.client,
self.config.messageAreaTag,
function msgs(err, msgList) {
if (!msgList || 0 === msgList.length) { if (!msgList || 0 === msgList.length) {
return callback(new Error('No messages in area')); return callback(new Error('No messages in area'));
} }
self.config.messageList = msgList; self.config.messageList = msgList;
return callback(err); return callback(err);
}); }
);
}, },
function getLastReadMessageId(callback) { function getLastReadMessageId(callback) {
// messageList entries can contain |isNew| if they want to be considered new // messageList entries can contain |isNew| if they want to be considered new
@ -237,24 +296,37 @@ exports.getModule = class MessageListModule extends MessageAreaConfTempSwitcher(
return callback(null); return callback(null);
} }
messageArea.getMessageAreaLastReadId(self.client.user.userId, self.config.messageAreaTag, function lastRead(err, lastReadId) { messageArea.getMessageAreaLastReadId(
self.client.user.userId,
self.config.messageAreaTag,
function lastRead(err, lastReadId) {
self.lastReadId = lastReadId || 0; self.lastReadId = lastReadId || 0;
return callback(null); // ignore any errors, e.g. missing value return callback(null); // ignore any errors, e.g. missing value
}); }
);
}, },
function updateMessageListObjects(callback) { function updateMessageListObjects(callback) {
const dateTimeFormat = self.menuConfig.config.dateTimeFormat || self.client.currentTheme.helpers.getDateTimeFormat(); const dateTimeFormat =
self.menuConfig.config.dateTimeFormat ||
self.client.currentTheme.helpers.getDateTimeFormat();
const newIndicator = self.menuConfig.config.newIndicator || '*'; const newIndicator = self.menuConfig.config.newIndicator || '*';
const regIndicator = ' '.repeat(newIndicator.length); // fill with space to avoid draw issues const regIndicator = ' '.repeat(newIndicator.length); // fill with space to avoid draw issues
let msgNum = 1; let msgNum = 1;
self.config.messageList.forEach((listItem, index) => { self.config.messageList.forEach((listItem, index) => {
listItem.msgNum = msgNum++; listItem.msgNum = msgNum++;
listItem.ts = moment(listItem.modTimestamp).format(dateTimeFormat); listItem.ts = moment(listItem.modTimestamp).format(
const isNew = _.isBoolean(listItem.isNew) ? listItem.isNew : listItem.messageId > self.lastReadId; dateTimeFormat
);
const isNew = _.isBoolean(listItem.isNew)
? listItem.isNew
: listItem.messageId > self.lastReadId;
listItem.newIndicator = isNew ? newIndicator : regIndicator; listItem.newIndicator = isNew ? newIndicator : regIndicator;
if(_.isUndefined(self.initialFocusIndex) && listItem.messageId > self.lastReadId) { if (
_.isUndefined(self.initialFocusIndex) &&
listItem.messageId > self.lastReadId
) {
self.initialFocusIndex = index; self.initialFocusIndex = index;
} }
@ -280,7 +352,10 @@ exports.getModule = class MessageListModule extends MessageAreaConfTempSwitcher(
], ],
err => { err => {
if (err) { if (err) {
self.client.log.error( { error : err.message }, 'Error loading message list'); self.client.log.error(
{ error: err.message },
'Error loading message list'
);
} }
return cb(err); return cb(err);
} }
@ -329,15 +404,22 @@ exports.getModule = class MessageListModule extends MessageAreaConfTempSwitcher(
} }
}); });
const regIndicator = ' '.repeat( (this.menuConfig.config.newIndicator || '*').length ); const regIndicator = ' '.repeat(
async.forEachOf(areaHighestIds, (highestId, areaTag, nextArea) => { (this.menuConfig.config.newIndicator || '*').length
);
async.forEachOf(
areaHighestIds,
(highestId, areaTag, nextArea) => {
messageArea.updateMessageAreaLastReadId( messageArea.updateMessageAreaLastReadId(
this.client.user.userId, this.client.user.userId,
areaTag, areaTag,
highestId, highestId,
err => { err => {
if (err) { if (err) {
this.client.log.warn( { error : err.message }, 'Failed marking area as read'); this.client.log.warn(
{ error: err.message },
'Failed marking area as read'
);
} else { } else {
// update newIndicator on messages // update newIndicator on messages
this.config.messageList.forEach(msg => { this.config.messageList.forEach(msg => {
@ -345,17 +427,24 @@ exports.getModule = class MessageListModule extends MessageAreaConfTempSwitcher(
msg.newIndicator = regIndicator; msg.newIndicator = regIndicator;
} }
}); });
const msgListView = this.viewControllers.allViews.getView(MciViewIds.allViews.msgList); const msgListView = this.viewControllers.allViews.getView(
MciViewIds.allViews.msgList
);
msgListView.setItems(this.config.messageList); msgListView.setItems(this.config.messageList);
msgListView.redraw(); msgListView.redraw();
this.client.log.info( { highestId, areaTag }, 'User marked area as read'); this.client.log.info(
{ highestId, areaTag },
'User marked area as read'
);
} }
return nextArea(null); // always continue return nextArea(null); // always continue
} }
); );
}, () => { },
() => {
return cb(null); return cb(null);
}); }
);
} }
updateMessageNumbersAfterDelete(startIndex) { updateMessageNumbersAfterDelete(startIndex) {
@ -383,7 +472,11 @@ exports.getModule = class MessageListModule extends MessageAreaConfTempSwitcher(
if (!this.selectedMessageForDelete.userHasDeleteRights(this.client.user)) { if (!this.selectedMessageForDelete.userHasDeleteRights(this.client.user)) {
this.selectedMessageForDelete = null; this.selectedMessageForDelete = null;
return cb(Errors.AccessDenied('User does not have rights to delete this message')); return cb(
Errors.AccessDenied(
'User does not have rights to delete this message'
)
);
} }
// user has rights to delete -- prompt/confirm then proceed // user has rights to delete -- prompt/confirm then proceed
@ -392,9 +485,15 @@ exports.getModule = class MessageListModule extends MessageAreaConfTempSwitcher(
} }
promptConfirmDelete(cb) { promptConfirmDelete(cb) {
const promptXyView = this.viewControllers.allViews.getView(MciViewIds.allViews.delPromptXy); const promptXyView = this.viewControllers.allViews.getView(
MciViewIds.allViews.delPromptXy
);
if (!promptXyView) { if (!promptXyView) {
return cb(Errors.MissingMci(`Missing prompt XY${MciViewIds.allViews.delPromptXy} MCI`)); return cb(
Errors.MissingMci(
`Missing prompt XY${MciViewIds.allViews.delPromptXy} MCI`
)
);
} }
const promptOpts = { const promptOpts = {
@ -408,7 +507,9 @@ exports.getModule = class MessageListModule extends MessageAreaConfTempSwitcher(
{ {
formName: 'delPrompt', formName: 'delPrompt',
formId: FormIds.delPrompt, formId: FormIds.delPrompt,
promptName : this.config.deleteMessageFromListPrompt || 'deleteMessageFromListPrompt', promptName:
this.config.deleteMessageFromListPrompt ||
'deleteMessageFromListPrompt',
prevFormName: 'allViews', prevFormName: 'allViews',
position: promptXyView.position, position: promptXyView.position,
}, },

View File

@ -17,7 +17,9 @@ function startup(cb) {
async.series( async.series(
[ [
function loadModules(callback) { function loadModules(callback) {
loadModulesForCategory('scannerTossers', (module, nextModule) => { loadModulesForCategory(
'scannerTossers',
(module, nextModule) => {
const modInst = new module.getModule(); const modInst = new module.getModule();
modInst.startup(err => { modInst.startup(err => {
@ -26,10 +28,12 @@ function startup(cb) {
} }
}); });
return nextModule(null); return nextModule(null);
}, err => { },
err => {
callback(err); callback(err);
});
} }
);
},
], ],
cb cb
); );
@ -56,10 +60,14 @@ function recordMessage(message, cb) {
// a chance to do something with |message|. Any or all can // a chance to do something with |message|. Any or all can
// choose to ignore it. // choose to ignore it.
// //
async.each(msgNetworkModules, (modInst, next) => { async.each(
msgNetworkModules,
(modInst, next) => {
modInst.record(message); modInst.record(message);
next(); next();
}, err => { },
err => {
cb(err); cb(err);
}); }
);
} }

View File

@ -20,5 +20,4 @@ MessageScanTossModule.prototype.shutdown = function(cb) {
return cb(null); return cb(null);
}; };
MessageScanTossModule.prototype.record = function(/*message*/) { MessageScanTossModule.prototype.record = function (/*message*/) {};
};

View File

@ -61,7 +61,6 @@ const _ = require('lodash');
// * Add word delete (CTRL+????) // * Add word delete (CTRL+????)
// * // *
const SPECIAL_KEY_MAP_DEFAULT = { const SPECIAL_KEY_MAP_DEFAULT = {
'line feed': ['return'], 'line feed': ['return'],
exit: ['esc'], exit: ['esc'],
@ -129,9 +128,11 @@ function MultiLineEditTextView(options) {
this.cursorPos = { col: 0, row: 0 }; this.cursorPos = { col: 0, row: 0 };
this.getSGRFor = function (sgrFor) { this.getSGRFor = function (sgrFor) {
return { return (
{
text: self.getSGR(), text: self.getSGR(),
}[sgrFor] || self.getSGR(); }[sgrFor] || self.getSGR()
);
}; };
this.isEditMode = function () { this.isEditMode = function () {
@ -168,7 +169,11 @@ function MultiLineEditTextView(options) {
}; };
this.toggleTextCursor = function (action) { this.toggleTextCursor = function (action) {
self.client.term.rawWrite(`${self.getSGRFor('text')}${'hide' === action ? ansi.hideCursor() : ansi.showCursor()}`); self.client.term.rawWrite(
`${self.getSGRFor('text')}${
'hide' === action ? ansi.hideCursor() : ansi.showCursor()
}`
);
}; };
this.redrawRows = function (startRow, endRow) { this.redrawRows = function (startRow, endRow) {
@ -287,7 +292,7 @@ function MultiLineEditTextView(options) {
this.getOutputText = function (startIndex, endIndex, eolMarker, options) { this.getOutputText = function (startIndex, endIndex, eolMarker, options) {
const lines = self.getTextLines(startIndex, endIndex); const lines = self.getTextLines(startIndex, endIndex);
let text = ''; let text = '';
const re = new RegExp('\\t{1,' + (self.tabWidth) + '}', 'g'); const re = new RegExp('\\t{1,' + self.tabWidth + '}', 'g');
lines.forEach(line => { lines.forEach(line => {
text += line.text.replace(re, '\t'); text += line.text.replace(re, '\t');
@ -314,7 +319,10 @@ function MultiLineEditTextView(options) {
this.replaceCharacterInText = function (c, index, col) { this.replaceCharacterInText = function (c, index, col) {
self.textLines[index].text = strUtil.replaceAt( self.textLines[index].text = strUtil.replaceAt(
self.textLines[index].text, col, c); self.textLines[index].text,
col,
c
);
}; };
/* /*
@ -338,14 +346,20 @@ function MultiLineEditTextView(options) {
this.updateTextWordWrap = function (index) { this.updateTextWordWrap = function (index) {
const nextEolIndex = self.getNextEndOfLineIndex(index); const nextEolIndex = self.getNextEndOfLineIndex(index);
const wrapped = self.wordWrapSingleLine(self.getContiguousText(index, nextEolIndex), 'tabsIntact'); const wrapped = self.wordWrapSingleLine(
const newLines = wrapped.wrapped.map(l => { return { text : l }; } ); self.getContiguousText(index, nextEolIndex),
'tabsIntact'
);
const newLines = wrapped.wrapped.map(l => {
return { text: l };
});
newLines[newLines.length - 1].eol = true; newLines[newLines.length - 1].eol = true;
Array.prototype.splice.apply( Array.prototype.splice.apply(
self.textLines, self.textLines,
[ index, (nextEolIndex - index) + 1 ].concat(newLines)); [index, nextEolIndex - index + 1].concat(newLines)
);
return wrapped.firstWrapRange; return wrapped.firstWrapRange;
}; };
@ -365,7 +379,7 @@ function MultiLineEditTextView(options) {
self.textLines[index].text.slice(0, col - (count - 1)) + self.textLines[index].text.slice(0, col - (count - 1)) +
self.textLines[index].text.slice(col + 1); self.textLines[index].text.slice(col + 1);
self.cursorPos.col -= (count - 1); self.cursorPos.col -= count - 1;
self.updateTextWordWrap(index); self.updateTextWordWrap(index);
self.redrawRows(self.cursorPos.row, self.dimens.height); self.redrawRows(self.cursorPos.row, self.dimens.height);
@ -379,7 +393,7 @@ function MultiLineEditTextView(options) {
// treat all of these things using the physical approach, but this seems // treat all of these things using the physical approach, but this seems
// a bit odd in this context. // a bit odd in this context.
// //
var isLastLine = (index === self.textLines.length - 1); var isLastLine = index === self.textLines.length - 1;
var hadEol = self.textLines[index].eol; var hadEol = self.textLines[index].eol;
self.textLines.splice(index, 1); self.textLines.splice(index, 1);
@ -418,7 +432,7 @@ function MultiLineEditTextView(options) {
self.textLines[index].text = [ self.textLines[index].text = [
self.textLines[index].text.slice(0, col), self.textLines[index].text.slice(0, col),
c, c,
self.textLines[index].text.slice(col) self.textLines[index].text.slice(col),
].join(''); ].join('');
self.cursorPos.col += c.length; self.cursorPos.col += c.length;
@ -449,18 +463,29 @@ function MultiLineEditTextView(options) {
self.client.term.rawWrite(ansi.right(cursorOffset)); self.client.term.rawWrite(ansi.right(cursorOffset));
} else { } else {
// adjust cursor after drawing new rows // adjust cursor after drawing new rows
const absPos = self.getAbsolutePosition(self.cursorPos.row, self.cursorPos.col); const absPos = self.getAbsolutePosition(
self.cursorPos.row,
self.cursorPos.col
);
self.client.term.rawWrite(ansi.goto(absPos.row, absPos.col)); self.client.term.rawWrite(ansi.goto(absPos.row, absPos.col));
} }
} else { } else {
// //
// We must only redraw from col -> end of current visible line // We must only redraw from col -> end of current visible line
// //
const absPos = self.getAbsolutePosition(self.cursorPos.row, self.cursorPos.col); const absPos = self.getAbsolutePosition(
const renderText = self.getRenderText(index).slice(self.cursorPos.col - c.length); self.cursorPos.row,
self.cursorPos.col
);
const renderText = self
.getRenderText(index)
.slice(self.cursorPos.col - c.length);
self.client.term.write( self.client.term.write(
`${ansi.hideCursor()}${self.getSGRFor('text')}${renderText}${ansi.goto(absPos.row, absPos.col)}${ansi.showCursor()}`, `${ansi.hideCursor()}${self.getSGRFor('text')}${renderText}${ansi.goto(
absPos.row,
absPos.col
)}${ansi.showCursor()}`,
false // convertLineFeeds false // convertLineFeeds
); );
} }
@ -500,23 +525,27 @@ function MultiLineEditTextView(options) {
}; };
this.wordWrapSingleLine = function (line, tabHandling = 'expand') { this.wordWrapSingleLine = function (line, tabHandling = 'expand') {
return wordWrapText( return wordWrapText(line, {
line,
{
width: self.dimens.width, width: self.dimens.width,
tabHandling: tabHandling, tabHandling: tabHandling,
tabWidth: self.tabWidth, tabWidth: self.tabWidth,
tabChar: '\t', tabChar: '\t',
} });
);
}; };
this.setTextLines = function (lines, index, termWithEol) { this.setTextLines = function (lines, index, termWithEol) {
if(0 === index && (0 === self.textLines.length || (self.textLines.length === 1 && '' === self.textLines[0].text) )) { if (
0 === index &&
(0 === self.textLines.length ||
(self.textLines.length === 1 && '' === self.textLines[0].text))
) {
// quick path: just set the things // quick path: just set the things
self.textLines = lines.slice(0, -1).map(l => { self.textLines = lines
.slice(0, -1)
.map(l => {
return { text: l }; return { text: l };
}).concat( { text : lines[lines.length - 1], eol : termWithEol } ); })
.concat({ text: lines[lines.length - 1], eol: termWithEol });
} else { } else {
// insert somewhere in textLines... // insert somewhere in textLines...
if (index > self.textLines.length) { if (index > self.textLines.length) {
@ -524,24 +553,24 @@ function MultiLineEditTextView(options) {
self.textLines.splice( self.textLines.splice(
self.textLines.length, self.textLines.length,
0, 0,
...Array.from( { length : index - self.textLines.length } ).map( () => { return { text : '' }; } ) ...Array.from({ length: index - self.textLines.length }).map(() => {
return { text: '' };
})
); );
} }
const newLines = lines.slice(0, -1).map(l => { const newLines = lines
.slice(0, -1)
.map(l => {
return { text: l }; return { text: l };
}).concat( { text : lines[lines.length - 1], eol : termWithEol } ); })
.concat({ text: lines[lines.length - 1], eol: termWithEol });
self.textLines.splice( self.textLines.splice(index, 0, ...newLines);
index,
0,
...newLines
);
} }
}; };
this.setAnsiWithOptions = function (ansi, options, cb) { this.setAnsiWithOptions = function (ansi, options, cb) {
function setLines(text) { function setLines(text) {
text = strUtil.splitTextAtTerms(text); text = strUtil.splitTextAtTerms(text);
@ -634,7 +663,6 @@ function MultiLineEditTextView(options) {
self.client.term.rawWrite(ansi.goto(absPos.row, absPos.col)); self.client.term.rawWrite(ansi.goto(absPos.row, absPos.col));
}; };
this.keyPressCharacter = function (c) { this.keyPressCharacter = function (c) {
var index = self.getTextLinesIndex(); var index = self.getTextLinesIndex();
@ -676,9 +704,9 @@ function MultiLineEditTextView(options) {
}; };
this.keyPressDown = function () { this.keyPressDown = function () {
var lastVisibleRow = Math.min( var lastVisibleRow =
self.dimens.height, Math.min(self.dimens.height, self.textLines.length - self.topVisibleIndex) -
(self.textLines.length - self.topVisibleIndex)) - 1; 1;
if (self.cursorPos.row < lastVisibleRow) { if (self.cursorPos.row < lastVisibleRow) {
self.cursorPos.row++; self.cursorPos.row++;
@ -780,7 +808,10 @@ function MultiLineEditTextView(options) {
var index = self.getTextLinesIndex(); var index = self.getTextLinesIndex();
var nextEolIndex = self.getNextEndOfLineIndex(index); var nextEolIndex = self.getNextEndOfLineIndex(index);
var text = self.getContiguousText(index, nextEolIndex); var text = self.getContiguousText(index, nextEolIndex);
const newLines = self.wordWrapSingleLine(text.slice(self.cursorPos.col), 'tabsIntact').wrapped; const newLines = self.wordWrapSingleLine(
text.slice(self.cursorPos.col),
'tabsIntact'
).wrapped;
newLines.unshift({ text: text.slice(0, self.cursorPos.col), eol: true }); newLines.unshift({ text: text.slice(0, self.cursorPos.col), eol: true });
for (var i = 1; i < newLines.length; ++i) { for (var i = 1; i < newLines.length; ++i) {
@ -790,7 +821,8 @@ function MultiLineEditTextView(options) {
Array.prototype.splice.apply( Array.prototype.splice.apply(
self.textLines, self.textLines,
[ index, (nextEolIndex - index) + 1 ].concat(newLines)); [index, nextEolIndex - index + 1].concat(newLines)
);
// redraw from current row to end of visible area // redraw from current row to end of visible area
self.redrawRows(self.cursorPos.row, self.dimens.height); self.redrawRows(self.cursorPos.row, self.dimens.height);
@ -805,7 +837,11 @@ function MultiLineEditTextView(options) {
this.keyPressTab = function () { this.keyPressTab = function () {
var index = self.getTextLinesIndex(); var index = self.getTextLinesIndex();
self.insertCharactersInText(self.expandTab(self.cursorPos.col, '\t') + '\t', index, self.cursorPos.col); self.insertCharactersInText(
self.expandTab(self.cursorPos.col, '\t') + '\t',
index,
self.cursorPos.col
);
self.emitEditPosition(); self.emitEditPosition();
}; };
@ -831,16 +867,12 @@ function MultiLineEditTextView(options) {
--col; --col;
} }
count = (self.cursorPos.col - col); count = self.cursorPos.col - col;
} else { } else {
count = 1; count = 1;
} }
self.removeCharactersFromText( self.removeCharactersFromText(index, self.cursorPos.col, 'backspace', count);
index,
self.cursorPos.col,
'backspace',
count);
} else { } else {
// //
// Delete character at end of line previous. // Delete character at end of line previous.
@ -858,22 +890,17 @@ function MultiLineEditTextView(options) {
this.keyPressDelete = function () { this.keyPressDelete = function () {
const lineIndex = self.getTextLinesIndex(); const lineIndex = self.getTextLinesIndex();
if(0 === self.cursorPos.col && 0 === self.textLines[lineIndex].text.length && self.textLines.length > 0) { if (
0 === self.cursorPos.col &&
0 === self.textLines[lineIndex].text.length &&
self.textLines.length > 0
) {
// //
// Start of line and nothing left. Just delete the line // Start of line and nothing left. Just delete the line
// //
self.removeCharactersFromText( self.removeCharactersFromText(lineIndex, 0, 'delete line');
lineIndex,
0,
'delete line'
);
} else { } else {
self.removeCharactersFromText( self.removeCharactersFromText(lineIndex, self.cursorPos.col, 'delete', 1);
lineIndex,
self.cursorPos.col,
'delete',
1
);
} }
self.emitEditPosition(); self.emitEditPosition();
@ -881,10 +908,7 @@ function MultiLineEditTextView(options) {
this.keyPressDeleteLine = function () { this.keyPressDeleteLine = function () {
if (self.textLines.length > 0) { if (self.textLines.length > 0) {
self.removeCharactersFromText( self.removeCharactersFromText(self.getTextLinesIndex(), 0, 'delete line');
self.getTextLinesIndex(),
0,
'delete line');
} }
self.emitEditPosition(); self.emitEditPosition();
@ -930,7 +954,10 @@ function MultiLineEditTextView(options) {
// Jump to the tabstop nearest the cursor // Jump to the tabstop nearest the cursor
// //
var newCol = self.tabStops.reduce(function r(prev, curr) { var newCol = self.tabStops.reduce(function r(prev, curr) {
return (Math.abs(curr - self.cursorPos.col) < Math.abs(prev - self.cursorPos.col) ? curr : prev); return Math.abs(curr - self.cursorPos.col) <
Math.abs(prev - self.cursorPos.col)
? curr
: prev;
}); });
if (newCol > self.cursorPos.col) { if (newCol > self.cursorPos.col) {
@ -960,7 +987,7 @@ function MultiLineEditTextView(options) {
this.cursorEndOfDocument = function () { this.cursorEndOfDocument = function () {
self.topVisibleIndex = Math.max(self.textLines.length - self.dimens.height, 0); self.topVisibleIndex = Math.max(self.textLines.length - self.dimens.height, 0);
self.cursorPos.row = (self.textLines.length - self.topVisibleIndex) - 1; self.cursorPos.row = self.textLines.length - self.topVisibleIndex - 1;
self.cursorPos.col = self.getTextEndOfLineColumn(); self.cursorPos.col = self.getTextEndOfLineColumn();
self.redraw(); self.redraw();
@ -1070,7 +1097,10 @@ MultiLineEditTextView.prototype.setFocus = function(focused) {
MultiLineEditTextView.super_.prototype.setFocus.call(this, focused); MultiLineEditTextView.super_.prototype.setFocus.call(this, focused);
}; };
MultiLineEditTextView.prototype.setText = function(text, options = { scrollMode : 'default' } ) { MultiLineEditTextView.prototype.setText = function (
text,
options = { scrollMode: 'default' }
) {
this.textLines = []; this.textLines = [];
this.addText(text, options); this.addText(text, options);
/*this.insertRawText(text); /*this.insertRawText(text);
@ -1082,12 +1112,19 @@ MultiLineEditTextView.prototype.setText = function(text, options = { scrollMode
}*/ }*/
}; };
MultiLineEditTextView.prototype.setAnsi = function(ansi, options = { prepped : false }, cb) { MultiLineEditTextView.prototype.setAnsi = function (
ansi,
options = { prepped: false },
cb
) {
this.textLines = []; this.textLines = [];
return this.setAnsiWithOptions(ansi, options, cb); return this.setAnsiWithOptions(ansi, options, cb);
}; };
MultiLineEditTextView.prototype.addText = function(text, options = { scrollMode : 'default' }) { MultiLineEditTextView.prototype.addText = function (
text,
options = { scrollMode: 'default' }
) {
this.insertRawText(text); this.insertRawText(text);
switch (options.scrollMode) { switch (options.scrollMode) {
@ -1139,19 +1176,23 @@ MultiLineEditTextView.prototype.setPropertyValue = function(propName, value) {
}; };
const HANDLED_SPECIAL_KEYS = [ const HANDLED_SPECIAL_KEYS = [
'up', 'down', 'left', 'right', 'up',
'home', 'end', 'down',
'page up', 'page down', 'left',
'right',
'home',
'end',
'page up',
'page down',
'line feed', 'line feed',
'insert', 'insert',
'tab', 'tab',
'backspace', 'delete', 'backspace',
'delete',
'delete line', 'delete line',
]; ];
const PREVIEW_MODE_KEYS = [ const PREVIEW_MODE_KEYS = ['up', 'down', 'page up', 'page down'];
'up', 'down', 'page up', 'page down'
];
MultiLineEditTextView.prototype.onKeyPress = function (ch, key) { MultiLineEditTextView.prototype.onKeyPress = function (ch, key) {
const self = this; const self = this;
@ -1160,8 +1201,10 @@ MultiLineEditTextView.prototype.onKeyPress = function(ch, key) {
if (key) { if (key) {
HANDLED_SPECIAL_KEYS.forEach(function aKey(specialKey) { HANDLED_SPECIAL_KEYS.forEach(function aKey(specialKey) {
if (self.isKeyMapped(specialKey, key.name)) { if (self.isKeyMapped(specialKey, key.name)) {
if (
if(self.isPreviewMode() && -1 === PREVIEW_MODE_KEYS.indexOf(specialKey)) { self.isPreviewMode() &&
-1 === PREVIEW_MODE_KEYS.indexOf(specialKey)
) {
return; return;
} }
@ -1208,7 +1251,7 @@ MultiLineEditTextView.prototype.getEditPosition = function() {
return { return {
row: this.getTextLinesIndex(this.cursorPos.row), row: this.getTextLinesIndex(this.cursorPos.row),
col: this.cursorPos.col, col: this.cursorPos.col,
percent : Math.floor(((currentIndex / this.textLines.length) * 100)), percent: Math.floor((currentIndex / this.textLines.length) * 100),
below: this.getRemainingLinesBelowRow(), below: this.getRemainingLinesBelowRow(),
}; };
}; };

View File

@ -5,9 +5,7 @@
const MenuModule = require('./menu_module.js').MenuModule; const MenuModule = require('./menu_module.js').MenuModule;
const Message = require('./message.js'); const Message = require('./message.js');
const UserProps = require('./user_property.js'); const UserProps = require('./user_property.js');
const { const { filterMessageListByReadACS } = require('./message_area.js');
filterMessageListByReadACS
} = require('./message_area.js');
exports.moduleInfo = { exports.moduleInfo = {
name: 'My Messages', name: 'My Messages',
@ -22,7 +20,10 @@ exports.getModule = class MyMessagesModule extends MenuModule {
initSequence() { initSequence() {
const filter = { const filter = {
toUserName : [ this.client.user.username, this.client.user.getProperty(UserProps.RealName) ], toUserName: [
this.client.user.username,
this.client.user.getProperty(UserProps.RealName),
],
sort: 'modTimestamp', sort: 'modTimestamp',
resultType: 'messageList', resultType: 'messageList',
limit: 1024 * 16, // we want some sort of limit... limit: 1024 * 16, // we want some sort of limit...
@ -30,7 +31,10 @@ exports.getModule = class MyMessagesModule extends MenuModule {
Message.findMessages(filter, (err, messageList) => { Message.findMessages(filter, (err, messageList) => {
if (err) { if (err) {
this.client.log.warn( { error : err.message }, 'Error finding messages addressed to current user'); this.client.log.warn(
{ error: err.message },
'Error finding messages addressed to current user'
);
return this.prevMenu(); return this.prevMenu();
} }
@ -52,7 +56,7 @@ exports.getModule = class MyMessagesModule extends MenuModule {
const menuOpts = { const menuOpts = {
extraArgs: { extraArgs: {
messageList: this.messageList, messageList: this.messageList,
noUpdateLastReadId : true noUpdateLastReadId: true,
}, },
menuFlags: ['popParent'], menuFlags: ['popParent'],
}; };

View File

@ -46,7 +46,6 @@ exports.getModule = class NewScanModule extends MenuModule {
constructor(options) { constructor(options) {
super(options); super(options);
this.newScanFullExit = _.get(options, 'lastMenuResult.fullExit', false); this.newScanFullExit = _.get(options, 'lastMenuResult.fullExit', false);
this.currentStep = Steps.MessageConfs; this.currentStep = Steps.MessageConfs;
@ -70,12 +69,15 @@ exports.getModule = class NewScanModule extends MenuModule {
if (!this.sortedMessageConfs) { if (!this.sortedMessageConfs) {
const getAvailOpts = { includeSystemInternal: true }; // find new private messages, bulletins, etc. const getAvailOpts = { includeSystemInternal: true }; // find new private messages, bulletins, etc.
this.sortedMessageConfs = _.map(msgArea.getAvailableMessageConferences(this.client, getAvailOpts), (v, k) => { this.sortedMessageConfs = _.map(
msgArea.getAvailableMessageConferences(this.client, getAvailOpts),
(v, k) => {
return { return {
confTag: k, confTag: k,
conf: v, conf: v,
}; };
}); }
);
// //
// Sort conferences by name, other than 'system_internal' which should // Sort conferences by name, other than 'system_internal' which should
@ -86,7 +88,10 @@ exports.getModule = class NewScanModule extends MenuModule {
if ('system_internal' === a.confTag) { if ('system_internal' === a.confTag) {
return -1; return -1;
} else { } else {
return a.conf.name.localeCompare(b.conf.name, { sensitivity : false, numeric : true } ); return a.conf.name.localeCompare(b.conf.name, {
sensitivity: false,
numeric: true,
});
} }
}); });
@ -111,8 +116,12 @@ exports.getModule = class NewScanModule extends MenuModule {
newScanMessageArea(conf, cb) { newScanMessageArea(conf, cb) {
// :TODO: it would be nice to cache this - must be done by conf! // :TODO: it would be nice to cache this - must be done by conf!
const omitMessageAreaTags = valueAsArray(_.get(this, 'menuConfig.config.omitMessageAreaTags', [])); const omitMessageAreaTags = valueAsArray(
const sortedAreas = msgArea.getSortedAvailMessageAreasByConfTag(conf.confTag, { client : this.client } ).filter(area => { _.get(this, 'menuConfig.config.omitMessageAreaTags', [])
);
const sortedAreas = msgArea
.getSortedAvailMessageAreasByConfTag(conf.confTag, { client: this.client })
.filter(area => {
return !omitMessageAreaTags.includes(area.areaTag); return !omitMessageAreaTags.includes(area.areaTag);
}); });
const currentArea = sortedAreas[this.currentScanAux.area]; const currentArea = sortedAreas[this.currentScanAux.area];
@ -135,17 +144,21 @@ exports.getModule = class NewScanModule extends MenuModule {
} }
}, },
function updateStatusScanStarted(callback) { function updateStatusScanStarted(callback) {
self.updateScanStatus(stringFormat(self.scanStartFmt, { self.updateScanStatus(
stringFormat(self.scanStartFmt, {
confName: conf.conf.name, confName: conf.conf.name,
confDesc: conf.conf.desc, confDesc: conf.conf.desc,
areaName: currentArea.area.name, areaName: currentArea.area.name,
areaDesc : currentArea.area.desc areaDesc: currentArea.area.desc,
})); })
);
return callback(null); return callback(null);
}, },
function getNewMessagesCountInArea(callback) { function getNewMessagesCountInArea(callback) {
msgArea.getNewMessageCountInAreaForUser( msgArea.getNewMessageCountInAreaForUser(
self.client.user.userId, currentArea.areaTag, (err, newMessageCount) => { self.client.user.userId,
currentArea.areaTag,
(err, newMessageCount) => {
callback(err, newMessageCount); callback(err, newMessageCount);
} }
); );
@ -158,11 +171,14 @@ exports.getModule = class NewScanModule extends MenuModule {
const nextModuleOpts = { const nextModuleOpts = {
extraArgs: { extraArgs: {
messageAreaTag: currentArea.areaTag, messageAreaTag: currentArea.areaTag,
} },
}; };
return self.gotoMenu(self.menuConfig.config.newScanMessageList || 'newScanMessageList', nextModuleOpts); return self.gotoMenu(
} self.menuConfig.config.newScanMessageList || 'newScanMessageList',
nextModuleOpts
);
},
], ],
err => { err => {
return cb(err); return cb(err);
@ -172,21 +188,28 @@ exports.getModule = class NewScanModule extends MenuModule {
newScanFileBase(cb) { newScanFileBase(cb) {
// :TODO: add in steps // :TODO: add in steps
const omitFileAreaTags = valueAsArray(_.get(this, 'menuConfig.config.omitFileAreaTags', [])); const omitFileAreaTags = valueAsArray(
_.get(this, 'menuConfig.config.omitFileAreaTags', [])
);
const filterCriteria = { const filterCriteria = {
newerThanFileId : FileBaseFilters.getFileBaseLastViewedFileIdByUser(this.client.user), newerThanFileId: FileBaseFilters.getFileBaseLastViewedFileIdByUser(
areaTag : getAvailableFileAreaTags(this.client).filter(ft => !omitFileAreaTags.includes(ft)), this.client.user
),
areaTag: getAvailableFileAreaTags(this.client).filter(
ft => !omitFileAreaTags.includes(ft)
),
order: 'ascending', // oldest first order: 'ascending', // oldest first
}; };
FileEntry.findFiles( FileEntry.findFiles(filterCriteria, (err, fileIds) => {
filterCriteria,
(err, fileIds) => {
if (err || 0 === fileIds.length) { if (err || 0 === fileIds.length) {
return cb(err ? err : Errors.DoesNotExist('No more new files')); return cb(err ? err : Errors.DoesNotExist('No more new files'));
} }
FileBaseFilters.setFileBaseLastViewedFileIdForUser( this.client.user, fileIds[fileIds.length - 1] ); FileBaseFilters.setFileBaseLastViewedFileIdForUser(
this.client.user,
fileIds[fileIds.length - 1]
);
const menuOpts = { const menuOpts = {
extraArgs: { extraArgs: {
@ -194,9 +217,11 @@ exports.getModule = class NewScanModule extends MenuModule {
}, },
}; };
return this.gotoMenu(this.menuConfig.config.newScanFileBaseList || 'newScanFileBaseList', menuOpts); return this.gotoMenu(
} this.menuConfig.config.newScanFileBaseList || 'newScanFileBaseList',
menuOpts
); );
});
} }
getSaveState() { getSaveState() {
@ -227,7 +252,8 @@ exports.getModule = class NewScanModule extends MenuModule {
}); });
break; break;
default : return cb(null); default:
return cb(null);
} }
} }
@ -243,7 +269,9 @@ exports.getModule = class NewScanModule extends MenuModule {
} }
const self = this; const self = this;
const vc = self.viewControllers.allViews = new ViewController( { client : self.client } ); const vc = (self.viewControllers.allViews = new ViewController({
client: self.client,
}));
// :TODO: display scan step/etc. // :TODO: display scan step/etc.
@ -260,11 +288,14 @@ exports.getModule = class NewScanModule extends MenuModule {
}, },
function performCurrentStepScan(callback) { function performCurrentStepScan(callback) {
return self.performScanCurrentStep(callback); return self.performScanCurrentStep(callback);
} },
], ],
err => { err => {
if (err) { if (err) {
self.client.log.error( { error : err.toString() }, 'Error during new scan'); self.client.log.error(
{ error: err.toString() },
'Error during new scan'
);
} }
return cb(err); return cb(err);
} }

View File

@ -37,13 +37,15 @@ const MciViewIds = {
preview: 3, preview: 3,
customRangeStart: 10, customRangeStart: 10,
} },
}; };
exports.getModule = class NodeMessageModule extends MenuModule { exports.getModule = class NodeMessageModule extends MenuModule {
constructor(options) { constructor(options) {
super(options); super(options);
this.config = Object.assign({}, _.get(options, 'menuConfig.config'), { extraArgs : options.extraArgs }); this.config = Object.assign({}, _.get(options, 'menuConfig.config'), {
extraArgs: options.extraArgs,
});
this.menuMethods = { this.menuMethods = {
sendMessage: (formData, extraArgs, cb) => { sendMessage: (formData, extraArgs, cb) => {
@ -65,7 +67,10 @@ exports.getModule = class NodeMessageModule extends MenuModule {
} }
} }
Events.emit(Events.getSystemEvents().UserSendNodeMsg, { user : this.client.user, global : -1 === nodeId } ); Events.emit(Events.getSystemEvents().UserSendNodeMsg, {
user: this.client.user,
global: -1 === nodeId,
});
return this.prevMenu(cb); return this.prevMenu(cb);
}); });
@ -81,18 +86,28 @@ exports.getModule = class NodeMessageModule extends MenuModule {
series( series(
[ [
(callback) => { callback => {
return this.prepViewController('sendMessage', FormIds.sendMessage, mciData.menu, callback); return this.prepViewController(
},
(callback) => {
return this.validateMCIByViewIds(
'sendMessage', 'sendMessage',
[ MciViewIds.sendMessage.nodeSelect, MciViewIds.sendMessage.message ], FormIds.sendMessage,
mciData.menu,
callback callback
); );
}, },
(callback) => { callback => {
const nodeSelectView = this.viewControllers.sendMessage.getView(MciViewIds.sendMessage.nodeSelect); return this.validateMCIByViewIds(
'sendMessage',
[
MciViewIds.sendMessage.nodeSelect,
MciViewIds.sendMessage.message,
],
callback
);
},
callback => {
const nodeSelectView = this.viewControllers.sendMessage.getView(
MciViewIds.sendMessage.nodeSelect
);
this.prepareNodeList(); this.prepareNodeList();
nodeSelectView.on('index update', idx => { nodeSelectView.on('index update', idx => {
@ -104,23 +119,32 @@ exports.getModule = class NodeMessageModule extends MenuModule {
this.nodeListSelectionIndexUpdate(0); this.nodeListSelectionIndexUpdate(0);
return callback(null); return callback(null);
}, },
(callback) => { callback => {
const previewView = this.viewControllers.sendMessage.getView(MciViewIds.sendMessage.preview); const previewView = this.viewControllers.sendMessage.getView(
MciViewIds.sendMessage.preview
);
if (!previewView) { if (!previewView) {
return callback(null); // preview is optional return callback(null); // preview is optional
} }
const messageView = this.viewControllers.sendMessage.getView(MciViewIds.sendMessage.message); const messageView = this.viewControllers.sendMessage.getView(
MciViewIds.sendMessage.message
);
let timerId; let timerId;
messageView.on('key press', () => { messageView.on(
'key press',
() => {
clearTimeout(timerId); clearTimeout(timerId);
const focused = this.viewControllers.sendMessage.getFocusedView(); const focused =
this.viewControllers.sendMessage.getFocusedView();
if (focused === messageView) { if (focused === messageView) {
previewView.setText(messageView.getData()); previewView.setText(messageView.getData());
focused.setFocus(true); focused.setFocus(true);
} }
}, 500); },
} 500
);
},
], ],
err => { err => {
return cb(err); return cb(err);
@ -130,7 +154,9 @@ exports.getModule = class NodeMessageModule extends MenuModule {
} }
createInterruptItem(message, cb) { createInterruptItem(message, cb) {
const dateTimeFormat = this.config.dateTimeFormat || this.client.currentTheme.helpers.getDateTimeFormat(); const dateTimeFormat =
this.config.dateTimeFormat ||
this.client.currentTheme.helpers.getDateTimeFormat();
const textFormatObj = { const textFormatObj = {
fromUserName: this.client.user.username, fromUserName: this.client.user.username,
@ -167,7 +193,7 @@ exports.getModule = class NodeMessageModule extends MenuModule {
async.waterfall( async.waterfall(
[ [
(callback) => { callback => {
getArt('header', headerArt => { getArt('header', headerArt => {
return callback(null, headerArt); return callback(null, headerArt);
}); });
@ -179,10 +205,12 @@ exports.getModule = class NodeMessageModule extends MenuModule {
}, },
(headerArt, footerArt, callback) => { (headerArt, footerArt, callback) => {
if (headerArt || footerArt) { if (headerArt || footerArt) {
item.contents = `${headerArt || ''}\r\n${pipeToAnsi(item.text)}\r\n${footerArt || ''}`; item.contents = `${headerArt || ''}\r\n${pipeToAnsi(
item.text
)}\r\n${footerArt || ''}`;
} }
return callback(null); return callback(null);
} },
], ],
err => { err => {
return cb(err, item); return cb(err, item);
@ -192,7 +220,8 @@ exports.getModule = class NodeMessageModule extends MenuModule {
prepareNodeList() { prepareNodeList() {
// standard node list with {text} field added for compliance // standard node list with {text} field added for compliance
this.nodeList = [{ this.nodeList = [
{
text: '-ALL-', text: '-ALL-',
// dummy fields: // dummy fields:
node: -1, node: -1,
@ -204,9 +233,16 @@ exports.getModule = class NodeMessageModule extends MenuModule {
location: 'N/A', location: 'N/A',
affils: 'N/A', affils: 'N/A',
timeOn: 'N/A', timeOn: 'N/A',
}].concat(getActiveConnectionList(true) },
.map(node => Object.assign(node, { text : -1 == node.node ? '-ALL-' : node.node.toString() } )) ]
).filter(node => node.node !== this.client.node); // remove our client's node .concat(
getActiveConnectionList(true).map(node =>
Object.assign(node, {
text: -1 == node.node ? '-ALL-' : node.node.toString(),
})
)
)
.filter(node => node.node !== this.client.node); // remove our client's node
this.nodeList.sort((a, b) => a.node - b.node); // sort by node this.nodeList.sort((a, b) => a.node - b.node); // sort by node
} }
@ -215,6 +251,10 @@ exports.getModule = class NodeMessageModule extends MenuModule {
if (!node) { if (!node) {
return; return;
} }
this.updateCustomViewTextsWithFilter('sendMessage', MciViewIds.sendMessage.customRangeStart, node); this.updateCustomViewTextsWithFilter(
'sendMessage',
MciViewIds.sendMessage.customRangeStart,
node
);
} }
}; };

View File

@ -8,9 +8,7 @@ const theme = require('./theme.js');
const login = require('./system_menu_method.js').login; const login = require('./system_menu_method.js').login;
const Config = require('./config.js').get; const Config = require('./config.js').get;
const messageArea = require('./message_area.js'); const messageArea = require('./message_area.js');
const { const { getISOTimestampString } = require('./database.js');
getISOTimestampString
} = require('./database.js');
const UserProps = require('./user_property.js'); const UserProps = require('./user_property.js');
// deps // deps
@ -29,7 +27,6 @@ const MciViewIds = {
}; };
exports.getModule = class NewUserAppModule extends MenuModule { exports.getModule = class NewUserAppModule extends MenuModule {
constructor(options) { constructor(options) {
super(options); super(options);
@ -40,8 +37,14 @@ exports.getModule = class NewUserAppModule extends MenuModule {
// Validation stuff // Validation stuff
// //
validatePassConfirmMatch: function (data, cb) { validatePassConfirmMatch: function (data, cb) {
const passwordView = self.viewControllers.menu.getView(MciViewIds.password); const passwordView = self.viewControllers.menu.getView(
return cb(passwordView.getData() === data ? null : new Error('Passwords do not match')); MciViewIds.password
);
return cb(
passwordView.getData() === data
? null
: new Error('Passwords do not match')
);
}, },
viewValidationListener: function (err, cb) { viewValidationListener: function (err, cb) {
@ -54,7 +57,9 @@ exports.getModule = class NewUserAppModule extends MenuModule {
if (err.view.getId() === MciViewIds.confirm) { if (err.view.getId() === MciViewIds.confirm) {
newFocusId = MciViewIds.password; newFocusId = MciViewIds.password;
self.viewControllers.menu.getView(MciViewIds.password).clearText(); self.viewControllers.menu
.getView(MciViewIds.password)
.clearText();
} }
} else { } else {
errMsgView.clearText(); errMsgView.clearText();
@ -63,7 +68,6 @@ exports.getModule = class NewUserAppModule extends MenuModule {
return cb(newFocusId); return cb(newFocusId);
}, },
// //
// Submit handlers // Submit handlers
// //
@ -76,8 +80,15 @@ exports.getModule = class NewUserAppModule extends MenuModule {
// //
// We have to disable ACS checks for initial default areas as the user is not yet ready // We have to disable ACS checks for initial default areas as the user is not yet ready
// //
let confTag = messageArea.getDefaultMessageConferenceTag(self.client, true); // true=disableAcsCheck let confTag = messageArea.getDefaultMessageConferenceTag(
let areaTag = messageArea.getDefaultMessageAreaTagByConfTag(self.client, confTag, true); // true=disableAcsCheck self.client,
true
); // true=disableAcsCheck
let areaTag = messageArea.getDefaultMessageAreaTagByConfTag(
self.client,
confTag,
true
); // true=disableAcsCheck
// can't store undefined! // can't store undefined!
confTag = confTag || ''; confTag = confTag || '';
@ -85,7 +96,9 @@ exports.getModule = class NewUserAppModule extends MenuModule {
newUser.properties = { newUser.properties = {
[UserProps.RealName]: formData.value.realName, [UserProps.RealName]: formData.value.realName,
[ UserProps.Birthdate ] : getISOTimestampString(formData.value.birthdate), [UserProps.Birthdate]: getISOTimestampString(
formData.value.birthdate
),
[UserProps.Sex]: formData.value.sex, [UserProps.Sex]: formData.value.sex,
[UserProps.Location]: formData.value.location, [UserProps.Location]: formData.value.location,
[UserProps.Affiliations]: formData.value.affils, [UserProps.Affiliations]: formData.value.affils,
@ -117,7 +130,10 @@ exports.getModule = class NewUserAppModule extends MenuModule {
}; };
newUser.create(createUserInfo, err => { newUser.create(createUserInfo, err => {
if (err) { if (err) {
self.client.log.info( { error : err, username : formData.value.username }, 'New user creation failed'); self.client.log.info(
{ error: err, username: formData.value.username },
'New user creation failed'
);
self.gotoMenu(extraArgs.error, err => { self.gotoMenu(extraArgs.error, err => {
if (err) { if (err) {
@ -126,7 +142,10 @@ exports.getModule = class NewUserAppModule extends MenuModule {
return cb(null); return cb(null);
}); });
} else { } else {
self.client.log.info( { username : formData.value.username, userId : newUser.userId }, 'New user created'); self.client.log.info(
{ username: formData.value.username, userId: newUser.userId },
'New user created'
);
// Cache SysOp information now // Cache SysOp information now
// :TODO: Similar to bbs.js. DRY // :TODO: Similar to bbs.js. DRY
@ -137,7 +156,10 @@ exports.getModule = class NewUserAppModule extends MenuModule {
}; };
} }
if(User.AccountStatus.inactive === self.client.user.properties[UserProps.AccountStatus]) { if (
User.AccountStatus.inactive ===
self.client.user.properties[UserProps.AccountStatus]
) {
return self.gotoMenu(extraArgs.inactive, cb); return self.gotoMenu(extraArgs.inactive, cb);
} else { } else {
// //

View File

@ -4,10 +4,7 @@
// ENiGMA½ // ENiGMA½
const MenuModule = require('./menu_module.js').MenuModule; const MenuModule = require('./menu_module.js').MenuModule;
const { const { getModDatabasePath, getTransactionDatabase } = require('./database.js');
getModDatabasePath,
getTransactionDatabase
} = require('./database.js');
// deps // deps
const sqlite3 = require('sqlite3'); const sqlite3 = require('sqlite3');
@ -36,7 +33,7 @@ const MciViewIds = {
newEntry: 1, newEntry: 1,
entryPreview: 2, entryPreview: 2,
addPrompt: 3, addPrompt: 3,
} },
}; };
const FormIds = { const FormIds = {
@ -56,18 +53,23 @@ exports.getModule = class OnelinerzModule extends MenuModule {
}, },
addEntry: function (formData, extraArgs, cb) { addEntry: function (formData, extraArgs, cb) {
if(_.isString(formData.value.oneliner) && formData.value.oneliner.length > 0) { if (
_.isString(formData.value.oneliner) &&
formData.value.oneliner.length > 0
) {
const oneliner = formData.value.oneliner.trim(); // remove any trailing ws const oneliner = formData.value.oneliner.trim(); // remove any trailing ws
self.storeNewOneliner(oneliner, err => { self.storeNewOneliner(oneliner, err => {
if (err) { if (err) {
self.client.log.warn( { error : err.message }, 'Failed saving oneliner'); self.client.log.warn(
{ error: err.message },
'Failed saving oneliner'
);
} }
self.clearAddForm(); self.clearAddForm();
return self.displayViewScreen(true, cb); // true=cls return self.displayViewScreen(true, cb); // true=cls
}); });
} else { } else {
// empty message - treat as if cancel was hit // empty message - treat as if cancel was hit
return self.displayViewScreen(true, cb); // true=cls return self.displayViewScreen(true, cb); // true=cls
@ -77,7 +79,7 @@ exports.getModule = class OnelinerzModule extends MenuModule {
cancelAdd: function (formData, extraArgs, cb) { cancelAdd: function (formData, extraArgs, cb) {
self.clearAddForm(); self.clearAddForm();
return self.displayViewScreen(true, cb); // true=cls return self.displayViewScreen(true, cb); // true=cls
} },
}; };
} }
@ -90,7 +92,7 @@ exports.getModule = class OnelinerzModule extends MenuModule {
}, },
function display(callback) { function display(callback) {
return self.displayViewScreen(false, callback); return self.displayViewScreen(false, callback);
} },
], ],
err => { err => {
if (err) { if (err) {
@ -116,19 +118,23 @@ exports.getModule = class OnelinerzModule extends MenuModule {
FormIds.view, FormIds.view,
{ {
clearScreen, clearScreen,
trailingLF : false trailingLF: false,
}, },
(err, artInfo, wasCreated) => { (err, artInfo, wasCreated) => {
if (!err && !wasCreated) { if (!err && !wasCreated) {
self.viewControllers.view.setFocus(true); self.viewControllers.view.setFocus(true);
self.viewControllers.view.getView(MciViewIds.view.addPrompt).redraw(); self.viewControllers.view
.getView(MciViewIds.view.addPrompt)
.redraw();
} }
return callback(err); return callback(err);
} }
); );
}, },
function fetchEntries(callback) { function fetchEntries(callback) {
const entriesView = self.viewControllers.view.getView(MciViewIds.view.entries); const entriesView = self.viewControllers.view.getView(
MciViewIds.view.entries
);
const limit = entriesView.dimens.height; const limit = entriesView.dimens.height;
let entries = []; let entries = [];
@ -158,7 +164,8 @@ exports.getModule = class OnelinerzModule extends MenuModule {
self.menuConfig.config.timestampFormat || // deprecated self.menuConfig.config.timestampFormat || // deprecated
self.client.currentTheme.helpers.getDateFormat('short'); self.client.currentTheme.helpers.getDateFormat('short');
entriesView.setItems(entries.map( e => { entriesView.setItems(
entries.map(e => {
return { return {
text: e.oneliner, // standard text: e.oneliner, // standard
userId: e.user_id, userId: e.user_id,
@ -166,16 +173,19 @@ exports.getModule = class OnelinerzModule extends MenuModule {
oneliner: e.oneliner, oneliner: e.oneliner,
ts: e.timestamp.format(tsFormat), ts: e.timestamp.format(tsFormat),
}; };
})); })
);
entriesView.redraw(); entriesView.redraw();
return callback(null); return callback(null);
}, },
function finalPrep(callback) { function finalPrep(callback) {
const promptView = self.viewControllers.view.getView(MciViewIds.view.addPrompt); const promptView = self.viewControllers.view.getView(
MciViewIds.view.addPrompt
);
promptView.setFocusItemIndex(1); // default to NO promptView.setFocusItemIndex(1); // default to NO
return callback(null); return callback(null);
} },
], ],
err => { err => {
if (cb) { if (cb) {
@ -198,21 +208,27 @@ exports.getModule = class OnelinerzModule extends MenuModule {
FormIds.add, FormIds.add,
{ {
clearScreen: true, clearScreen: true,
trailingLF : false trailingLF: false,
}, },
(err, artInfo, wasCreated) => { (err, artInfo, wasCreated) => {
if (!wasCreated) { if (!wasCreated) {
self.viewControllers.add.setFocus(true); self.viewControllers.add.setFocus(true);
self.viewControllers.add.redrawAll(); self.viewControllers.add.redrawAll();
self.viewControllers.add.switchFocus(MciViewIds.add.newEntry); self.viewControllers.add.switchFocus(
MciViewIds.add.newEntry
);
} }
return callback(err); return callback(err);
} }
); );
}, },
function initPreviewUpdates(callback) { function initPreviewUpdates(callback) {
const previewView = self.viewControllers.add.getView(MciViewIds.add.entryPreview); const previewView = self.viewControllers.add.getView(
const entryView = self.viewControllers.add.getView(MciViewIds.add.newEntry); MciViewIds.add.entryPreview
);
const entryView = self.viewControllers.add.getView(
MciViewIds.add.newEntry
);
if (previewView) { if (previewView) {
let timerId; let timerId;
entryView.on('key press', () => { entryView.on('key press', () => {
@ -227,7 +243,7 @@ exports.getModule = class OnelinerzModule extends MenuModule {
}); });
} }
return callback(null); return callback(null);
} },
], ],
err => { err => {
if (cb) { if (cb) {
@ -249,12 +265,14 @@ exports.getModule = class OnelinerzModule extends MenuModule {
[ [
function openDatabase(callback) { function openDatabase(callback) {
const dbSuffix = self.menuConfig.config.dbSuffix; const dbSuffix = self.menuConfig.config.dbSuffix;
self.db = getTransactionDatabase(new sqlite3.Database( self.db = getTransactionDatabase(
new sqlite3.Database(
getModDatabasePath(exports.moduleInfo, dbSuffix), getModDatabasePath(exports.moduleInfo, dbSuffix),
err => { err => {
return callback(err); return callback(err);
} }
)); )
);
}, },
function createTables(callback) { function createTables(callback) {
self.db.run( self.db.run(
@ -264,12 +282,12 @@ exports.getModule = class OnelinerzModule extends MenuModule {
user_name VARCHAR NOT NULL, user_name VARCHAR NOT NULL,
oneliner VARCHAR NOT NULL, oneliner VARCHAR NOT NULL,
timestamp DATETIME NOT NULL timestamp DATETIME NOT NULL
);` );`,
,
err => { err => {
return callback(err); return callback(err);
});
} }
);
},
], ],
err => { err => {
return cb(err); return cb(err);
@ -287,7 +305,12 @@ exports.getModule = class OnelinerzModule extends MenuModule {
self.db.run( self.db.run(
`INSERT INTO onelinerz (user_id, user_name, oneliner, timestamp) `INSERT INTO onelinerz (user_id, user_name, oneliner, timestamp)
VALUES (?, ?, ?, ?);`, VALUES (?, ?, ?, ?);`,
[ self.client.user.userId, self.client.user.username, oneliner, ts ], [
self.client.user.userId,
self.client.user.username,
oneliner,
ts,
],
callback callback
); );
}, },
@ -304,7 +327,7 @@ exports.getModule = class OnelinerzModule extends MenuModule {
);`, );`,
callback callback
); );
} },
], ],
err => { err => {
return cb(err); return cb(err);

Some files were not shown because too many files have changed in this diff Show More