Initial sync up with master after Prettier

This commit is contained in:
Bryan Ashby 2022-06-12 13:57:46 -06:00
commit e0fca9f8f7
No known key found for this signature in database
GPG Key ID: C2C1B501E4EFD994
177 changed files with 23158 additions and 17630 deletions

View File

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

View File

@ -22,7 +22,7 @@ A clear and concise description of what you expected to happen.
If applicable, add screenshots to help explain your problem.
**Environment**
* [ ] I am using Node.js v12.x LTS or higher
* [ ] I am using Node.js v14.x LTS or higher
* [ ] `npm install` or `yarn` reports success
* Actual Node.js version (`node --version`):
* Operating system (`uname -a` on *nix systems):

12
.prettierignore Normal file
View File

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

19
.prettierrc.json Normal file
View File

@ -0,0 +1,19 @@
{
"arrowParens": "avoid",
"bracketSameLine": false,
"bracketSpacing": true,
"embeddedLanguageFormatting": "auto",
"htmlWhitespaceSensitivity": "css",
"insertPragma": false,
"jsxSingleQuote": false,
"printWidth": 90,
"proseWrap": "preserve",
"quoteProps": "as-needed",
"requirePragma": false,
"semi": true,
"singleQuote": true,
"tabWidth": 4,
"trailingComma": "es5",
"useTabs": false,
"vueIndentScriptAndStyle": false
}

View File

@ -1,6 +1,7 @@
# Contributing
## Style
Though you'll see a lot of older style callback code, please utilize modern JavaScript. ES6 classes, arrow functions, and builtins.
There is almost never a reason to use `var`. Prefer `const` where you can and and `let` otherwise.
Save with UNIX line feeds, UTF-8 without BOM, and tabs set to 4 spaces.
## Style & Formatting
* In general, [Prettier](https://prettier.io) is used. See the [Prettier installation and basic instructions](https://prettier.io/docs/en/install.html) for more information.
* Though you'll see a lot of older style callback code, please utilize modern JavaScript. ES6 classes, arrow functions, and builtins.
* There is almost never a reason to use `var`. Prefer `const` where you can and and `let` otherwise.
* Save with UNIX line feeds, UTF-8 without BOM, and tabs set to 4 spaces.

View File

@ -2,16 +2,16 @@
This document attempts to track **major** changes and additions in ENiGMA½. For details, see GitHub.
## 0.0.13-beta
* Removed terminal `cursor position reports` from most locations in the code. This should greatly increase the number of terminal programs that work with ENiGMA½. For more information, see [Issue #222](https://github.com/NuSkooler/enigma-bbs/issues/222). This may also resolve other issues, such as [Issue #365](https://github.com/NuSkooler/enigma-bbs/issues/365), and [Issue #320](https://github.com/NuSkooler/enigma-bbs/issues/320). Anyone that previously had terminal incompatibilities please re-check and let us know!
* Bumped up the minimum [Node.js](https://nodejs.org/en/) version to V14. This will allow more expressive Javascript programming syntax with ECMAScript 2020 to improve the development experience.
* Added new configuration options for `term.checkUtf8Encoding`, `term.checkAnsiHomePostion`, `term.cp437TermList`, and `term.utf8TermList`. More information on these options is available in `UPGRADE.md`
* **Note for contributors**: ENiGMA has switched to [Prettier](https://prettier.io) for formatting/style. Please see [CONTRIBUTING](CONTRIBUTING.md) and the Prettier website for more information.
* Removed terminal `cursor position reports` from most locations in the code. This should greatly increase the number of terminal programs that work with Enigma 1/2. For more information, see [Issue #222](https://github.com/NuSkooler/enigma-bbs/issues/222). This may also resolve other issues, such as [Issue #365](https://github.com/NuSkooler/enigma-bbs/issues/365), and [Issue #320](https://github.com/NuSkooler/enigma-bbs/issues/320). Anyone that previously had terminal incompatibilities please re-check and let us know!
* Bumped up the minimum [Node.js](https://nodejs.org/en/) version to v14. This will allow more expressive Javascript programming syntax with ECMAScript 2020 to improve the development experience.
* Added new configuration options for `term.checkUtf8Encoding`, `term.checkAnsiHomePostion`, `term.cp437TermList`, and `term.utf8TermList`. More information on these options is available in [UPGRADE](UPGRADE.md).
* New Waiting For Caller (WFC) support via the `wfc.js` module.
* Many new system statistics available via the StatLog such as current and average load, memory, etc.
* Many new MCI codes: `MB`, `MF`, `LA`, `CL`, `UU`, `FT`, `DD`, `FB`, `DB`, `LC`, `LT`, `LD`, and more. See [MCI](./docs/art/mci.md).
* SyncTERM style font support detection.
* Many additional backward-compatible bug fixes since the first release of 0.0.12-beta. See the [project repository](https://github.com/NuSkooler/enigma-bbs) for more information.
## 0.0.12-beta
* The `master` branch has become mainline. What this means to users is `git pull` will always give you the latest and greatest. Make sure to read [Updating](./docs/admin/updating.md) and keep an eye on `WHATSNEW.md` (this file) and [UPGRADE](UPGRADE.md)! See also [ticket #276](https://github.com/NuSkooler/enigma-bbs/issues/276).
* Development now occurs against [Node.js 14 LTS](https://github.com/nodejs/node/blob/master/doc/changelogs/CHANGELOG_V14.md).

View File

@ -7,10 +7,7 @@ const Door = require('./door.js');
const theme = require('./theme.js');
const ansi = require('./ansi_term.js');
const { Errors } = require('./enig_error.js');
const {
trackDoorRunBegin,
trackDoorRunEnd
} = require('./door_util.js');
const { trackDoorRunBegin, trackDoorRunEnd } = require('./door_util.js');
const Log = require('./logger').log;
// deps
@ -71,8 +68,8 @@ exports.getModule = class AbracadabraModule extends MenuModule {
this.config = options.menuConfig.config;
// :TODO: MenuModule.validateConfig(cb) -- validate config section gracefully instead of asserts! -- { key : type, key2 : type2, ... }
// .. and/or EnigAssert
assert(_.isString(this.config.name, 'Config \'name\' is required'));
assert(_.isString(this.config.cmd, 'Config \'cmd\' is required'));
assert(_.isString(this.config.name, "Config 'name' is required"));
assert(_.isString(this.config.cmd, "Config 'cmd' is required"));
this.config.nodeMax = this.config.nodeMax || 0;
this.config.args = this.config.args || [];
@ -100,14 +97,16 @@ exports.getModule = class AbracadabraModule extends MenuModule {
async.series(
[
function validateNodeCount(callback) {
if(self.config.nodeMax > 0 &&
if (
self.config.nodeMax > 0 &&
_.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(
{
name: self.config.name,
activeCount : activeDoorNodeInstances[self.config.name]
activeCount: activeDoorNodeInstances[self.config.name],
},
`Too many active instances of door "${self.config.name}"`);
@ -118,11 +117,15 @@ exports.getModule = class AbracadabraModule extends MenuModule {
});
});
} 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()
self.pausePrompt(() => {
return callback(Errors.AccessDenied('Too many active instances'));
return callback(
Errors.AccessDenied('Too many active instances')
);
});
}
} else {
@ -135,21 +138,26 @@ exports.getModule = class AbracadabraModule extends MenuModule {
return self.doorInstance.prepare(self.config.io || 'stdio', 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);
}
self.dropFile = new DropFile(
self.client,
{ fileType : self.config.dropFileType }
);
self.dropFile = new DropFile(self.client, {
fileType: self.config.dropFileType,
});
return self.dropFile.createFile(callback);
}
},
],
function complete(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.prevMenu();
} else {
@ -188,7 +196,10 @@ exports.getModule = class AbracadabraModule extends MenuModule {
if (exeInfo.dropFilePath) {
fs.unlink(exeInfo.dropFilePath, 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 { getConfigPath } = require('./config_util');
const UserDb = require('./database.js').dbs.user;
const {
getISOTimestampString
} = require('./database.js');
const { getISOTimestampString } = require('./database.js');
const UserInterruptQueue = require('./user_interrupt_queue.js');
const {
getConnectionByUserId
} = require('./client_connections.js');
const { getConnectionByUserId } = require('./client_connections.js');
const UserProps = require('./user_property.js');
const {
Errors,
ErrorReasons
} = require('./enig_error.js');
const { Errors, ErrorReasons } = require('./enig_error.js');
const { getThemeArt } = require('./theme.js');
const {
pipeToAnsi,
stripMciColorCodes
} = require('./color_codes.js');
const { pipeToAnsi, stripMciColorCodes } = require('./color_codes.js');
const stringFormat = require('./string_format.js');
const StatLog = require('./stat_log.js');
const Log = require('./logger.js').log;
@ -55,7 +45,8 @@ class Achievement {
achievement = new UserStatAchievement(data);
break;
default : return;
default:
return;
}
if (achievement.isValid()) {
@ -84,19 +75,24 @@ class Achievement {
}
break;
default : return false;
default:
return false;
}
return true;
}
getMatchDetails(/*matchAgainst*/) {
}
getMatchDetails(/*matchAgainst*/) {}
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 (_.isString(details.globalText) || !details.globalText);
return _.isString(details.globalText) || !details.globalText;
}
}
@ -105,7 +101,9 @@ class UserStatAchievement extends Achievement {
super(data);
// 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() {
@ -167,7 +165,7 @@ class Achievements {
if (!err) {
configLoaded();
}
}
},
});
this.config.init(configPath, err => {
@ -202,14 +200,23 @@ class Achievements {
record(info, localInterruptItem, cb) {
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 cleanText = stripMciColorCodes(localInterruptItem.achievText);
const recordData = [
info.client.user.userId, info.achievementTag, getISOTimestampString(info.timestamp), info.matchField,
cleanTitle, cleanText, info.details.points,
info.client.user.userId,
info.achievementTag,
getISOTimestampString(info.timestamp),
info.matchField,
cleanTitle,
cleanText,
info.details.points,
];
UserDb.run(
@ -221,16 +228,13 @@ class Achievements {
return cb(err);
}
this.events.emit(
Events.getSystemEvents().UserAchievementEarned,
{
this.events.emit(Events.getSystemEvents().UserAchievementEarned, {
user: info.client.user,
achievementTag: info.achievementTag,
points: info.details.points,
title: cleanTitle,
text: cleanText,
}
);
});
return cb(null);
}
@ -252,7 +256,7 @@ class Achievements {
recordAndDisplayAchievement(info, cb) {
async.waterfall(
[
(callback) => {
callback => {
return this.createAchievementInterruptItems(info, callback);
},
(interruptItems, callback) => {
@ -262,7 +266,7 @@ class Achievements {
},
(interruptItems, callback) => {
return this.display(info, interruptItems, callback);
}
},
],
err => {
return cb(err);
@ -277,20 +281,31 @@ class Achievements {
const listenEvents = [
Events.getSystemEvents().UserStatSet,
Events.getSystemEvents().UserStatIncrement
Events.getSystemEvents().UserStatIncrement,
];
this.userStatEventListeners = this.events.addMultipleEventListener(listenEvents, userStatEvent => {
if([ UserProps.AchievementTotalCount, UserProps.AchievementTotalPoints ].includes(userStatEvent.statName)) {
this.userStatEventListeners = this.events.addMultipleEventListener(
listenEvents,
userStatEvent => {
if (
[
UserProps.AchievementTotalCount,
UserProps.AchievementTotalPoints,
].includes(userStatEvent.statName)
) {
return;
}
if(!_.isNumber(userStatEvent.statValue) && !_.isNumber(userStatEvent.statIncrementBy)) {
if (
!_.isNumber(userStatEvent.statValue) &&
!_.isNumber(userStatEvent.statIncrementBy)
) {
return;
}
// :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', {}),
achievement => {
if (false === achievement.enabled) {
@ -301,48 +316,78 @@ class Achievements {
Achievement.Types.UserStatInc,
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) {
return;
}
async.eachSeries(achievementTags, (achievementTag, nextAchievementTag) => {
const achievement = Achievement.factory(this.getAchievementByTag(achievementTag));
async.eachSeries(
achievementTags,
(achievementTag, nextAchievementTag) => {
const achievement = Achievement.factory(
this.getAchievementByTag(achievementTag)
);
if (!achievement) {
return nextAchievementTag(null);
}
const statValue = parseInt(
[ Achievement.Types.UserStatSet, Achievement.Types.UserStatIncNewVal ].includes(achievement.data.type) ?
userStatEvent.statValue :
userStatEvent.statIncrementBy
[
Achievement.Types.UserStatSet,
Achievement.Types.UserStatIncNewVal,
].includes(achievement.data.type)
? userStatEvent.statValue
: userStatEvent.statIncrementBy
);
if (isNaN(statValue)) {
return nextAchievementTag(null);
}
const [ details, matchField, matchValue ] = achievement.getMatchDetails(statValue);
const [details, matchField, matchValue] =
achievement.getMatchDetails(statValue);
if (!details) {
return nextAchievementTag(null);
}
async.waterfall(
[
(callback) => {
this.loadAchievementHitCount(userStatEvent.user, achievementTag, matchField, (err, count) => {
callback => {
this.loadAchievementHitCount(
userStatEvent.user,
achievementTag,
matchField,
(err, count) => {
if (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) => {
const client = getConnectionByUserId(userStatEvent.user.userId);
callback => {
const client = getConnectionByUserId(
userStatEvent.user.userId
);
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 = {
@ -365,8 +410,13 @@ class Achievements {
return callback(null, achievementsInfo);
}
const index = achievement.matchKeys.findIndex(v => v < matchField);
if(-1 === index || !Array.isArray(achievement.matchKeys)) {
const index = achievement.matchKeys.findIndex(
v => v < matchField
);
if (
-1 === index ||
!Array.isArray(achievement.matchKeys)
) {
return callback(null, achievementsInfo);
}
@ -375,56 +425,74 @@ class Achievements {
// ^---- we met here
// ^------------^ retroactive range
//
async.eachSeries(achievement.matchKeys.slice(index), (k, nextKey) => {
const [ det, fld, val ] = achievement.getMatchDetails(k);
async.eachSeries(
achievement.matchKeys.slice(index),
(k, nextKey) => {
const [det, fld, val] =
achievement.getMatchDetails(k);
if (!det) {
return nextKey(null);
}
this.loadAchievementHitCount(userStatEvent.user, achievementTag, fld, (err, count) => {
if(!err || count && 0 === count) {
achievementsInfo.push(Object.assign(
{},
basicInfo,
{
this.loadAchievementHitCount(
userStatEvent.user,
achievementTag,
fld,
(err, count) => {
if (!err || (count && 0 === count)) {
achievementsInfo.push(
Object.assign({}, basicInfo, {
details: det,
matchField: fld,
achievedValue: fld,
matchValue: val,
}
));
})
);
}
return nextKey(null);
});
}
);
},
() => {
return callback(null, achievementsInfo);
});
}
);
},
(achievementsInfo, callback) => {
// reverse achievementsInfo so we display smallest > largest
achievementsInfo.reverse();
async.eachSeries(achievementsInfo, (achInfo, nextAchInfo) => {
return this.recordAndDisplayAchievement(achInfo, err => {
async.eachSeries(
achievementsInfo,
(achInfo, nextAchInfo) => {
return this.recordAndDisplayAchievement(
achInfo,
err => {
return nextAchInfo(err);
});
}
);
},
err => {
return callback(err);
});
}
);
},
],
err => {
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
}
);
});
});
}
);
}
);
}
stopMonitoringUserStatEvents() {
@ -453,12 +521,16 @@ class Achievements {
}
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 formatObj = this.getFormatObject(info);
const wrap = (input) => {
const wrap = input => {
const re = new RegExp(`{(${Object.keys(formatObj).join('|')})([^}]*)}`, 'g');
return input.replace(re, (m, formatVar, formatOpts) => {
const varSgr = themeDefaults[`${formatVar}SGR`] || textTypeSgr;
@ -512,10 +584,12 @@ class Achievements {
itemTypes.push('global');
}
async.each(itemTypes, (itemType, nextItemType) => {
async.each(
itemTypes,
(itemType, nextItemType) => {
async.waterfall(
[
(callback) => {
callback => {
getArt(`${itemType}Header`, headerArt => {
return callback(null, headerArt);
});
@ -534,24 +608,40 @@ class Achievements {
pause: true,
};
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 contentsFormat = 'global' === itemType ?
themeDefaults.globalFormat || defaultContentsFormat :
themeDefaults.format || defaultContentsFormat;
const contentsFormat =
'global' === itemType
? themeDefaults.globalFormat ||
defaultContentsFormat
: themeDefaults.format || defaultContentsFormat;
const formatObj = Object.assign(this.getFormatObject(info), {
title : this.getFormattedTextFor(info, 'title', ''), // ''=defaultSgr
const formatObj = Object.assign(
this.getFormatObject(info),
{
title: this.getFormattedTextFor(
info,
'title',
''
), // ''=defaultSgr
message: itemText,
});
}
);
const contents = pipeToAnsi(stringFormat(contentsFormat, formatObj));
const contents = pipeToAnsi(
stringFormat(contentsFormat, formatObj)
);
interruptItems[itemType].contents =
`${headerArt || ''}\r\n${contents}\r\n${footerArt || ''}`;
interruptItems[itemType].contents = `${
headerArt || ''
}\r\n${contents}\r\n${footerArt || ''}`;
}
return callback(null);
}
},
],
err => {
return nextItemType(err);
@ -560,7 +650,8 @@ class Achievements {
},
err => {
return cb(err, interruptItems);
});
}
);
}
}
@ -582,9 +673,11 @@ function getAchievementsEarnedByUser(userId, cb) {
return cb(err);
}
const earned = rows.map(row => {
const achievement = Achievement.factory(achievementsInstance.getAchievementByTag(row.achievement_tag));
const earned = rows
.map(row => {
const achievement = Achievement.factory(
achievementsInstance.getAchievementByTag(row.achievement_tag)
);
if (!achievement) {
return;
}
@ -608,7 +701,8 @@ function getAchievementsEarnedByUser(userId, cb) {
}
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);
}

View File

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

View File

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

View File

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

View File

@ -4,10 +4,7 @@
// ENiGMA½
const ANSIEscapeParser = require('./ansi_escape_parser.js').ANSIEscapeParser;
const ANSI = require('./ansi_term.js');
const {
splitTextAtTerms,
renderStringLength
} = require('./string_util.js');
const { splitTextAtTerms, renderStringLength } = require('./string_util.js');
// deps
const _ = require('lodash');
@ -27,8 +24,14 @@ module.exports = function ansiPrep(input, options, cb) {
options.indent = options.indent || 0;
// 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 parser = new ANSIEscapeParser( { termHeight : options.termHeight, termWidth : options.termWidth } );
const canvas = Array.from(
{ 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 = {
row: 0,
@ -63,7 +66,10 @@ module.exports = function ansiPrep(input, options, cb) {
literal = literal.replace(/\r?\n|[\r\u2028\u2029]/g, '');
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);
if (0 === state.col) {
@ -113,16 +119,21 @@ module.exports = function ansiPrep(input, options, cb) {
const lastCol = getLastPopulatedColumn(row) + 1;
let i;
line = options.indent ?
output.length > 0 ? ' '.repeat(options.indent) : '' :
'';
line = options.indent
? output.length > 0
? ' '.repeat(options.indent)
: ''
: '';
for (i = 0; i < lastCol; ++i) {
const col = row[i];
sgr = !options.asciiMode && 0 === i ?
col.initialSgr ? ANSI.getSGRFromGraphicRendition(col.initialSgr) : '' :
'';
sgr =
!options.asciiMode && 0 === i
? col.initialSgr
? ANSI.getSGRFromGraphicRendition(col.initialSgr)
: ''
: '';
if (!options.asciiMode && col.sgr) {
sgr += ANSI.getSGRFromGraphicRendition(col.sgr);
@ -136,7 +147,10 @@ module.exports = function ansiPrep(input, options, cb) {
if (i < row.length) {
output += `${options.asciiMode ? '' : ANSI.blackBG()}`;
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);
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)}`;
} else {
exportOutput += ANSI.up();

View File

@ -194,7 +194,10 @@ const SGRValues = {
function getFullMatchRegExp(flags = 'g') {
// :TODO: expand this a bit - see strip-ansi/etc.
// :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) {
@ -205,7 +208,6 @@ function getBGColorValue(name) {
return SGRValues[name + 'BG'];
}
// See http://cvs.synchro.net/cgi-bin/viewcvs.cgi/*checkout*/src/conio/cterm.txt
// :TODO: document
// :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.
//
const FONT_ALIAS_TO_SYNCTERM_MAP = {
'cp437' : 'cp437',
'ibm_vga' : 'cp437',
'ibmpc' : 'cp437',
'ibm_pc' : 'cp437',
'pc' : 'cp437',
'cp437_art' : 'cp437',
'ibmpcart' : 'cp437',
'ibmpc_art' : 'cp437',
'ibm_pc_art' : 'cp437',
'msdos_art' : 'cp437',
'msdosart' : 'cp437',
'pc_art' : 'cp437',
'pcart' : 'cp437',
cp437: 'cp437',
ibm_vga: 'cp437',
ibmpc: 'cp437',
ibm_pc: 'cp437',
pc: 'cp437',
cp437_art: 'cp437',
ibmpcart: 'cp437',
ibmpc_art: 'cp437',
ibm_pc_art: 'cp437',
msdos_art: 'cp437',
msdosart: 'cp437',
pc_art: 'cp437',
pcart: 'cp437',
'ibm_vga50' : 'cp437',
'ibm_vga25g' : 'cp437',
'ibm_ega' : 'cp437',
'ibm_ega43' : 'cp437',
ibm_vga50: 'cp437',
ibm_vga25g: 'cp437',
ibm_ega: 'cp437',
ibm_ega43: 'cp437',
'topaz' : 'topaz',
'amiga_topaz_1' : 'topaz',
topaz: 'topaz',
amiga_topaz_1: 'topaz',
'amiga_topaz_1+': 'topaz_plus',
'topazplus' : 'topaz_plus',
'topaz_plus' : 'topaz_plus',
'amiga_topaz_2' : 'topaz',
topazplus: 'topaz_plus',
topaz_plus: 'topaz_plus',
amiga_topaz_2: 'topaz',
'amiga_topaz_2+': 'topaz_plus',
'topaz2plus' : 'topaz_plus',
topaz2plus: 'topaz_plus',
'pot_noodle' : 'pot_noodle',
'p0tnoodle' : 'pot_noodle',
pot_noodle: 'pot_noodle',
p0tnoodle: 'pot_noodle',
'amiga_p0t-noodle': 'pot_noodle',
'mo_soul' : 'mo_soul',
'mosoul' : 'mo_soul',
'mo\'soul' : 'mo_soul',
'amiga_mosoul' : 'mo_soul',
mo_soul: 'mo_soul',
mosoul: 'mo_soul',
"mo'soul": 'mo_soul',
amiga_mosoul: 'mo_soul',
'amiga_microknight' : 'microknight',
amiga_microknight: 'microknight',
'amiga_microknight+': 'microknight_plus',
'atari' : 'atari',
'atarist' : 'atari',
atari: 'atari',
atarist: 'atari',
};
function setSyncTermFont(name, fontPage) {
@ -344,7 +345,7 @@ function setSyncTermFontWithAlias(nameOrAlias) {
const DEC_CURSOR_STYLE = {
'blinking block': 0,
'default' : 1,
default: 1,
'steady block': 2,
'blinking underline': 3,
'steady underline': 4,
@ -358,7 +359,6 @@ function setCursorStyle(cursorStyle) {
return `${ESC_CSI}${ps} q`;
}
return '';
}
// Create methods such as up(), nextLine(),...
@ -476,7 +476,8 @@ function disableVT100LineWrapping() {
}
function setEmulatedBaudRate(rate) {
const speed = {
const speed =
{
unlimited: 0,
off: 0,
0: 0,
@ -502,6 +503,9 @@ function vtxHyperlink(client, url, len) {
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}\\`;
}

View File

@ -38,7 +38,9 @@ exports.getModule = class ArchaicNETModule extends MenuModule {
const reqConfs = ['username', 'password', 'bbsTag'];
for (let req of reqConfs) {
if (!_.isString(_.get(self, ['config', req]))) {
return callback(Errors.MissingConfig(`Config requires "${req}"`));
return callback(
Errors.MissingConfig(`Config requires "${req}"`)
);
}
}
return callback(null);
@ -61,14 +63,21 @@ exports.getModule = class ArchaicNETModule extends MenuModule {
sshClient.on('ready', () => {
// track client termination so we can clean up early
self.client.once('end', () => {
self.client.log.info('Connection ended. Terminating ArchaicNET connection');
self.client.log.info(
'Connection ended. Terminating ArchaicNET connection'
);
clientTerminated = true;
return sshClient.end();
});
// establish tunnel for rlogin
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) {
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
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'));
});
needRestore = true;
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'));
});
@ -95,11 +108,14 @@ exports.getModule = class ArchaicNETModule extends MenuModule {
restorePipe();
return sshClient.end();
});
});
}
);
});
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 => {
@ -110,14 +126,17 @@ exports.getModule = class ArchaicNETModule extends MenuModule {
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({
host: self.config.host,
port: self.config.sshPort,
username: self.config.username,
password: self.config.password,
});
}
},
],
err => {
if (err) {
@ -132,4 +151,3 @@ exports.getModule = class ArchaicNETModule extends MenuModule {
);
}
};

View File

@ -33,17 +33,28 @@ class Archiver {
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'); }
canDecompress() { return this.can('decompress'); }
canList() { return this.can('list'); } // :TODO: validate entryMatch
canExtract() { return this.can('extract'); }
canCompress() {
return this.can('compress');
}
canDecompress() {
return this.can('decompress');
}
canList() {
return this.can('list');
} // :TODO: validate entryMatch
canExtract() {
return this.can('extract');
}
}
module.exports = class ArchiveUtil {
constructor() {
this.archivers = {};
this.longestSignature = 0;
@ -71,7 +82,6 @@ module.exports = class ArchiveUtil {
const config = Config();
if (_.has(config, 'archives.archivers')) {
Object.keys(config.archives.archivers).forEach(archKey => {
const archConfig = config.archives.archivers[archKey];
const archiver = new Archiver(archConfig);
@ -84,7 +94,7 @@ module.exports = class ArchiveUtil {
}
if (_.isObject(config.fileTypes)) {
const updateSig = (ft) => {
const updateSig = ft => {
ft.sig = Buffer.from(ft.sig, 'hex');
ft.offset = ft.offset || 0;
@ -113,7 +123,8 @@ module.exports = class ArchiveUtil {
getArchiver(mimeTypeOrExtension, justExtention) {
const mimeType = resolveMimeType(mimeTypeOrExtension);
if(!mimeType) { // lookup returns false on failure
if (!mimeType) {
// lookup returns false on failure
return;
}
@ -149,8 +160,10 @@ module.exports = class ArchiveUtil {
*/
detectType(path, cb) {
const closeFile = (fd) => {
fs.close(fd, () => { /* sadface */ });
const closeFile = fd => {
fs.close(fd, () => {
/* sadface */
});
};
fs.open(path, 'r', (err, fd) => {
@ -166,7 +179,9 @@ module.exports = class ArchiveUtil {
}
const archFormat = _.findKey(Config().fileTypes, fileTypeInfo => {
const fileTypeInfos = Array.isArray(fileTypeInfo) ? fileTypeInfo : [ fileTypeInfo ];
const fileTypeInfos = Array.isArray(fileTypeInfo)
? fileTypeInfo
: [fileTypeInfo];
return fileTypeInfos.find(fti => {
if (!fti.sig || !fti.archiveHandler) {
return false;
@ -179,7 +194,7 @@ module.exports = class ArchiveUtil {
}
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 => {
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 {
proc = pty.spawn(archiver.compress.cmd, args, this.getPtyOpts(workDir));
} catch (e) {
return cb(Errors.ExternalProcess(
`Error spawning archiver process "${archiver.compress.cmd}" with args "${args.join(' ')}": ${e.message}`)
return cb(
Errors.ExternalProcess(
`Error spawning archiver process "${
archiver.compress.cmd
}" with args "${args.join(' ')}": ${e.message}`
)
);
}
@ -288,12 +313,16 @@ module.exports = class ArchiveUtil {
try {
proc = pty.spawn(archiver[action].cmd, args, this.getPtyOpts(extractPath));
} catch (e) {
return cb(Errors.ExternalProcess(
`Error spawning archiver process "${archiver[action].cmd}" with args "${args.join(' ')}": ${e.message}`)
return cb(
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) {
@ -313,8 +342,12 @@ module.exports = class ArchiveUtil {
try {
proc = pty.spawn(archiver.list.cmd, args, this.getPtyOpts());
} catch (e) {
return cb(Errors.ExternalProcess(
`Error spawning archiver process "${archiver.list.cmd}" with args "${args.join(' ')}": ${e.message}`)
return cb(
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 => {
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 entryMatchRe = new RegExp(archiver.list.entryMatch, 'gm');

View File

@ -88,7 +88,10 @@ function getArtFromPath(path, options, cb) {
return iconv.decode(data, encoding);
} else {
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 ('' !== 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);
}
@ -210,7 +215,10 @@ function getArt(name, options, cb) {
//
let readPath;
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 {
assert(1 === filtered.length);
readPath = paths.join(options.basePath, filtered[0]);
@ -257,7 +265,7 @@ function display(client, art, options, cb) {
if (!_.isBoolean(options.iceColors)) {
// 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;
}
}
@ -295,7 +303,6 @@ function display(client, art, options, cb) {
++generatedId;
}
}
});
ansiParser.on('literal', literal => client.term.write(literal, false));

View File

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

View File

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

View File

@ -31,11 +31,12 @@ exports.main = main;
const initServices = {};
// 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 HELP =
`${FULL_COPYRIGHT}
const HELP = `${FULL_COPYRIGHT}
usage: main.js <args>
eg : main.js --config /enigma_install_path/config/
@ -72,7 +73,11 @@ function main() {
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) {
const configFile = configPath + 'config.hjson';
@ -85,7 +90,9 @@ function main() {
if (err) {
if ('ENOENT' === err.code) {
if (configPathSupplied) {
console.error('Configuration file does not exist: ' + configFile);
console.error(
'Configuration file does not exist: ' + configFile
);
} else {
configPathSupplied = null; // make non-fatal; we'll go with defaults
}
@ -110,18 +117,22 @@ function main() {
}
return callback(err);
});
}
},
],
function complete(err) {
if (!err) {
// 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);
if (!err) {
console.info(banner);
}
console.info('System started!');
});
}
);
}
if (err && !errorDisplayed) {
@ -146,7 +157,9 @@ function shutdownSystem() {
while (i--) {
const activeTerm = activeConnections[i].term;
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]);
}
@ -173,7 +186,7 @@ function shutdownSystem() {
},
function stopMsgNetwork(callback) {
require('./msg_network.js').shutdown(callback);
}
},
],
() => {
console.info('Goodbye!');
@ -187,16 +200,25 @@ function initialize(cb) {
[
function createMissingDirectories(callback) {
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) {
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);
});
}, function dirCreationComplete(err) {
},
function dirCreationComplete(err) {
return callback(err);
});
}
);
},
function basicInit(callback) {
logger.init();
@ -240,8 +262,11 @@ function initialize(cb) {
const propLoadOpts = {
names: [
UserProps.RealName, UserProps.Sex, UserProps.EmailAddress,
UserProps.Location, UserProps.Affiliations,
UserProps.RealName,
UserProps.Sex,
UserProps.EmailAddress,
UserProps.Location,
UserProps.Affiliations,
],
};
@ -251,9 +276,13 @@ function initialize(cb) {
return User.getUserName(User.RootUserID, next);
},
function getOpProps(opUserName, next) {
User.loadProperties(User.RootUserID, propLoadOpts, (err, opProps) => {
User.loadProperties(
User.RootUserID,
propLoadOpts,
(err, opProps) => {
return next(err, opUserName, opProps);
});
}
);
},
],
(err, opUserName, opProps) => {
@ -286,7 +315,10 @@ function initialize(cb) {
StatLog.findSystemLogEntries(filter, (err, callsToday) => {
if (!err) {
StatLog.setNonPersistentSystemStat(SysProps.LoginsToday, callsToday);
StatLog.setNonPersistentSystemStat(
SysProps.LoginsToday,
callsToday
);
}
return callback(null);
});
@ -390,7 +422,8 @@ function initialize(cb) {
return require('./file_area_web.js').startup(callback);
},
function readyPasswordReset(callback) {
const WebPasswordReset = require('./web_password_reset.js').WebPasswordReset;
const WebPasswordReset =
require('./web_password_reset.js').WebPasswordReset;
return WebPasswordReset.startup(callback);
},
function ready2FA_OTPRegister(callback) {
@ -398,7 +431,8 @@ function initialize(cb) {
return User2FA_OTPWebRegister.startup(callback);
},
function readyEventScheduler(callback) {
const EventSchedulerModule = require('./event_scheduler.js').EventSchedulerModule;
const EventSchedulerModule =
require('./event_scheduler.js').EventSchedulerModule;
EventSchedulerModule.loadAndStart((err, modInst) => {
initServices.eventScheduler = modInst;
return callback(err);
@ -406,7 +440,7 @@ function initialize(cb) {
},
function listenUserEventsForStatLog(callback) {
return require('./stat_log.js').initUserEvents(callback);
}
},
],
function onComplete(err) {
return cb(err);

View File

@ -4,10 +4,7 @@
const { MenuModule } = require('./menu_module.js');
const { resetScreen } = require('./ansi_term.js');
const { Errors } = require('./enig_error.js');
const {
trackDoorRunBegin,
trackDoorRunEnd
} = require('./door_util.js');
const { trackDoorRunBegin, trackDoorRunEnd } = require('./door_util.js');
// deps
const async = require('async');
@ -86,15 +83,22 @@ exports.getModule = class BBSLinkModule extends MenuModule {
callback(ex);
} else {
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) {
callback(err);
} else {
token = body.trim();
self.client.log.trace( { token : token }, 'BBSLink token');
self.client.log.trace(
{ token: token },
'BBSLink token'
);
callback(null);
}
});
}
);
}
});
},
@ -105,8 +109,14 @@ exports.getModule = class BBSLinkModule extends MenuModule {
const headers = {
'X-User': self.client.user.userId.toString(),
'X-System': self.config.sysCode,
'X-Auth' : crypto.createHash('md5').update(self.config.authCode + token).digest('hex'),
'X-Code' : crypto.createHash('md5').update(self.config.schemeCode + token).digest('hex'),
'X-Auth': crypto
.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-Key': randomKey,
'X-Door': self.config.door,
@ -115,14 +125,22 @@ exports.getModule = class BBSLinkModule extends MenuModule {
'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();
if ('complete' === status) {
return callback(null);
}
return callback(Errors.AccessDenied(`Bad authentication status: ${status}`));
});
return callback(
Errors.AccessDenied(
`Bad authentication status: ${status}`
)
);
}
);
},
function createTelnetBridge(callback) {
//
@ -137,25 +155,38 @@ exports.getModule = class BBSLinkModule extends MenuModule {
let dataOut;
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() {
self.client.log.info(connectOpts, 'BBSLink bridge connection established');
const bridgeConnection = net.createConnection(
connectOpts,
function connected() {
self.client.log.info(
connectOpts,
'BBSLink bridge connection established'
);
dataOut = (data) => {
dataOut = data => {
return bridgeConnection.write(data);
};
self.client.term.output.on('data', dataOut);
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;
bridgeConnection.end();
});
});
}
);
const restore = () => {
if (dataOut && self.client.term.output) {
@ -174,19 +205,28 @@ exports.getModule = class BBSLinkModule extends MenuModule {
bridgeConnection.on('end', function connectionEnd() {
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) {
self.client.log.info('BBSLink bridge connection error: ' + err.message);
self.client.log.info(
'BBSLink bridge connection error: ' + err.message
);
restore();
return callback(err);
});
}
},
],
function complete(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) {

View File

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

View File

@ -21,7 +21,7 @@ function ButtonView(options) {
util.inherits(ButtonView, TextView);
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.emit('action', 'accept');
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_META_KEYCODE_ANYWHERE = /(?:\u001b)([a-zA-Z0-9])/;
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+))?([~^$])',
'(?:M([@ #!a`])(.)(.))', // mouse stuff
'(?:1;)?(\\d+)?([a-zA-Z@])'
].join('|') + ')');
'(?:1;)?(\\d+)?([a-zA-Z@])',
].join('|') +
')'
);
/* eslint-enable no-control-regex */
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_META_KEYCODE_ANYWHERE.source,
RE_DSR_RESPONSE_ANYWHERE.source,
RE_DEV_ATTR_RESPONSE_ANYWHERE.source,
/\u001b./.source // eslint-disable-line no-control-regex
].join('|'));
/\u001b./.source, // eslint-disable-line no-control-regex
].join('|')
);
function Client(/*input, output*/) {
stream.call(this);
@ -100,25 +105,25 @@ function Client(/*input, output*/) {
author: 'N/A',
description: 'N/A',
group: 'N/A',
}
},
};
}
},
set : (theme) => {
set: theme => {
this.currentThemeConfig = theme;
}
},
});
Object.defineProperty(this, 'node', {
get: function () {
return self.session.id;
}
},
});
Object.defineProperty(this, 'currentMenuModule', {
get: function () {
return self.menuStack.currentModule;
}
},
});
this.setTemporaryDirectDataHandler = function (handler) {
@ -135,7 +140,9 @@ function Client(/*input, output*/) {
this.themeChangedListener = function ({ 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 */
this.isMouseInput = function (data) {
return /\x1b\[M/.test(data) ||
return (
/\x1b\[M/.test(data) ||
/\u001b\[M([\x00\u0020-\uffff]{3})/.test(data) ||
/\u001b\[(\d+;\d+;\d+)M/.test(data) ||
/\u001b\[<(\d+;\d+;\d+)([mM])/.test(data) ||
/\u001b\[<(\d+;\d+;\d+;\d+)&w/.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 */
this.getKeyComponentsFromCode = function (code) {
return {
// xterm/gnome
'OP' : { name : 'f1' },
'OQ' : { name : 'f2' },
'OR' : { name : 'f3' },
'OS' : { name : 'f4' },
OP: { name: 'f1' },
OQ: { name: 'f2' },
OR: { name: 'f3' },
OS: { name: 'f4' },
'OA' : { name : 'up arrow' },
'OB' : { name : 'down arrow' },
'OC' : { name : 'right arrow' },
'OD' : { name : 'left arrow' },
'OE' : { name : 'clear' },
'OF' : { name : 'end' },
'OH' : { name : 'home' },
OA: { name: 'up arrow' },
OB: { name: 'down arrow' },
OC: { name: 'right arrow' },
OD: { name: 'left arrow' },
OE: { name: 'clear' },
OF: { name: 'end' },
OH: { name: 'home' },
// xterm/rxvt
'[11~': { name: 'f1' },
@ -261,11 +270,11 @@ function Client(/*input, output*/) {
'[7$': { name: 'home', shift: true },
'[8$': { name: 'end', shift: true },
'Oa' : { name : 'up arrow', ctrl : true },
'Ob' : { name : 'down arrow', ctrl : true },
'Oc' : { name : 'right arrow', ctrl : true },
'Od' : { name : 'left arrow', ctrl : true },
'Oe' : { name : 'clear', ctrl : true },
Oa: { name: 'up arrow', ctrl: true },
Ob: { name: 'down arrow', ctrl: true },
Oc: { name: 'right arrow', ctrl: true },
Od: { name: 'left arrow', ctrl: true },
Oe: { name: 'clear', ctrl: true },
'[2^': { name: 'insert', ctrl: true },
'[3^': { name: 'delete', ctrl: true },
@ -321,7 +330,7 @@ function Client(/*input, output*/) {
if ((parts = RE_DSR_RESPONSE_ANYWHERE.exec(s))) {
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 (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) {
// backspace, CTRL-H
key.name = 'backspace';
key.meta = ('\x1b' === s.charAt(0));
key.meta = '\x1b' === s.charAt(0);
} else if ('\x1b' === s || '\x1b\x1b' === s) {
key.name = 'escape';
key.meta = (2 === s.length);
key.meta = 2 === s.length;
} else if (' ' === s || '\x1b ' === s) {
// rather annoying that space can come in other than just " "
key.name = 'space';
key.meta = (2 === s.length);
key.meta = 2 === s.length;
} else if (1 === s.length && s <= '\x1a') {
// CTRL-<letter>
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]);
} else if ((parts = RE_FUNCTION_KEYCODE.exec(s))) {
var code =
(parts[1] || '') + (parts[2] || '') +
(parts[4] || '') + (parts[9] || '');
(parts[1] || '') +
(parts[2] || '') +
(parts[4] || '') +
(parts[9] || '');
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
// such as achievements. Send off every 5m.
//
const minOnline = this.user.incrementProperty(UserProps.MinutesOnlineTotalCount, 1);
if(0 === (minOnline % 5)) {
Events.emit(
Events.getSystemEvents().UserStatIncrement,
{
const minOnline = this.user.incrementProperty(
UserProps.MinutesOnlineTotalCount,
1
);
if (0 === minOnline % 5) {
Events.emit(Events.getSystemEvents().UserStatIncrement, {
user: this.user,
statName: UserProps.MinutesOnlineTotalCount,
statIncrementBy: 1,
statValue : minOnline
}
);
statValue: minOnline,
});
}
} else {
idleLogoutSeconds = Config().users.preAuthIdleLogoutSeconds;
@ -493,7 +504,10 @@ Client.prototype.startIdleMonitor = function() {
// use override value if set
idleLogoutSeconds = this.idleLogoutSecondsOverride || idleLogoutSeconds;
if(idleLogoutSeconds > 0 && (nowMs - this.lastActivityTime >= (idleLogoutSeconds * 1000))) {
if (
idleLogoutSeconds > 0 &&
nowMs - this.lastActivityTime >= idleLogoutSeconds * 1000
) {
this.emit('idle timeout');
}
}, 1000 * 60);
@ -523,7 +537,10 @@ Client.prototype.end = function () {
this.term.disconnect();
}
Events.removeListener(Events.getSystemEvents().ThemeChanged, this.themeChangedListener);
Events.removeListener(
Events.getSystemEvents().ThemeChanged,
this.themeChangedListener
);
const currentModule = this.menuStack.getCurrentModule;
@ -598,7 +615,6 @@ Client.prototype.defaultHandlerMissingMod = function() {
self.term.write('This has been logged for your SysOp to review.\n');
self.term.write('\nGoodbye!\n');
//self.term.write(err);
//if(miscUtil.isDevelopment() && err.stack) {

View File

@ -100,9 +100,12 @@ function addNewClient(client, clientSock) {
}
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
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.sort((c1, c2) => c1.session.id - c2.session.id);
@ -125,10 +128,10 @@ function addNewClient(client, clientSock) {
client.log.info(connInfo, `Client connected (${connInfo.serverName}/${connInfo.port})`);
Events.emit(
Events.getSystemEvents().ClientConnected,
{ client : client, connectionCount : clientConnections.length }
);
Events.emit(Events.getSystemEvents().ClientConnected, {
client: client,
connectionCount: clientConnections.length,
});
return nodeId;
}
@ -149,14 +152,20 @@ function removeClient(client) {
);
if (client.user && client.user.isValid()) {
const minutesOnline = moment().diff(moment(client.user.properties[UserProps.LastLoginTs]), 'minutes');
Events.emit(Events.getSystemEvents().UserLogoff, { user : client.user, minutesOnline } );
const minutesOnline = moment().diff(
moment(client.user.properties[UserProps.LastLoginTs]),
'minutes'
);
Events.emit(Events.getSystemEvents().UserLogoff, {
user: client.user,
minutesOnline,
});
}
Events.emit(
Events.getSystemEvents().ClientDisconnected,
{ client : client, connectionCount : clientConnections.length }
);
Events.emit(Events.getSystemEvents().ClientDisconnected, {
client: client,
connectionCount: clientConnections.length,
});
}
}

View File

@ -9,7 +9,6 @@ var iconv = require('iconv-lite');
var assert = require('assert');
var _ = require('lodash');
exports.ClientTerminal = ClientTerminal;
function ClientTerminal(output) {
@ -47,7 +46,7 @@ function ClientTerminal(output) {
} else {
Log.warn({ encoding: enc }, 'Unknown encoding');
}
}
},
});
Object.defineProperty(this, 'termType', {
@ -68,8 +67,11 @@ function ClientTerminal(output) {
// Windows telnet will send "VTNT". If so, set termClient='windows'
// 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', {
@ -80,7 +82,7 @@ function ClientTerminal(output) {
if (width > 0) {
termWidth = width;
}
}
},
});
Object.defineProperty(this, 'termHeight', {
@ -91,7 +93,7 @@ function ClientTerminal(output) {
if (height > 0) {
termHeight = height;
}
}
},
});
Object.defineProperty(this, 'termClient', {
@ -102,7 +104,7 @@ function ClientTerminal(output) {
termClient = tc;
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);
};

View File

@ -23,7 +23,8 @@ function pipeStringLength(s) {
}
function ansiSgrFromRenegadeColorCode(cc) {
return ANSI.sgr({
return ANSI.sgr(
{
0: ['reset', 'black'],
1: ['reset', 'blue'],
2: ['reset', 'green'],
@ -59,11 +60,13 @@ function ansiSgrFromRenegadeColorCode(cc) {
29: ['blink', 'magentaBG'],
30: ['blink', 'yellowBG'],
31: ['blink', 'whiteBG'],
}[cc] || 'normal');
}[cc] || 'normal'
);
}
function ansiSgrFromCnetStyleColorCode(cc) {
return ANSI.sgr({
return ANSI.sgr(
{
c0: ['reset', 'black'],
c1: ['reset', 'red'],
c2: ['reset', 'green'],
@ -90,7 +93,8 @@ function ansiSgrFromCnetStyleColorCode(cc) {
z5: ['magentaBG'],
z6: ['cyanBG'],
z7: ['whiteBG'],
}[cc] || 'normal');
}[cc] || 'normal'
);
}
function renegadeToAnsi(s, client) {
@ -121,7 +125,7 @@ function renegadeToAnsi(s, client) {
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
//
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 result = '';
@ -231,7 +236,8 @@ function controlCodesToAnsi(s, client) {
if (isNaN(v)) {
v += m[0];
} else {
v = ANSI.sgr({
v = ANSI.sgr(
{
0: ['reset', 'black'],
1: ['bold', 'cyan'],
2: ['bold', 'yellow'],
@ -242,7 +248,8 @@ function controlCodesToAnsi(s, client) {
7: ['bold', 'blue'],
8: ['reset', 'blue'],
9: ['reset', 'cyan'],
}[v] || 'normal');
}[v] || 'normal'
);
}
result += s.substr(lastIndex, m.index - lastIndex) + v;
@ -270,5 +277,5 @@ function controlCodesToAnsi(s, client) {
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 { resetScreen } = require('../core/ansi_term.js');
const { Errors } = require('./enig_error.js');
const {
trackDoorRunBegin,
trackDoorRunEnd
} = require('./door_util.js');
const { trackDoorRunBegin, trackDoorRunEnd } = require('./door_util.js');
// deps
const async = require('async');
@ -54,7 +51,10 @@ exports.getModule = class CombatNetModule extends MenuModule {
const restorePipeToNormal = function () {
if (self.client.term.output) {
self.client.term.output.removeListener('data', sendToRloginBuffer);
self.client.term.output.removeListener(
'data',
sendToRloginBuffer
);
if (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,
serverUsername: `${self.config.bbsTag}${self.client.user.username}`,
host: self.config.host,
port: self.config.rloginPort,
terminalType: self.client.term.termClient,
terminalSpeed : 57600
}
);
terminalSpeed: 57600,
});
// If there was an error ...
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();
return callback(err);
});
@ -91,7 +91,8 @@ exports.getModule = class CombatNetModule extends MenuModule {
rlogin.send(buffer);
}
rlogin.on('connect',
rlogin.on(
'connect',
/* The 'connect' event handler will be supplied with one argument,
a boolean indicating whether or not the connection was established. */
@ -102,13 +103,17 @@ exports.getModule = class CombatNetModule extends MenuModule {
doorTracking = trackDoorRunBegin(self.client);
} 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 ...
rlogin.on('data', (data) => {
rlogin.on('data', data => {
self.client.term.rawWrite(data);
});
@ -116,7 +121,7 @@ exports.getModule = class CombatNetModule extends MenuModule {
rlogin.connect();
// note: no explicit callback() until we're finished!
}
},
],
err => {
if (err) {

View File

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

View File

@ -8,8 +8,7 @@ const hjson = require('hjson');
const sane = require('sane');
const _ = require('lodash');
module.exports = new class ConfigCache
{
module.exports = new (class ConfigCache {
constructor() {
this.cache = new Map(); // path->parsed config
}
@ -22,23 +21,30 @@ module.exports = new class ConfigCache
this.recacheConfigFromFile(options.filePath, (err, config) => {
if (!err && !cached) {
if (options.hotReload) {
const watcher = sane(
paths.dirname(options.filePath),
{
glob : `**/${paths.basename(options.filePath)}`
}
);
const watcher = sane(paths.dirname(options.filePath), {
glob: `**/${paths.basename(options.filePath)}`,
});
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 (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);
} catch (e) {
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) {
// 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);
});
}
};
})();

View File

@ -30,9 +30,28 @@ module.exports = () => {
checkAnsiHomePosition: true,
// 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
utf8TermList : ['xterm', 'linux', 'screen', 'dumb', 'rxvt', 'konsole', 'gnome', 'x11 terminal emulator'],
utf8TermList: [
'xterm',
'linux',
'screen',
'dumb',
'rxvt',
'konsole',
'gnome',
'x11 terminal emulator',
],
},
users: {
@ -68,9 +87,19 @@ module.exports = () => {
newUserNames: ['new', 'apply'], // Names reserved for applying
badUserNames: [
'sysop', 'admin', 'administrator', 'root', 'all',
'areamgr', 'filemgr', 'filefix', 'areafix', 'allfix',
'server', 'client', 'notme'
'sysop',
'admin',
'administrator',
'root',
'all',
'areamgr',
'filemgr',
'filefix',
'areafix',
'allfix',
'server',
'client',
'notme',
],
preAuthIdleLogoutSeconds: 60 * 3, // 3m
@ -87,11 +116,20 @@ module.exports = () => {
method: 'googleAuth',
otp: {
registerEmailText : paths.join(__dirname, '../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'),
}
}
registerEmailText: paths.join(
__dirname,
'../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: {
@ -109,7 +147,7 @@ module.exports = () => {
dateTimeFormat: {
short: 'MM/DD/YYYY h:mm a',
long: 'ddd, MMMM Do, YYYY, h:mm a',
}
},
},
menus: {
@ -167,7 +205,10 @@ module.exports = () => {
// - https://blog.sleeplessbeastie.eu/2017/12/28/how-to-generate-private-key/
// - 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',
firstMenuNewUser: 'sshConnectedNewUser',
@ -220,7 +261,7 @@ module.exports = () => {
'hmac-md5-96',
],
// note that we disable compression by default due to issues with many clients. YMMV.
compress : [ 'none' ]
compress: ['none'],
},
},
webSocket: {
@ -257,12 +298,21 @@ module.exports = () => {
// URL to POST submit reset form.
// templates for pw reset *email*
resetPassEmailText : paths.join(__dirname, '../misc/reset_password_email.template.txt'), // plain text version
resetPassEmailHtml : paths.join(__dirname, '../misc/reset_password_email.template.html'), // HTML version
resetPassEmailText: paths.join(
__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*
//
resetPageTemplate : paths.join(__dirname, './../www/reset_password.template.html'),
resetPageTemplate: paths.join(
__dirname,
'./../www/reset_password.template.html'
),
},
http: {
@ -274,7 +324,7 @@ module.exports = () => {
port: 8443,
certPem: paths.join(__dirname, './../config/https_cert.pem'),
keyPem: paths.join(__dirname, './../config/https_cert_key.pem'),
}
},
},
gopher: {
@ -314,8 +364,8 @@ module.exports = () => {
port: 8563,
certPem: paths.join(__dirname, './../config/nntps_cert.pem'),
keyPem: paths.join(__dirname, './../config/nntps_key.pem'),
}
}
},
},
},
chatServers: {
@ -325,7 +375,7 @@ module.exports = () => {
serverPort: 5000,
retryDelay: 10000,
multiplexerPort: 5000,
}
},
},
infoExtractUtils: {
@ -335,22 +385,33 @@ module.exports = () => {
Exiftool: {
cmd: 'exiftool',
args: [
'-charset', 'utf8', '{filePath}',
'-charset',
'utf8',
'{filePath}',
// exclude the following:
'--directory', '--filepermissions', '--exiftoolversion', '--filename', '--filesize',
'--filemodifydate', '--fileaccessdate', '--fileinodechangedate', '--createdate', '--modifydate',
'--metadatadate', '--xmptoolkit'
]
'--directory',
'--filepermissions',
'--exiftoolversion',
'--filename',
'--filesize',
'--filemodifydate',
'--fileaccessdate',
'--fileinodechangedate',
'--createdate',
'--modifydate',
'--metadatadate',
'--xmptoolkit',
],
},
XDMS2Desc: {
// http://manpages.ubuntu.com/manpages/trusty/man1/xdms.1.html
cmd: 'xdms',
args : [ 'd', '{filePath}' ]
args: ['d', '{filePath}'],
},
XDMS2LongDesc: {
// http://manpages.ubuntu.com/manpages/trusty/man1/xdms.1.html
cmd: 'xdms',
args : [ 'f', '{filePath}' ]
args: ['f', '{filePath}'],
},
},
@ -498,29 +559,37 @@ module.exports = () => {
sig: '9602', // 16bit sum of "NICKATARI"
ext: '.atr',
archiveHandler: 'Atr',
}
]
},
],
},
archives: {
archivers: {
'7Zip' : { // p7zip package
'7Zip': {
// p7zip package
compress: {
cmd: '7za',
args: ['a', '-tzip', '{archivePath}', '{fileList}'],
},
decompress: {
cmd: '7za',
args : [ 'e', '-y', '-o{extractPath}', '{archivePath}' ] // :TODO: should be 'x'?
args: ['e', '-y', '-o{extractPath}', '{archivePath}'], // :TODO: should be 'x'?
},
list: {
cmd: '7za',
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: {
cmd: '7za',
args : [ 'e', '-y', '-o{extractPath}', '{archivePath}', '{fileList}' ],
args: [
'e',
'-y',
'-o{extractPath}',
'{archivePath}',
'{fileList}',
],
},
},
@ -537,12 +606,19 @@ module.exports = () => {
cmd: 'unzip',
args: ['-l', '{archivePath}'],
// 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: {
cmd: 'unzip',
args : [ '-n', '{archivePath}', '{fileList}', '-d', '{extractPath}' ],
}
args: [
'-n',
'{archivePath}',
'{fileList}',
'-d',
'{extractPath}',
],
},
},
Lha: {
@ -559,12 +635,13 @@ module.exports = () => {
list: {
cmd: 'lha',
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: {
cmd: 'lha',
args : [ '-efw={extractPath}', '{archivePath}', '{fileList}' ]
}
args: ['-efw={extractPath}', '{archivePath}', '{fileList}'],
},
},
Lzx: {
@ -582,8 +659,9 @@ module.exports = () => {
list: {
cmd: 'unlzx',
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: {
@ -598,16 +676,18 @@ module.exports = () => {
list: {
cmd: 'arj',
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]+)$',
entryGroupOrder : { // defaults to { byteSize : 1, fileName : 2 }
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]+)$',
entryGroupOrder: {
// defaults to { byteSize : 1, fileName : 2 }
fileName: 1,
byteSize: 2,
}
},
},
extract: {
cmd: 'arj',
args: ['e', '{archivePath}', '{extractPath}', '{fileList}'],
}
},
},
Rar: {
@ -618,46 +698,69 @@ module.exports = () => {
list: {
cmd: 'unrar',
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: {
cmd: 'unrar',
args: ['e', '{archivePath}', '{extractPath}', '{fileList}'],
}
},
},
TarGz: {
decompress: {
cmd: 'tar',
args : [ '-xf', '{archivePath}', '-C', '{extractPath}', '--strip-components=1' ],
args: [
'-xf',
'{archivePath}',
'-C',
'{extractPath}',
'--strip-components=1',
],
},
list: {
cmd: 'tar',
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: {
cmd: 'tar',
args : [ '-xvf', '{archivePath}', '-C', '{extractPath}', '{fileList}' ],
}
args: [
'-xvf',
'{archivePath}',
'-C',
'{extractPath}',
'{fileList}',
],
},
},
Atr: {
decompress: {
cmd: 'atr',
args : [ '{archivePath}', 'x', '-a', '-o', '{extractPath}' ]
args: ['{archivePath}', 'x', '-a', '-o', '{extractPath}'],
},
list: {
cmd: 'atr',
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: {
cmd: 'atr',
// 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',
recvArgs: ['-telnet', '-8', 'rz', '{uploadDir}'],
recvArgsNonBatch: ['-telnet', '-8', 'rz', '{fileName}'],
}
},
},
xmodemSexyz: {
@ -688,8 +791,8 @@ module.exports = () => {
sendCmd: 'sexyz',
sendArgs: ['-telnet', 'sX', '@{fileListPath}'],
recvCmd: 'sexyz',
recvArgsNonBatch : [ '-telnet', 'rC', '{fileName}' ]
}
recvArgsNonBatch: ['-telnet', 'rC', '{fileName}'],
},
},
ymodemSexyz: {
@ -701,7 +804,7 @@ module.exports = () => {
sendArgs: ['-telnet', 'sY', '@{fileListPath}'],
recvCmd: 'sexyz',
recvArgs: ['-telnet', 'ry', '{uploadDir}'],
}
},
},
zmodem8kSz: {
@ -712,15 +815,22 @@ module.exports = () => {
sendCmd: 'sz', // Avail on Debian/Ubuntu based systems as the package "lrzsz"
sendArgs: [
// :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"
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)
}
}
},
},
},
messageAreaDefaults: {
@ -746,9 +856,9 @@ module.exports = () => {
local_bulletin: {
name: 'System Bulletins',
desc: 'Bulletin messages for all users',
}
}
}
},
},
},
},
scannerTossers: {
@ -777,8 +887,8 @@ module.exports = () => {
uploadBy: 'ENiGMA TIC', // default upload by username (override @ network)
allowReplace: false, // use "Replaces" TIC field
descPriority: 'diz', // May be diz=.DIZ/etc., or tic=from TIC Ldesc
}
}
},
},
},
fileBase: {
@ -793,24 +903,25 @@ module.exports = () => {
// 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.
desc: [
'^.*FILE_ID\.ANS$', '^.*FILE_ID\.DIZ$', // eslint-disable-line no-useless-escape
'^.*DESC\.SDI$', // eslint-disable-line no-useless-escape
'^.*DESCRIPT\.ION$', // eslint-disable-line no-useless-escape
'^.*FILE\.DES$', // eslint-disable-line no-useless-escape
'^.*FILE\.SDI$', // eslint-disable-line no-useless-escape
'^.*DISK\.ID$' // eslint-disable-line no-useless-escape
'^.*FILE_ID.ANS$',
'^.*FILE_ID.DIZ$', // eslint-disable-line no-useless-escape
'^.*DESC.SDI$', // eslint-disable-line no-useless-escape
'^.*DESCRIPT.ION$', // eslint-disable-line no-useless-escape
'^.*FILE.DES$', // 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
descLong: [
'^[^/\]*\.NFO$', // eslint-disable-line no-useless-escape
'^.*README\.1ST$', // eslint-disable-line no-useless-escape
'^.*README\.NOW$', // eslint-disable-line no-useless-escape
'^.*README\.TXT$', // eslint-disable-line no-useless-escape
'^.*READ\.ME$', // eslint-disable-line no-useless-escape
'^[^/]*.NFO$', // eslint-disable-line no-useless-escape
'^.*README.1ST$', // eslint-disable-line no-useless-escape
'^.*README.NOW$', // eslint-disable-line no-useless-escape
'^.*README.TXT$', // eslint-disable-line no-useless-escape
'^.*READ.ME$', // eslint-disable-line no-useless-escape
'^.*README$', // eslint-disable-line no-useless-escape
'^.*README\.md$', // eslint-disable-line no-useless-escape
'^RELEASE-INFO.ASC$' // eslint-disable-line no-useless-escape
'^.*README.md$', // 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
'\\(((?: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\'([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.
],
@ -859,12 +970,11 @@ module.exports = () => {
name: 'System Temporary Downloads',
desc: 'Temporary downloadables',
storageTags: ['sys_temp_download'],
}
}
},
},
},
eventScheduler: {
events: {
dailyMaintenance: {
schedule: 'at 11:59pm',
@ -896,7 +1006,7 @@ module.exports = () => {
forgotPasswordMaintenance: {
schedule: 'every 24 hours',
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: {
@ -905,7 +1015,7 @@ module.exports = () => {
args: [
'auth_factor2_otp_register',
'24 hours', // expire time
]
],
},
//
@ -918,17 +1028,18 @@ module.exports = () => {
action : '@method:core/file_base_list_export.js:updateFileBaseDescFilesScheduledEvent',
}
*/
}
},
},
logging: {
rotatingFile : { // set to 'disabled' or false to disable
rotatingFile: {
// set to 'disabled' or false to disable
type: 'rotating-file',
fileName: 'enigma-bbs.log',
period: '1d',
count: 3,
level: 'debug',
}
},
// :TODO: syslog - https://github.com/mcavage/node-bunyan-syslog
},
@ -940,7 +1051,7 @@ module.exports = () => {
statLog: {
systemEvents: {
loginHistoryMax: -1, // set to -1 for forever
}
},
},
};
};

View File

@ -14,16 +14,14 @@ module.exports = class ConfigLoader {
defaultsCustomizer = null,
onReload = null,
keepWsc = false,
} =
{
} = {
hotReload: true,
defaultConfig: {},
defaultsCustomizer: null,
onReload: null,
keepWsc: false,
}
)
{
) {
this.current = {};
this.hotReload = hotReload;
@ -61,7 +59,7 @@ module.exports = class ConfigLoader {
//
async.waterfall(
[
(callback) => {
callback => {
return this._loadConfigFile(baseConfigPath, callback);
},
(config, callback) => {
@ -72,7 +70,8 @@ module.exports = class ConfigLoader {
config,
(defaultVal, configVal, key, target, source) => {
var path;
while (true) { // eslint-disable-line no-constant-condition
while (true) {
// eslint-disable-line no-constant-condition
if (!stack.length) {
stack.push({ source, path: [] });
}
@ -89,7 +88,12 @@ module.exports = class ConfigLoader {
}
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) {
case 'bool':
case 'boolean':
value = ('1' === value || 'true' === value.toLowerCase());
value = '1' === value || 'true' === value.toLowerCase();
break;
case 'number':
@ -162,7 +166,9 @@ module.exports = class ConfigLoader {
let value = process.env[varName];
if (!value) {
// 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) {
@ -212,7 +218,9 @@ module.exports = class ConfigLoader {
// If a included file is changed, we need to re-cache, so this
// must be tracked...
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) => {
if (err) {
return nextIncludePath(err);
@ -225,13 +233,12 @@ module.exports = class ConfigLoader {
err => {
this.configPaths = [this.baseConfigPath, ...includePaths];
return cb(err, config);
});
}
);
}
_resolveAtSpecs(config) {
return mapValuesDeep(
config,
value => {
return mapValuesDeep(config, value => {
if (_.isString(value) && '@' === value.charAt(0)) {
if (value.startsWith('@reference:')) {
const refPath = value.slice(11);
@ -242,7 +249,6 @@ module.exports = class ConfigLoader {
}
return value;
}
);
});
}
};

View File

@ -21,7 +21,7 @@ const withCursorPositionReport = (client, cprHandler, failMessage, cb) => {
return cb(err);
};
const cprListener = (pos) => {
const cprListener = pos => {
cprHandler(pos);
return done(null);
};
@ -32,7 +32,7 @@ const withCursorPositionReport = (client, cprHandler, failMessage, cb) => {
giveUpTimer = setTimeout(() => {
return done(Errors.General(failMessage));
}, 2000);
}
};
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
//
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
//
@ -99,17 +102,21 @@ function ansiAttemptDetectUTF8(client, cb) {
pos => {
initialPosition = pos;
withCursorPositionReport(client,
withCursorPositionReport(
client,
pos => {
const [_, w] = pos;
const len = w - initialPosition[1];
if(!isNaN(len) && len >= ASCIIPortion.length + 6) { // CP437 displays 3 chars each Unicode skull
client.log.info('Terminal identified as UTF-8 but does not appear to be. Overriding to "ansi".');
if (!isNaN(len) && len >= ASCIIPortion.length + 6) {
// 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');
}
},
'Detect UTF-8 stage 2 timed out',
cb,
cb
);
client.term.rawWrite(`\u9760${ASCIIPortion}\u9760`); // Unicode skulls on each side
@ -158,8 +165,10 @@ const ansiQuerySyncTermFontSupport = (client, 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) {
if (client.term.termHeight > 0 || client.term.termWidth > 0) {
@ -185,7 +194,8 @@ function ansiQueryTermSizeIfNeeded(client, cb) {
if (h < 10 || h === 999 || w < 10 || w === 999) {
return client.log.warn(
{ 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;
@ -195,7 +205,7 @@ function ansiQueryTermSizeIfNeeded(client, cb) {
{
termWidth: client.term.termWidth,
termHeight: client.term.termHeight,
source : 'ANSI CPR'
source: 'ANSI CPR',
},
'Window size updated'
);
@ -226,8 +236,7 @@ function displayBanner(term) {
|06Connected to |02EN|10i|02GMA|10½ |06BBS version |12|VN
|06Copyright (c) 2014-2022 Bryan Ashby |14- |12http://l33t.codes/
|06Updates & source |14- |12https://github.com/NuSkooler/enigma-bbs/
|00`
);
|00`);
}
function connectEntry(client, nextMenu) {
@ -255,7 +264,10 @@ function connectEntry(client, nextMenu) {
// Default to DOS size 80x25.
//
// :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.termWidth = 80;

View File

@ -1,47 +1,265 @@
const CP437UnicodeTable = [
'\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'
'\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',
];
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 isCP437Encodable = (s) => {
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 isCP437Encodable = s => {
if (!s.length) {
return true;
}

View File

@ -38,7 +38,7 @@ const CRC32_TABLE = new Int32Array([
0x3e6e77db, 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5,
0x47b2cf7f, 0x30b5ffe9, 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605,
0xcdd70693, 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d
0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d,
]);
exports.CRC32 = class CRC32 {
@ -86,6 +86,6 @@ exports.CRC32 = class CRC32 {
}
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
// 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(_.isString(moduleInfo.packageName), 'moduleInfo must define "packageName"!');
@ -51,20 +52,20 @@ function getModDatabasePath(moduleInfo, suffix) {
}
assert(
(full.split('.').length > 1 && HOST_RE.test(full)),
'packageName must follow Reverse Domain Name Notation - https://en.wikipedia.org/wiki/Reverse_domain_name_notation');
full.split('.').length > 1 && HOST_RE.test(full),
'packageName must follow Reverse Domain Name Notation - https://en.wikipedia.org/wiki/Reverse_domain_name_notation'
);
const Config = conf.get();
return paths.join(Config.paths.modsDb, `${full}.sqlite3`);
}
function loadDatabaseForMod(modInfo, cb) {
const db = getTransactionDatabase(new sqlite3.Database(
getModDatabasePath(modInfo),
err => {
const db = getTransactionDatabase(
new sqlite3.Database(getModDatabasePath(modInfo), err => {
return cb(err, db);
}
));
})
);
}
function getISOTimestampString(ts) {
@ -79,17 +80,24 @@ function getISOTimestampString(ts) {
}
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) {
case '\0' : return '\\0';
case '\x08' : return '\\b';
case '\x09' : return '\\t';
case '\x1a' : return '\\z';
case '\n' : return '\\n';
case '\r' : return '\\r';
case '\0':
return '\\0';
case '\x08':
return '\\b';
case '\x09':
return '\\t';
case '\x1a':
return '\\z';
case '\n':
return '\\n';
case '\r':
return '\\r';
case '"':
case '\'' :
case "'":
return `${c}${c}`;
case '\\':
@ -100,8 +108,11 @@ function sanitizeString(s) {
}
function initializeDatabases(cb) {
async.eachSeries( [ 'system', 'user', 'message', 'file' ], (dbName, next) => {
dbs[dbName] = sqlite3Trans.wrap(new sqlite3.Database(getDatabasePath(dbName), err => {
async.eachSeries(
['system', 'user', 'message', 'file'],
(dbName, next) => {
dbs[dbName] = sqlite3Trans.wrap(
new sqlite3.Database(getDatabasePath(dbName), err => {
if (err) {
return cb(err);
}
@ -111,10 +122,13 @@ function initializeDatabases(cb) {
return next(null);
});
});
}));
}, err => {
})
);
},
err => {
return cb(err);
});
}
);
}
function enableForeignKeys(db) {
@ -122,7 +136,7 @@ function enableForeignKeys(db) {
}
const DB_INIT_TABLE = {
system : (cb) => {
system: cb => {
enableForeignKeys(dbs.system);
// Various stat/event logging - see stat_log.js
@ -160,7 +174,7 @@ const DB_INIT_TABLE = {
return cb(null);
},
user : (cb) => {
user: cb => {
enableForeignKeys(dbs.user);
dbs.user.run(
@ -229,7 +243,7 @@ const DB_INIT_TABLE = {
return cb(null);
},
message : (cb) => {
message: cb => {
enableForeignKeys(dbs.message);
dbs.message.run(
@ -296,7 +310,6 @@ const DB_INIT_TABLE = {
);`
);
// :TODO: need SQL to ensure cleaned up if delete from message?
/*
dbs.message.run(
@ -337,7 +350,7 @@ const DB_INIT_TABLE = {
return cb(null);
},
file : (cb) => {
file: cb => {
enableForeignKeys(dbs.file);
dbs.file.run(
@ -457,5 +470,5 @@ const DB_INIT_TABLE = {
);
return cb(null);
}
},
};

View File

@ -35,12 +35,16 @@ module.exports = class DescriptIonFile {
// DESCRIPT.ION entries are terminated with a CR and/or LF
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.
// 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) {
return nextLine(null);
}
@ -54,24 +58,25 @@ module.exports = class DescriptIonFile {
//
const desc = parts[3].replace(/\\r\\n|\\n|[^@]@n/g, '\r\n');
descIonFile.entries.set(
fileName,
{
descIonFile.entries.set(fileName, {
desc: desc,
programId: parts[4],
programData: parts[5],
}
);
});
return nextLine(null);
},
() => {
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
);
});
}
);
});
}
};

View File

@ -93,7 +93,10 @@ module.exports = class Door {
// PID is launched. Make sure it's killed off if the user disconnects.
//
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(
{ pid: this.doorPty.pid },
'User has disconnected; Killing door process.'
@ -103,7 +106,8 @@ module.exports = class Door {
});
this.client.log.debug(
{ processId : this.doorPty.pid }, 'External door process spawned'
{ processId: this.doorPty.pid },
'External door process spawned'
);
if ('stdio' === this.io) {
@ -118,7 +122,10 @@ module.exports = class Door {
});
} else if ('socket' === this.io) {
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'
);
}

View File

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

View File

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

View File

@ -14,7 +14,9 @@ module.exports = class DownloadQueue {
if (!Array.isArray(this.client.user.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 {
this.client.user.downloadQueue = [];
}
@ -35,7 +37,9 @@ module.exports = class DownloadQueue {
toggle(fileEntry, systemFile = false) {
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 {
this.add(fileEntry, systemFile);
}
@ -57,7 +61,10 @@ module.exports = class DownloadQueue {
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;
return removed;
}
@ -67,10 +74,14 @@ module.exports = class DownloadQueue {
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) {
try {
@ -78,7 +89,10 @@ module.exports = class DownloadQueue {
} catch (e) {
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 => {
const Log = require('./logger').log;
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 {
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/
//
module.exports = class DropFile {
constructor(client, { fileType = 'DORINFO', baseDir = Config().paths.dropFiles } = {} ) {
constructor(
client,
{ fileType = 'DORINFO', baseDir = Config().paths.dropFiles } = {}
) {
this.client = client;
this.fileType = fileType.toUpperCase();
this.baseDir = baseDir;
}
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() {
@ -91,13 +94,18 @@ module.exports = class DropFile {
const bd = moment(prop[UserProps.Birthdate]).format('MM/DD/YY');
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 default protocol -- user prop: transfer_protocol
return iconv.encode( [
return iconv.encode(
[
'COM1:', // "Comm Port - COM0: = LOCAL MODE"
'57600', // "Baud Rate - 300 to 38400" (Note: set as 57600 instead!)
'8', // "Parity - 7 or 8"
@ -152,7 +160,9 @@ module.exports = class DropFile {
prop[UserProps.UserComment] || 'None', // "User Comment"
'0', // "Total Doors Opened"
'0', // "Total Messages Left"
].join('\r\n') + '\r\n', 'cp437');
].join('\r\n') + '\r\n',
'cp437'
);
}
getDoor32Buffer() {
@ -170,7 +180,8 @@ module.exports = class DropFile {
const commType = Door32CommTypes.Telnet;
return iconv.encode([
return iconv.encode(
[
commType.toString(),
'-1',
'115200',
@ -182,7 +193,9 @@ module.exports = class DropFile {
'546', // :TODO: Minutes left!
'1', // ANSI
this.client.node.toString(),
].join('\r\n') + '\r\n', 'cp437');
].join('\r\n') + '\r\n',
'cp437'
);
}
getDoorInfoDefBuffer() {
@ -194,12 +207,15 @@ module.exports = class DropFile {
//
// 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 secLevel = this.client.user.getLegacySecurityLevel().toString();
const location = this.client.user.properties[UserProps.Location];
return iconv.encode( [
return iconv.encode(
[
Config().general.boardName, // "The name of the system."
opUserName, // "The sysop's name up to 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."
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."
'-1' // "The number "-1" if using an external serial driver or "0" if using internal serial routines."
].join('\r\n') + '\r\n', 'cp437');
'-1', // "The number "-1" if using an external serial driver or "0" if using internal serial routines."
].join('\r\n') + '\r\n',
'cp437'
);
}
createFile(cb) {

View File

@ -41,10 +41,12 @@ function EditTextView(options) {
this.cursorPos.col -= 1;
if (this.cursorPos.col >= 0) {
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);

View File

@ -18,7 +18,7 @@ class EnigError extends Error {
if (typeof Error.captureStackTrace === 'function') {
Error.captureStackTrace(this, this.constructor);
} 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.Errors = {
General : (reason, reasonCode) => new EnigError('An error occurred', -33000, reason, reasonCode),
MenuStack : (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),
General: (reason, reasonCode) =>
new EnigError('An error occurred', -33000, reason, reasonCode),
MenuStack: (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),
ExternalProcess : (reason, reasonCode) => new EnigError('External process error', -32005, reason, reasonCode),
MissingConfig : (reason, reasonCode) => new EnigError('Missing configuration', -32006, reason, reasonCode),
UnexpectedState : (reason, reasonCode) => new EnigError('Unexpected state', -32007, reason, reasonCode),
MissingParam : (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),
ExternalProcess: (reason, reasonCode) =>
new EnigError('External process error', -32005, reason, reasonCode),
MissingConfig: (reason, reasonCode) =>
new EnigError('Missing configuration', -32006, reason, reasonCode),
UnexpectedState: (reason, reasonCode) =>
new EnigError('Unexpected state', -32007, reason, reasonCode),
MissingParam: (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 = {

View File

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

View File

@ -39,7 +39,11 @@ class ScheduledEvent {
}
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;
}
@ -117,8 +121,13 @@ class ScheduledEvent {
methodModule[this.action.what](this.action.args, err => {
if (err) {
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);
@ -126,7 +135,8 @@ class ScheduledEvent {
} catch (e) {
Log.warn(
{ error: e.message, eventName: this.name, action: this.action },
'Failed to perform scheduled event action');
'Failed to perform scheduled event action'
);
return cb(e);
}
@ -143,16 +153,14 @@ class ScheduledEvent {
try {
proc = pty.spawn(this.action.what, this.action.args, opts);
} catch (e) {
Log.warn(
{
Log.warn({
error: 'Failed to spawn @execute process',
reason: e.message,
eventName: this.name,
action: this.action,
what: this.action.what,
args : this.action.args
}
);
args: this.action.args,
});
return cb(e);
}
@ -160,9 +168,16 @@ class ScheduledEvent {
if (exitCode) {
Log.warn(
{ 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 +229,6 @@ EventSchedulerModule.loadAndStart = function(cb) {
};
EventSchedulerModule.prototype.startup = function (cb) {
this.eventTimers = [];
const self = this;
@ -234,24 +248,27 @@ EventSchedulerModule.prototype.startup = function(cb) {
eventName: schedEvent.name,
schedule: this.moduleConfig.events[schedEvent.name].schedule,
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'
);
if (schedEvent.schedule.sched) {
this.eventTimers.push(later.setInterval( () => {
this.eventTimers.push(
later.setInterval(() => {
self.performAction(schedEvent, 'Schedule');
}, schedEvent.schedule.sched));
}, schedEvent.schedule.sched)
);
}
if (schedEvent.schedule.watchFile) {
const watcher = sane(
paths.dirname(schedEvent.schedule.watchFile),
{
glob : `**/${paths.basename(schedEvent.schedule.watchFile)}`
}
);
const watcher = sane(paths.dirname(schedEvent.schedule.watchFile), {
glob: `**/${paths.basename(schedEvent.schedule.watchFile)}`,
});
// :TODO: should track watched files & stop watching @ shutdown?
@ -266,7 +283,10 @@ EventSchedulerModule.prototype.startup = function(cb) {
fse.exists(schedEvent.schedule.watchFile, 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
const _ = require('lodash');
module.exports = new class Events extends events.EventEmitter {
module.exports = new (class Events extends events.EventEmitter {
constructor() {
super();
this.setMaxListeners(64); // :TODO: play with this...
@ -73,4 +73,4 @@ module.exports = new class Events extends events.EventEmitter {
startup(cb) {
return cb(null);
}
};
})();

View File

@ -7,13 +7,8 @@ const { resetScreen } = require('./ansi_term.js');
const Config = require('./config.js').get;
const { Errors } = require('./enig_error.js');
const Log = require('./logger.js').log;
const {
getEnigmaUserAgent
} = require('./misc_util.js');
const {
trackDoorRunBegin,
trackDoorRunEnd
} = require('./door_util.js');
const { getEnigmaUserAgent } = require('./misc_util.js');
const { trackDoorRunBegin, trackDoorRunEnd } = require('./door_util.js');
// deps
const async = require('async');
@ -66,17 +61,17 @@ exports.getModule = class ExodusModule extends MenuModule {
this.config = options.menuConfig.config || {};
this.config.ticketHost = this.config.ticketHost || 'oddnetwork.org';
this.config.ticketPort = this.config.ticketPort || 1984,
this.config.ticketPath = this.config.ticketPath || '/exodus';
(this.config.ticketPort = this.config.ticketPort || 1984),
(this.config.ticketPath = this.config.ticketPath || '/exodus');
this.config.rejectUnauthorized = _.get(this.config, 'rejectUnauthorized', true);
this.config.sshHost = this.config.sshHost || this.config.ticketHost;
this.config.sshPort = this.config.sshPort || 22;
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() {
const self = this;
let clientTerminated = false;
@ -84,9 +79,15 @@ exports.getModule = class ExodusModule extends MenuModule {
[
function validateConfig(callback) {
// very basic validation on optionals
async.each( [ 'board', 'key', 'door' ], (key, next) => {
return _.isString(self.config[key]) ? next(null) : next(Errors.MissingConfig(`Config requires "${key}"!`));
}, callback);
async.each(
['board', 'key', 'door'],
(key, next) => {
return _.isString(self.config[key])
? next(null)
: next(Errors.MissingConfig(`Config requires "${key}"!`));
},
callback
);
},
function loadCertAuthorities(callback) {
if (!_.isString(self.config.caPem)) {
@ -99,7 +100,10 @@ exports.getModule = class ExodusModule extends MenuModule {
},
function getTicket(certAuthorities, callback) {
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 postData = querystring.stringify({
@ -119,7 +123,7 @@ exports.getModule = class ExodusModule extends MenuModule {
'Content-Type': 'application/x-www-form-urlencoded',
'Content-Length': postData.length,
'User-Agent': getEnigmaUserAgent(),
}
},
};
if (certAuthorities) {
@ -134,7 +138,9 @@ exports.getModule = class ExodusModule extends MenuModule {
res.on('end', () => {
if (ticket.length !== 36) {
return callback(Errors.Invalid(`Invalid Exodus ticket: ${ticket}`));
return callback(
Errors.Invalid(`Invalid Exodus ticket: ${ticket}`)
);
}
return callback(null, ticket);
@ -154,7 +160,6 @@ exports.getModule = class ExodusModule extends MenuModule {
});
},
function establishSecureConnection(ticket, privateKey, callback) {
let pipeRestored = false;
let pipedStream;
let doorTracking;
@ -171,7 +176,9 @@ exports.getModule = class ExodusModule extends MenuModule {
}
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();
@ -191,13 +198,18 @@ exports.getModule = class ExodusModule extends MenuModule {
sshClient.on('ready', () => {
self.client.once('end', () => {
self.client.log.info('Connection ended. Terminating Exodus connection');
self.client.log.info(
'Connection ended. Terminating Exodus connection'
);
clientTerminated = true;
return sshClient.end();
});
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
self.client.term.output.pipe(stream);
@ -212,7 +224,10 @@ exports.getModule = class ExodusModule extends MenuModule {
});
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,
privateKey: privateKey,
});
}
},
],
err => {
if (err) {

View File

@ -4,7 +4,8 @@
// ENiGMA½
const MenuModule = require('./menu_module.js').MenuModule;
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 stringFormat = require('./string_format.js');
const UserProps = require('./user_property.js');
@ -32,7 +33,7 @@ const MciViewIds = {
selectedFilterInfo: 10, // { ...filter object ... }
activeFilterInfo: 11, // { ...filter object ... }
error: 12, // validation errors
}
},
};
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 = {
@ -108,17 +112,21 @@ exports.getModule = class FileAreaFilterEdit extends MenuModule {
const filters = new FileBaseFilters(this.client);
filters.remove(filterUuid);
filters.persist(() => {
//
// 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];
if (newActive) {
filters.setActive(newActive.uuid);
} else {
// 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) => {
const errorView = this.viewControllers.editor.getView(MciViewIds.editor.error);
const errorView = this.viewControllers.editor.getView(
MciViewIds.editor.error
);
let newFocusId;
if (errorView) {
@ -170,15 +180,23 @@ exports.getModule = class FileAreaFilterEdit extends MenuModule {
}
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(
[
function loadFromConfig(callback) {
return vc.loadFromMenuConfig( { callingMenu : self, mciMap : mciData.menu }, callback);
return vc.loadFromMenuConfig(
{ callingMenu: self, mciMap: mciData.menu },
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);
if (areasView) {
@ -189,7 +207,7 @@ exports.getModule = class FileAreaFilterEdit extends MenuModule {
self.loadDataForFilter(self.currentFilterIndex);
self.viewControllers.editor.resetInitialFocus();
return callback(null);
}
},
],
err => {
return cb(err);
@ -213,7 +231,10 @@ exports.getModule = class FileAreaFilterEdit extends MenuModule {
const activeFilter = FileBaseFilters.getActiveFilter(this.client);
if (activeFilter) {
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) {
[ 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, '');
});
[ 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);
});
}
);
if (newFocusId) {
this.viewControllers.editor.switchFocus(newFocusId);
@ -260,7 +287,10 @@ exports.getModule = class FileAreaFilterEdit extends MenuModule {
const filter = this.getCurrentFilter();
if (filter) {
// 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 {
index = 0;
}
@ -271,7 +301,8 @@ exports.getModule = class FileAreaFilterEdit extends MenuModule {
let index;
const filter = this.getCurrentFilter();
if (filter) {
index = FileBaseFilters.OrderByValues.findIndex( ob => filter.order === ob ) || 0;
index =
FileBaseFilters.OrderByValues.findIndex(ob => filter.order === ob) || 0;
} else {
index = 0;
}

View File

@ -70,7 +70,6 @@ const MciViewIds = {
};
exports.getModule = class FileAreaList extends MenuModule {
constructor(options) {
super(options);
@ -180,12 +179,20 @@ exports.getModule = class FileAreaList extends MenuModule {
if (_.isNumber(this.lastMenuResultValue.rating)) {
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) {
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);
});
}
);
} else {
return cb(null);
}
@ -205,11 +212,14 @@ exports.getModule = class FileAreaList extends MenuModule {
function display(callback) {
return self.displayBrowsePage(false, err => {
if (err) {
self.gotoMenu(self.menuConfig.config.noResultsMenu || 'fileBaseListEntriesNoResults');
self.gotoMenu(
self.menuConfig.config.noResultsMenu ||
'fileBaseListEntriesNoResults'
);
}
return callback(err);
});
}
},
],
() => {
self.finishedLoading();
@ -221,13 +231,15 @@ exports.getModule = class FileAreaList extends MenuModule {
const config = this.menuConfig.config;
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 hashTagsSep = config.hashTagsSep || ', ';
const isQueuedIndicator = config.isQueuedIndicator || 'Y';
const isNotQueuedIndicator = config.isNotQueuedIndicator || 'N';
const entryInfo = currEntry.entryInfo = {
const entryInfo = (currEntry.entryInfo = {
fileId: currEntry.fileId,
areaTag: currEntry.areaTag,
areaName: _.get(area, 'name') || 'N/A',
@ -237,12 +249,16 @@ exports.getModule = class FileAreaList extends MenuModule {
desc: currEntry.desc || '',
descLong: currEntry.descLong || '',
userRating: currEntry.userRating,
uploadTimestamp : moment(currEntry.uploadTimestamp).format(uploadTimestampFormat),
uploadTimestamp: moment(currEntry.uploadTimestamp).format(
uploadTimestampFormat
),
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
webDlExpire: '', // :TODO: fetch web d/l link expire time
};
});
//
// 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;
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;
});
@ -262,7 +280,9 @@ exports.getModule = class FileAreaList extends MenuModule {
if (Array.isArray(fileType)) {
// 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;
}
@ -271,7 +291,8 @@ exports.getModule = class FileAreaList extends MenuModule {
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)';
// 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.userRatingString = userRatingTicked.repeat(entryInfo.userRating);
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) {
entryInfo.webDlExpire = '';
if (ErrNotEnabled === err.reasonCode) {
entryInfo.webDlExpire = config.webDlLinkNoWebserver || 'Web server is not enabled';
entryInfo.webDlExpire =
config.webDlLinkNoWebserver || 'Web server is not enabled';
} else {
entryInfo.webDlLink = config.webDlLinkNeedsGenerated || 'Not yet generated';
entryInfo.webDlLink =
config.webDlLinkNeedsGenerated || 'Not yet generated';
}
} 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.webDlExpire = moment(serveItem.expireTimestamp).format(webDlExpireTimeFormat);
entryInfo.webDlLink =
ansi.vtxHyperlink(this.client, serveItem.url) + serveItem.url;
entryInfo.webDlExpire = moment(serveItem.expireTimestamp).format(
webDlExpireTimeFormat
);
}
return cb(null);
});
}
);
}
populateCustomLabels(category, startId) {
return this.updateCustomViewTextsWithFilter(category, startId, this.currentFileEntry.entryInfo);
return this.updateCustomViewTextsWithFilter(
category,
startId,
this.currentFileEntry.entryInfo
);
}
displayArtDataPrepCallback(name, artData, viewController) {
@ -332,7 +370,9 @@ exports.getModule = class FileAreaList extends MenuModule {
},
function checkEmptyResults(callback) {
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);
},
@ -347,17 +387,22 @@ exports.getModule = class FileAreaList extends MenuModule {
function loadCurrentFileInfo(callback) {
self.currentFileEntry = new FileEntry();
self.currentFileEntry.load( self.fileList[ self.fileListPosition ], err => {
self.currentFileEntry.load(
self.fileList[self.fileListPosition],
err => {
if (err) {
return callback(err);
}
return self.populateCurrentEntryInfo(callback);
});
}
);
},
function populateDesc(callback) {
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) {
//
// For descriptions we want to support as many color code systems
@ -369,17 +414,23 @@ exports.getModule = class FileAreaList extends MenuModule {
// it as text.
//
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 = {
prepped: false,
forceLineTerm : true
forceLineTerm: true,
};
//
// if SAUCE states a term width, honor it else we may see
// 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)) {
opts.termWidth = sauceTermWidth;
}
@ -398,9 +449,12 @@ exports.getModule = class FileAreaList extends MenuModule {
},
function populateAdditionalViews(callback) {
self.updateQueueIndicator();
self.populateCustomLabels('browse', MciViewIds.browse.customRangeStart);
self.populateCustomLabels(
'browse',
MciViewIds.browse.customRangeStart
);
return callback(null);
}
},
],
err => {
if (cb) {
@ -424,14 +478,19 @@ exports.getModule = class FileAreaList extends MenuModule {
);
},
function populateViews(callback) {
self.populateCustomLabels('details', MciViewIds.details.customRangeStart);
self.populateCustomLabels(
'details',
MciViewIds.details.customRangeStart
);
return callback(null);
},
function prepSection(callback) {
return self.displayDetailsSection('general', false, 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.on('index update', index => {
@ -447,7 +506,7 @@ exports.getModule = class FileAreaList extends MenuModule {
});
return callback(null);
}
},
],
err => {
return cb(err);
@ -456,15 +515,11 @@ exports.getModule = class FileAreaList extends MenuModule {
}
displayHelpPage(cb) {
this.displayAsset(
this.menuConfig.config.art.help,
{ clearScreen : true },
() => {
this.displayAsset(this.menuConfig.config.art.help, { clearScreen: true }, () => {
this.client.waitForKeyPress(() => {
return this.displayBrowsePage(true, cb);
});
}
);
});
}
_handleMovementKeyPress(keyName, cb) {
@ -474,10 +529,18 @@ exports.getModule = class FileAreaList extends MenuModule {
}
switch (keyName) {
case 'down arrow' : descView.scrollDocumentUp(); break;
case 'up arrow' : descView.scrollDocumentDown(); break;
case 'page up' : descView.keyPressPageUp(); break;
case 'page down' : descView.keyPressPageDown(); break;
case 'down arrow':
descView.scrollDocumentUp();
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);
@ -490,12 +553,14 @@ exports.getModule = class FileAreaList extends MenuModule {
async.series(
[
function generateLinkIfNeeded(callback) {
if (self.currentFileEntry.webDlExpireTime < moment()) {
return callback(null);
}
const expireTime = moment().add(Config().fileBase.web.expireMinutes, 'minutes');
const expireTime = moment().add(
Config().fileBase.web.expireMinutes,
'minutes'
);
FileAreaWeb.createAndServeTempDownload(
self.client,
@ -508,10 +573,14 @@ exports.getModule = class FileAreaList extends MenuModule {
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.webDlExpire = expireTime.format(webDlExpireTimeFormat);
self.currentFileEntry.entryInfo.webDlLink =
ansi.vtxHyperlink(self.client, url) + url;
self.currentFileEntry.entryInfo.webDlExpire =
expireTime.format(webDlExpireTimeFormat);
return callback(null);
}
@ -520,11 +589,12 @@ exports.getModule = class FileAreaList extends MenuModule {
function updateActiveViews(callback) {
self.updateCustomViewTextsWithFilter(
'browse',
MciViewIds.browse.customRangeStart, self.currentFileEntry.entryInfo,
MciViewIds.browse.customRangeStart,
self.currentFileEntry.entryInfo,
{ filter: ['{webDlLink}', '{webDlExpire}'] }
);
return callback(null);
}
},
],
err => {
return cb(err);
@ -537,9 +607,9 @@ exports.getModule = class FileAreaList extends MenuModule {
const isNotQueuedIndicator = this.menuConfig.config.isNotQueuedIndicator || 'N';
this.currentFileEntry.entryInfo.isQueued = stringFormat(
this.dlQueue.isQueued(this.currentFileEntry) ?
isQueuedIndicator :
isNotQueuedIndicator
this.dlQueue.isQueued(this.currentFileEntry)
? isQueuedIndicator
: isNotQueuedIndicator
);
this.updateCustomViewTextsWithFilter(
@ -564,19 +634,27 @@ exports.getModule = class FileAreaList extends MenuModule {
const filePath = this.currentFileEntry.filePath;
const archiveUtil = ArchiveUtil.getInstance();
archiveUtil.listEntries(filePath, this.currentFileEntry.entryInfo.archiveType, (err, entries) => {
archiveUtil.listEntries(
filePath,
this.currentFileEntry.entryInfo.archiveType,
(err, entries) => {
if (err) {
return cb(err);
}
// 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');
});
}
);
}
setFileListNoListing(text) {
const fileListView = this.viewControllers.detailsFileList.getView(MciViewIds.detailsFileList.fileList);
const fileListView = this.viewControllers.detailsFileList.getView(
MciViewIds.detailsFileList.fileList
);
if (fileListView) {
fileListView.complexItems = false;
fileListView.setItems([text]);
@ -585,7 +663,9 @@ exports.getModule = class FileAreaList extends MenuModule {
}
populateFileListing() {
const fileListView = this.viewControllers.detailsFileList.getView(MciViewIds.detailsFileList.fileList);
const fileListView = this.viewControllers.detailsFileList.getView(
MciViewIds.detailsFileList.fileList
);
if (this.currentFileEntry.entryInfo.archiveType) {
this.cacheArchiveEntries((err, cacheStatus) => {
@ -599,7 +679,10 @@ exports.getModule = class FileAreaList extends MenuModule {
}
});
} 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);
}
}
@ -617,9 +700,10 @@ exports.getModule = class FileAreaList extends MenuModule {
return callback(null);
},
function prepArtAndViewController(callback) {
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();
@ -654,7 +738,9 @@ exports.getModule = class FileAreaList extends MenuModule {
switch (sectionName) {
case 'nfo':
{
const nfoView = self.viewControllers.detailsNfo.getView(MciViewIds.detailsNfo.nfo);
const nfoView = self.viewControllers.detailsNfo.getView(
MciViewIds.detailsNfo.nfo
);
if (!nfoView) {
return callback(null);
}
@ -671,7 +757,9 @@ exports.getModule = class FileAreaList extends MenuModule {
}
);
} else {
nfoView.setText(self.currentFileEntry.entryInfo.descLong);
nfoView.setText(
self.currentFileEntry.entryInfo.descLong
);
return callback(null);
}
}
@ -688,7 +776,7 @@ exports.getModule = class FileAreaList extends MenuModule {
function setLabels(callback) {
self.populateCustomLabels(name, MciViewIds[name].customRangeStart);
return callback(null);
}
},
],
err => {
if (cb) {
@ -699,7 +787,11 @@ exports.getModule = class FileAreaList extends MenuModule {
}
loadFileIds(force, cb) {
if(force || (_.isUndefined(this.fileList) || _.isUndefined(this.fileListPosition))) {
if (
force ||
_.isUndefined(this.fileList) ||
_.isUndefined(this.fileListPosition)
) {
this.fileListPosition = 0;
const filterCriteria = Object.assign({}, this.filterCriteria);

View File

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

View File

@ -53,27 +53,30 @@ exports.cleanUpTempSessionItems = cleanUpTempSessionItems;
// for scheduler:
exports.updateAreaStatsScheduledEvent = updateAreaStatsScheduledEvent;
const WellKnownAreaTags = exports.WellKnownAreaTags = {
const WellKnownAreaTags = (exports.WellKnownAreaTags = {
Invalid: '',
MessageAreaAttach: 'system_message_attachment',
TempDownloads: 'system_temporary_download',
};
});
function startup(cb) {
async.series(
[
(callback) => {
callback => {
return cleanUpTempSessionItems(callback);
},
(callback) => {
callback => {
getAreaStats((err, stats) => {
if (!err) {
StatLog.setNonPersistentSystemStat(SysProps.FileBaseAreaStats, stats);
StatLog.setNonPersistentSystemStat(
SysProps.FileBaseAreaStats,
stats
);
}
return callback(null);
});
}
},
],
err => {
return cb(err);
@ -82,14 +85,19 @@ function startup(cb) {
}
function isInternalArea(areaTag) {
return [ WellKnownAreaTags.MessageAreaAttach, WellKnownAreaTags.TempDownloads ].includes(areaTag);
return [
WellKnownAreaTags.MessageAreaAttach,
WellKnownAreaTags.TempDownloads,
].includes(areaTag);
}
function getAvailableFileAreas(client, options) {
options = options || {};
// 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 => {
if (!options.includeSystemInternal && isInternalArea(areaInfo.areaTag)) {
@ -130,7 +138,10 @@ function getDefaultFileAreaTag(client, disableAcsCheck) {
// just use anything we can
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;
@ -153,8 +164,7 @@ function getFileAreaByTag(areaTag) {
}
function getFileAreasByTagWildcardRule(rule) {
const areaTags = Object.keys(Config().fileBase.areas)
.filter(areaTag => {
const areaTags = Object.keys(Config().fileBase.areas).filter(areaTag => {
return !isInternalArea(areaTag) && wildcardMatch(areaTag, rule);
});
@ -166,7 +176,10 @@ function changeFileAreaWithOptions(client, areaTag, options, cb) {
[
function getArea(callback) {
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) {
if (!client.acs.hasFileAreaRead(area)) {
@ -182,13 +195,19 @@ function changeFileAreaWithOptions(client, areaTag, options, cb) {
client.user.properties[UserProps.FileAreaTag] = areaTag;
return callback(null, area);
}
}
},
],
(err, area) => {
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 {
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);
@ -202,7 +221,7 @@ function isValidStorageTag(storageTag) {
function getAreaStorageDirectoryByTag(storageTag) {
const config = Config();
const storageLocation = (storageTag && config.fileBase.storageTags[storageTag]);
const storageLocation = storageTag && config.fileBase.storageTags[storageTag];
return paths.resolve(config.fileBase.areaStoragePrefix, storageLocation || '');
}
@ -212,21 +231,22 @@ function getAreaDefaultStorageDirectory(areaInfo) {
}
function getAreaStorageLocations(areaInfo) {
const storageTags = Array.isArray(areaInfo.storageTags) ?
areaInfo.storageTags :
[ areaInfo.storageTags || '' ];
const storageTags = Array.isArray(areaInfo.storageTags)
? areaInfo.storageTags
: [areaInfo.storageTags || ''];
const avail = Config().fileBase.storageTags;
return _.compact(storageTags.map(storageTag => {
return _.compact(
storageTags.map(storageTag => {
if (avail[storageTag]) {
return {
storageTag: storageTag,
dir: getAreaStorageDirectoryByTag(storageTag),
};
}
}));
})
);
}
function getFileEntryPath(fileEntry) {
@ -261,7 +281,7 @@ function getExistingFileEntriesBySha256(sha256, cb) {
// :TODO: This is basically sliceAtEOF() from art.js .... DRY!
function sliceAtSauceMarker(data) {
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--) {
if (0x1a === data[i]) {
@ -341,7 +361,9 @@ function extractAndProcessDescFiles(fileEntry, filePath, archiveEntries, cb) {
const extractList = [];
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) {
@ -349,7 +371,9 @@ function extractAndProcessDescFiles(fileEntry, filePath, archiveEntries, cb) {
}
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) {
@ -366,23 +390,41 @@ function extractAndProcessDescFiles(fileEntry, filePath, archiveEntries, cb) {
}
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) {
return callback(err);
}
const descFiles = {
desc : shortDescFile ? paths.join(tempDir, paths.basename(shortDescFile.fileName)) : null,
descLong : longDescFile ? paths.join(tempDir, paths.basename(longDescFile.fileName)) : null,
desc: shortDescFile
? paths.join(
tempDir,
paths.basename(shortDescFile.fileName)
)
: null,
descLong: longDescFile
? paths.join(
tempDir,
paths.basename(longDescFile.fileName)
)
: null,
};
return callback(null, descFiles);
});
}
);
});
},
function readDescFiles(descFiles, callback) {
const config = Config();
async.each(Object.keys(descFiles), (descType, next) => {
async.each(
Object.keys(descFiles),
(descType, next) => {
const path = descFiles[descType];
if (!path) {
return next(null);
@ -394,9 +436,20 @@ function extractAndProcessDescFiles(fileEntry, filePath, archiveEntries, cb) {
}
// skip entries that are too large
const maxFileSizeKey = `max${_.upperFirst(descType)}FileByteSize`;
if(config.fileBase[maxFileSizeKey] && stats.size > config.fileBase[maxFileSizeKey]) {
logDebug( { byteSize : stats.size, maxByteSize : config.fileBase[maxFileSizeKey] }, `Skipping "${descType}"; Too large` );
const maxFileSizeKey = `max${_.upperFirst(
descType
)}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);
}
@ -409,7 +462,9 @@ function extractAndProcessDescFiles(fileEntry, filePath, archiveEntries, cb) {
if (sauce) {
// if we have SAUCE, this information will be kept as well,
// but separate/pre-parsed.
const metaKey = `desc${'descLong' === descType ? '_long' : ''}_sauce`;
const metaKey = `desc${
'descLong' === descType ? '_long' : ''
}_sauce`;
fileEntry.meta[metaKey] = JSON.stringify(sauce);
}
@ -425,14 +480,19 @@ function extractAndProcessDescFiles(fileEntry, filePath, archiveEntries, cb) {
});
});
});
}, () => {
},
() => {
// cleanup but don't wait
temptmp.cleanup(paths => {
// 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);
});
}
);
},
],
err => {
@ -442,7 +502,6 @@ function extractAndProcessDescFiles(fileEntry, filePath, archiveEntries, cb) {
}
function extractAndProcessSingleArchiveEntry(fileEntry, filePath, archiveEntries, cb) {
async.waterfall(
[
function extractToTemp(callback) {
@ -455,15 +514,23 @@ function extractAndProcessSingleArchiveEntry(fileEntry, filePath, archiveEntries
const archiveUtil = ArchiveUtil.getInstance();
// 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) {
return callback(err);
}
return callback(null, paths.join(tempDir, extractList[0]));
});
}
);
});
},
function processSingleExtractedFile(extractedFile, callback) {
@ -474,7 +541,7 @@ function extractAndProcessSingleArchiveEntry(fileEntry, filePath, archiveEntries
}
return callback(err);
});
}
},
],
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
// and README.1ST
//
const archDescHandler = (1 === entries.length) ? extractAndProcessSingleArchiveEntry : extractAndProcessDescFiles;
const archDescHandler =
1 === entries.length
? extractAndProcessSingleArchiveEntry
: extractAndProcessDescFiles;
archDescHandler(fileEntry, filePath, entries, err => {
return callback(err);
});
@ -577,13 +647,17 @@ function populateFileEntryInfoFromFile(fileEntry, filePath, cb) {
return cb(null);
}
async.eachSeries( [ 'short', 'long' ], (descType, nextDesc) => {
async.eachSeries(
['short', 'long'],
(descType, nextDesc) => {
const util = getInfoExtractUtilForDesc(mimeType, filePath, descType);
if (!util) {
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) => {
if (err || !stdout) {
@ -604,7 +678,9 @@ function populateFileEntryInfoFromFile(fileEntry, filePath, cb) {
//
// 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;
@ -614,13 +690,14 @@ function populateFileEntryInfoFromFile(fileEntry, filePath, cb) {
return nextDesc(null);
});
}, () => {
},
() => {
return cb(null);
});
}
);
}
function populateFileEntryNonArchive(fileEntry, filePath, stepInfo, iterator, cb) {
async.series(
[
function processDescFilesStart(callback) {
@ -654,7 +731,7 @@ function addNewFileEntry(fileEntry, filePath, cb) {
[
function addNewDbRecord(callback) {
return fileEntry.persist(callback);
}
},
],
err => {
return cb(err);
@ -665,7 +742,6 @@ function addNewFileEntry(fileEntry, filePath, cb) {
const HASH_NAMES = ['sha1', 'sha256', 'md5', 'crc32'];
function scanFile(filePath, options, iterator, cb) {
if (3 === arguments.length && _.isFunction(iterator)) {
cb = iterator;
iterator = null;
@ -689,7 +765,7 @@ function scanFile(filePath, options, iterator, cb) {
fileName: paths.basename(filePath),
};
const callIter = (next) => {
const callIter = next => {
return iterator ? iterator(stepInfo, next) : next(null);
};
@ -737,13 +813,13 @@ function scanFile(filePath, options, iterator, cb) {
const hashes = {};
hashesToCalc.forEach(hashName => {
if ('crc32' === hashName) {
hashes.crc32 = new CRC32;
hashes.crc32 = new CRC32();
} else {
hashes[hashName] = crypto.createHash(hashName);
}
});
const updateHashes = (data) => {
const updateHashes = data => {
for (let i = 0; i < hashesToCalc.length; ++i) {
hashes[hashesToCalc[i]].update(data);
}
@ -767,7 +843,10 @@ function scanFile(filePath, options, iterator, cb) {
if (err) {
return fs.close(fd, 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);
});
@ -780,31 +859,49 @@ function scanFile(filePath, options, iterator, cb) {
for (let i = 0; i < hashesToCalc.length; ++i) {
const hashName = hashesToCalc[i];
if ('sha256' === hashName) {
stepInfo.sha256 = fileEntry.fileSha256 = hashes.sha256.digest('hex');
} else if('sha1' === hashName || 'md5' === hashName) {
stepInfo[hashName] = fileEntry.meta[`file_${hashName}`] = hashes[hashName].digest('hex');
stepInfo.sha256 = fileEntry.fileSha256 =
hashes.sha256.digest('hex');
} else if (
'sha1' === hashName ||
'md5' === hashName
) {
stepInfo[hashName] = fileEntry.meta[
`file_${hashName}`
] = hashes[hashName].digest('hex');
} 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';
return fs.close(fd, closeErr => {
if (closeErr) {
logError( { filePath, error : err.message }, 'Failed to close file');
logError(
{ filePath, error: err.message },
'Failed to close file'
);
}
return callIter(callback);
});
}
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
//
const data = bytesRead < chunkSize ? buffer.slice(0, bytesRead) : buffer;
if(!iterator || stepInfo.calcHashPercent === lastCalcHashPercent) {
const data =
bytesRead < chunkSize
? buffer.slice(0, bytesRead)
: buffer;
if (
!iterator ||
stepInfo.calcHashPercent === lastCalcHashPercent
) {
updateHashes(data);
return nextChunk();
} else {
@ -834,39 +931,66 @@ function scanFile(filePath, options, iterator, cb) {
// save this off
fileEntry.meta.archive_type = archiveType;
populateFileEntryWithArchive(fileEntry, filePath, stepInfo, callIter, err => {
populateFileEntryWithArchive(
fileEntry,
filePath,
stepInfo,
callIter,
err => {
if (err) {
populateFileEntryNonArchive(fileEntry, filePath, stepInfo, callIter, err => {
populateFileEntryNonArchive(
fileEntry,
filePath,
stepInfo,
callIter,
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
});
}
);
} else {
return callback(null);
}
});
}
);
} else {
populateFileEntryNonArchive(fileEntry, filePath, stepInfo, callIter, err => {
populateFileEntryNonArchive(
fileEntry,
filePath,
stepInfo,
callIter,
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
});
}
);
}
});
},
function fetchExistingEntry(callback) {
getExistingFileEntriesBySha256(fileEntry.fileSha256, (err, dupeEntries) => {
getExistingFileEntriesBySha256(
fileEntry.fileSha256,
(err, dupeEntries) => {
return callback(err, dupeEntries);
});
}
);
},
function finished(dupeEntries, callback) {
stepInfo.step = 'finished';
callIter(() => {
return callback(null, dupeEntries);
});
}
},
],
(err, dupeEntries) => {
if (err) {
@ -982,9 +1106,10 @@ function getDescFromFileName(fileName) {
const ext = paths.extname(fileName);
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, ' '));
};
@ -1076,9 +1201,9 @@ function cleanUpTempSessionItems(cb) {
metaPairs: [
{
name: 'session_temp_dl',
value : 1
}
]
value: 1,
},
],
};
FileEntry.findFiles(filter, (err, fileIds) => {
@ -1086,23 +1211,36 @@ function cleanUpTempSessionItems(cb) {
return cb(err);
}
async.each(fileIds, (fileId, nextFileId) => {
async.each(
fileIds,
(fileId, nextFileId) => {
const fileEntry = new FileEntry();
fileEntry.load(fileId, 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);
}
FileEntry.removeEntry(fileEntry, { removePhysFile: true }, 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 cb(null);
});
}
);
});
}

View File

@ -37,8 +37,13 @@ exports.getModule = class FileAreaSelectModule extends MenuModule {
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(
[
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
const availAreas = getSortedAvailableFileAreas(self.client);
@ -66,18 +73,30 @@ exports.getModule = class FileAreaSelectModule extends MenuModule {
return callback(null, availAreas);
},
function prepView(availAreas, callback) {
self.prepViewController('allViews', 0, mciData.menu, (err, vc) => {
self.prepViewController(
'allViews',
0,
mciData.menu,
(err, vc) => {
if (err) {
return callback(err);
}
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();
return callback(null);
});
}
);
},
],
err => {
return cb(err);

View File

@ -35,7 +35,6 @@ const MciViewIds = {
};
exports.getModule = class FileBaseDownloadQueueManager extends MenuModule {
constructor(options) {
super(options);
@ -53,10 +52,15 @@ exports.getModule = class FileBaseDownloadQueueManager extends MenuModule {
extraArgs: {
sendQueue: this.dlQueue.items,
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) => {
const selectedItem = this.dlQueue.items[formData.value.queueItem];
@ -67,14 +71,17 @@ exports.getModule = class FileBaseDownloadQueueManager extends MenuModule {
this.dlQueue.removeItems(selectedItem.fileId);
// :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) => {
this.dlQueue.clear();
// :TODO: broken: does not redraw menu properly - needs fixed!
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
return this.gotoMenu(this.menuConfig.config.emptyQueueMenu || 'fileBaseDownloadManagerEmptyQueue');
return this.gotoMenu(
this.menuConfig.config.emptyQueueMenu ||
'fileBaseDownloadManagerEmptyQueue'
);
}
const self = this;
@ -98,7 +108,7 @@ exports.getModule = class FileBaseDownloadQueueManager extends MenuModule {
},
function display(callback) {
return self.displayQueueManagerPage(false, callback);
}
},
],
() => {
return self.finishedLoading();
@ -107,7 +117,9 @@ exports.getModule = class FileBaseDownloadQueueManager extends MenuModule {
}
removeItemsFromDownloadQueueView(itemIndex, cb) {
const queueView = this.viewControllers.queueManager.getView(MciViewIds.queueManager.queue);
const queueView = this.viewControllers.queueManager.getView(
MciViewIds.queueManager.queue
);
if (!queueView) {
return cb(Errors.DoesNotExist('Queue view does not exist'));
}
@ -124,12 +136,20 @@ exports.getModule = class FileBaseDownloadQueueManager extends MenuModule {
}
displayWebDownloadLinkForFileEntry(fileEntry) {
FileAreaWeb.getExistingTempDownloadServeItem(this.client, fileEntry, (err, serveItem) => {
FileAreaWeb.getExistingTempDownloadServeItem(
this.client,
fileEntry,
(err, serveItem) => {
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.webDlExpire = moment(serveItem.expireTimestamp).format(webDlExpireTimeFormat);
fileEntry.webDlLink =
ansi.vtxHyperlink(this.client, serveItem.url) + serveItem.url;
fileEntry.webDlExpire = moment(serveItem.expireTimestamp).format(
webDlExpireTimeFormat
);
} else {
fileEntry.webDlLink = '';
fileEntry.webDlExpire = '';
@ -137,14 +157,18 @@ exports.getModule = class FileBaseDownloadQueueManager extends MenuModule {
this.updateCustomViewTextsWithFilter(
'queueManager',
MciViewIds.queueManager.customRangeStart, fileEntry,
MciViewIds.queueManager.customRangeStart,
fileEntry,
{ filter: ['{webDlLink}', '{webDlExpire}'] }
);
});
}
);
}
updateDownloadQueueView(cb) {
const queueView = this.viewControllers.queueManager.getView(MciViewIds.queueManager.queue);
const queueView = this.viewControllers.queueManager.getView(
MciViewIds.queueManager.queue
);
if (!queueView) {
return cb(Errors.DoesNotExist('Queue view does not exist'));
}
@ -176,7 +200,7 @@ exports.getModule = class FileBaseDownloadQueueManager extends MenuModule {
},
function populateViews(callback) {
return self.updateDownloadQueueView(callback);
}
},
],
err => {
if (cb) {

View File

@ -78,7 +78,10 @@ module.exports = class FileBaseFilters {
} catch (e) {
this.filters = FileBaseFilters.getBuiltInSystemFilters(); // something bad happened; reset everything back to defaults :(
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) {
@ -92,11 +95,18 @@ module.exports = class FileBaseFilters {
}
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) {
return tags.toLowerCase().replace(/,?\s+|,/g, ' ').trim();
return tags
.toLowerCase()
.replace(/,?\s+|,/g, ' ')
.trim();
}
setActive(filterUuid) {
@ -104,7 +114,10 @@ module.exports = class FileBaseFilters {
if (activeFilter) {
this.activeFilter = activeFilter;
this.client.user.persistProperty(UserProps.FileBaseFilterActiveUuid, filterUuid);
this.client.user.persistProperty(
UserProps.FileBaseFilterActiveUuid,
filterUuid
);
return true;
}
@ -124,18 +137,20 @@ module.exports = class FileBaseFilters {
sort: 'upload_timestamp',
uuid: U_LATEST,
system: true,
}
},
};
return filters;
}
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) {
return parseInt((user.properties[UserProps.FileBaseLastViewedId] || 0));
return parseInt(user.properties[UserProps.FileBaseLastViewedId] || 0);
}
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 Config = require('./config.js').get;
const { Errors } = require('./enig_error.js');
const {
splitTextAtTerms,
isAnsi,
} = require('./string_util.js');
const { splitTextAtTerms, isAnsi } = require('./string_util.js');
const AnsiPrep = require('./ansi_prep.js');
const Log = require('./logger.js').log;
@ -27,7 +24,8 @@ exports.updateFileBaseDescFilesScheduledEvent = updateFileBaseDescFilesSchedul
function exportFileList(filterCriteria, options, cb) {
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.descWidth = options.descWidth || 45; // FILE_ID.DIZ spec
options.escapeDesc = _.get(options, 'escapeDesc', false); // escape \r and \n in desc?
@ -43,15 +41,13 @@ function exportFileList(filterCriteria, options, cb) {
status: 'Preparing',
};
const updateProgress = _.isFunction(options.progress) ?
progCb => {
const updateProgress = _.isFunction(options.progress)
? progCb => {
return options.progress(state, progCb);
} :
progCb => {
return progCb(null);
}
;
: progCb => {
return progCb(null);
};
async.waterfall(
[
function readTemplateFiles(callback) {
@ -62,26 +58,35 @@ function exportFileList(filterCriteria, options, cb) {
const templateFiles = [
{ name: options.headerTemplate, req: false },
{ name : options.entryTemplate, req : true }
{ name: options.entryTemplate, req: true },
];
const config = Config();
async.map(templateFiles, (template, nextTemplate) => {
async.map(
templateFiles,
(template, nextTemplate) => {
if (!template.name && !template.req) {
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) => {
return nextTemplate(err, data);
});
}, (err, templates) => {
},
(err, templates) => {
if (err) {
return callback(Errors.General(err.message));
}
// 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
let descIndent = 0;
@ -97,7 +102,8 @@ function exportFileList(filterCriteria, options, cb) {
}
return callback(null, templates[0], templates[1], descIndent);
});
}
);
});
},
function findFiles(headerTemplate, entryTemplate, descIndent, callback) {
@ -110,14 +116,28 @@ function exportFileList(filterCriteria, options, cb) {
FileEntry.findFiles(filterCriteria, (err, fileIds) => {
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 = {
totalFileCount: fileIds.length,
};
@ -129,7 +149,9 @@ function exportFileList(filterCriteria, options, cb) {
state.step = 'file';
async.eachSeries(fileIds, (fileId, nextFileId) => {
async.eachSeries(
fileIds,
(fileId, nextFileId) => {
const fileInfo = new FileEntry();
current += 1;
@ -142,11 +164,17 @@ function exportFileList(filterCriteria, options, cb) {
const appendFileInfo = () => {
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) {
formatObj.fileDesc = formatObj.fileDesc.slice(0, options.maxDescLen);
formatObj.fileDesc = formatObj.fileDesc.slice(
0,
options.maxDescLen
);
}
listBody += stringFormat(entryTemplate, formatObj);
@ -169,22 +197,36 @@ function exportFileList(filterCriteria, options, cb) {
formatObj.fileName = fileInfo.fileName;
formatObj.fileSize = fileInfo.meta.byte_size;
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.fileCrc32 = fileInfo.meta.file_crc32;
formatObj.fileMd5 = fileInfo.meta.file_md5;
formatObj.fileSha1 = fileInfo.meta.file_sha1;
formatObj.uploadBy = fileInfo.meta.upload_by_username || 'N/A';
formatObj.fileUploadTs = moment(fileInfo.uploadTimestamp).format(options.tsFormat);
formatObj.fileHashTags = fileInfo.hashTags.size > 0 ? Array.from(fileInfo.hashTags).join(', ') : 'N/A';
formatObj.uploadBy =
fileInfo.meta.upload_by_username || '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.progress = Math.floor( (current / fileIds.length) * 100 );
formatObj.progress = Math.floor(
(current / fileIds.length) * 100
);
if (isAnsi(fileInfo.desc)) {
AnsiPrep(
fileInfo.desc,
{
cols : Math.min(options.descWidth, 79 - descIndent),
cols: Math.min(
options.descWidth,
79 - descIndent
),
forceLineTerm: true, // ensure each line is term'd
asciiMode: true, // export to ASCII
fillLines: false, // don't fill up to |cols|
@ -198,14 +240,20 @@ function exportFileList(filterCriteria, options, cb) {
}
);
} else {
const indentSpc = descIndent > 0 ? ' '.repeat(descIndent) : '';
formatObj.fileDesc = splitTextAtTerms(formatObj.fileDesc).join(`\r\n${indentSpc}`) + '\r\n';
const indentSpc =
descIndent > 0 ? ' '.repeat(descIndent) : '';
formatObj.fileDesc =
splitTextAtTerms(formatObj.fileDesc).join(
`\r\n${indentSpc}`
) + '\r\n';
return appendFileInfo();
}
});
}, err => {
},
err => {
return callback(err, listBody, headerTemplate, totals);
});
}
);
},
function buildHeader(listBody, headerTemplate, totals, callback) {
// header is built last such that we can have totals/etc.
@ -243,8 +291,9 @@ function exportFileList(filterCriteria, options, cb) {
updateProgress(() => {
return callback(null, listBody);
});
}
], (err, listBody) => {
},
],
(err, listBody) => {
return cb(err, listBody);
}
);
@ -264,10 +313,14 @@ function updateFileBaseDescFilesScheduledEvent(args, cb) {
const headerTemplate = args[1];
const areas = FileArea.getAvailableFileAreas(null, { skipAcsCheck: true });
async.each(areas, (area, nextArea) => {
async.each(
areas,
(area, nextArea) => {
const storageLocations = FileArea.getAreaStorageLocations(area);
async.each(storageLocations, (storageLoc, nextStorageLoc) => {
async.each(
storageLocations,
(storageLoc, nextStorageLoc) => {
const filterCriteria = {
areaTag: area.areaTag,
storageTag: storageLoc.storageTag,
@ -281,21 +334,34 @@ function updateFileBaseDescFilesScheduledEvent(args, cb) {
};
exportFileList(filterCriteria, exportOpts, (err, listBody) => {
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) {
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 {
Log.debug( { path : descIonPath }, '(Re)generated DESCRIPT.ION');
Log.debug(
{ path: descIonPath },
'(Re)generated DESCRIPT.ION'
);
}
return nextStorageLoc(null);
});
});
}, () => {
return nextArea(null);
});
}, () => {
return cb(null);
});
}
);
});
},
() => {
return nextArea(null);
}
);
},
() => {
return cb(null);
}
);
}

View File

@ -4,7 +4,8 @@
// ENiGMA½
const MenuModule = require('./menu_module.js').MenuModule;
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');
// deps
@ -25,7 +26,7 @@ const MciViewIds = {
orderBy: 5,
sort: 6,
advSearch: 7,
}
},
};
exports.getModule = class FileBaseSearch extends MenuModule {
@ -47,15 +48,23 @@ exports.getModule = class FileBaseSearch extends MenuModule {
}
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(
[
function loadFromConfig(callback) {
return vc.loadFromMenuConfig( { callingMenu : self, mciMap : mciData.menu }, callback);
return vc.loadFromMenuConfig(
{ callingMenu: self, mciMap: mciData.menu },
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);
areasView.setItems(self.availAreas.map(a => a.name));
@ -63,7 +72,7 @@ exports.getModule = class FileBaseSearch extends MenuModule {
vc.switchFocus(MciViewIds.search.searchTerms);
return callback(null);
}
},
],
err => {
return cb(err);
@ -115,6 +124,10 @@ exports.getModule = class FileBaseSearch extends MenuModule {
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,
customRangeStart: 10,
}
},
};
exports.getModule = class FileBaseListExport extends MenuModule {
constructor(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.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.progBarChar = renderSubstr( (this.config.progBarChar || '▒'), 0, 1);
this.config.compressThreshold = this.config.compressThreshold || (1440000); // >= 1.44M by default :)
this.config.progBarChar = renderSubstr(this.config.progBarChar || '▒', 0, 1);
this.config.compressThreshold = this.config.compressThreshold || 1440000; // >= 1.44M by default :)
}
mciReady(mciData, cb) {
@ -83,13 +88,22 @@ exports.getModule = class FileBaseListExport extends MenuModule {
async.series(
[
(callback) => this.prepViewController('main', FormIds.main, mciData.menu, callback),
(callback) => this.prepareList(callback),
callback =>
this.prepViewController(
'main',
FormIds.main,
mciData.menu,
callback
),
callback => this.prepareList(callback),
],
err => {
if (err) {
if ('NORESULTS' === err.reasonCode) {
return this.gotoMenu(this.menuConfig.config.noResultsMenu || 'fileBaseExportListNoResults');
return this.gotoMenu(
this.menuConfig.config.noResultsMenu ||
'fileBaseExportListNoResults'
);
}
return this.prevMenu();
@ -108,13 +122,15 @@ exports.getModule = class FileBaseListExport extends MenuModule {
const self = this;
const statusView = self.viewControllers.main.getView(MciViewIds.main.status);
const updateStatus = (status) => {
const updateStatus = status => {
if (statusView) {
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) => {
if (progBarView) {
const prog = Math.floor((curr / total) * progBarView.dimens.width);
@ -133,7 +149,11 @@ exports.getModule = class FileBaseListExport extends MenuModule {
case 'file':
updateStatus(state.status);
updateProgressBar(state.current, state.total);
self.updateCustomViewTextsWithFilter('main', MciViewIds.main.customRangeStart, state.fileInfo);
self.updateCustomViewTextsWithFilter(
'main',
MciViewIds.main.customRangeStart,
state.fileInfo
);
break;
default:
break;
@ -159,13 +179,23 @@ exports.getModule = class FileBaseListExport extends MenuModule {
const filterCriteria = Object.assign({}, self.config.filterCriteria);
if (!filterCriteria.areaTag) {
filterCriteria.areaTag = FileArea.getAvailableFileAreaTags(self.client);
filterCriteria.areaTag = FileArea.getAvailableFileAreaTags(
self.client
);
}
const opts = {
templateEncoding: self.config.templateEncoding,
headerTemplate : _.get(self.config, 'templates.header', 'file_list_header.asc'),
entryTemplate : _.get(self.config, 'templates.entry', 'file_list_entry.asc'),
headerTemplate: _.get(
self.config,
'templates.header',
'file_list_header.asc'
),
entryTemplate: _.get(
self.config,
'templates.entry',
'file_list_entry.asc'
),
tsFormat: self.config.tsFormat,
descWidth: self.config.descWidth,
progress: exportListProgress,
@ -178,8 +208,11 @@ exports.getModule = class FileBaseListExport extends MenuModule {
function persistList(listBody, callback) {
updateStatus('Persisting list');
const sysTempDownloadArea = FileArea.getFileAreaByTag(FileArea.WellKnownAreaTags.TempDownloads);
const sysTempDownloadDir = FileArea.getAreaDefaultStorageDirectory(sysTempDownloadArea);
const sysTempDownloadArea = FileArea.getFileAreaByTag(
FileArea.WellKnownAreaTags.TempDownloads
);
const sysTempDownloadDir =
FileArea.getAreaDefaultStorageDirectory(sysTempDownloadArea);
fse.mkdirs(sysTempDownloadDir, err => {
if (err) {
@ -188,7 +221,9 @@ exports.getModule = class FileBaseListExport extends MenuModule {
const outputFileName = paths.join(
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 => {
@ -196,13 +231,26 @@ exports.getModule = class FileBaseListExport extends MenuModule {
return callback(err);
}
self.getSizeAndCompressIfMeetsSizeThreshold(outputFileName, (err, finalOutputFileName, fileSize) => {
return callback(err, finalOutputFileName, fileSize, sysTempDownloadArea);
});
self.getSizeAndCompressIfMeetsSizeThreshold(
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({
areaTag: sysTempDownloadArea.areaTag,
fileName: paths.basename(outputFileName),
@ -212,7 +260,7 @@ exports.getModule = class FileBaseListExport extends MenuModule {
upload_by_user_id: self.client.user.userId,
byte_size: fileSize,
session_temp_dl: 1, // download is valid until session is over
}
},
});
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');
return callback(null);
}
},
],
err => {
self.client.removeListener('key press', keyPressHandler);

View File

@ -24,7 +24,7 @@ exports.moduleInfo = {
};
const FormIds = {
queueManager : 0
queueManager: 0,
};
const MciViewIds = {
@ -33,11 +33,10 @@ const MciViewIds = {
navMenu: 2,
customRangeStart: 10,
}
},
};
exports.getModule = class FileBaseWebDownloadQueueManager extends MenuModule {
constructor(options) {
super(options);
@ -53,7 +52,10 @@ exports.getModule = class FileBaseWebDownloadQueueManager extends MenuModule {
this.dlQueue.removeItems(selectedItem.fileId);
// :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) => {
this.dlQueue.clear();
@ -63,13 +65,16 @@ exports.getModule = class FileBaseWebDownloadQueueManager extends MenuModule {
},
getBatchLink: (formData, extraArgs, cb) => {
return this.generateAndDisplayBatchLink(cb);
}
},
};
}
initSequence() {
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;
@ -81,7 +86,7 @@ exports.getModule = class FileBaseWebDownloadQueueManager extends MenuModule {
},
function display(callback) {
return self.displayQueueManagerPage(false, callback);
}
},
],
() => {
return self.finishedLoading();
@ -90,7 +95,9 @@ exports.getModule = class FileBaseWebDownloadQueueManager extends MenuModule {
}
removeItemsFromDownloadQueueView(itemIndex, cb) {
const queueView = this.viewControllers.queueManager.getView(MciViewIds.queueManager.queue);
const queueView = this.viewControllers.queueManager.getView(
MciViewIds.queueManager.queue
);
if (!queueView) {
return cb(Errors.DoesNotExist('Queue view does not exist'));
}
@ -109,13 +116,16 @@ exports.getModule = class FileBaseWebDownloadQueueManager extends MenuModule {
displayFileInfoForFileEntry(fileEntry) {
this.updateCustomViewTextsWithFilter(
'queueManager',
MciViewIds.queueManager.customRangeStart, fileEntry,
MciViewIds.queueManager.customRangeStart,
fileEntry,
{ filter: ['{webDlLink}', '{webDlExpire}', '{fileName}'] } // :TODO: Others....
);
}
updateDownloadQueueView(cb) {
const queueView = this.viewControllers.queueManager.getView(MciViewIds.queueManager.queue);
const queueView = this.viewControllers.queueManager.getView(
MciViewIds.queueManager.queue
);
if (!queueView) {
return cb(Errors.DoesNotExist('Queue view does not exist'));
}
@ -140,7 +150,7 @@ exports.getModule = class FileBaseWebDownloadQueueManager extends MenuModule {
this.client,
this.dlQueue.items,
{
expireTime : expireTime
expireTime: expireTime,
},
(err, webBatchDlLink) => {
// :TODO: handle not enabled -> display such
@ -148,10 +158,12 @@ exports.getModule = class FileBaseWebDownloadQueueManager extends MenuModule {
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 = {
webBatchDlLink : ansi.vtxHyperlink(this.client, webBatchDlLink) + webBatchDlLink,
webBatchDlLink:
ansi.vtxHyperlink(this.client, webBatchDlLink) + webBatchDlLink,
webBatchDlExpire: expireTime.format(webDlExpireTimeFormat),
};
@ -181,17 +193,27 @@ exports.getModule = class FileBaseWebDownloadQueueManager extends MenuModule {
);
},
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();
async.each(self.dlQueue.items, (fileEntry, nextFileEntry) => {
FileAreaWeb.getExistingTempDownloadServeItem(self.client, fileEntry, (err, serveItem) => {
async.each(
self.dlQueue.items,
(fileEntry, nextFileEntry) => {
FileAreaWeb.getExistingTempDownloadServeItem(
self.client,
fileEntry,
(err, serveItem) => {
if (err) {
if (ErrNotEnabled === err.reasonCode) {
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(
self.client,
@ -203,26 +225,40 @@ exports.getModule = class FileBaseWebDownloadQueueManager extends MenuModule {
}
fileEntry.webDlLinkRaw = url;
fileEntry.webDlLink = ansi.vtxHyperlink(self.client, url) + url;
fileEntry.webDlExpire = expireTime.format(webDlExpireTimeFormat);
fileEntry.webDlLink =
ansi.vtxHyperlink(self.client, url) +
url;
fileEntry.webDlExpire =
expireTime.format(
webDlExpireTimeFormat
);
return nextFileEntry(null);
}
);
} else {
fileEntry.webDlLinkRaw = serveItem.url;
fileEntry.webDlLink = ansi.vtxHyperlink(self.client, serveItem.url) + serveItem.url;
fileEntry.webDlExpire = moment(serveItem.expireTimestamp).format(webDlExpireTimeFormat);
fileEntry.webDlLink =
ansi.vtxHyperlink(
self.client,
serveItem.url
) + serveItem.url;
fileEntry.webDlExpire = moment(
serveItem.expireTimestamp
).format(webDlExpireTimeFormat);
return nextFileEntry(null);
}
});
}, err => {
}
);
},
err => {
return callback(err);
});
}
);
},
function populateViews(callback) {
return self.updateDownloadQueueView(callback);
}
},
],
err => {
if (cb) {

View File

@ -3,10 +3,7 @@
const fileDb = require('./database.js').dbs.file;
const Errors = require('./enig_error.js').Errors;
const {
getISOTimestampString,
sanitizeString
} = require('./database.js');
const { getISOTimestampString, sanitizeString } = require('./database.js');
const Config = require('./config.js').get;
// deps
@ -19,28 +16,34 @@ const crypto = require('crypto');
const moment = require('moment');
const FILE_TABLE_MEMBERS = [
'file_id', 'area_tag', 'file_sha256', 'file_name', 'storage_tag',
'desc', 'desc_long', 'upload_timestamp'
'file_id',
'area_tag',
'file_sha256',
'file_name',
'storage_tag',
'desc',
'desc_long',
'upload_timestamp',
];
const FILE_WELL_KNOWN_META = {
// name -> *read* converter, if any
upload_by_username: null,
upload_by_user_id : (u) => parseInt(u) || 0,
upload_by_user_id: u => parseInt(u) || 0,
file_md5: null,
file_sha1: null,
file_crc32: null,
est_release_year : (y) => parseInt(y) || new Date().getFullYear(),
dl_count : (d) => parseInt(d) || 0,
byte_size : (b) => parseInt(b) || 0,
est_release_year: y => parseInt(y) || new Date().getFullYear(),
dl_count: d => parseInt(d) || 0,
byte_size: b => parseInt(b) || 0,
archive_type: null,
short_file_name: null, // e.g. DOS 8.3 filename, avail in some scenarios such as TIC import
tic_origin: null, // TIC "Origin"
tic_desc: null, // TIC "Desc"
tic_ldesc: null, // TIC "Ldesc" joined by '\n'
session_temp_dl : (v) => parseInt(v) ? true : false,
desc_sauce : (s) => JSON.parse(s) || {},
desc_long_sauce : (s) => JSON.parse(s) || {},
session_temp_dl: v => (parseInt(v) ? true : false),
desc_sauce: s => JSON.parse(s) || {},
desc_long_sauce: s => JSON.parse(s) || {},
};
module.exports = class FileEntry {
@ -100,7 +103,7 @@ module.exports = class FileEntry {
},
function loadUserRating(callback) {
return self.loadRating(callback);
}
},
],
err => {
return cb(err);
@ -120,7 +123,11 @@ module.exports = class FileEntry {
[
function check(callback) {
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);
},
@ -130,7 +137,11 @@ module.exports = class FileEntry {
}
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) => {
@ -152,7 +163,16 @@ module.exports = class FileEntry {
trans.run(
`REPLACE INTO file (file_id, area_tag, file_sha256, file_name, storage_tag, desc, desc_long, upload_timestamp)
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 => {
return callback(err, trans);
}
@ -161,8 +181,17 @@ module.exports = class FileEntry {
trans.run(
`REPLACE INTO file (area_tag, file_sha256, file_name, storage_tag, desc, desc_long, upload_timestamp)
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) {
self.fileId = this.lastID;
}
@ -172,23 +201,40 @@ module.exports = class FileEntry {
}
},
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];
return FileEntry.persistMetaValue(self.fileId, n, v, trans, next);
return FileEntry.persistMetaValue(
self.fileId,
n,
v,
trans,
next
);
},
err => {
return callback(err, trans);
});
}
);
},
function storeHashTags(trans, callback) {
const hashTagsArray = Array.from(self.hashTags);
async.each(hashTagsArray, (hashTag, next) => {
return FileEntry.persistHashTag(self.fileId, hashTag, trans, next);
async.each(
hashTagsArray,
(hashTag, next) => {
return FileEntry.persistHashTag(
self.fileId,
hashTag,
trans,
next
);
},
err => {
return callback(err, trans);
});
}
);
},
],
(err, trans) => {
// :TODO: Log orig err
@ -205,7 +251,7 @@ module.exports = class FileEntry {
static getAreaStorageDirectoryByTag(storageTag) {
const config = Config();
const storageLocation = (storageTag && config.fileBase.storageTags[storageTag]);
const storageLocation = storageTag && config.fileBase.storageTags[storageTag];
// absolute paths as-is
if (storageLocation && '/' === storageLocation.charAt(0)) {
@ -290,7 +336,9 @@ module.exports = class FileEntry {
(err, meta) => {
if (meta) {
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 => {
@ -450,7 +498,9 @@ module.exports = class FileEntry {
}
const entries = [];
async.each(fileIdRows, (row, nextRow) => {
async.each(
fileIdRows,
(row, nextRow) => {
const fileEntry = new FileEntry();
fileEntry.load(row.file_id, err => {
if (!err) {
@ -461,7 +511,8 @@ module.exports = class FileEntry {
},
err => {
return cb(err, entries);
});
}
);
}
);
}
@ -506,19 +557,20 @@ module.exports = class FileEntry {
}
if (filter.sort && filter.sort.length > 0) {
if(Object.keys(FILE_WELL_KNOWN_META).indexOf(filter.sort) > -1) { // sorting via a meta value?
sql =
`SELECT DISTINCT f.file_id
if (Object.keys(FILE_WELL_KNOWN_META).indexOf(filter.sort) > -1) {
// sorting via a meta value?
sql = `SELECT DISTINCT f.file_id
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}`;
} else {
// additional special treatment for user ratings: we need to average them
if ('user_rating' === filter.sort) {
sql =
`SELECT DISTINCT f.file_id,
sql = `SELECT DISTINCT f.file_id,
(SELECT IFNULL(AVG(rating), 0) rating
FROM file_user_rating
WHERE file_id = f.file_id)
@ -527,16 +579,15 @@ module.exports = class FileEntry {
sqlOrderBy = `ORDER BY avg_rating ${sqlOrderDir}`;
} else {
sql =
`SELECT DISTINCT f.file_id
sql = `SELECT DISTINCT f.file_id
FROM file f`;
sqlOrderBy = getOrderByWithCast(`f.${filter.sort}`) + ' ' + sqlOrderDir;
sqlOrderBy =
getOrderByWithCast(`f.${filter.sort}`) + ' ' + sqlOrderDir;
}
}
} else {
sql =
`SELECT DISTINCT f.file_id
sql = `SELECT DISTINCT f.file_id
FROM file f`;
sqlOrderBy = `${getOrderByWithCast('f.file_id')} ${sqlOrderDir}`;
@ -552,7 +603,6 @@ module.exports = class FileEntry {
}
if (filter.metaPairs && filter.metaPairs.length > 0) {
filter.metaPairs.forEach(mp => {
if (mp.wildcards) {
// 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) {
// 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(
`f.file_id IN (
@ -623,8 +678,13 @@ module.exports = class FileEntry {
);
}
if(_.isString(filter.newerThanTimestamp) && filter.newerThanTimestamp.length > 0) {
appendWhereClause(`DATETIME(f.upload_timestamp) > DATETIME("${filter.newerThanTimestamp}", "+1 seconds")`);
if (
_.isString(filter.newerThanTimestamp) &&
filter.newerThanTimestamp.length > 0
) {
appendWhereClause(
`DATETIME(f.upload_timestamp) > DATETIME("${filter.newerThanTimestamp}", "+1 seconds")`
);
}
if (_.isNumber(filter.newerThanFileId)) {
@ -646,7 +706,10 @@ module.exports = class FileEntry {
if (!rows || 0 === rows.length) {
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 => {
return callback(err);
});
}
},
],
err => {
return cb(err);
@ -720,7 +783,7 @@ module.exports = class FileEntry {
return callback(err);
}
);
}
},
],
err => {
return cb(err);

View File

@ -61,7 +61,8 @@ exports.getModule = class TransferFileModule extends MenuModule {
const config = Config();
if (options.extraArgs) {
if (options.extraArgs.protocol) {
this.protocolConfig = config.fileTransferProtocols[options.extraArgs.protocol];
this.protocolConfig =
config.fileTransferProtocols[options.extraArgs.protocol];
}
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.sendQueue = this.sendQueue || [];
@ -118,7 +120,7 @@ exports.getModule = class TransferFileModule extends MenuModule {
}
isSending() {
return ('send' === this.direction);
return 'send' === this.direction;
}
restorePipeAfterExternalProc() {
@ -135,13 +137,15 @@ exports.getModule = class TransferFileModule extends MenuModule {
const allFiles = this.sendQueue.map(f => f.path);
this.executeExternalProtocolHandlerForSend(allFiles, 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 {
const sentFiles = [];
this.sendQueue.forEach(f => {
f.sent = true;
sentFiles.push(f.path);
});
this.client.log.info( { sentFiles : sentFiles }, `User "${self.client.user.username}" uploaded ${sentFiles.length} file(s)` );
@ -205,13 +209,16 @@ exports.getModule = class TransferFileModule extends MenuModule {
let tryDstPath;
async.until(
(callback) => callback(null, movedOk), // until moved OK
(cb) => {
callback => callback(null, movedOk), // until moved OK
cb => {
if (0 === renameIndex) {
// try originally supplied path first
tryDstPath = dst;
} else {
tryDstPath = paths.join(dstPath, `${dstFileSuffix}(${renameIndex})${dstFileExt}`);
tryDstPath = paths.join(
dstPath,
`${dstFileSuffix}(${renameIndex})${dstFileExt}`
);
}
fse.move(src, tryDstPath, err => {
@ -254,7 +261,9 @@ exports.getModule = class TransferFileModule extends MenuModule {
}
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);
@ -270,12 +279,16 @@ exports.getModule = class TransferFileModule extends MenuModule {
}
// stat each to grab files only
async.each(files, (fileName, nextFile) => {
async.each(
files,
(fileName, nextFile) => {
const recvFullPath = paths.join(this.recvDirectory, fileName);
fs.stat(recvFullPath, (err, stats) => {
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
}
@ -285,9 +298,11 @@ exports.getModule = class TransferFileModule extends MenuModule {
return nextFile(null);
});
}, () => {
},
() => {
return cb(null);
});
}
);
});
}
});
@ -306,12 +321,16 @@ exports.getModule = class TransferFileModule extends MenuModule {
async.waterfall(
[
function getTempFileListPath(callback) {
const hasFileList = externalArgs.find(ea => (ea.indexOf('{fileListPath}') > -1) );
const hasFileList = externalArgs.find(
ea => ea.indexOf('{fileListPath}') > -1
);
if (!hasFileList) {
return callback(null, null);
}
temptmp.open( { prefix : TEMP_SUFFIX, suffix : '.txt' }, (err, tempFileInfo) => {
temptmp.open(
{ prefix: TEMP_SUFFIX, suffix: '.txt' },
(err, tempFileInfo) => {
if (err) {
return callback(err); // failed to create it
}
@ -324,12 +343,15 @@ exports.getModule = class TransferFileModule extends MenuModule {
return callback(err, tempFileInfo.path);
});
});
});
}
);
},
function createArgs(tempFileListPath, callback) {
// initial args: ignore {filePaths} as we must break that into it's own sep array items
const args = externalArgs.map(arg => {
return '{filePaths}' === arg ? arg : stringFormat(arg, {
return '{filePaths}' === arg
? arg
: stringFormat(arg, {
fileListPath: tempFileListPath || '',
});
});
@ -341,7 +363,7 @@ exports.getModule = class TransferFileModule extends MenuModule {
}
return callback(null, args);
}
},
],
(err, args) => {
return cb(err, args);
@ -352,10 +374,12 @@ exports.getModule = class TransferFileModule extends MenuModule {
prepAndBuildRecvArgs(cb) {
const argsKey = this.recvFileName ? 'recvArgsNonBatch' : 'recvArgs';
const externalArgs = this.protocolConfig.external[argsKey];
const args = externalArgs.map(arg => stringFormat(arg, {
const args = externalArgs.map(arg =>
stringFormat(arg, {
uploadDir: this.recvDirectory,
fileName: this.recvFileName || '',
}));
})
);
return cb(null, args);
}
@ -365,9 +389,7 @@ exports.getModule = class TransferFileModule extends MenuModule {
const cmd = external[`${this.direction}Cmd`];
// support for handlers that need IACs taken care of over Telnet/etc.
const processIACs =
external.processIACs ||
external.escapeTelnet; // deprecated name
const processIACs = external.processIACs || external.escapeTelnet; // deprecated name
// :TODO: we should only do this when over Telnet (or derived, such as WebSockets)?
@ -375,7 +397,12 @@ exports.getModule = class TransferFileModule extends MenuModule {
const EscapedIAC = Buffer.from([255, 255]);
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'
);
@ -390,7 +417,7 @@ exports.getModule = class TransferFileModule extends MenuModule {
let dataHits = 0;
const updateActivity = () => {
if (0 === (dataHits++ % 4)) {
if (0 === dataHits++ % 4) {
this.client.explicitActivityTimeUpdate();
}
};
@ -459,13 +486,23 @@ exports.getModule = class TransferFileModule extends MenuModule {
return this.restorePipeAfterExternalProc();
});
externalProc.once('exit', (exitCode) => {
this.client.log.debug( { cmd : cmd, args : args, exitCode : exitCode }, 'Process exited' );
externalProc.once('exit', exitCode => {
this.client.log.debug(
{ cmd: cmd, args: args, exitCode: exitCode },
'Process exited'
);
this.restorePipeAfterExternalProc();
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 +547,9 @@ exports.getModule = class TransferFileModule extends MenuModule {
let downloadCount = 0;
let fileIds = [];
async.each(this.sendQueue, (queueItem, next) => {
async.each(
this.sendQueue,
(queueItem, next) => {
if (!queueItem.sent) {
return next(null);
}
@ -528,7 +567,10 @@ exports.getModule = class TransferFileModule extends MenuModule {
// we just have a path - figure it out
fs.stat(queueItem.path, (err, stats) => {
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 {
downloadCount += 1;
downloadBytes += stats.size;
@ -536,34 +578,46 @@ exports.getModule = class TransferFileModule extends MenuModule {
return next(null);
});
}, () => {
},
() => {
// 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(this.client.user, UserProps.FileDlTotalBytes, downloadBytes);
StatLog.incrementUserStat(
this.client.user,
UserProps.FileDlTotalCount,
downloadCount
);
StatLog.incrementUserStat(
this.client.user,
UserProps.FileDlTotalBytes,
downloadBytes
);
StatLog.incrementSystemStat(SysProps.FileDlTotalCount, downloadCount);
StatLog.incrementSystemStat(SysProps.FileDlTotalBytes, downloadBytes);
StatLog.incrementNonPersistentSystemStat(SysProps.FileDlTodayCount, downloadCount);
StatLog.incrementNonPersistentSystemStat(SysProps.FileDlTodayBytes, downloadBytes);
fileIds.forEach(fileId => {
FileEntry.incrementAndPersistMetaValue(fileId, 'dl_count', 1);
});
return cb(null);
});
}
);
}
updateRecvStats(cb) {
let uploadBytes = 0;
let uploadCount = 0;
async.each(this.recvFilePaths, (filePath, next) => {
async.each(
this.recvFilePaths,
(filePath, next) => {
// we just have a path - figure it out
fs.stat(filePath, (err, stats) => {
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 {
uploadCount += 1;
uploadBytes += stats.size;
@ -571,18 +625,25 @@ exports.getModule = class TransferFileModule extends MenuModule {
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.FileUlTotalBytes, uploadBytes);
StatLog.incrementNonPersistentSystemStat(SysProps.FileUlTodayCount, uploadCount);
StatLog.incrementNonPersistentSystemStat(SysProps.FileUlTodayBytes, uploadBytes);
return cb(null);
});
}
);
}
initSequence() {
@ -621,13 +682,10 @@ exports.getModule = class TransferFileModule extends MenuModule {
const dlFileEntries = dlQueue.removeItems(sentFileIds);
// fire event for downloaded entries
Events.emit(
Events.getSystemEvents().UserDownload,
{
Events.emit(Events.getSystemEvents().UserDownload, {
user: self.client.user,
files : dlFileEntries
}
);
files: dlFileEntries,
});
self.sentFileIds = sentFileIds;
}
@ -642,7 +700,10 @@ exports.getModule = class TransferFileModule extends MenuModule {
},
function cleanupTempFiles(callback) {
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);
@ -653,7 +714,7 @@ exports.getModule = class TransferFileModule extends MenuModule {
} else {
return self.updateRecvStats(callback);
}
}
},
],
err => {
if (err) {

View File

@ -21,7 +21,6 @@ const MciViewIds = {
};
exports.getModule = class FileTransferProtocolSelectModule extends MenuModule {
constructor(options) {
super(options);
@ -53,16 +52,28 @@ exports.getModule = class FileTransferProtocolSelectModule extends MenuModule {
selectProtocol: (formData, extraArgs, cb) => {
const protocol = this.protocols[formData.value.protocol];
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 = {
extraArgs: finalExtraArgs,
};
if ('send' === this.config.direction) {
return this.gotoMenu(this.config.downloadFilesMenu || 'sendFilesToUser', modOpts, cb);
return this.gotoMenu(
this.config.downloadFilesMenu || 'sendFilesToUser',
modOpts,
cb
);
} 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 vc = self.viewControllers.allViews = new ViewController( { client : self.client } );
const vc = (self.viewControllers.allViews = new ViewController({
client: self.client,
}));
async.series(
[
function loadFromConfig(callback) {
const loadOpts = {
callingMenu: self,
mciMap : mciData.menu
mciMap: mciData.menu,
};
return vc.loadFromMenuConfig(loadOpts, callback);
@ -113,7 +126,7 @@ exports.getModule = class FileTransferProtocolSelectModule extends MenuModule {
protListView.redraw();
return callback(null);
}
},
],
err => {
return cb(err);
@ -135,7 +148,8 @@ exports.getModule = class FileTransferProtocolSelectModule extends MenuModule {
});
// 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);
} else {
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)) {
return a.sort - b.sort;
} 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(
(callback) => callback(null, opOk), // until moved OK
(cb) => {
callback => callback(null, opOk), // until moved OK
cb => {
if (0 === renameIndex) {
// try originally supplied path first
tryDstPath = dst;
} else {
tryDstPath = paths.join(dstPath, `${dstFileSuffix}(${renameIndex})${dstFileExt}`);
tryDstPath = paths.join(
dstPath,
`${dstFileSuffix}(${renameIndex})${dstFileExt}`
);
}
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 filesBbs = new FilesBBSFile();
const isBadDescription = (desc) => {
const isBadDescription = desc => {
return IgnoredDescriptions.find(d => desc.startsWith(d)) ? true : false;
};
@ -59,9 +59,7 @@ module.exports = class FilesBBSFile {
const detectDecoder = () => {
// helpers
const regExpTestUpTo = (n, re) => {
return lines
.slice(0, n)
.some(l => re.test(l));
return lines.slice(0, n).some(l => re.test(l));
};
//
@ -70,7 +68,8 @@ module.exports = class FilesBBSFile {
const decoders = [
{
// 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 () {
return regExpTestUpTo(10, this.lineRegExp);
},
@ -99,7 +98,7 @@ module.exports = class FilesBBSFile {
}
filesBbs.entries.set(fileName, { timestamp, desc });
}
}
},
},
{
@ -122,7 +121,11 @@ module.exports = class FilesBBSFile {
for (let j = i + 1; j < lines.length; ++j) {
line = lines[j];
// -------------------------------------------------v 32
if(!line.startsWith(' | ')) {
if (
!line.startsWith(
' | '
)
) {
break;
}
long.push(line.substr(33));
@ -137,7 +140,7 @@ module.exports = class FilesBBSFile {
filesBbs.entries.set(fileName, { desc });
}
}
},
},
{
@ -177,7 +180,7 @@ module.exports = class FilesBBSFile {
filesBbs.entries.set(fileName, { desc });
}
}
},
},
{
@ -187,7 +190,8 @@ module.exports = class FilesBBSFile {
// Examples:
// - 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 () {
return regExpTestUpTo(10, this.lineRegExp);
},
@ -215,13 +219,17 @@ module.exports = class FilesBBSFile {
const size = parseInt(hdr[2]);
const timestamp = moment(hdr[3], 'MM-DD-YY');
if(isBadDescription(desc) || isNaN(size) || !timestamp.isValid()) {
if (
isBadDescription(desc) ||
isNaN(size) ||
!timestamp.isValid()
) {
continue;
}
filesBbs.entries.set(fileName, { desc, size, timestamp });
}
}
},
},
{
@ -253,7 +261,7 @@ module.exports = class FilesBBSFile {
filesBbs.entries.set(fileName, { desc });
}
});
}
},
},
{
@ -281,11 +289,12 @@ module.exports = class FilesBBSFile {
}
size *= 1024; // K->bytes.
if(desc) { // omit empty entries
if (desc) {
// omit empty entries
filesBbs.entries.set(fileName, { size, desc });
}
});
}
},
},
];
@ -301,11 +310,11 @@ module.exports = class FilesBBSFile {
decoder.extract(decoder);
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
);
});
}
};

View File

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

View File

@ -7,24 +7,14 @@ const { ViewController } = require('./view_controller.js');
const ansi = require('./ansi_term.js');
const theme = require('./theme.js');
const Message = require('./message.js');
const {
updateMessageAreaLastReadId
} = require('./message_area.js');
const { updateMessageAreaLastReadId } = require('./message_area.js');
const { getMessageAreaByTag } = require('./message_area.js');
const User = require('./user.js');
const StatLog = require('./stat_log.js');
const stringFormat = require('./string_format.js');
const {
MessageAreaConfTempSwitcher
} = require('./mod_mixins.js');
const {
isAnsi, stripAnsiControlCodes,
insert
} = require('./string_util.js');
const {
stripMciColorCodes,
controlCodesToAnsi,
} = require('./color_codes.js');
const { MessageAreaConfTempSwitcher } = require('./mod_mixins.js');
const { isAnsi, stripAnsiControlCodes, insert } = require('./string_util.js');
const { stripMciColorCodes, controlCodesToAnsi } = require('./color_codes.js');
const Config = require('./config.js').get;
const { getAddressedToInfo } = require('./mail_util.js');
const Events = require('./events.js');
@ -80,7 +70,7 @@ const MciViewIds = {
quotedMsg: 1,
// 2 NYI
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
exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModule extends MessageAreaConfTempSwitcher(MenuModule) {
exports.FullScreenEditorModule =
exports.getModule = class FullScreenEditorModule extends (
MessageAreaConfTempSwitcher(MenuModule)
) {
constructor(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;
@ -164,7 +161,9 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
// Validation stuff
//
viewValidationListener: function (err, cb) {
var errMsgView = self.viewControllers.header.getView(MciViewIds.header.errorMsg);
var errMsgView = self.viewControllers.header.getView(
MciViewIds.header.errorMsg
);
var newFocusViewId;
if (errMsgView) {
if (err) {
@ -184,7 +183,8 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
return cb(null);
},
editModeEscPressed: function (formData, extraArgs, cb) {
self.footerMode = 'editor' === self.footerMode ? 'editorMenu' : 'editor';
self.footerMode =
'editor' === self.footerMode ? 'editorMenu' : 'editor';
self.switchFooter(function next(err) {
if (err) {
@ -193,7 +193,9 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
switch (self.footerMode) {
case 'editor':
if(!_.isUndefined(self.viewControllers.footerEditorMenu)) {
if (
!_.isUndefined(self.viewControllers.footerEditorMenu)
) {
self.viewControllers.footerEditorMenu.detachClientEvents();
}
self.viewControllers.body.switchFocus(1);
@ -205,7 +207,8 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
self.viewControllers.footerEditorMenu.switchFocus(1);
break;
default : throw new Error('Unexpected mode');
default:
throw new Error('Unexpected mode');
}
return cb(null);
@ -217,7 +220,9 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
return cb(null);
},
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) {
self.newQuoteBlock = false;
@ -227,7 +232,9 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
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);
quoteMsgView.addText(quoteText);
@ -310,8 +317,11 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
getHeaderFormatObj() {
const remoteUserNotAvail = this.menuConfig.config.remoteUserNotAvail || 'N/A';
const localUserIdNotAvail = this.menuConfig.config.localUserIdNotAvail || 'N/A';
const modTimestampFormat = this.menuConfig.config.modTimestampFormat || this.client.currentTheme.helpers.getDateTimeFormat();
const localUserIdNotAvail =
this.menuConfig.config.localUserIdNotAvail || 'N/A';
const modTimestampFormat =
this.menuConfig.config.modTimestampFormat ||
this.client.currentTheme.helpers.getDateTimeFormat();
return {
// :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:
//fromRealName
//toRealName
fromUserId : _.get(this.message, 'meta.System.local_from_user_id', 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),
fromUserId: _.get(
this.message,
'meta.System.local_from_user_id',
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,
modTimestamp: this.message.modTimestamp.format(modTimestampFormat),
msgNum: this.messageIndex + 1,
@ -334,8 +360,12 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
setInitialFooterMode() {
switch (this.editorMode) {
case 'edit' : this.footerMode = 'editor'; break;
case 'view' : this.footerMode = 'view'; break;
case 'edit':
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 getFromUserName = () => {
return (area && area.realNames) ?
this.client.user.getProperty(UserProps.RealName) || this.client.user.username :
this.client.user.username;
return area && area.realNames
? this.client.user.getProperty(UserProps.RealName) ||
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 = {
areaTag: this.messageAreaTag,
@ -368,8 +401,19 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
// to packetAnsiMsgEncoding (generally cp437) as various boards
// really don't like ANSI messages in UTF-8 encoding (they should!)
//
msgOpts.meta = { 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}`;
msgOpts.meta = {
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(
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.initFooterViewMode();
const bodyMessageView = this.viewControllers.body.getView(MciViewIds.body.message);
const bodyMessageView = this.viewControllers.body.getView(
MciViewIds.body.message
);
let msg = this.message.message;
if (bodyMessageView && _.has(this, 'message.message')) {
@ -424,7 +473,11 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
const tearLinePos = Message.getTearLinePosition(msg);
if (tearLinePos > -1) {
msg = insert(msg, tearLinePos, bodyMessageView.getSGRFor('text'));
msg = insert(
msg,
tearLinePos,
bodyMessageView.getSGRFor('text')
);
}
bodyMessageView.setAnsi(
@ -456,17 +509,27 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
if (this.menuConfig.config.quoteStyleLevel1) {
// 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
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;
msg = msg.replace(QuoteRegex, (m, spc1, initials, spc2, text) => {
const QuoteRegex =
/^([ ]?)([!-~]{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}`;
});
}
);
}
if (this.menuConfig.config.tearLineStyle) {
// '---' and TEXT
const style = styleToArray(this.menuConfig.config.tearLineStyle, 2);
const style = styleToArray(
this.menuConfig.config.tearLineStyle,
2
);
const TearLineRegex = /^--- (.+)$(?![\s\S]*^--- .+$)/m;
msg = msg.replace(TearLineRegex, (m, text) => {
@ -475,7 +538,10 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
}
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;
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
// for export with the remote to address.
//
if(self.replyToMessage && self.replyToMessage.isFromRemoteUser()) {
self.message.setRemoteToUser(self.replyToMessage.meta.System[Message.SystemMetaNames.RemoteFromUser]);
self.message.setExternalFlavor(self.replyToMessage.meta.System[Message.SystemMetaNames.ExternalFlavor]);
if (
self.replyToMessage &&
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);
}
@ -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
//
// :TODO: how to plug in support without tying to various types here? isSupportedExteranlType() or such
const addressedToInfo = getAddressedToInfo(self.message.toUserName);
if(addressedToInfo.name && Message.AddressFlavor.FTN === addressedToInfo.flavor) {
const addressedToInfo = getAddressedToInfo(
self.message.toUserName
);
if (
addressedToInfo.name &&
Message.AddressFlavor.FTN === addressedToInfo.flavor
) {
self.message.setRemoteToUser(addressedToInfo.remote);
self.message.setExternalFlavor(addressedToInfo.flavor);
self.message.toUserName = addressedToInfo.name;
@ -538,15 +620,18 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
}
// we need to look it up
User.getUserIdAndNameByLookup(self.message.toUserName, (err, toUserId) => {
User.getUserIdAndNameByLookup(
self.message.toUserName,
(err, toUserId) => {
if (err) {
return callback(err);
}
self.message.setLocalToUserId(toUserId);
return callback(null);
});
}
);
},
],
err => {
return cb(err, self.message);
@ -556,18 +641,28 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
updateUserAndSystemStats(cb) {
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) {
cb(null);
}
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.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) {
@ -603,12 +698,15 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
theme.displayThemedAsset(
footerArt,
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) {
callback(err, artData);
}
);
}
},
],
function complete(err, artData) {
cb(err, artData);
@ -642,12 +740,15 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
theme.displayThemedAsset(
art['header'],
self.client,
{ font : self.menuConfig.font, startRow: artInfo.height + 1 },
{
font: self.menuConfig.font,
startRow: artInfo.height + 1,
},
function displayed(err, artInfo) {
return callback(err, artInfo);
}
);
}
},
],
function complete(err) {
//self.body.height = self.client.term.termHeight - self.header.height - 1;
@ -657,9 +758,12 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
},
function displayFooter(callback) {
// 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);
});
}
);
},
function refreshViews(callback) {
comps.push(self.getFooterName());
@ -669,7 +773,7 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
});
callback(null);
}
},
],
function complete(err) {
cb(err);
@ -692,7 +796,7 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
var menuLoadOpts = {
callingMenu: this,
formId: formId,
mciMap : artData.mciMap
mciMap: artData.mciMap,
};
this.addViewController(
@ -742,24 +846,30 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
function displayed(err, artInfo) {
if (artInfo) {
mciData['body'] = artInfo;
self.body = {height: artInfo.height - self.header.height};
self.body = {
height: artInfo.height - self.header.height,
};
}
return callback(err, artInfo);
});
}
);
},
function displayFooter(artInfo, callback) {
self.setInitialFooterMode();
var footerName = self.getFooterName();
self.redrawFooter( { footerName : footerName }, function artDisplayed(err, artData) {
self.redrawFooter(
{ footerName: footerName },
function artDisplayed(err, artData) {
mciData[footerName] = artData;
callback(err);
});
}
);
},
function afterArtDisplayed(callback) {
self.mciReady(mciData, callback);
}
},
],
function complete(err) {
if (err) {
@ -784,7 +894,10 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
self.addViewController(
'header',
new ViewController( { client : self.client, formId : menuLoadOpts.formId } )
new ViewController({
client: self.client,
formId: menuLoadOpts.formId,
})
).loadFromMenuConfig(menuLoadOpts, function headerReady(err) {
callback(err);
});
@ -795,7 +908,10 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
self.addViewController(
'body',
new ViewController( { client : self.client, formId : menuLoadOpts.formId } )
new ViewController({
client: self.client,
formId: menuLoadOpts.formId,
})
).loadFromMenuConfig(menuLoadOpts, function bodyReady(err) {
callback(err);
});
@ -808,19 +924,26 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
self.addViewController(
footerName,
new ViewController( { client : self.client, formId : menuLoadOpts.formId } )
new ViewController({
client: self.client,
formId: menuLoadOpts.formId,
})
).loadFromMenuConfig(menuLoadOpts, function footerReady(err) {
callback(err);
});
},
function prepareViewStates(callback) {
let from = self.viewControllers.header.getView(MciViewIds.header.from);
let from = self.viewControllers.header.getView(
MciViewIds.header.from
);
if (from) {
from.acceptsFocus = false;
}
// :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.updateEditModePosition(body.getEditPosition());
@ -829,28 +952,41 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
callback(null);
},
function setInitialData(callback) {
switch (self.editorMode) {
case 'view':
if (self.message) {
self.initHeaderViewMode();
self.initFooterViewMode();
var bodyMessageView = self.viewControllers.body.getView(MciViewIds.body.message);
if(bodyMessageView && _.has(self, 'message.message')) {
var bodyMessageView =
self.viewControllers.body.getView(
MciViewIds.body.message
);
if (
bodyMessageView &&
_.has(self, 'message.message')
) {
//self.setBodyMessageViewText();
bodyMessageView.setText(stripAnsiControlCodes(self.message.message));
bodyMessageView.setText(
stripAnsiControlCodes(self.message.message)
);
}
}
break;
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);
if (fromView !== undefined) {
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 {
fromView.setText(self.client.user.username);
}
@ -866,7 +1002,6 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
callback(null);
},
function setInitialFocus(callback) {
switch (self.editorMode) {
case 'edit':
self.switchToHeader();
@ -879,7 +1014,7 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
}
callback(null);
}
},
],
function complete(err) {
return cb(err);
@ -888,7 +1023,6 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
}
mciReadyHandler(mciData, cb) {
this.createInitialViews(mciData, err => {
// :TODO: Can probably be replaced with @systemMethod:validateUserNameExists when the framework is in
// place - if this is for existing usernames else validate spec
@ -917,7 +1051,11 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
if (posView) {
this.client.term.rawWrite(ansi.savePos());
// :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());
}
}
@ -940,20 +1078,33 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
initHeaderViewMode() {
// 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.to, this.message.toUserName);
this.setHeaderText(MciViewIds.header.subject, this.message.subject);
this.setHeaderText(MciViewIds.header.modTimestamp, moment(this.message.modTimestamp).format(
this.menuConfig.config.modTimestampFormat || this.client.currentTheme.helpers.getDateTimeFormat())
this.setHeaderText(
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.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
this.refreshPredefinedMciViewsByCode('header', ['MA', 'MC', 'ML', 'CM']);
@ -977,8 +1128,16 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
}
initFooterViewMode() {
this.setViewText('footerView', MciViewIds.ViewModeFooter.msgNum, (this.messageIndex + 1).toString() );
this.setViewText('footerView', MciViewIds.ViewModeFooter.msgTotal, this.messageTotal.toString() );
this.setViewText(
'footerView',
MciViewIds.ViewModeFooter.msgNum,
(this.messageIndex + 1).toString()
);
this.setViewText(
'footerView',
MciViewIds.ViewModeFooter.msgTotal,
this.messageTotal.toString()
);
}
displayHelp(cb) {
@ -998,25 +1157,32 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
}
addToDownloadQueue(cb) {
const sysTempDownloadArea = FileArea.getFileAreaByTag(FileArea.WellKnownAreaTags.TempDownloads);
const sysTempDownloadDir = FileArea.getAreaDefaultStorageDirectory(sysTempDownloadArea);
const sysTempDownloadArea = FileArea.getFileAreaByTag(
FileArea.WellKnownAreaTags.TempDownloads
);
const sysTempDownloadDir =
FileArea.getAreaDefaultStorageDirectory(sysTempDownloadArea);
const msgInfo = this.getHeaderFormatObj();
const outputFileName = paths.join(
sysTempDownloadDir,
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(
[
(callback) => {
const header =
`+${'-'.repeat(79)}
callback => {
const header = `+${'-'.repeat(79)}
| To : ${msgInfo.toUserName}
| 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}
| ID : ${this.message.messageUuid} (${msgInfo.messageId})
+${'-'.repeat(79)}
@ -1036,9 +1202,14 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
});
},
(exportedMessage, callback) => {
return fs.writeFile(outputFileName, exportedMessage, 'utf8', callback);
return fs.writeFile(
outputFileName,
exportedMessage,
'utf8',
callback
);
},
(callback) => {
callback => {
fs.stat(outputFileName, (err, stats) => {
return callback(err, stats.size);
});
@ -1053,7 +1224,7 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
upload_by_user_id: this.client.user.userId,
byte_size: fileSize,
session_temp_dl: 1, // download is valid until session is over
}
},
});
newEntry.desc = `${msgInfo.messageId} - ${msgInfo.subject}`;
@ -1061,27 +1232,30 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
newEntry.persist(err => {
if (!err) {
// queue it!
DownloadQueue.get(this.client).addTemporaryDownload(newEntry);
DownloadQueue.get(this.client).addTemporaryDownload(
newEntry
);
}
return callback(err);
});
},
(callback) => {
const artSpec = this.menuConfig.config.art.expToDlQueue ||
Buffer.from('Exported message has been added to your download queue!');
this.displayAsset(
artSpec,
{ clearScreen : true },
() => {
callback => {
const artSpec =
this.menuConfig.config.art.expToDlQueue ||
Buffer.from(
'Exported message has been added to your download queue!'
);
this.displayAsset(artSpec, { clearScreen: true }, () => {
this.client.waitForKeyPress(() => {
this.redrawScreen(() => {
this.viewControllers[this.getFooterName()].setFocus(true);
this.viewControllers[this.getFooterName()].setFocus(
true
);
return callback(null);
});
});
}
);
}
});
},
],
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:
self.client.term.rawWrite(
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);
});
}
);
},
function createViewsIfNecessary(artData, callback) {
var formId = self.getFormId('quoteBuilder');
@ -1120,18 +1303,28 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
self.addViewController(
'quoteBuilder',
new ViewController( { client : self.client, formId : formId } )
).loadFromMenuConfig(menuLoadOpts, function quoteViewsReady(err) {
new ViewController({
client: self.client,
formId: formId,
})
).loadFromMenuConfig(
menuLoadOpts,
function quoteViewsReady(err) {
callback(err);
});
}
);
} else {
self.viewControllers.quoteBuilder.redrawAll();
callback(null);
}
},
function loadQuoteLines(callback) {
const quoteView = self.viewControllers.quoteBuilder.getView(MciViewIds.quoteBuilder.quoteLines);
const bodyView = self.viewControllers.body.getView(MciViewIds.body.message);
const quoteView = self.viewControllers.quoteBuilder.getView(
MciViewIds.quoteBuilder.quoteLines
);
const bodyView = self.viewControllers.body.getView(
MciViewIds.body.message
);
self.replyToMessage.getQuoteLines(
{
@ -1152,8 +1345,12 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
quoteView.setItems(quoteLines);
quoteView.setFocusItems(focusQuoteLines);
self.viewControllers.quoteBuilder.getView(MciViewIds.quoteBuilder.quotedMsg).setFocus(false);
self.viewControllers.quoteBuilder.switchFocus(MciViewIds.quoteBuilder.quoteLines);
self.viewControllers.quoteBuilder
.getView(MciViewIds.quoteBuilder.quotedMsg)
.setFocus(false);
self.viewControllers.quoteBuilder.switchFocus(
MciViewIds.quoteBuilder.quoteLines
);
return callback(null);
}
@ -1162,7 +1359,10 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
],
function complete(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() {
// :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);
let quoteLines = quoteMsgView.getData().trim();
if (quoteLines.length > 0) {
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')}`;
}
msgView.addText(`${quoteLines}\n\n`);
@ -1254,7 +1458,9 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
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, {
dateTime: moment(this.replyToMessage.modTimestamp).format(dtFormat),
userName: this.replyToMessage.fromUserName,

View File

@ -4,7 +4,8 @@
const _ = require('lodash');
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 {
constructor(addr) {

View File

@ -53,7 +53,8 @@ class PacketHeader {
this.prodData = 0x47694e45; // "ENiG"
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.prodRevHi = 0;
@ -133,7 +134,7 @@ class PacketHeader {
date: this.day,
hour: this.hour,
minute: this.minute,
second : this.second
second: this.second,
});
}
@ -249,10 +250,17 @@ function Packet(options) {
}
// 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) {
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 >> 8) & 0xff);
if(capWordValidateSwapped === packetHeader.capWord &&
if (
capWordValidateSwapped === packetHeader.capWord &&
0 != packetHeader.capWord &&
packetHeader.capWord & 0x0001)
{
packetHeader.capWord & 0x0001
) {
packetHeader.version = '2+';
// See FSC-0048
@ -299,7 +308,7 @@ function Packet(options) {
date: packetHeader.day,
hour: packetHeader.hour,
minute: packetHeader.minute,
second : packetHeader.second
second: packetHeader.second,
});
const ph = new PacketHeader();
@ -322,7 +331,10 @@ function Packet(options) {
buffer.writeUInt16LE(packetHeader.baud, 16);
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.writeUInt8(packetHeader.prodCodeLo, 24);
buffer.writeUInt8(packetHeader.prodRevHi, 25);
@ -360,7 +372,10 @@ function Packet(options) {
buffer.writeUInt16LE(packetHeader.baud, 16);
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.writeUInt8(packetHeader.prodCodeLo, 24);
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
// 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*
const sauceHeaderPosition = messageBodyBuffer.indexOf(FTN_MESSAGE_SAUCE_HEADER);
const sauceHeaderPosition = messageBodyBuffer.indexOf(
FTN_MESSAGE_SAUCE_HEADER
);
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) {
// 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);
messageBodyData.sauce = theSauce;
} 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
});
}
);
} else {
callback(null);
}
@ -483,7 +513,9 @@ function Packet(options) {
// Also according to the spec, the deprecated "CHARSET" value may be used
// :TODO: Look into CHARSET more - should we bother supporting it?
// :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]);
let chrsPrefixIndex = messageBodyBuffer.indexOf(FTN_CHRS_PREFIX);
@ -493,18 +525,25 @@ function Packet(options) {
chrsPrefixIndex += FTN_CHRS_PREFIX.length;
const chrsEndIndex = messageBodyBuffer.indexOf(FTN_CHRS_SUFFIX, chrsPrefixIndex);
const chrsEndIndex = messageBodyBuffer.indexOf(
FTN_CHRS_SUFFIX,
chrsPrefixIndex
);
if (chrsEndIndex < 0) {
return callback(null);
}
let chrsContent = messageBodyBuffer.slice(chrsPrefixIndex, chrsEndIndex);
let chrsContent = messageBodyBuffer.slice(
chrsPrefixIndex,
chrsEndIndex
);
if (0 === chrsContent.length) {
return callback(null);
}
chrsContent = iconv.decode(chrsContent, 'CP437');
const chrsEncoding = ftn.getEncodingFromCharacterSetIdentifier(chrsContent);
const chrsEncoding =
ftn.getEncodingFromCharacterSetIdentifier(chrsContent);
if (chrsEncoding) {
encoding = chrsEncoding;
}
@ -519,11 +558,16 @@ function Packet(options) {
try {
decoded = iconv.decode(messageBodyBuffer, encoding);
} 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');
}
const messageLines = strUtil.splitTextAtTerms(decoded.replace(/\xec/g, ''));
const messageLines = strUtil.splitTextAtTerms(
decoded.replace(/\xec/g, '')
);
let endOfMessage = false;
messageLines.forEach(line => {
@ -533,16 +577,21 @@ function Packet(options) {
}
if (line.startsWith('AREA:')) {
messageBodyData.area = line.substring(line.indexOf(':') + 1).trim();
messageBodyData.area = line
.substring(line.indexOf(':') + 1)
.trim();
} else if (line.startsWith('--- ')) {
// Tear Lines are tracked allowing for specialized display/etc.
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;
endOfMessage = true; // Anything past origin is not part of the message body
} else if (line.startsWith('SEEN-BY:')) {
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)) {
if ('PATH:' === line.slice(1, 6)) {
endOfMessage = true; // Anything pats the first PATH is not part of the message body
@ -555,7 +604,7 @@ function Packet(options) {
});
return callback(null);
}
},
],
() => {
messageBodyData.message = messageBodyData.message.join('\n');
@ -571,7 +620,10 @@ function Packet(options) {
//
if (packetBuffer.length < 3) {
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
return cb(null);
}
@ -586,7 +638,9 @@ function Packet(options) {
}
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.
//
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) {
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) {
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) {
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
@ -705,10 +775,14 @@ function Packet(options) {
// :TODO: Parser should give is this info:
const bytesRead =
14 + // fixed header size
msgData.modDateTime.length + 1 + // +1 = NULL
msgData.toUserName.length + 1 + // +1 = NULL
msgData.fromUserName.length + 1 + // +1 = NULL
msgData.subject.length + 1 + // +1 = NULL
msgData.modDateTime.length +
1 + // +1 = NULL
msgData.toUserName.length +
1 + // +1 = NULL
msgData.fromUserName.length +
1 + // +1 = NULL
msgData.subject.length +
1 + // +1 = NULL
msgData.message.length; // includes NULL
const nextBuf = packetBuffer.slice(bytesRead);
@ -747,7 +821,8 @@ function Packet(options) {
Message.FtnPropertyNames.FtnMsgDestNet,
].forEach(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
self.sanatizeFtnProperties(message);
const destNode = 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;
const destNode =
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(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_cost, 12);
const dateTimeBuffer = Buffer.from(ftn.getDateTimeString(message.modTimestamp) + '\0');
const dateTimeBuffer = Buffer.from(
ftn.getDateTimeString(message.modTimestamp) + '\0'
);
dateTimeBuffer.copy(buf, 14);
};
this.getMessageEntryBuffer = function (message, options, cb) {
function getAppendMeta(k, m, sepChar = ':') {
let append = '';
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.
//
const toUserNameBuf = strUtil.stringToNullTermBuffer(message.toUserName, { encoding : 'cp437', maxBufLen : 36 } );
const fromUserNameBuf = strUtil.stringToNullTermBuffer(message.fromUserName, { encoding : 'cp437', maxBufLen : 36 } );
const subjectBuf = strUtil.stringToNullTermBuffer(message.subject, { encoding : 'cp437', maxBufLen : 72 } );
const toUserNameBuf = strUtil.stringToNullTermBuffer(
message.toUserName,
{ 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
@ -827,20 +916,49 @@ function Packet(options) {
case 'FMPT':
case 'TOPT':
case 'INTL':
msgBody += getAppendMeta(`\x01${k}`, message.meta.FtnKludge[k], ''); // no sepChar
msgBody += getAppendMeta(
`\x01${k}`,
message.meta.FtnKludge[k],
''
); // no sepChar
break;
default:
msgBody += getAppendMeta(`\x01${k}`, message.meta.FtnKludge[k]);
msgBody += getAppendMeta(
`\x01${k}`,
message.meta.FtnKludge[k]
);
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)) {
return callback(null, basicHeader, toUserNameBuf, fromUserNameBuf, subjectBuf, msgBody, message.message);
return callback(
null,
basicHeader,
toUserNameBuf,
fromUserNameBuf,
subjectBuf,
msgBody,
message.message
);
}
ansiPrep(
@ -852,11 +970,27 @@ function Packet(options) {
exportMode: true,
},
(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';
//
@ -878,7 +1012,10 @@ function Packet(options) {
// FTN-0004.001 @ http://ftsc.org/docs/fts-0004.001
// 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']);
let msgBodyEncoded;
@ -895,10 +1032,10 @@ function Packet(options) {
toUserNameBuf,
fromUserNameBuf,
subjectBuf,
msgBodyEncoded
msgBodyEncoded,
])
);
}
},
],
(err, msgEntryBuffer) => {
return cb(err, msgEntryBuffer);
@ -959,14 +1096,19 @@ function Packet(options) {
Object.keys(message.meta.FtnKludge).forEach(k => {
switch (k) {
case 'PATH' : break; // skip & save for last
case 'PATH':
break; // skip & save for last
case 'Via':
case 'FMPT':
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,
packetBuffer.slice(FTN_PACKET_HEADER_SIZE),
iterator,
callback);
}
callback
);
},
],
cb // complete
);
@ -1075,7 +1218,7 @@ Packet.prototype.read = function(pathOrBuffer, iterator, cb) {
self.parsePacketBuffer(pathOrBuffer, iterator, err => {
callback(err);
});
}
},
],
err => {
cb(err);

View File

@ -93,7 +93,7 @@ function getDateTimeString(m) {
}
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);
return `00000000${hash}`.substr(-8);
}
@ -143,10 +143,13 @@ function getMessageSerialNumber(messageId) {
//
function getMessageIdentifier(message, address, isNetMail = false) {
const addrStr = new Address(address).toString('5D');
return isNetMail ?
`${addrStr} ${getMessageSerialNumber(message.messageId)}` :
`${message.messageId}.${message.areaTag.toLowerCase()}@${addrStr} ${getMessageSerialNumber(message.messageId)}`
;
return isNetMail
? `${addrStr} ${getMessageSerialNumber(message.messageId)}`
: `${
message.messageId
}.${message.areaTag.toLowerCase()}@${addrStr} ${getMessageSerialNumber(
message.messageId
)}`;
}
//
@ -183,7 +186,10 @@ function getQuotePrefix(name) {
const parts = name.split(' ');
if (parts.length > 1) {
// 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 {
// Just use the first two - (NuSkooler -> Nu)
initials = _.capitalize(name.slice(0, 2));
@ -198,9 +204,9 @@ function getQuotePrefix(name) {
//
function getOrigin(address) {
const config = Config();
const origin = _.has(config, 'messageNetworks.originLine') ?
config.messageNetworks.originLine :
config.general.boardName;
const origin = _.has(config, 'messageNetworks.originLine')
? config.messageNetworks.originLine
: config.general.boardName;
const addrStr = new Address(address).toString('5D');
return ` * Origin: ${origin} (${addrStr})`;
@ -208,7 +214,9 @@ function getOrigin(address) {
function getTearLine() {
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.push(getAbbreviatedNetNodeList(
parseAbbreviatedNetNodeList(localAddress)));
existingEntries.push(
getAbbreviatedNetNodeList(parseAbbreviatedNetNodeList(localAddress))
);
return existingEntries;
}
@ -368,7 +377,6 @@ const ENCODING_TO_FTS_5003_001_CHARS = {
'utf-8': ['UTF-8', 4],
};
function getCharacterSetIdentifierByEncoding(encodingName) {
const value = ENCODING_TO_FTS_5003_001_CHARS[encodingName.toLowerCase()];
return value ? `${value[0]} ${value[1]}` : encodingName.toUpperCase();
@ -376,31 +384,31 @@ function getCharacterSetIdentifierByEncoding(encodingName) {
const CHRSToEncodingTable = {
Level1: {
'ASCII' : 'ascii', // ISO-646-1
'DUTCH' : 'ascii', // ISO-646
'FINNISH' : 'ascii', // ISO-646-10
'FRENCH' : 'ascii', // ISO-646
'CANADIAN' : 'ascii', // ISO-646
'GERMAN' : 'ascii', // ISO-646
'ITALIAN' : 'ascii', // ISO-646
'NORWEIG' : 'ascii', // ISO-646
'PORTU' : 'ascii', // ISO-646
'SPANISH' : 'iso-656',
'SWEDISH' : 'ascii', // ISO-646-10
'SWISS' : 'ascii', // ISO-646
'UK' : 'ascii', // ISO-646
ASCII: 'ascii', // ISO-646-1
DUTCH: 'ascii', // ISO-646
FINNISH: 'ascii', // ISO-646-10
FRENCH: 'ascii', // ISO-646
CANADIAN: 'ascii', // ISO-646
GERMAN: 'ascii', // ISO-646
ITALIAN: 'ascii', // ISO-646
NORWEIG: 'ascii', // ISO-646
PORTU: 'ascii', // ISO-646
SPANISH: 'iso-656',
SWEDISH: 'ascii', // ISO-646-10
SWISS: 'ascii', // ISO-646
UK: 'ascii', // ISO-646
'ISO-10': 'ascii', // ISO-646-10
},
Level2: {
'CP437' : 'cp437',
'CP850' : 'cp850',
'CP852' : 'cp852',
'CP866' : 'cp866',
'CP848' : 'cp848',
'CP1250' : 'cp1250',
'CP1251' : 'cp1251',
'CP1252' : 'cp1252',
'CP10000' : 'macroman',
CP437: 'cp437',
CP850: 'cp850',
CP852: 'cp852',
CP866: 'cp866',
CP848: 'cp848',
CP1250: 'cp1250',
CP1251: 'cp1251',
CP1252: 'cp1252',
CP10000: 'macroman',
'LATIN-1': 'iso-8859-1',
'LATIN-2': 'iso-8859-2',
'LATIN-5': 'iso-8859-9',
@ -412,11 +420,11 @@ const CHRSToEncodingTable = {
},
DeprecatedMisc: {
'IBMPC' : 'cp1250', // :TODO: validate
IBMPC: 'cp1250', // :TODO: validate
'+7_FIDO': '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

View File

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

View File

@ -60,17 +60,36 @@ function HorizontalMenuView(options) {
text = focusItem ? focusItem.text : item.text;
sgr = '';
} else if (this.complexItems) {
text = pipeToAnsi(formatString(item.focused && this.focusItemFormat ? this.focusItemFormat : this.itemFormat, item));
sgr = this.focusItemFormat ? '' : (index === self.focusedItemIndex ? self.getFocusSGR() : self.getSGR());
text = pipeToAnsi(
formatString(
item.focused && this.focusItemFormat
? this.focusItemFormat
: this.itemFormat,
item
)
);
sgr = this.focusItemFormat
? ''
: index === self.focusedItemIndex
? self.getFocusSGR()
: self.getSGR();
} else {
text = strUtil.stylizeString(item.text, item.focused ? self.focusTextStyle : self.textStyle);
sgr = (index === self.focusedItemIndex ? self.getFocusSGR() : self.getSGR());
text = strUtil.stylizeString(
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(
`${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 () {
if (0 === this.focusedItemIndex) {
this.focusedItemIndex = this.items.length - 1;
} else {

View File

@ -34,7 +34,11 @@ module.exports = class KeyEntryView extends View {
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.client.term.write(stylizeString(ch, this.textStyle));
}
@ -73,5 +77,7 @@ module.exports = class KeyEntryView extends View {
super.setPropertyValue(propName, propValue);
}
getData() { return this.keyEntered; }
getData() {
return this.keyEntered;
}
};

View File

@ -19,7 +19,7 @@ exports.moduleInfo = {
name: 'Last Callers',
desc: 'Last callers to the system',
author: 'NuSkooler',
packageName : 'codes.l33t.enigma.lastcallers'
packageName: 'codes.l33t.enigma.lastcallers',
};
const MciViewIds = {
@ -31,7 +31,11 @@ exports.getModule = class LastCallersModule extends MenuModule {
super(options);
this.actionIndicators = _.get(options, 'menuConfig.config.actionIndicators', {});
this.actionIndicatorDefault = _.get(options, 'menuConfig.config.actionIndicatorDefault', '-');
this.actionIndicatorDefault = _.get(
options,
'menuConfig.config.actionIndicatorDefault',
'-'
);
}
mciReady(mciData, cb) {
@ -42,34 +46,46 @@ exports.getModule = class LastCallersModule extends MenuModule {
async.waterfall(
[
(callback) => {
callback => {
this.prepViewController('callers', 0, mciData.menu, err => {
return callback(err);
});
},
(callback) => {
callback => {
this.fetchHistory((err, loginHistory) => {
return callback(err, loginHistory);
});
},
(loginHistory, callback) => {
this.loadUserForHistoryItems(loginHistory, (err, updatedHistory) => {
this.loadUserForHistoryItems(
loginHistory,
(err, updatedHistory) => {
return callback(err, updatedHistory);
});
}
);
},
(loginHistory, callback) => {
const callersView = this.viewControllers.callers.getView(MciViewIds.callerList);
const callersView = this.viewControllers.callers.getView(
MciViewIds.callerList
);
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.redraw();
return callback(null);
}
},
],
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);
}
@ -79,7 +95,9 @@ exports.getModule = class LastCallersModule extends MenuModule {
getCollapse(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) {
return moment.duration(parseInt(collapse[1]), collapse[2]);
}
@ -101,7 +119,10 @@ exports.getModule = class LastCallersModule extends MenuModule {
}
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 => {
try {
@ -119,25 +140,29 @@ exports.getModule = class LastCallersModule extends MenuModule {
item.timestamp = moment(item.timestamp);
return Object.assign(
item,
{
ts : moment(item.timestamp).format(dateTimeFormat)
}
);
return Object.assign(item, {
ts: moment(item.timestamp).format(dateTimeFormat),
});
});
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) => {
let lastUserId;
let lastTimestamp;
loginHistory = loginHistory.filter(item => {
const secApart = lastTimestamp ? moment.duration(lastTimestamp.diff(item.timestamp)).asSeconds() : 0;
const collapse = (null === withUserId ? true : withUserId === item.userId) &&
(lastUserId === item.userId) &&
(secApart < minAge);
const secApart = lastTimestamp
? moment
.duration(lastTimestamp.diff(item.timestamp))
.asSeconds()
: 0;
const collapse =
(null === withUserId ? true : withUserId === item.userId) &&
lastUserId === item.userId &&
secApart < minAge;
lastUserId = item.userId;
lastTimestamp = item.timestamp;
@ -147,7 +172,9 @@ exports.getModule = class LastCallersModule extends MenuModule {
};
if (hideSysOp) {
loginHistory = loginHistory.filter(item => false === User.isRootUserId(item.userId));
loginHistory = loginHistory.filter(
item => false === User.isRootUserId(item.userId)
);
} else if (sysOpCollapse) {
collapseList(User.RootUserID, sysOpCollapse.asSeconds());
}
@ -167,18 +194,22 @@ exports.getModule = class LastCallersModule extends MenuModule {
loadUserForHistoryItems(loginHistory, cb) {
const getPropOpts = {
names : [ UserProps.RealName, UserProps.Location, UserProps.Affiliations ]
names: [UserProps.RealName, UserProps.Location, UserProps.Affiliations],
};
const actionIndicatorNames = _.map(this.actionIndicators, (v, k) => k);
let indicatorSumsSql;
if (actionIndicatorNames.length > 0) {
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) => {
if (err) {
return nextHistoryItem(null, null);
@ -188,7 +219,8 @@ exports.getModule = class LastCallersModule extends MenuModule {
User.loadProperties(item.userId, getPropOpts, (err, props) => {
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]) || '';
if (!indicatorSumsSql) {
@ -205,7 +237,11 @@ exports.getModule = class LastCallersModule extends MenuModule {
if (_.isObject(results)) {
item.actions = '';
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.actions += indicator;
});
@ -217,7 +253,11 @@ exports.getModule = class LastCallersModule extends MenuModule {
});
},
(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) {
const moduleUtil = require('./module_util.js'); // late load so we get Config
async.each( [ 'login', 'content', 'chat' ], (category, next) => {
moduleUtil.loadModulesForCategory(`${category}Servers`, (module, nextModule) => {
async.each(
['login', 'content', 'chat'],
(category, next) => {
moduleUtil.loadModulesForCategory(
`${category}Servers`,
(module, nextModule) => {
const moduleInst = new module.getModule();
try {
moduleInst.createServer(err => {
@ -54,10 +58,14 @@ function startListening(cb) {
logger.log.error(e, 'Exception caught creating server!');
return nextModule(e);
}
}, err => {
},
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');
module.exports = class Log {
static init() {
const Config = require('./config.js').get();
const logPath = Config.paths.logs;
@ -21,7 +20,10 @@ module.exports = class Log {
const logStreams = [];
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);
}
@ -31,7 +33,7 @@ module.exports = class Log {
// try to remove sensitive info by default, e.g. 'password' fields
['formData', 'formValue'].forEach(keyName => {
serializers[keyName] = (fd) => Log.hideSensitive(fd);
serializers[keyName] = fd => Log.hideSensitive(fd);
});
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
//
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}":"********"`;
})
}
)
);
} catch (e) {
// 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.isSecure = _.isBoolean(client.isSecure) ? client.isSecure : (modInfo.isSecure || false);
client.session.isSecure = _.isBoolean(client.isSecure)
? client.isSecure
: modInfo.isSecure || false;
clientConns.addNewClient(client, clientSock);
client.on('ready', readyOptions => {
client.startIdleMonitor();
// Go to module -- use default error handler
@ -72,7 +73,10 @@ module.exports = class LoginServerModule extends ServerModule {
});
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 => {

View File

@ -6,7 +6,8 @@ const Message = require('./message.js');
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
@ -44,7 +45,11 @@ function getAddressedToInfo(input) {
addr = Address.fromString(input.slice(lessThanPos + 1, greaterThanPos));
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 };
@ -56,7 +61,11 @@ function getAddressedToInfo(input) {
const addr = input.slice(lessThanPos + 1, greaterThanPos);
const m = addr.match(EMAIL_REGEX);
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 };
@ -64,7 +73,11 @@ function getAddressedToInfo(input) {
let m = input.match(EMAIL_REGEX);
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?
@ -74,7 +87,11 @@ function getAddressedToInfo(input) {
addr = Address.fromString(input.slice(firstAtPos + 1).trim());
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 };

View File

@ -47,11 +47,16 @@ function MaskEditTextView(options) {
this.clientBackspace = function () {
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) {
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);
@ -61,13 +66,18 @@ function MaskEditTextView(options) {
while (i < self.patternArray.length) {
if (_.isRegExp(self.patternArray[i])) {
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++;
} else {
self.client.term.write((self.getStyleSGR(3) || '') + self.fillChar);
}
} 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]);
}
i++;
@ -81,7 +91,9 @@ function MaskEditTextView(options) {
for (var i = 0; i < self.maskPattern.length; i++) {
// :TODO: support escaped characters, e.g. \#. Also allow \\ for a '\' mark!
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;
} else {
self.patternArray.push(self.maskPattern[i]);
@ -94,14 +106,13 @@ function MaskEditTextView(options) {
};
this.buildPattern();
}
require('util').inherits(MaskEditTextView, TextView);
MaskEditTextView.maskPatternCharacterRegEx = {
'#': /[0-9]/, // Numeric
'A' : /[a-zA-Z]/, // Alpha
A: /[a-zA-Z]/, // Alpha
'@': /[0-9a-zA-Z]/, // Alphanumeric
'&': /[\w\d\s]/, // Any "printable" 32-126, 128-255
};
@ -109,7 +120,8 @@ MaskEditTextView.maskPatternCharacterRegEx = {
MaskEditTextView.prototype.setText = function (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;
}
};
@ -135,7 +147,12 @@ MaskEditTextView.prototype.onKeyPress = function(ch, key) {
while (this.patternArrayPos >= 0) {
if (_.isRegExp(this.patternArray[this.patternArrayPos])) {
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();
break;
}
@ -165,14 +182,17 @@ MaskEditTextView.prototype.onKeyPress = function(ch, key) {
this.text += ch;
this.patternArrayPos++;
while(this.patternArrayPos < this.patternArray.length &&
!_.isRegExp(this.patternArray[this.patternArrayPos]))
{
while (
this.patternArrayPos < this.patternArray.length &&
!_.isRegExp(this.patternArray[this.patternArrayPos])
) {
this.patternArrayPos++;
}
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) {
switch (propName) {
case 'maskPattern' : this.setMaskPattern(value); break;
case 'maskPattern':
this.setMaskPattern(value);
break;
}
MaskEditTextView.super_.prototype.setPropertyValue.call(this, propName, value);

View File

@ -9,7 +9,7 @@ const { Errors } = require('./enig_error');
//
// Number to 32bit MBF
const numToMbf32 = (v) => {
const numToMbf32 = v => {
const mbf = Buffer.alloc(4);
if (0 === v) {
@ -36,7 +36,7 @@ const numToMbf32 = (v) => {
return mbf;
};
const mbf32ToNum = (mbf) => {
const mbf32ToNum = mbf => {
if (0 === mbf[3]) {
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 MaskEditTextView = require('./mask_edit_text_view.js').MaskEditTextView;
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 ansi = require('./ansi_term.js');
@ -28,7 +29,18 @@ function MCIViewFactory(client) {
}
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
@ -38,9 +50,7 @@ MCIViewFactory.UserViewCodes = [
'XY',
];
MCIViewFactory.MovementCodes = [
'CF', 'CB', 'CU', 'CD',
];
MCIViewFactory.MovementCodes = ['CF', 'CB', 'CU', 'CD'];
MCIViewFactory.prototype.createFromMCI = function (mci) {
assert(mci.code);
@ -73,7 +83,11 @@ MCIViewFactory.prototype.createFromMCI = function(mci) {
}
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];
}
}

View File

@ -8,7 +8,8 @@ const ViewController = require('./view_controller.js').ViewController
const menuUtil = require('./menu_util.js');
const Config = require('./config.js').get;
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 { getPredefinedMCIValue } = require('../core/predefined_mci.js');
const EnigAssert = require('./enigma_assert');
@ -20,7 +21,6 @@ const _ = require('lodash');
const iconvDecode = require('iconv-lite').decode;
exports.MenuModule = class MenuModule extends PluginModule {
constructor(options) {
super(options);
@ -31,7 +31,11 @@ exports.MenuModule = class MenuModule extends PluginModule {
this.menuConfig.config = this.menuConfig.config || {};
this.cls = _.get(this.menuConfig.config, 'cls', Config().menus.cls);
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) {
this.realTimeInterrupt = 'blocked';
@ -60,8 +64,11 @@ exports.MenuModule = class MenuModule extends PluginModule {
let pausePosition = { row: 0, column: 0 };
const hasArt = () => {
return _.isString(self.menuConfig.art) ||
(Array.isArray(self.menuConfig.art) && _.has(self.menuConfig.art[0], 'acs'));
return (
_.isString(self.menuConfig.art) ||
(Array.isArray(self.menuConfig.art) &&
_.has(self.menuConfig.art[0], 'acs'))
);
};
async.waterfall(
@ -82,7 +89,10 @@ exports.MenuModule = class MenuModule extends PluginModule {
self.menuConfig.config,
(err, artData) => {
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 {
mciData.menu = artData.mciMap;
}
@ -101,7 +111,11 @@ exports.MenuModule = class MenuModule extends PluginModule {
}
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);
@ -131,7 +145,10 @@ exports.MenuModule = class MenuModule extends PluginModule {
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
pausePosition.row = self.client.termHeight;
}
@ -142,13 +159,17 @@ exports.MenuModule = class MenuModule extends PluginModule {
self.finishedLoading();
self.realTimeInterrupt = 'allowed';
return self.autoNextMenu(callback);
}
},
],
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 */
});
}
}
);
@ -157,7 +178,9 @@ exports.MenuModule = class MenuModule extends PluginModule {
beforeArt(cb) {
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
this.client.term.rawWrite(ansi.setEmulatedBaudRate(this.menuConfig.config.baudRate));
this.client.term.rawWrite(
ansi.setEmulatedBaudRate(this.menuConfig.config.baudRate)
);
}
if (this.cls) {
@ -184,7 +207,7 @@ exports.MenuModule = class MenuModule extends PluginModule {
let opts = { cls: true }; // clear screen for first message
async.whilst(
(callback) => callback(null, this.client.interruptQueue.hasItems()),
callback => callback(null, this.client.interruptQueue.hasItems()),
next => {
this.client.interruptQueue.displayNext(opts, err => {
opts = {};
@ -198,7 +221,10 @@ exports.MenuModule = class MenuModule extends PluginModule {
}
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
}
@ -221,7 +247,8 @@ exports.MenuModule = class MenuModule extends PluginModule {
this.reload(err => {
return done(err, err ? false : true);
});
});
}
);
}
getSaveState() {
@ -308,7 +335,10 @@ exports.MenuModule = class MenuModule extends PluginModule {
}
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;
return vc;
@ -328,7 +358,10 @@ exports.MenuModule = class MenuModule extends PluginModule {
}
shouldPause() {
return ('end' === this.menuConfig.config.pause || true === this.menuConfig.config.pause);
return (
'end' === this.menuConfig.config.pause ||
true === this.menuConfig.config.pause
);
}
hasNextTimeout() {
@ -336,7 +369,7 @@ exports.MenuModule = class MenuModule extends PluginModule {
}
haveNext() {
return (_.isString(this.menuConfig.next) || _.isArray(this.menuConfig.next));
return _.isString(this.menuConfig.next) || _.isArray(this.menuConfig.next);
}
autoNextMenu(cb) {
@ -350,7 +383,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()) {
setTimeout(() => {
return gotoNextMenu();
@ -375,7 +411,10 @@ exports.MenuModule = class MenuModule extends PluginModule {
function addViewControllers(callback) {
_.forEach(mciData, (mciMap, 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);
@ -405,10 +444,13 @@ exports.MenuModule = class MenuModule extends PluginModule {
mciMap: mciData.prompt,
};
self.viewControllers.prompt.loadFromPromptConfig(promptLoadOpts, err => {
self.viewControllers.prompt.loadFromPromptConfig(
promptLoadOpts,
err => {
return callback(err);
});
}
);
},
],
err => {
return cb(err);
@ -426,19 +468,18 @@ exports.MenuModule = class MenuModule extends PluginModule {
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)) {
const data = iconvDecode(nameOrData, options.encoding || 'cp437');
return theme.displayPreparedArt(
options,
{ data },
(err, artData) => {
return theme.displayPreparedArt(options, { data }, (err, artData) => {
if (cb) {
return cb(err, artData);
}
}
);
});
}
return theme.displayThemedAsset(
@ -480,17 +521,13 @@ exports.MenuModule = class MenuModule extends PluginModule {
}
prepViewControllerWithArt(name, formId, options, cb) {
this.displayAsset(
this.menuConfig.config.art[name],
options,
(err, artData) => {
this.displayAsset(this.menuConfig.config.art[name], options, (err, artData) => {
if (err) {
return cb(err);
}
return this.prepViewController(name, formId, artData.mciMap, cb);
}
);
});
}
optionalMoveToPosition(position) {
@ -513,7 +550,11 @@ exports.MenuModule = class MenuModule extends PluginModule {
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)) {
cb = options;
options = {};
@ -543,7 +584,9 @@ exports.MenuModule = class MenuModule extends PluginModule {
if (options.clearAtSubmit) {
this.optionalMoveToPosition(position);
if (options.clearWidth) {
this.client.term.rawWrite(`${ansi.reset()}${' '.repeat(options.clearWidth)}`);
this.client.term.rawWrite(
`${ansi.reset()}${' '.repeat(options.clearWidth)}`
);
} else {
// :TODO: handle multi-rows via artHeight
this.client.term.rawWrite(ansi.eraseLine());
@ -630,7 +673,7 @@ exports.MenuModule = class MenuModule extends PluginModule {
return;
}
if(appendMultiLine && (view instanceof MultiLineEditTextView)) {
if (appendMultiLine && view instanceof MultiLineEditTextView) {
view.addText(text);
} else {
view.setText(text);
@ -650,14 +693,23 @@ exports.MenuModule = class MenuModule extends PluginModule {
const config = this.menuConfig.config;
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 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);
if(options.appendMultiLine && (textView instanceof MultiLineEditTextView)) {
if (
options.appendMultiLine &&
textView instanceof MultiLineEditTextView
) {
textView.addText(text);
} else if (textView.getData() != text) {
textView.setText(text);
@ -726,10 +778,18 @@ exports.MenuModule = class MenuModule extends PluginModule {
badReason = `Missing "${key}", expected ${type}`;
} else {
switch (type) {
case 'string' : typeOk = _.isString(c); break;
case 'object' : typeOk = _.isObject(c); break;
case 'array' : typeOk = Array.isArray(c); break;
case 'number' : typeOk = !isNaN(parseInt(c)); break;
case 'string':
typeOk = _.isString(c);
break;
case 'object':
typeOk = _.isObject(c);
break;
case 'array':
typeOk = Array.isArray(c);
break;
case 'number':
typeOk = !isNaN(parseInt(c));
break;
default:
typeOk = false;
badReason = `Don't know how to validate ${type}`;
@ -745,7 +805,13 @@ exports.MenuModule = class MenuModule extends PluginModule {
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})`
)
);
}
// Various common helpers

View File

@ -3,13 +3,8 @@
// ENiGMA½
const loadMenu = require('./menu_util.js').loadMenu;
const {
Errors,
ErrorReasons
} = require('./enig_error.js');
const {
getResolvedSpec
} = require('./menu_util.js');
const { Errors, ErrorReasons } = require('./enig_error.js');
const { getResolvedSpec } = require('./menu_util.js');
// deps
const _ = require('lodash');
@ -58,14 +53,26 @@ module.exports = class MenuStack {
const menuConfig = currentModuleInfo.instance.menuConfig;
const nextMenu = getResolvedSpec(this.client, menuConfig.next, 'next');
if (!nextMenu) {
return cb(Array.isArray(menuConfig.next) ?
Errors.MenuStack('No matching condition for "next"', ErrorReasons.NoConditionMatch) :
Errors.MenuStack('Invalid or missing "next" member in menu config', ErrorReasons.InvalidNextMenu)
return cb(
Array.isArray(menuConfig.next)
? 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) {
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);
@ -89,7 +96,9 @@ module.exports = class MenuStack {
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) {
@ -105,7 +114,12 @@ module.exports = class MenuStack {
if (currentModuleInfo && name === currentModuleInfo.name) {
if (cb) {
cb(Errors.MenuStack('Already at supplied menu', ErrorReasons.AlreadyThere));
cb(
Errors.MenuStack(
'Already at supplied menu',
ErrorReasons.AlreadyThere
)
);
}
return;
}
@ -146,7 +160,10 @@ module.exports = class MenuStack {
{ options: modInst.menuConfig.options },
'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;
}
@ -161,14 +178,18 @@ module.exports = class MenuStack {
menuFlags = modInst.menuConfig.config.menuFlags;
// 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));
}
}
if (currentModuleInfo) {
// save stack state
currentModuleInfo.savedState = currentModuleInfo.instance.getSaveState();
currentModuleInfo.savedState =
currentModuleInfo.instance.getSaveState();
currentModuleInfo.instance.leave();
@ -196,7 +217,9 @@ module.exports = class MenuStack {
const stackEntries = self.stack.map(stackEntry => {
let name = stackEntry.name;
if (stackEntry.instance.menuConfig.config.menuFlags.length > 0) {
name += ` (${stackEntry.instance.menuConfig.config.menuFlags.join(', ')})`;
name += ` (${stackEntry.instance.menuConfig.config.menuFlags.join(
', '
)})`;
}
return name;
});

View File

@ -34,13 +34,16 @@ function getMenuConfig(client, name, cb) {
function locatePromptConfig(menuConfig, callback) {
if (_.isString(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(Errors.DoesNotExist(`No prompt entry for "${menuConfig.prompt}"`));
return callback(
Errors.DoesNotExist(`No prompt entry for "${menuConfig.prompt}"`)
);
}
return callback(null, menuConfig);
}
},
],
(err, menuConfig) => {
return cb(err, menuConfig);
@ -62,7 +65,6 @@ function loadMenu(options, cb) {
});
},
function loadMenuModule(menuConfig, callback) {
menuConfig.config = menuConfig.config || {};
menuConfig.config.menuFlags = menuConfig.config.menuFlags || [];
if (!Array.isArray(menuConfig.config.menuFlags)) {
@ -74,8 +76,12 @@ function loadMenu(options, cb) {
const modLoadOpts = {
name: modSupplied ? modAsset.asset : 'standard_menu',
path : (!modSupplied || 'systemModule' === modAsset.type) ? __dirname : Config().paths.mods,
category : (!modSupplied || 'systemModule' === modAsset.type) ? null : 'mods',
path:
!modSupplied || 'systemModule' === modAsset.type
? __dirname
: Config().paths.mods,
category:
!modSupplied || 'systemModule' === modAsset.type ? null : 'mods',
};
moduleUtil.loadModuleEx(modLoadOpts, (err, mod) => {
@ -90,8 +96,14 @@ function loadMenu(options, cb) {
},
function createModuleInstance(modData, callback) {
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;
try {
@ -107,7 +119,7 @@ function loadMenu(options, cb) {
}
return callback(null, moduleInstance);
}
},
],
(err, modInst) => {
return cb(err, modInst);
@ -125,7 +137,7 @@ function getFormConfigByIDAndMap(menuConfig, formId, mciMap, cb) {
}
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;
}).join('');
@ -147,7 +159,9 @@ function getFormConfigByIDAndMap(menuConfig, formId, mciMap, cb) {
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...
@ -158,13 +172,27 @@ function callModuleMenuMethod(client, asset, path, formData, extraArgs, cb) {
try {
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);
return methodMod[asset.asset](client.currentMenuModule, formData || { }, extraArgs, cb);
return methodMod[asset.asset](
client.currentMenuModule,
formData || {},
extraArgs,
cb
);
} 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);
}
}
@ -190,7 +218,8 @@ function handleAction(client, formData, conf, cb) {
paths.join(Config().paths.mods, actionAsset.location),
formData,
conf.extraArgs,
cb);
cb
);
} else if ('systemMethod' === actionAsset.type) {
// :TODO: Need to pass optional args here -- conf.extraArgs and args between e.g. ()
// :TODO: Probably better as system_method.js
@ -200,12 +229,17 @@ function handleAction(client, formData, conf, cb) {
paths.join(__dirname, 'system_menu_method.js'),
formData,
conf.extraArgs,
cb);
cb
);
} else {
// local to current module
const currentModule = client.currentMenuModule;
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');
@ -214,7 +248,11 @@ function handleAction(client, formData, conf, cb) {
}
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 'systemMethod':
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) {
// :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 {
// local to current module
const currentModule = client.currentMenuModule;
if (_.isFunction(currentModule.menuMethods[nextAsset.asset])) {
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');
@ -279,7 +335,11 @@ function handleNext(client, nextSpec, conf, cb) {
}
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"');

View File

@ -31,15 +31,21 @@ function MenuView(options) {
this.renderCache = {};
this.caseInsensitiveHotKeys = miscUtil.valueWithDefault(options.caseInsensitiveHotKeys, true);
this.caseInsensitiveHotKeys = miscUtil.valueWithDefault(
options.caseInsensitiveHotKeys,
true
);
this.setHotKeys(options.hotKeys);
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.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
this.focusPrefix = options.focusPrefix || '';
@ -53,7 +59,8 @@ function MenuView(options) {
this.getHotKeyItemIndex = function (ch) {
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)) {
return keyIndex;
}
@ -71,11 +78,11 @@ util.inherits(MenuView, View);
MenuView.prototype.setTextOverflow = function (overflow) {
this.textOverflow = overflow;
this.invalidateRenderCache();
}
};
MenuView.prototype.hasTextOverflow = function () {
return this.textOverflow != undefined;
}
};
MenuView.prototype.setItems = function (items) {
if (Array.isArray(items)) {
@ -249,11 +256,9 @@ MenuView.prototype.setFocusItems = function(items) {
if (items) {
this.focusItems = [];
items.forEach(itemText => {
this.focusItems.push(
{
text : self.disablePipe ? itemText : pipeToAnsi(itemText, self.client)
}
);
this.focusItems.push({
text: self.disablePipe ? itemText : pipeToAnsi(itemText, self.client),
});
});
}
};
@ -276,16 +281,36 @@ MenuView.prototype.setItemHorizSpacing = function(itemHorizSpacing) {
MenuView.prototype.setPropertyValue = function (propName, value) {
switch (propName) {
case 'itemSpacing' : this.setItemSpacing(value); break;
case 'itemHorizSpacing' : this.setItemHorizSpacing(value); break;
case 'items' : this.setItems(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 'itemSpacing':
this.setItemSpacing(value);
break;
case 'itemHorizSpacing':
this.setItemHorizSpacing(value);
break;
case 'items':
this.setItems(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 'focusItemFormat':
@ -294,7 +319,9 @@ MenuView.prototype.setPropertyValue = function(propName, value) {
this.invalidateRenderCache();
break;
case 'sort' : this.setSort(value); break;
case 'sort':
this.setSort(value);
break;
}
MenuView.super_.prototype.setPropertyValue.call(this, propName, value);
@ -303,13 +330,13 @@ MenuView.prototype.setPropertyValue = function(propName, value) {
MenuView.prototype.setFillChar = function (fillChar) {
this.fillChar = miscUtil.valueWithDefault(fillChar, ' ').substr(0, 1);
this.invalidateRenderCache();
}
};
MenuView.prototype.setJustify = function (justify) {
this.justify = justify;
this.invalidateRenderCache();
this.positionCacheExpired = true;
}
};
MenuView.prototype.setHotKeys = function (hotKeys) {
if (_.isObject(hotKeys)) {
@ -323,4 +350,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 Errors = require('./enig_error.js').Errors;
const ANSI = require('./ansi_term.js');
const {
sanitizeString,
getISOTimestampString } = require('./database.js');
const { sanitizeString, getISOTimestampString } = require('./database.js');
const { isCP437Encodable } = require('./cp437util');
const { containsNonLatinCodepoints } = require('./string_util');
const {
isAnsi, isFormattedLine,
isAnsi,
isFormattedLine,
splitTextAtTerms,
renderSubstr
renderSubstr,
} = require('./string_util.js');
const ansiPrep = require('./ansi_prep.js');
@ -30,7 +29,9 @@ const assert = require('assert');
const moment = require('moment');
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 = {
Invalid: '',
@ -101,12 +102,11 @@ const QWKPropertyNames = {
// :TODO: this is a ugly hack due to bad variable names - clean it up & just _.camelCase(k)!
const MESSAGE_ROW_MAP = {
reply_to_message_id: 'replyToMsgId',
modified_timestamp : 'modTimestamp'
modified_timestamp: 'modTimestamp',
};
module.exports = class Message {
constructor(
{
constructor({
messageId = 0,
areaTag = Message.WellKnownAreaTags.Invalid,
uuid,
@ -118,9 +118,7 @@ module.exports = class Message {
modTimestamp = moment(),
meta,
hashTags = [],
} = { }
)
{
} = {}) {
this.messageId = messageId;
this.areaTag = areaTag;
this.messageUuid = uuid;
@ -142,11 +140,14 @@ module.exports = class Message {
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;
}
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) {
return areaTag.toLowerCase() === Message.WellKnownAreaTags.Private;
@ -161,17 +162,21 @@ module.exports = class Message {
}
isCP437Encodable() {
return isCP437Encodable(this.toUserName) &&
return (
isCP437Encodable(this.toUserName) &&
isCP437Encodable(this.fromUserName) &&
isCP437Encodable(this.subject) &&
isCP437Encodable(this.message);
isCP437Encodable(this.message)
);
}
containsNonLatinCodepoints() {
return containsNonLatinCodepoints(this.toUserName) ||
return (
containsNonLatinCodepoints(this.toUserName) ||
containsNonLatinCodepoints(this.fromUserName) ||
containsNonLatinCodepoints(this.subject) ||
containsNonLatinCodepoints(this.message);
containsNonLatinCodepoints(this.message)
);
}
/*
@ -196,7 +201,9 @@ module.exports = class Message {
*/
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();
}
@ -257,9 +264,17 @@ module.exports = class Message {
areaTag = iconvEncode(areaTag.toUpperCase(), 'CP437');
modTimestamp = iconvEncode(modTimestamp.format('DD MMM YY HH:mm:ss'), '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) {
@ -313,9 +328,17 @@ module.exports = class Message {
filter.operator = filter.operator || 'AND';
if ('messageList' === filter.resultType) {
filter.extraFields = _.uniq(filter.extraFields.concat(
[ 'area_tag', 'message_uuid', 'reply_to_message_id', 'to_user_name', 'from_user_name', 'subject', 'modified_timestamp' ]
));
filter.extraFields = _.uniq(
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';
@ -326,13 +349,14 @@ module.exports = class Message {
let sql;
if ('count' === filter.resultType) {
sql =
`SELECT COUNT() AS count
sql = `SELECT COUNT() AS count
FROM message m`;
} else {
sql =
`SELECT DISTINCT m.${field}${filter.extraFields.length > 0 ? ', ' + filter.extraFields.map(f => `m.${f}`).join(', ') : ''}
sql = `SELECT DISTINCT m.${field}${
filter.extraFields.length > 0
? ', ' + filter.extraFields.map(f => `m.${f}`).join(', ')
: ''
}
FROM message m`;
}
@ -365,7 +389,6 @@ module.exports = class Message {
appendWhereClause(`m.message_id IN (${uuidList})`);
}
if (_.isNumber(filter.privateTagUserId)) {
appendWhereClause(`m.area_tag = "${Message.WellKnownAreaTags.Private}"`);
appendWhereClause(
@ -373,7 +396,8 @@ module.exports = class Message {
SELECT message_id
FROM message_meta
WHERE meta_category = "System" AND meta_name = "${Message.SystemMetaNames.LocalToUserID}" AND meta_value = ${filter.privateTagUserId}
)`);
)`
);
} else {
if (filter.areaTag && filter.areaTag.length > 0) {
if (!Array.isArray(filter.areaTag)) {
@ -382,7 +406,8 @@ module.exports = class Message {
const areaList = filter.areaTag
.filter(t => t !== Message.WellKnownAreaTags.Private)
.map(t => `"${t}"`).join(', ');
.map(t => `"${t}"`)
.join(', ');
if (areaList.length > 0) {
appendWhereClause(`m.area_tag IN(${areaList})`);
} else {
@ -391,7 +416,10 @@ module.exports = class Message {
}
} else {
// 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];
}
if (Array.isArray(val)) {
val = '(' + val.map(v => {
val =
'(' +
val
.map(v => {
return `m.${_.snakeCase(field)} LIKE "${sanitizeString(v)}"`;
}).join(' OR ') + ')';
})
.join(' OR ') +
')';
appendWhereClause(val);
}
});
if(_.isString(filter.newerThanTimestamp) && filter.newerThanTimestamp.length > 0) {
if (
_.isString(filter.newerThanTimestamp) &&
filter.newerThanTimestamp.length > 0
) {
// :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)) {
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)) {
@ -440,7 +482,11 @@ module.exports = class Message {
if (Array.isArray(filter.metaTuples)) {
let sub = [];
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} `);
appendWhereClause(
@ -468,15 +514,22 @@ module.exports = class Message {
const matches = [];
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)) {
matches.push(extra ? rowConv(row) : row[field]);
}
}, err => {
},
err => {
return cb(err, matches);
});
}
);
}
}
@ -493,7 +546,7 @@ module.exports = class Message {
return cb(err);
}
const success = (row && row.message_id);
const success = row && row.message_id;
return cb(
success ? null : Errors.DoesNotExist(`No message for UUID ${uuid}`),
success ? row.message_id : null
@ -513,14 +566,16 @@ module.exports = class Message {
if (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) {
const sql =
`SELECT meta_value
const sql = `SELECT meta_value
FROM message_meta
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.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) {
Message.getMetaValuesByMessageId(messageId, category, name, (err, values) => {
Message.getMetaValuesByMessageId(
messageId,
category,
name,
(err, values) => {
return callback(err, values);
});
}
);
},
],
(err, values) => {
return cb(err, values);
@ -575,13 +638,15 @@ module.exports = class Message {
}
}
*/
const sql =
`SELECT meta_category, meta_name, meta_value
const sql = `SELECT meta_category, meta_name, meta_value
FROM message_meta
WHERE message_id = ?;`;
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)) {
self.meta[row.meta_category] = {};
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;
} else {
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);
}
}
}, err => {
},
err => {
return cb(err);
});
}
);
}
load(loadWith, cb) {
@ -623,7 +692,9 @@ module.exports = class Message {
}
if (!msgRow) {
return callback(Errors.DoesNotExist('Message (no longer) available'));
return callback(
Errors.DoesNotExist('Message (no longer) available')
);
}
self.messageId = msgRow.message_id;
@ -636,7 +707,9 @@ module.exports = class Message {
self.message = msgRow.message;
// 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);
}
@ -650,7 +723,7 @@ module.exports = class Message {
function loadHashTags(callback) {
// :TODO:
return callback(null);
}
},
],
err => {
return cb(err);
@ -666,7 +739,8 @@ module.exports = class Message {
const metaStmt = transOrDb.prepare(
`INSERT INTO message_meta (message_id, meta_category, meta_name, meta_value)
VALUES (?, ?, ?, ?);`);
VALUES (?, ?, ?, ?);`
);
if (!_.isArray(value)) {
value = [value];
@ -674,13 +748,17 @@ module.exports = class Message {
const self = this;
async.each(value, (v, next) => {
async.each(
value,
(v, next) => {
metaStmt.run(self.messageId, category, name, v, err => {
return next(err);
});
}, err => {
},
err => {
return cb(err);
});
}
);
}
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)
VALUES (?, ?, ?, ?, ?, ?, ?, ?);`,
[
self.areaTag, self.messageUuid, self.replyToMsgId, self.toUserName,
self.fromUserName, self.subject, self.message, getISOTimestampString(self.modTimestamp)
self.areaTag,
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) {
self.messageId = this.lastID;
}
@ -738,23 +823,36 @@ module.exports = class Message {
}
}
*/
async.each(Object.keys(self.meta), (category, nextCat) => {
async.each(Object.keys(self.meta[category]), (name, nextName) => {
self.persistMetaValue(category, name, self.meta[category][name], trans, err => {
async.each(
Object.keys(self.meta),
(category, nextCat) => {
async.each(
Object.keys(self.meta[category]),
(name, nextName) => {
self.persistMetaValue(
category,
name,
self.meta[category][name],
trans,
err => {
return nextName(err);
});
}, err => {
}
);
},
err => {
return nextCat(err);
});
}, err => {
}
);
},
err => {
return callback(err, trans);
});
}
);
},
function storeHashTags(trans, callback) {
// :TODO: hash tag support
return callback(null, trans);
}
},
],
(err, trans) => {
if (trans) {
@ -770,7 +868,9 @@ module.exports = class Message {
deleteMessage(requestingUser, cb) {
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(
@ -802,8 +902,12 @@ module.exports = class Message {
options.startCol = options.startCol || 1;
options.includePrefix = _.get(options, 'includePrefix', true);
options.ansiResetSgr = options.ansiResetSgr || ANSI.getSGRFromGraphicRendition( { fg : 39, bg : 49 }, true);
options.ansiFocusPrefixSgr = options.ansiFocusPrefixSgr || ANSI.getSGRFromGraphicRendition( { intensity : 'bold', fg : 39, bg : 49 } );
options.ansiResetSgr =
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
/*
@ -817,7 +921,9 @@ module.exports = class Message {
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) {
extraPrefix = extraPrefix ? ` ${extraPrefix}` : '';
@ -829,7 +935,9 @@ module.exports = class Message {
};
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 => {
quoteLines.push(`${lastSgr}${l}`);
focusQuoteLines.push(`${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
focusQuoteLines.push(
`${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;
@ -894,7 +1011,10 @@ module.exports = class Message {
let tearLinePos = Message.getTearLinePosition(input);
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:
// - New line - line
@ -931,7 +1051,9 @@ module.exports = class Message {
case 'line':
if (quoteMatch) {
if (isFormattedLine(line)) {
quoted.push(getFormattedLine(line.replace(/\s/, '')));
quoted.push(
getFormattedLine(line.replace(/\s/, ''))
);
} else {
quoted.push(...getWrapped(buf, quoteMatch[1]));
state = 'quote_line';
@ -963,7 +1085,8 @@ module.exports = class Message {
quoted.push(getFormattedLine(line));
} else {
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;
}
@ -972,7 +1095,10 @@ module.exports = class Message {
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));
});

View File

@ -52,22 +52,31 @@ function startup(cb) {
// by default, private messages are NOT included
async.series(
[
(callback) => {
callback => {
Message.findMessages({ resultType: 'count' }, (err, count) => {
if (count) {
StatLog.setNonPersistentSystemStat(SysProps.MessageTotalCount, count);
StatLog.setNonPersistentSystemStat(
SysProps.MessageTotalCount,
count
);
}
return callback(err);
});
},
(callback) => {
Message.findMessages( { resultType : 'count', date : moment() }, (err, count) => {
callback => {
Message.findMessages(
{ resultType: 'count', date: moment() },
(err, count) => {
if (count) {
StatLog.setNonPersistentSystemStat(SysProps.MessagesToday, count);
StatLog.setNonPersistentSystemStat(
SysProps.MessagesToday,
count
);
}
return callback(err);
});
}
);
},
],
err => {
return cb(err);
@ -150,7 +159,9 @@ function getAllAvailableMessageAreaTags(client, options) {
const areaOpts = Object.assign({}, options, { client });
Object.keys(getAvailableMessageConferences(client, confOpts)).forEach(confTag => {
areaTags.push(...Object.keys(getAvailableMessageAreasByConfTag(confTag, areaOpts)));
areaTags.push(
...Object.keys(getAvailableMessageAreasByConfTag(confTag, areaOpts))
);
});
return areaTags;
@ -180,7 +191,10 @@ function getDefaultMessageConferenceTag(client, disableAcsCheck) {
// just use anything we can
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;
@ -211,7 +225,7 @@ function getDefaultMessageAreaTagByConfTag(client, confTag, disableAcsCheck) {
if (Message.isPrivateAreaTag(areaTag)) {
return false;
}
return (true === disableAcsCheck || client.acs.hasMessageAreaRead(area));
return true === disableAcsCheck || client.acs.hasMessageAreaRead(area);
});
return defaultArea;
@ -242,7 +256,10 @@ function getSuitableMessageConfAndAreaTags(client) {
return;
}
_.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;
areaTag = at;
return false; // stop inner iteration
@ -263,7 +280,7 @@ function getMessageConferenceByTag(confTag) {
function getMessageConfTagByAreaTag(areaTag) {
const confs = Config().messageConferences;
return Object.keys(confs).find( (confTag) => {
return Object.keys(confs).find(confTag => {
return _.has(confs, [confTag, 'areas', areaTag]);
});
}
@ -321,8 +338,13 @@ function changeMessageConference(client, confTag, cb) {
}
},
function validateAccess(conf, areaInfo, callback) {
if(!client.acs.hasMessageConfRead(conf) || !client.acs.hasMessageAreaRead(areaInfo.area)) {
return callback(new Error('Access denied to message area and/or conference'));
if (
!client.acs.hasMessageConfRead(conf) ||
!client.acs.hasMessageAreaRead(areaInfo.area)
) {
return callback(
new Error('Access denied to message area and/or conference')
);
} else {
return callback(null, conf, areaInfo);
}
@ -339,9 +361,15 @@ function changeMessageConference(client, confTag, cb) {
],
function complete(err, conf, areaInfo) {
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 {
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);
}
@ -369,20 +397,30 @@ function changeMessageAreaWithOptions(client, areaTag, options, cb) {
},
function changeArea(area, callback) {
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);
});
}
);
} else {
client.user.properties[UserProps.MessageAreaTag] = areaTag;
return callback(null, area);
}
}
},
],
function complete(err, area) {
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 {
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);
@ -425,7 +463,9 @@ function hasMessageConfAndAreaRead(client, areaOrTag) {
areaOrTag = getMessageAreaByTag(areaOrTag) || {};
}
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) {
@ -433,7 +473,9 @@ function hasMessageConfAndAreaWrite(client, areaOrTag) {
areaOrTag = getMessageAreaByTag(areaOrTag) || {};
}
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) {
@ -539,7 +581,7 @@ function getMessageListForArea(client, areaTag, filter, cb)
areaTag,
resultType: 'messageList',
sort: 'messageId',
order : 'ascending'
order: 'ascending',
};
} else {
Object.assign(filter, { areaTag });
@ -616,18 +658,25 @@ function updateMessageAreaLastReadId(userId, areaTag, messageId, allowOlder, cb)
} else {
callback(null);
}
}
},
],
function complete(err, didUpdate) {
if (err) {
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 {
if (true === didUpdate) {
Log.trace(
{ userId: userId, areaTag: areaTag, messageId: messageId },
'Area last read ID updated');
'Area last read ID updated'
);
}
}
cb(err);
@ -643,7 +692,7 @@ function persistMessage(message, cb) {
},
function recordToMessageNetworks(callback) {
return msgNetRecord(message, callback);
}
},
],
cb
);
@ -651,7 +700,6 @@ function persistMessage(message, cb) {
// method exposed for event scheduler
function trimMessageAreasScheduledEvent(args, cb) {
function trimMessageAreaByMaxMessages(areaInfo, cb) {
if (0 === areaInfo.maxMessages) {
return cb(null);
@ -667,11 +715,18 @@ function trimMessageAreasScheduledEvent(args, cb) {
LIMIT -1 OFFSET ${areaInfo.maxMessages}
);`,
[areaInfo.areaTag.toLowerCase()],
function result(err) { // no arrow func; need this
function result(err) {
// no arrow func; need this
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 {
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);
}
@ -687,11 +742,18 @@ function trimMessageAreasScheduledEvent(args, cb) {
`DELETE FROM message
WHERE area_tag = ? AND modified_timestamp < date('now', '-${areaInfo.maxAgeDays} days');`,
[areaInfo.areaTag],
function result(err) { // no arrow func; need this
function result(err) {
// no arrow func; need this
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 {
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);
}
@ -730,7 +792,6 @@ function trimMessageAreasScheduledEvent(args, cb) {
// determine maxMessages & maxAgeDays per area
const config = Config();
areaTags.forEach(areaTag => {
let maxMessages = config.messageAreaDefaults.maxMessages;
let maxAgeDays = config.messageAreaDefaults.maxAgeDays;
@ -795,17 +856,24 @@ function trimMessageAreasScheduledEvent(args, cb) {
(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')
);`,
function results(err) { // no arrow func; need this
function results(err) {
// no arrow func; need this
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 {
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);
}
},
],
err => {
return cb(err);

View File

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

View File

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

View File

@ -21,17 +21,22 @@ function isProduction() {
}
function isDevelopment() {
return (!(isProduction()));
return !isProduction();
}
function valueWithDefault(val, defVal) {
return (typeof val !== 'undefined' ? val : defVal);
return typeof val !== 'undefined' ? val : defVal;
}
function resolvePath(path) {
if (path.substr(0, 2) === '~/') {
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);
}
@ -40,8 +45,7 @@ function getCleanEnigmaVersion() {
return packageJson.version
.replace(/-/g, '.')
.replace(/alpha/, 'a')
.replace(/beta/,'b')
;
.replace(/beta/, 'b');
}
// See also ftn_util.js getTearLine() & getProductIdentifier()

View File

@ -7,10 +7,11 @@ const UserProps = require('./user_property.js');
// deps
const { get } = require('lodash');
exports.MessageAreaConfTempSwitcher = Sup => class extends Sup {
exports.MessageAreaConfTempSwitcher = Sup =>
class extends Sup {
tempMessageConfAndAreaSwitch(messageAreaTag, recordPrevious = true) {
messageAreaTag = messageAreaTag || get(this, 'config.messageAreaTag', this.messageAreaTag);
messageAreaTag =
messageAreaTag || get(this, 'config.messageAreaTag', this.messageAreaTag);
if (!messageAreaTag) {
return; // nothing to do!
}
@ -23,14 +24,19 @@ exports.MessageAreaConfTempSwitcher = Sup => class extends Sup {
}
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() {
if (this.prevMessageConfAndArea) {
this.client.user.properties[UserProps.MessageConfTag] = this.prevMessageConfAndArea.confTag;
this.client.user.properties[UserProps.MessageAreaTag] = this.prevMessageConfAndArea.areaTag;
this.client.user.properties[UserProps.MessageConfTag] =
this.prevMessageConfAndArea.confTag;
this.client.user.properties[UserProps.MessageAreaTag] =
this.prevMessageConfAndArea.areaTag;
}
}
};

View File

@ -4,10 +4,7 @@
// ENiGMA½
const Config = require('./config.js').get;
const Log = require('./logger.js').log;
const {
Errors,
ErrorReasons
} = require('./enig_error.js');
const { Errors, ErrorReasons } = require('./enig_error.js');
// deps
const fs = require('graceful-fs');
@ -29,10 +26,17 @@ function loadModuleEx(options, cb) {
assert(_.isString(options.name));
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) {
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)) {
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)) {
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);
@ -72,16 +80,22 @@ function loadModule(name, category, cb) {
const path = Config().paths[category];
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);
});
}
);
}
function loadModulesForCategory(category, iterator, complete) {
fs.readdir(Config().paths[category], (err, files) => {
if (err) {
return iterator(err);
@ -91,7 +105,9 @@ function loadModulesForCategory(category, iterator, complete) {
return '.js' === paths.extname(file);
});
async.each(jsModules, (file, next) => {
async.each(
jsModules,
(file, next) => {
loadModule(paths.basename(file, '.js'), category, (err, mod) => {
if (err) {
if (ErrorReasons.Disabled === err.reasonCode) {
@ -103,11 +119,13 @@ function loadModulesForCategory(category, iterator, complete) {
}
return iterator(mod, next);
});
}, err => {
},
err => {
if (complete) {
return complete(err);
}
});
}
);
});
}
@ -127,7 +145,9 @@ function initializeModules(cb) {
const modulePaths = getModulePaths().concat(__dirname);
async.each(modulePaths, (modulePath, nextPath) => {
async.each(
modulePaths,
(modulePath, nextPath) => {
glob('*{.js,/*.js}', { cwd: modulePath }, (err, files) => {
if (err) {
return nextPath(err);
@ -135,7 +155,9 @@ function initializeModules(cb) {
const ourPath = paths.join(__dirname, __filename);
async.each(files, (moduleName, nextModule) => {
async.each(
files,
(moduleName, nextModule) => {
const fullModulePath = paths.join(modulePath, moduleName);
if (ourPath === fullModulePath) {
return nextModule(null);
@ -151,7 +173,13 @@ function initializeModules(cb) {
mod.moduleInitialize(initInfo, 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);
});
@ -159,16 +187,21 @@ function initializeModules(cb) {
return nextModule(null);
}
} catch (e) {
Log.warn( { error : e.message, fullModulePath }, 'Exception during "moduleInitialize"');
Log.warn(
{ error: e.message, fullModulePath },
'Exception during "moduleInitialize"'
);
return nextModule(null);
}
},
err => {
return nextPath(err);
});
}
);
});
},
err => {
return cb(err);
});
}
);
}

View File

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

View File

@ -38,7 +38,9 @@ exports.getModule = class MessageAreaListModule extends MenuModule {
messageArea.changeMessageArea(this.client, area.areaTag, 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);
}
@ -47,10 +49,15 @@ exports.getModule = class MessageAreaListModule extends MenuModule {
extraArgs: {
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);
@ -58,7 +65,7 @@ exports.getModule = class MessageAreaListModule extends MenuModule {
} else {
return cb(null);
}
}
},
};
}
@ -70,13 +77,19 @@ exports.getModule = class MessageAreaListModule extends MenuModule {
async.series(
[
(next) => {
next => {
return this.prepViewController('areaList', 0, mciData.menu, next);
},
(next) => {
const areaListView = this.viewControllers.areaList.getView(MciViewIds.areaList);
next => {
const areaListView = this.viewControllers.areaList.getView(
MciViewIds.areaList
);
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 => {
@ -87,11 +100,14 @@ exports.getModule = class MessageAreaListModule extends MenuModule {
areaListView.redraw();
this.selectionIndexUpdate(0);
return next(null);
}
},
],
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);
}
@ -105,15 +121,21 @@ exports.getModule = class MessageAreaListModule extends MenuModule {
return;
}
this.setViewText('areaList', MciViewIds.areaDesc, area.desc);
this.updateCustomViewTextsWithFilter('areaList', MciViewIds.customRangeStart, area);
this.updateCustomViewTextsWithFilter(
'areaList',
MciViewIds.customRangeStart,
area
);
}
initList() {
let index = 1;
this.messageAreas = messageArea.getSortedAvailMessageAreasByConfTag(
this.messageAreas = messageArea
.getSortedAvailMessageAreasByConfTag(
this.client.user.properties[UserProps.MessageConfTag],
{ client: this.client }
).map(area => {
)
.map(area => {
return {
index: index++,
areaTag: area.areaTag,

View File

@ -4,9 +4,7 @@
const FullScreenEditorModule = require('./fse.js').FullScreenEditorModule;
const persistMessage = require('./message_area.js').persistMessage;
const UserProps = require('./user_property.js');
const {
hasMessageConfAndAreaWrite,
} = require('./message_area.js');
const { hasMessageConfAndAreaWrite } = require('./message_area.js');
const async = require('async');
@ -26,7 +24,6 @@ exports.getModule = class AreaPostFSEModule extends FullScreenEditorModule {
this.editorMode = 'edit';
this.menuMethods.editModeMenuSave = function (formData, extraArgs, cb) {
var msg;
async.series(
[
@ -41,7 +38,7 @@ exports.getModule = class AreaPostFSEModule extends FullScreenEditorModule {
},
function updateStats(callback) {
self.updateUserAndSystemStats(callback);
}
},
],
function complete(err) {
if (err) {
@ -62,8 +59,7 @@ exports.getModule = class AreaPostFSEModule extends FullScreenEditorModule {
enter() {
this.messageAreaTag =
this.messageAreaTag ||
this.client.user.getProperty(UserProps.MessageAreaTag);
this.messageAreaTag || this.client.user.getProperty(UserProps.MessageAreaTag);
super.enter();
}

View File

@ -46,7 +46,10 @@ exports.getModule = class AreaViewFSEModule extends FullScreenEditorModule {
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
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?
@ -65,7 +68,10 @@ exports.getModule = class AreaViewFSEModule extends FullScreenEditorModule {
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
return self.loadMessageByUuid(self.messageList[self.messageIndex].messageUuid, cb);
return self.loadMessageByUuid(
self.messageList[self.messageIndex].messageUuid,
cb
);
}
return cb(null);
@ -76,10 +82,18 @@ exports.getModule = class AreaViewFSEModule extends FullScreenEditorModule {
// :TODO: Create methods for up/down vs using keyPressXXXXX
switch (formData.key.name) {
case 'down arrow' : bodyView.scrollDocumentUp(); break;
case 'up arrow' : bodyView.scrollDocumentDown(); break;
case 'page up' : bodyView.keyPressPageUp(); break;
case 'page down' : bodyView.keyPressPageDown(); break;
case 'down arrow':
bodyView.scrollDocumentUp();
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
@ -94,7 +108,7 @@ exports.getModule = class AreaViewFSEModule extends FullScreenEditorModule {
extraArgs: {
messageAreaTag: self.messageAreaTag,
replyToMessage: self.message,
}
},
};
return self.gotoMenu(extraArgs.menu, modOpts, cb);

View File

@ -33,9 +33,14 @@ exports.getModule = class MessageConfListModule extends MenuModule {
if (1 === formData.submitId) {
const conf = this.messageConfs[formData.value.conf];
messageArea.changeMessageConference(this.client, conf.confTag, err => {
messageArea.changeMessageConference(
this.client,
conf.confTag,
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);
}
@ -44,18 +49,24 @@ exports.getModule = class MessageConfListModule extends MenuModule {
extraArgs: {
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);
});
}
);
} else {
return cb(null);
}
}
},
};
}
@ -67,13 +78,19 @@ exports.getModule = class MessageConfListModule extends MenuModule {
async.series(
[
(next) => {
next => {
return this.prepViewController('confList', 0, mciData.menu, next);
},
(next) => {
const confListView = this.viewControllers.confList.getView(MciViewIds.confList);
next => {
const confListView = this.viewControllers.confList.getView(
MciViewIds.confList
);
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 => {
@ -84,11 +101,14 @@ exports.getModule = class MessageConfListModule extends MenuModule {
confListView.redraw();
this.selectionIndexUpdate(0);
return next(null);
}
},
],
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;
}
this.setViewText('confList', MciViewIds.confDesc, conf.desc);
this.updateCustomViewTextsWithFilter('confList', MciViewIds.customRangeStart, conf);
this.updateCustomViewTextsWithFilter(
'confList',
MciViewIds.customRangeStart,
conf
);
}
initList()
{
initList() {
let index = 1;
this.messageConfs = messageArea.getSortedAvailMessageConferences(this.client).map(conf => {
this.messageConfs = messageArea
.getSortedAvailMessageConferences(this.client)
.map(conf => {
return {
index: index++,
confTag: conf.confTag,

View File

@ -5,7 +5,8 @@
const MenuModule = require('./menu_module.js').MenuModule;
const ViewController = require('./view_controller.js').ViewController;
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 Message = require('./message.js');
const UserProps = require('./user_property.js');
@ -45,35 +46,52 @@ const MciViewIds = {
delPrompt: {
prompt: 1,
}
},
};
exports.getModule = class MessageListModule extends MessageAreaConfTempSwitcher(MenuModule) {
exports.getModule = class MessageListModule extends (
MessageAreaConfTempSwitcher(MenuModule)
) {
constructor(options) {
super(options);
// :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 = {
selectMessage: (formData, extraArgs, cb) => {
if (MciViewIds.allViews.msgList === formData.submitId) {
// '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 = {
extraArgs: {
messageAreaTag : this.getSelectedAreaTag(this.initialFocusIndex),
messageAreaTag: this.getSelectedAreaTag(
this.initialFocusIndex
),
messageList: this.config.messageList,
messageIndex: this.initialFocusIndex,
lastMessageNextExit: true,
}
},
};
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;
modOpts.extraArgs.toJSON = function () {
const logMsgList = (self.config.messageList.length <= 4) ?
self.config.messageList :
self.config.messageList.slice(0, 2).concat(self.config.messageList.slice(-2));
const logMsgList =
self.config.messageList.length <= 4
? self.config.messageList
: self.config.messageList
.slice(0, 2)
.concat(self.config.messageList.slice(-2));
return {
// 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 {
return cb(null);
}
@ -110,25 +135,42 @@ exports.getModule = class MessageListModule extends MessageAreaConfTempSwitcher(
}
// 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);
},
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);
if (this.selectedMessageForDelete) {
this.selectedMessageForDelete.deleteMessage(this.client.user, 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 {
this.client.log.info(`User deleted message: ${this.selectedMessageForDelete.messageUuid}`);
this.config.messageList.splice(msgListView.focusedItemIndex, 1);
this.updateMessageNumbersAfterDelete(msgListView.focusedItemIndex);
this.client.log.info(
`User deleted message: ${this.selectedMessageForDelete.messageUuid}`
);
this.config.messageList.splice(
msgListView.focusedItemIndex,
1
);
this.updateMessageNumbersAfterDelete(
msgListView.focusedItemIndex
);
msgListView.setItems(this.config.messageList);
}
this.selectedMessageForDelete = null;
msgListView.redraw();
this.populateCustomLabelsForSelected(msgListView.focusedItemIndex);
this.populateCustomLabelsForSelected(
msgListView.focusedItemIndex
);
return cb(null);
});
} else {
@ -136,7 +178,9 @@ exports.getModule = class MessageListModule extends MessageAreaConfTempSwitcher(
}
},
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);
return cb(null);
},
@ -146,7 +190,7 @@ exports.getModule = class MessageListModule extends MessageAreaConfTempSwitcher(
}
return this.markAllMessagesAsRead(cb);
}
},
};
}
@ -171,7 +215,8 @@ exports.getModule = class MessageListModule extends MessageAreaConfTempSwitcher(
if (this.config.messageAreaTag) {
this.tempMessageConfAndAreaSwitch(this.config.messageAreaTag);
} 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) {
const formatObj = Object.assign(
{
msgNumSelected : (selectedIndex + 1),
msgNumSelected: selectedIndex + 1,
msgNumTotal: this.config.messageList.length,
},
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) {
@ -199,7 +248,9 @@ exports.getModule = class MessageListModule extends MessageAreaConfTempSwitcher(
}
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;
async.series(
@ -207,7 +258,7 @@ exports.getModule = class MessageListModule extends MessageAreaConfTempSwitcher(
function loadFromConfig(callback) {
const loadOpts = {
callingMenu: self,
mciMap : mciData.menu
mciMap: mciData.menu,
};
return vc.loadFromMenuConfig(loadOpts, callback);
@ -218,17 +269,25 @@ exports.getModule = class MessageListModule extends MessageAreaConfTempSwitcher(
//
if (_.isArray(self.config.messageList)) {
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) {
return callback(new Error('No messages in area'));
}
self.config.messageList = msgList;
return callback(err);
});
}
);
},
function getLastReadMessageId(callback) {
// 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);
}
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;
return callback(null); // ignore any errors, e.g. missing value
});
}
);
},
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 regIndicator = ' '.repeat(newIndicator.length); // fill with space to avoid draw issues
let msgNum = 1;
self.config.messageList.forEach((listItem, index) => {
listItem.msgNum = msgNum++;
listItem.ts = moment(listItem.modTimestamp).format(dateTimeFormat);
const isNew = _.isBoolean(listItem.isNew) ? listItem.isNew : listItem.messageId > self.lastReadId;
listItem.ts = moment(listItem.modTimestamp).format(
dateTimeFormat
);
const isNew = _.isBoolean(listItem.isNew)
? listItem.isNew
: listItem.messageId > self.lastReadId;
listItem.newIndicator = isNew ? newIndicator : regIndicator;
if(_.isUndefined(self.initialFocusIndex) && listItem.messageId > self.lastReadId) {
if (
_.isUndefined(self.initialFocusIndex) &&
listItem.messageId > self.lastReadId
) {
self.initialFocusIndex = index;
}
@ -280,7 +352,10 @@ exports.getModule = class MessageListModule extends MessageAreaConfTempSwitcher(
],
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);
}
@ -329,15 +404,22 @@ exports.getModule = class MessageListModule extends MessageAreaConfTempSwitcher(
}
});
const regIndicator = ' '.repeat( (this.menuConfig.config.newIndicator || '*').length );
async.forEachOf(areaHighestIds, (highestId, areaTag, nextArea) => {
const regIndicator = ' '.repeat(
(this.menuConfig.config.newIndicator || '*').length
);
async.forEachOf(
areaHighestIds,
(highestId, areaTag, nextArea) => {
messageArea.updateMessageAreaLastReadId(
this.client.user.userId,
areaTag,
highestId,
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 {
// update newIndicator on messages
this.config.messageList.forEach(msg => {
@ -345,17 +427,24 @@ exports.getModule = class MessageListModule extends MessageAreaConfTempSwitcher(
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.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 cb(null);
});
}
);
}
updateMessageNumbersAfterDelete(startIndex) {
@ -383,7 +472,11 @@ exports.getModule = class MessageListModule extends MessageAreaConfTempSwitcher(
if (!this.selectedMessageForDelete.userHasDeleteRights(this.client.user)) {
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
@ -392,9 +485,15 @@ exports.getModule = class MessageListModule extends MessageAreaConfTempSwitcher(
}
promptConfirmDelete(cb) {
const promptXyView = this.viewControllers.allViews.getView(MciViewIds.allViews.delPromptXy);
const promptXyView = this.viewControllers.allViews.getView(
MciViewIds.allViews.delPromptXy
);
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 = {
@ -408,7 +507,9 @@ exports.getModule = class MessageListModule extends MessageAreaConfTempSwitcher(
{
formName: 'delPrompt',
formId: FormIds.delPrompt,
promptName : this.config.deleteMessageFromListPrompt || 'deleteMessageFromListPrompt',
promptName:
this.config.deleteMessageFromListPrompt ||
'deleteMessageFromListPrompt',
prevFormName: 'allViews',
position: promptXyView.position,
},

View File

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

View File

@ -20,5 +20,4 @@ MessageScanTossModule.prototype.shutdown = function(cb) {
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+????)
// *
const SPECIAL_KEY_MAP_DEFAULT = {
'line feed': ['return'],
exit: ['esc'],
@ -129,9 +128,11 @@ function MultiLineEditTextView(options) {
this.cursorPos = { col: 0, row: 0 };
this.getSGRFor = function (sgrFor) {
return {
return (
{
text: self.getSGR(),
}[sgrFor] || self.getSGR();
}[sgrFor] || self.getSGR()
);
};
this.isEditMode = function () {
@ -168,7 +169,11 @@ function MultiLineEditTextView(options) {
};
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) {
@ -287,7 +292,7 @@ function MultiLineEditTextView(options) {
this.getOutputText = function (startIndex, endIndex, eolMarker, options) {
const lines = self.getTextLines(startIndex, endIndex);
let text = '';
const re = new RegExp('\\t{1,' + (self.tabWidth) + '}', 'g');
const re = new RegExp('\\t{1,' + self.tabWidth + '}', 'g');
lines.forEach(line => {
text += line.text.replace(re, '\t');
@ -314,7 +319,10 @@ function MultiLineEditTextView(options) {
this.replaceCharacterInText = function (c, index, col) {
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) {
const nextEolIndex = self.getNextEndOfLineIndex(index);
const wrapped = self.wordWrapSingleLine(self.getContiguousText(index, nextEolIndex), 'tabsIntact');
const newLines = wrapped.wrapped.map(l => { return { text : l }; } );
const wrapped = self.wordWrapSingleLine(
self.getContiguousText(index, nextEolIndex),
'tabsIntact'
);
const newLines = wrapped.wrapped.map(l => {
return { text: l };
});
newLines[newLines.length - 1].eol = true;
Array.prototype.splice.apply(
self.textLines,
[ index, (nextEolIndex - index) + 1 ].concat(newLines));
[index, nextEolIndex - index + 1].concat(newLines)
);
return wrapped.firstWrapRange;
};
@ -365,7 +379,7 @@ function MultiLineEditTextView(options) {
self.textLines[index].text.slice(0, col - (count - 1)) +
self.textLines[index].text.slice(col + 1);
self.cursorPos.col -= (count - 1);
self.cursorPos.col -= count - 1;
self.updateTextWordWrap(index);
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
// 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;
self.textLines.splice(index, 1);
@ -418,7 +432,7 @@ function MultiLineEditTextView(options) {
self.textLines[index].text = [
self.textLines[index].text.slice(0, col),
c,
self.textLines[index].text.slice(col)
self.textLines[index].text.slice(col),
].join('');
self.cursorPos.col += c.length;
@ -449,18 +463,29 @@ function MultiLineEditTextView(options) {
self.client.term.rawWrite(ansi.right(cursorOffset));
} else {
// 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));
}
} else {
//
// We must only redraw from col -> end of current visible line
//
const absPos = self.getAbsolutePosition(self.cursorPos.row, self.cursorPos.col);
const renderText = self.getRenderText(index).slice(self.cursorPos.col - c.length);
const absPos = self.getAbsolutePosition(
self.cursorPos.row,
self.cursorPos.col
);
const renderText = self
.getRenderText(index)
.slice(self.cursorPos.col - c.length);
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
);
}
@ -500,23 +525,27 @@ function MultiLineEditTextView(options) {
};
this.wordWrapSingleLine = function (line, tabHandling = 'expand') {
return wordWrapText(
line,
{
return wordWrapText(line, {
width: self.dimens.width,
tabHandling: tabHandling,
tabWidth: self.tabWidth,
tabChar: '\t',
}
);
});
};
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
self.textLines = lines.slice(0, -1).map(l => {
self.textLines = lines
.slice(0, -1)
.map(l => {
return { text: l };
}).concat( { text : lines[lines.length - 1], eol : termWithEol } );
})
.concat({ text: lines[lines.length - 1], eol: termWithEol });
} else {
// insert somewhere in textLines...
if (index > self.textLines.length) {
@ -524,24 +553,24 @@ function MultiLineEditTextView(options) {
self.textLines.splice(
self.textLines.length,
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 };
}).concat( { text : lines[lines.length - 1], eol : termWithEol } );
})
.concat({ text: lines[lines.length - 1], eol: termWithEol });
self.textLines.splice(
index,
0,
...newLines
);
self.textLines.splice(index, 0, ...newLines);
}
};
this.setAnsiWithOptions = function (ansi, options, cb) {
function setLines(text) {
text = strUtil.splitTextAtTerms(text);
@ -634,7 +663,6 @@ function MultiLineEditTextView(options) {
self.client.term.rawWrite(ansi.goto(absPos.row, absPos.col));
};
this.keyPressCharacter = function (c) {
var index = self.getTextLinesIndex();
@ -676,9 +704,9 @@ function MultiLineEditTextView(options) {
};
this.keyPressDown = function () {
var lastVisibleRow = Math.min(
self.dimens.height,
(self.textLines.length - self.topVisibleIndex)) - 1;
var lastVisibleRow =
Math.min(self.dimens.height, self.textLines.length - self.topVisibleIndex) -
1;
if (self.cursorPos.row < lastVisibleRow) {
self.cursorPos.row++;
@ -780,7 +808,10 @@ function MultiLineEditTextView(options) {
var index = self.getTextLinesIndex();
var nextEolIndex = self.getNextEndOfLineIndex(index);
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 });
for (var i = 1; i < newLines.length; ++i) {
@ -790,7 +821,8 @@ function MultiLineEditTextView(options) {
Array.prototype.splice.apply(
self.textLines,
[ index, (nextEolIndex - index) + 1 ].concat(newLines));
[index, nextEolIndex - index + 1].concat(newLines)
);
// redraw from current row to end of visible area
self.redrawRows(self.cursorPos.row, self.dimens.height);
@ -805,7 +837,11 @@ function MultiLineEditTextView(options) {
this.keyPressTab = function () {
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();
};
@ -831,16 +867,12 @@ function MultiLineEditTextView(options) {
--col;
}
count = (self.cursorPos.col - col);
count = self.cursorPos.col - col;
} else {
count = 1;
}
self.removeCharactersFromText(
index,
self.cursorPos.col,
'backspace',
count);
self.removeCharactersFromText(index, self.cursorPos.col, 'backspace', count);
} else {
//
// Delete character at end of line previous.
@ -858,22 +890,17 @@ function MultiLineEditTextView(options) {
this.keyPressDelete = function () {
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
//
self.removeCharactersFromText(
lineIndex,
0,
'delete line'
);
self.removeCharactersFromText(lineIndex, 0, 'delete line');
} else {
self.removeCharactersFromText(
lineIndex,
self.cursorPos.col,
'delete',
1
);
self.removeCharactersFromText(lineIndex, self.cursorPos.col, 'delete', 1);
}
self.emitEditPosition();
@ -881,10 +908,7 @@ function MultiLineEditTextView(options) {
this.keyPressDeleteLine = function () {
if (self.textLines.length > 0) {
self.removeCharactersFromText(
self.getTextLinesIndex(),
0,
'delete line');
self.removeCharactersFromText(self.getTextLinesIndex(), 0, 'delete line');
}
self.emitEditPosition();
@ -930,7 +954,10 @@ function MultiLineEditTextView(options) {
// Jump to the tabstop nearest the cursor
//
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) {
@ -960,7 +987,7 @@ function MultiLineEditTextView(options) {
this.cursorEndOfDocument = function () {
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.redraw();
@ -1070,7 +1097,10 @@ MultiLineEditTextView.prototype.setFocus = function(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.addText(text, options);
/*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 = [];
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);
switch (options.scrollMode) {
@ -1139,19 +1176,23 @@ MultiLineEditTextView.prototype.setPropertyValue = function(propName, value) {
};
const HANDLED_SPECIAL_KEYS = [
'up', 'down', 'left', 'right',
'home', 'end',
'page up', 'page down',
'up',
'down',
'left',
'right',
'home',
'end',
'page up',
'page down',
'line feed',
'insert',
'tab',
'backspace', 'delete',
'backspace',
'delete',
'delete line',
];
const PREVIEW_MODE_KEYS = [
'up', 'down', 'page up', 'page down'
];
const PREVIEW_MODE_KEYS = ['up', 'down', 'page up', 'page down'];
MultiLineEditTextView.prototype.onKeyPress = function (ch, key) {
const self = this;
@ -1160,8 +1201,10 @@ MultiLineEditTextView.prototype.onKeyPress = function(ch, key) {
if (key) {
HANDLED_SPECIAL_KEYS.forEach(function aKey(specialKey) {
if (self.isKeyMapped(specialKey, key.name)) {
if(self.isPreviewMode() && -1 === PREVIEW_MODE_KEYS.indexOf(specialKey)) {
if (
self.isPreviewMode() &&
-1 === PREVIEW_MODE_KEYS.indexOf(specialKey)
) {
return;
}
@ -1208,7 +1251,7 @@ MultiLineEditTextView.prototype.getEditPosition = function() {
return {
row: this.getTextLinesIndex(this.cursorPos.row),
col: this.cursorPos.col,
percent : Math.floor(((currentIndex / this.textLines.length) * 100)),
percent: Math.floor((currentIndex / this.textLines.length) * 100),
below: this.getRemainingLinesBelowRow(),
};
};

View File

@ -5,9 +5,7 @@
const MenuModule = require('./menu_module.js').MenuModule;
const Message = require('./message.js');
const UserProps = require('./user_property.js');
const {
filterMessageListByReadACS
} = require('./message_area.js');
const { filterMessageListByReadACS } = require('./message_area.js');
exports.moduleInfo = {
name: 'My Messages',
@ -22,7 +20,10 @@ exports.getModule = class MyMessagesModule extends MenuModule {
initSequence() {
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',
resultType: 'messageList',
limit: 1024 * 16, // we want some sort of limit...
@ -30,7 +31,10 @@ exports.getModule = class MyMessagesModule extends MenuModule {
Message.findMessages(filter, (err, messageList) => {
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();
}
@ -52,7 +56,7 @@ exports.getModule = class MyMessagesModule extends MenuModule {
const menuOpts = {
extraArgs: {
messageList: this.messageList,
noUpdateLastReadId : true
noUpdateLastReadId: true,
},
menuFlags: ['popParent'],
};

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