First pass formatting with Prettier
* Added .prettierrc.json * Added .prettierignore * Formatted
This commit is contained in:
parent
eecfb33ad5
commit
4881c2123a
|
@ -3,9 +3,7 @@
|
|||
"es6": true,
|
||||
"node": true
|
||||
},
|
||||
"extends": [
|
||||
"eslint:recommended"
|
||||
],
|
||||
"extends": ["eslint:recommended"],
|
||||
"rules": {
|
||||
"indent": [
|
||||
"error",
|
||||
|
@ -14,18 +12,9 @@
|
|||
"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"
|
||||
},
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
art
|
||||
config
|
||||
db
|
||||
docs
|
||||
drop
|
||||
gopher
|
||||
logs
|
||||
misc
|
||||
www
|
||||
mkdocs.yml
|
||||
*.md
|
||||
.github
|
|
@ -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,29 +97,43 @@ 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');
|
||||
'Too many active instances'
|
||||
);
|
||||
|
||||
if (_.isString(self.config.tooManyArt)) {
|
||||
theme.displayThemeArt( { client : self.client, name : self.config.tooManyArt }, function displayed() {
|
||||
theme.displayThemeArt(
|
||||
{ client: self.client, name: self.config.tooManyArt },
|
||||
function displayed() {
|
||||
self.pausePrompt(() => {
|
||||
return callback(Errors.AccessDenied('Too many active instances'));
|
||||
});
|
||||
return callback(
|
||||
Errors.AccessDenied(
|
||||
'Too many active instances'
|
||||
)
|
||||
);
|
||||
});
|
||||
}
|
||||
);
|
||||
} 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 +146,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 {
|
||||
|
@ -187,7 +203,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.'
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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}\\`;
|
||||
}
|
|
@ -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 {
|
|||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -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');
|
||||
|
|
17
core/art.js
17
core/art.js
|
@ -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));
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
|
78
core/bbs.js
78
core/bbs.js
|
@ -30,11 +30,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/
|
||||
|
||||
|
@ -71,7 +72,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';
|
||||
|
@ -84,7 +89,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
|
||||
}
|
||||
|
@ -109,18 +116,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) {
|
||||
|
@ -145,7 +156,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]);
|
||||
}
|
||||
|
@ -172,7 +185,7 @@ function shutdownSystem() {
|
|||
},
|
||||
function stopMsgNetwork(callback) {
|
||||
require('./msg_network.js').shutdown(callback);
|
||||
}
|
||||
},
|
||||
],
|
||||
() => {
|
||||
console.info('Goodbye!');
|
||||
|
@ -186,16 +199,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();
|
||||
|
@ -237,8 +259,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,
|
||||
],
|
||||
};
|
||||
|
||||
|
@ -248,9 +273,13 @@ function initialize(cb) {
|
|||
return User.getUserName(1, 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) => {
|
||||
|
@ -282,7 +311,10 @@ function initialize(cb) {
|
|||
|
||||
StatLog.findSystemLogEntries(filter, (err, callsToday) => {
|
||||
if (!err) {
|
||||
StatLog.setNonPersistentSystemStat(SysProps.LoginsToday, callsToday);
|
||||
StatLog.setNonPersistentSystemStat(
|
||||
SysProps.LoginsToday,
|
||||
callsToday
|
||||
);
|
||||
}
|
||||
return callback(null);
|
||||
});
|
||||
|
@ -312,7 +344,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) {
|
||||
|
@ -320,7 +353,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);
|
||||
|
@ -328,7 +362,7 @@ function initialize(cb) {
|
|||
},
|
||||
function listenUserEventsForStatLog(callback) {
|
||||
return require('./stat_log.js').initUserEvents(callback);
|
||||
}
|
||||
},
|
||||
],
|
||||
function onComplete(err) {
|
||||
return cb(err);
|
||||
|
|
|
@ -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) {
|
||||
|
|
122
core/bbs_list.js
122
core/bbs_list.js
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
112
core/client.js
112
core/client.js
|
@ -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;
|
||||
|
||||
|
@ -591,7 +608,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) {
|
||||
|
|
|
@ -23,12 +23,11 @@ exports.clientConnections = clientConnections;
|
|||
|
||||
function getActiveConnections(authUsersOnly = false) {
|
||||
return clientConnections.filter(conn => {
|
||||
return ((authUsersOnly && conn.user.isAuthenticated()) || !authUsersOnly);
|
||||
return (authUsersOnly && conn.user.isAuthenticated()) || !authUsersOnly;
|
||||
});
|
||||
}
|
||||
|
||||
function getActiveConnectionList(authUsersOnly) {
|
||||
|
||||
if (!_.isBoolean(authUsersOnly)) {
|
||||
authUsersOnly = true;
|
||||
}
|
||||
|
@ -52,7 +51,10 @@ function getActiveConnectionList(authUsersOnly) {
|
|||
entry.location = ac.user.properties[UserProps.Location];
|
||||
entry.affils = entry.affiliation = ac.user.properties[UserProps.Affiliations];
|
||||
|
||||
const diff = now.diff(moment(ac.user.properties[UserProps.LastLoginTs]), 'minutes');
|
||||
const diff = now.diff(
|
||||
moment(ac.user.properties[UserProps.LastLoginTs]),
|
||||
'minutes'
|
||||
);
|
||||
entry.timeOn = moment.duration(diff, 'minutes');
|
||||
}
|
||||
return entry;
|
||||
|
@ -72,9 +74,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);
|
||||
|
@ -96,10 +101,10 @@ function addNewClient(client, clientSock) {
|
|||
|
||||
client.log.info(connInfo, 'Client connected');
|
||||
|
||||
Events.emit(
|
||||
Events.getSystemEvents().ClientConnected,
|
||||
{ client : client, connectionCount : clientConnections.length }
|
||||
);
|
||||
Events.emit(Events.getSystemEvents().ClientConnected, {
|
||||
client: client,
|
||||
connectionCount: clientConnections.length,
|
||||
});
|
||||
|
||||
return nodeId;
|
||||
}
|
||||
|
@ -120,14 +125,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,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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) {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
|
|
@ -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
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
|
@ -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;
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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
|
||||
|
@ -150,7 +157,9 @@ const ansiQuerySyncTermFontSupport = (client, cb) => {
|
|||
const [_, w] = pos;
|
||||
if (w === 1) {
|
||||
// cursor didn't move
|
||||
client.log.info('Client supports SyncTERM fonts or properly ignores unknown ESC sequence');
|
||||
client.log.info(
|
||||
'Client supports SyncTERM fonts or properly ignores unknown ESC sequence'
|
||||
);
|
||||
client.term.syncTermFontsEnabled = true;
|
||||
}
|
||||
},
|
||||
|
@ -158,8 +167,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 +196,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 +207,7 @@ function ansiQueryTermSizeIfNeeded(client, cb) {
|
|||
{
|
||||
termWidth: client.term.termWidth,
|
||||
termHeight: client.term.termHeight,
|
||||
source : 'ANSI CPR'
|
||||
source: 'ANSI CPR',
|
||||
},
|
||||
'Window size updated'
|
||||
);
|
||||
|
@ -226,8 +238,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 +266,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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
},
|
||||
};
|
|
@ -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
|
||||
);
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
|
18
core/door.js
18
core/door.js
|
@ -32,7 +32,10 @@ module.exports = class Door {
|
|||
});
|
||||
|
||||
conn.once('error', err => {
|
||||
this.client.log.info( { error : err.message }, 'Door socket server connection');
|
||||
this.client.log.info(
|
||||
{ error: err.message },
|
||||
'Door socket server connection'
|
||||
);
|
||||
return this.restoreIo(conn);
|
||||
});
|
||||
|
||||
|
@ -93,7 +96,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 +109,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 +125,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'
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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'
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -108,7 +112,10 @@ class ScheduledEvent {
|
|||
}
|
||||
|
||||
executeAction(reason, cb) {
|
||||
Log.info( { eventName : this.name, action : this.action, reason : reason }, 'Executing scheduled event action...');
|
||||
Log.info(
|
||||
{ eventName: this.name, action: this.action, reason: reason },
|
||||
'Executing scheduled event action...'
|
||||
);
|
||||
|
||||
if ('method' === this.action.type) {
|
||||
const modulePath = path.join(__dirname, '../', this.action.location); // enigma-bbs base + supplied location (path/file.js')
|
||||
|
@ -117,8 +124,13 @@ class ScheduledEvent {
|
|||
methodModule[this.action.what](this.action.args, err => {
|
||||
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 +138,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 +156,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 +171,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 +232,6 @@ EventSchedulerModule.loadAndStart = function(cb) {
|
|||
};
|
||||
|
||||
EventSchedulerModule.prototype.startup = function (cb) {
|
||||
|
||||
this.eventTimers = [];
|
||||
const self = this;
|
||||
|
||||
|
@ -234,24 +251,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 +286,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}`
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
);
|
||||
}
|
||||
|
||||
displayArtAndPrepViewController(name, options, cb) {
|
||||
|
@ -337,7 +375,10 @@ exports.getModule = class FileAreaList extends MenuModule {
|
|||
vcOpts.noInput = options.noInput;
|
||||
}
|
||||
|
||||
const vc = self.addViewController(name, new ViewController(vcOpts));
|
||||
const vc = self.addViewController(
|
||||
name,
|
||||
new ViewController(vcOpts)
|
||||
);
|
||||
|
||||
if ('details' === name) {
|
||||
try {
|
||||
|
@ -346,7 +387,11 @@ exports.getModule = class FileAreaList extends MenuModule {
|
|||
bottom: artData.mciMap.XY3.position,
|
||||
};
|
||||
} catch (e) {
|
||||
return callback(Errors.DoesNotExist('Missing XY2 and XY3 position indicators!'));
|
||||
return callback(
|
||||
Errors.DoesNotExist(
|
||||
'Missing XY2 and XY3 position indicators!'
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -361,7 +406,6 @@ exports.getModule = class FileAreaList extends MenuModule {
|
|||
|
||||
self.viewControllers[name].setFocus(true);
|
||||
return callback(null);
|
||||
|
||||
},
|
||||
],
|
||||
err => {
|
||||
|
@ -383,27 +427,38 @@ 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);
|
||||
},
|
||||
function prepArtAndViewController(callback) {
|
||||
return self.displayArtAndPrepViewController('browse', { clearScreen : clearScreen }, callback);
|
||||
return self.displayArtAndPrepViewController(
|
||||
'browse',
|
||||
{ clearScreen: clearScreen },
|
||||
callback
|
||||
);
|
||||
},
|
||||
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
|
||||
|
@ -415,17 +470,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;
|
||||
}
|
||||
|
@ -444,9 +505,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) {
|
||||
|
@ -462,17 +526,26 @@ exports.getModule = class FileAreaList extends MenuModule {
|
|||
async.series(
|
||||
[
|
||||
function prepArtAndViewController(callback) {
|
||||
return self.displayArtAndPrepViewController('details', { clearScreen : true }, callback);
|
||||
return self.displayArtAndPrepViewController(
|
||||
'details',
|
||||
{ clearScreen: true },
|
||||
callback
|
||||
);
|
||||
},
|
||||
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 => {
|
||||
|
@ -488,7 +561,7 @@ exports.getModule = class FileAreaList extends MenuModule {
|
|||
});
|
||||
|
||||
return callback(null);
|
||||
}
|
||||
},
|
||||
],
|
||||
err => {
|
||||
return cb(err);
|
||||
|
@ -497,15 +570,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) {
|
||||
|
@ -515,10 +584,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);
|
||||
|
@ -531,12 +608,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,
|
||||
|
@ -549,10 +628,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);
|
||||
}
|
||||
|
@ -561,11 +644,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);
|
||||
|
@ -578,9 +662,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(
|
||||
|
@ -605,19 +689,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]);
|
||||
|
@ -626,7 +718,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) => {
|
||||
|
@ -640,7 +734,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);
|
||||
}
|
||||
}
|
||||
|
@ -658,9 +755,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();
|
||||
|
@ -678,7 +776,11 @@ exports.getModule = class FileAreaList extends MenuModule {
|
|||
gotoTopPos();
|
||||
}
|
||||
|
||||
return self.displayArtAndPrepViewController(name, { clearScreen : false, noInput : true }, callback);
|
||||
return self.displayArtAndPrepViewController(
|
||||
name,
|
||||
{ clearScreen: false, noInput: true },
|
||||
callback
|
||||
);
|
||||
},
|
||||
function populateViews(callback) {
|
||||
self.lastDetailsViewController = self.viewControllers[name];
|
||||
|
@ -686,7 +788,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);
|
||||
}
|
||||
|
@ -703,7 +807,9 @@ exports.getModule = class FileAreaList extends MenuModule {
|
|||
}
|
||||
);
|
||||
} else {
|
||||
nfoView.setText(self.currentFileEntry.entryInfo.descLong);
|
||||
nfoView.setText(
|
||||
self.currentFileEntry.entryInfo.descLong
|
||||
);
|
||||
return callback(null);
|
||||
}
|
||||
}
|
||||
|
@ -720,7 +826,7 @@ exports.getModule = class FileAreaList extends MenuModule {
|
|||
function setLabels(callback) {
|
||||
self.populateCustomLabels(name, MciViewIds[name].customRangeStart);
|
||||
return callback(null);
|
||||
}
|
||||
},
|
||||
],
|
||||
err => {
|
||||
if (cb) {
|
||||
|
@ -731,7 +837,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);
|
||||
|
|
|
@ -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) {
|
||||
|
@ -481,17 +537,13 @@ class FileAreaWebAccess {
|
|||
return callback(null, user);
|
||||
},
|
||||
function sendEvent(user, callback) {
|
||||
Events.emit(
|
||||
Events.getSystemEvents().UserDownload,
|
||||
{
|
||||
Events.emit(Events.getSystemEvents().UserDownload, {
|
||||
user: user,
|
||||
files: fileEntries,
|
||||
}
|
||||
);
|
||||
});
|
||||
return callback(null);
|
||||
}
|
||||
]
|
||||
);
|
||||
},
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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'));
|
||||
}
|
||||
|
@ -168,11 +192,15 @@ exports.getModule = class FileBaseDownloadQueueManager extends MenuModule {
|
|||
async.series(
|
||||
[
|
||||
function prepArtAndViewController(callback) {
|
||||
return self.displayArtAndPrepViewController('queueManager', { clearScreen : clearScreen }, callback);
|
||||
return self.displayArtAndPrepViewController(
|
||||
'queueManager',
|
||||
{ clearScreen: clearScreen },
|
||||
callback
|
||||
);
|
||||
},
|
||||
function populateViews(callback) {
|
||||
return self.updateDownloadQueueView(callback);
|
||||
}
|
||||
},
|
||||
],
|
||||
err => {
|
||||
if (cb) {
|
||||
|
@ -213,7 +241,10 @@ exports.getModule = class FileBaseDownloadQueueManager extends MenuModule {
|
|||
vcOpts.noInput = options.noInput;
|
||||
}
|
||||
|
||||
const vc = self.addViewController(name, new ViewController(vcOpts));
|
||||
const vc = self.addViewController(
|
||||
name,
|
||||
new ViewController(vcOpts)
|
||||
);
|
||||
|
||||
const loadOpts = {
|
||||
callingMenu: self,
|
||||
|
@ -226,7 +257,6 @@ exports.getModule = class FileBaseDownloadQueueManager extends MenuModule {
|
|||
|
||||
self.viewControllers[name].setFocus(true);
|
||||
return callback(null);
|
||||
|
||||
},
|
||||
],
|
||||
err => {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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),
|
||||
};
|
||||
|
||||
|
@ -173,20 +185,34 @@ exports.getModule = class FileBaseWebDownloadQueueManager extends MenuModule {
|
|||
async.series(
|
||||
[
|
||||
function prepArtAndViewController(callback) {
|
||||
return self.displayArtAndPrepViewController('queueManager', { clearScreen : clearScreen }, callback);
|
||||
return self.displayArtAndPrepViewController(
|
||||
'queueManager',
|
||||
{ clearScreen: clearScreen },
|
||||
callback
|
||||
);
|
||||
},
|
||||
function prepareQueueDownloadLinks(callback) {
|
||||
const webDlExpireTimeFormat = self.menuConfig.config.webDlExpireTimeFormat || 'YYYY-MMM-DD @ h:mm';
|
||||
const webDlExpireTimeFormat =
|
||||
self.menuConfig.config.webDlExpireTimeFormat ||
|
||||
'YYYY-MMM-DD @ h:mm';
|
||||
|
||||
const config = Config();
|
||||
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,
|
||||
|
@ -198,26 +224,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) {
|
||||
|
@ -258,7 +298,10 @@ exports.getModule = class FileBaseWebDownloadQueueManager extends MenuModule {
|
|||
vcOpts.noInput = options.noInput;
|
||||
}
|
||||
|
||||
const vc = self.addViewController(name, new ViewController(vcOpts));
|
||||
const vc = self.addViewController(
|
||||
name,
|
||||
new ViewController(vcOpts)
|
||||
);
|
||||
|
||||
const loadOpts = {
|
||||
callingMenu: self,
|
||||
|
@ -271,7 +314,6 @@ exports.getModule = class FileBaseWebDownloadQueueManager extends MenuModule {
|
|||
|
||||
self.viewControllers[name].setFocus(true);
|
||||
return callback(null);
|
||||
|
||||
},
|
||||
],
|
||||
err => {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,16 +137,21 @@ 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 }, `Successfully sent ${sentFiles.length} file(s)` );
|
||||
this.client.log.info(
|
||||
{ sentFiles: sentFiles },
|
||||
`Successfully sent ${sentFiles.length} file(s)`
|
||||
);
|
||||
}
|
||||
return cb(err);
|
||||
});
|
||||
|
@ -205,13 +212,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 +264,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 +282,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 +301,11 @@ exports.getModule = class TransferFileModule extends MenuModule {
|
|||
|
||||
return nextFile(null);
|
||||
});
|
||||
}, () => {
|
||||
},
|
||||
() => {
|
||||
return cb(null);
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
@ -306,12 +324,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 +346,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 +366,7 @@ exports.getModule = class TransferFileModule extends MenuModule {
|
|||
}
|
||||
|
||||
return callback(null, args);
|
||||
}
|
||||
},
|
||||
],
|
||||
(err, args) => {
|
||||
return cb(err, args);
|
||||
|
@ -352,10 +377,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 +392,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 +400,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 +420,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 +489,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 +550,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 +570,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,10 +581,19 @@ 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);
|
||||
|
@ -549,18 +603,24 @@ exports.getModule = class TransferFileModule extends MenuModule {
|
|||
});
|
||||
|
||||
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;
|
||||
|
@ -568,15 +628,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);
|
||||
|
||||
return cb(null);
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
initSequence() {
|
||||
|
@ -615,13 +685,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;
|
||||
}
|
||||
|
@ -636,7 +703,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);
|
||||
|
@ -647,7 +717,7 @@ exports.getModule = class TransferFileModule extends MenuModule {
|
|||
} else {
|
||||
return self.updateRecvStats(callback);
|
||||
}
|
||||
}
|
||||
},
|
||||
],
|
||||
err => {
|
||||
if (err) {
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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 => {
|
||||
|
|
|
@ -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
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
462
core/fse.js
462
core/fse.js
|
@ -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,
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
};
|
|
@ -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
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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!
|
||||
|
|
|
@ -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 => {
|
||||
|
|
|
@ -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 };
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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');
|
||||
|
||||
|
@ -19,7 +20,6 @@ const _ = require('lodash');
|
|||
const iconvDecode = require('iconv-lite').decode;
|
||||
|
||||
exports.MenuModule = class MenuModule extends PluginModule {
|
||||
|
||||
constructor(options) {
|
||||
super(options);
|
||||
|
||||
|
@ -30,7 +30,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';
|
||||
|
@ -59,8 +63,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(
|
||||
|
@ -81,7 +88,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;
|
||||
}
|
||||
|
@ -100,7 +110,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);
|
||||
|
@ -130,7 +144,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;
|
||||
}
|
||||
|
@ -141,13 +158,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 */
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
|
@ -156,7 +177,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) {
|
||||
|
@ -183,7 +206,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 = {};
|
||||
|
@ -197,7 +220,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
|
||||
}
|
||||
|
||||
|
@ -220,7 +246,8 @@ exports.MenuModule = class MenuModule extends PluginModule {
|
|||
this.reload(err => {
|
||||
return done(err, err ? false : true);
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
getSaveState() {
|
||||
|
@ -307,7 +334,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;
|
||||
|
@ -327,7 +357,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() {
|
||||
|
@ -335,7 +368,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) {
|
||||
|
@ -349,7 +382,10 @@ exports.MenuModule = class MenuModule extends PluginModule {
|
|||
}
|
||||
};
|
||||
|
||||
if(_.has(this.menuConfig, 'runtime.autoNext') && true === this.menuConfig.runtime.autoNext) {
|
||||
if (
|
||||
_.has(this.menuConfig, 'runtime.autoNext') &&
|
||||
true === this.menuConfig.runtime.autoNext
|
||||
) {
|
||||
if (this.hasNextTimeout()) {
|
||||
setTimeout(() => {
|
||||
return gotoNextMenu();
|
||||
|
@ -374,7 +410,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);
|
||||
|
@ -404,10 +443,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);
|
||||
|
@ -425,19 +467,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(
|
||||
|
@ -479,17 +520,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) {
|
||||
|
@ -512,7 +549,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 = {};
|
||||
|
@ -542,7 +583,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());
|
||||
|
@ -569,7 +612,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);
|
||||
|
@ -589,14 +632,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 {
|
||||
textView.setText(text);
|
||||
|
@ -665,10 +717,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}`;
|
||||
|
@ -684,6 +744,12 @@ 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})`
|
||||
)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
});
|
||||
|
|
|
@ -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"');
|
||||
|
|
|
@ -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)) {
|
||||
|
@ -245,11 +252,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),
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
@ -272,16 +277,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':
|
||||
|
@ -290,7 +315,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);
|
||||
|
@ -299,13 +326,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)) {
|
||||
|
@ -319,4 +346,3 @@ MenuView.prototype.setHotKeys = function(hotKeys) {
|
|||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
|
300
core/message.js
300
core/message.js
|
@ -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));
|
||||
});
|
||||
|
||||
|
|
|
@ -51,22 +51,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);
|
||||
|
@ -149,7 +158,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;
|
||||
|
@ -179,7 +190,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;
|
||||
|
@ -210,7 +224,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;
|
||||
|
@ -241,7 +255,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
|
||||
|
@ -262,7 +279,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]);
|
||||
});
|
||||
}
|
||||
|
@ -320,8 +337,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);
|
||||
}
|
||||
|
@ -338,9 +360,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);
|
||||
}
|
||||
|
@ -368,20 +396,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);
|
||||
|
@ -424,7 +462,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) {
|
||||
|
@ -432,7 +472,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) {
|
||||
|
@ -509,15 +551,14 @@ function getNewMessagesInAreaForUser(userId, areaTag, cb) {
|
|||
});
|
||||
}
|
||||
|
||||
function getMessageListForArea(client, areaTag, filter, cb)
|
||||
{
|
||||
function getMessageListForArea(client, areaTag, filter, cb) {
|
||||
if (!cb && _.isFunction(filter)) {
|
||||
cb = filter;
|
||||
filter = {
|
||||
areaTag,
|
||||
resultType: 'messageList',
|
||||
sort: 'messageId',
|
||||
order : 'ascending'
|
||||
order: 'ascending',
|
||||
};
|
||||
} else {
|
||||
Object.assign(filter, { areaTag });
|
||||
|
@ -594,18 +635,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);
|
||||
|
@ -621,7 +669,7 @@ function persistMessage(message, cb) {
|
|||
},
|
||||
function recordToMessageNetworks(callback) {
|
||||
return msgNetRecord(message, callback);
|
||||
}
|
||||
},
|
||||
],
|
||||
cb
|
||||
);
|
||||
|
@ -629,7 +677,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);
|
||||
|
@ -645,11 +692,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);
|
||||
}
|
||||
|
@ -665,11 +719,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);
|
||||
}
|
||||
|
@ -708,7 +769,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;
|
||||
|
||||
|
@ -773,17 +833,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);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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'],
|
||||
};
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
181
core/mrc.js
181
core/mrc.js
|
@ -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();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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) {
|
||||
|
@ -49,7 +46,11 @@ exports.getModule = class AreaPostFSEModule extends FullScreenEditorModule {
|
|||
} else {
|
||||
// note: not logging 'from' here as it's part of client.log.xxxx()
|
||||
self.client.log.info(
|
||||
{ to : msg.toUserName, subject : msg.subject, uuid : msg.messageUuid },
|
||||
{
|
||||
to: msg.toUserName,
|
||||
subject: msg.subject,
|
||||
uuid: msg.messageUuid,
|
||||
},
|
||||
'Message persisted'
|
||||
);
|
||||
}
|
||||
|
@ -62,8 +63,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();
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
|
|
197
core/msg_list.js
197
core/msg_list.js
|
@ -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,
|
||||
},
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
|
@ -20,5 +20,4 @@ MessageScanTossModule.prototype.shutdown = function(cb) {
|
|||
return cb(null);
|
||||
};
|
||||
|
||||
MessageScanTossModule.prototype.record = function(/*message*/) {
|
||||
};
|
||||
MessageScanTossModule.prototype.record = function (/*message*/) {};
|
||||
|
|
|
@ -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(),
|
||||
};
|
||||
};
|
||||
|
|
|
@ -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'],
|
||||
};
|
||||
|
|
|
@ -46,7 +46,6 @@ exports.getModule = class NewScanModule extends MenuModule {
|
|||
constructor(options) {
|
||||
super(options);
|
||||
|
||||
|
||||
this.newScanFullExit = _.get(options, 'lastMenuResult.fullExit', false);
|
||||
|
||||
this.currentStep = Steps.MessageConfs;
|
||||
|
@ -70,12 +69,15 @@ exports.getModule = class NewScanModule extends MenuModule {
|
|||
if (!this.sortedMessageConfs) {
|
||||
const getAvailOpts = { includeSystemInternal: true }; // find new private messages, bulletins, etc.
|
||||
|
||||
this.sortedMessageConfs = _.map(msgArea.getAvailableMessageConferences(this.client, getAvailOpts), (v, k) => {
|
||||
this.sortedMessageConfs = _.map(
|
||||
msgArea.getAvailableMessageConferences(this.client, getAvailOpts),
|
||||
(v, k) => {
|
||||
return {
|
||||
confTag: k,
|
||||
conf: v,
|
||||
};
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
//
|
||||
// Sort conferences by name, other than 'system_internal' which should
|
||||
|
@ -86,7 +88,10 @@ exports.getModule = class NewScanModule extends MenuModule {
|
|||
if ('system_internal' === a.confTag) {
|
||||
return -1;
|
||||
} else {
|
||||
return a.conf.name.localeCompare(b.conf.name, { sensitivity : false, numeric : true } );
|
||||
return a.conf.name.localeCompare(b.conf.name, {
|
||||
sensitivity: false,
|
||||
numeric: true,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -111,8 +116,12 @@ exports.getModule = class NewScanModule extends MenuModule {
|
|||
|
||||
newScanMessageArea(conf, cb) {
|
||||
// :TODO: it would be nice to cache this - must be done by conf!
|
||||
const omitMessageAreaTags = valueAsArray(_.get(this, 'menuConfig.config.omitMessageAreaTags', []));
|
||||
const sortedAreas = msgArea.getSortedAvailMessageAreasByConfTag(conf.confTag, { client : this.client } ).filter(area => {
|
||||
const omitMessageAreaTags = valueAsArray(
|
||||
_.get(this, 'menuConfig.config.omitMessageAreaTags', [])
|
||||
);
|
||||
const sortedAreas = msgArea
|
||||
.getSortedAvailMessageAreasByConfTag(conf.confTag, { client: this.client })
|
||||
.filter(area => {
|
||||
return !omitMessageAreaTags.includes(area.areaTag);
|
||||
});
|
||||
const currentArea = sortedAreas[this.currentScanAux.area];
|
||||
|
@ -135,17 +144,21 @@ exports.getModule = class NewScanModule extends MenuModule {
|
|||
}
|
||||
},
|
||||
function updateStatusScanStarted(callback) {
|
||||
self.updateScanStatus(stringFormat(self.scanStartFmt, {
|
||||
self.updateScanStatus(
|
||||
stringFormat(self.scanStartFmt, {
|
||||
confName: conf.conf.name,
|
||||
confDesc: conf.conf.desc,
|
||||
areaName: currentArea.area.name,
|
||||
areaDesc : currentArea.area.desc
|
||||
}));
|
||||
areaDesc: currentArea.area.desc,
|
||||
})
|
||||
);
|
||||
return callback(null);
|
||||
},
|
||||
function getNewMessagesCountInArea(callback) {
|
||||
msgArea.getNewMessageCountInAreaForUser(
|
||||
self.client.user.userId, currentArea.areaTag, (err, newMessageCount) => {
|
||||
self.client.user.userId,
|
||||
currentArea.areaTag,
|
||||
(err, newMessageCount) => {
|
||||
callback(err, newMessageCount);
|
||||
}
|
||||
);
|
||||
|
@ -158,11 +171,14 @@ exports.getModule = class NewScanModule extends MenuModule {
|
|||
const nextModuleOpts = {
|
||||
extraArgs: {
|
||||
messageAreaTag: currentArea.areaTag,
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
return self.gotoMenu(self.menuConfig.config.newScanMessageList || 'newScanMessageList', nextModuleOpts);
|
||||
}
|
||||
return self.gotoMenu(
|
||||
self.menuConfig.config.newScanMessageList || 'newScanMessageList',
|
||||
nextModuleOpts
|
||||
);
|
||||
},
|
||||
],
|
||||
err => {
|
||||
return cb(err);
|
||||
|
@ -172,21 +188,28 @@ exports.getModule = class NewScanModule extends MenuModule {
|
|||
|
||||
newScanFileBase(cb) {
|
||||
// :TODO: add in steps
|
||||
const omitFileAreaTags = valueAsArray(_.get(this, 'menuConfig.config.omitFileAreaTags', []));
|
||||
const omitFileAreaTags = valueAsArray(
|
||||
_.get(this, 'menuConfig.config.omitFileAreaTags', [])
|
||||
);
|
||||
const filterCriteria = {
|
||||
newerThanFileId : FileBaseFilters.getFileBaseLastViewedFileIdByUser(this.client.user),
|
||||
areaTag : getAvailableFileAreaTags(this.client).filter(ft => !omitFileAreaTags.includes(ft)),
|
||||
newerThanFileId: FileBaseFilters.getFileBaseLastViewedFileIdByUser(
|
||||
this.client.user
|
||||
),
|
||||
areaTag: getAvailableFileAreaTags(this.client).filter(
|
||||
ft => !omitFileAreaTags.includes(ft)
|
||||
),
|
||||
order: 'ascending', // oldest first
|
||||
};
|
||||
|
||||
FileEntry.findFiles(
|
||||
filterCriteria,
|
||||
(err, fileIds) => {
|
||||
FileEntry.findFiles(filterCriteria, (err, fileIds) => {
|
||||
if (err || 0 === fileIds.length) {
|
||||
return cb(err ? err : Errors.DoesNotExist('No more new files'));
|
||||
}
|
||||
|
||||
FileBaseFilters.setFileBaseLastViewedFileIdForUser( this.client.user, fileIds[fileIds.length - 1] );
|
||||
FileBaseFilters.setFileBaseLastViewedFileIdForUser(
|
||||
this.client.user,
|
||||
fileIds[fileIds.length - 1]
|
||||
);
|
||||
|
||||
const menuOpts = {
|
||||
extraArgs: {
|
||||
|
@ -194,9 +217,11 @@ exports.getModule = class NewScanModule extends MenuModule {
|
|||
},
|
||||
};
|
||||
|
||||
return this.gotoMenu(this.menuConfig.config.newScanFileBaseList || 'newScanFileBaseList', menuOpts);
|
||||
}
|
||||
return this.gotoMenu(
|
||||
this.menuConfig.config.newScanFileBaseList || 'newScanFileBaseList',
|
||||
menuOpts
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
getSaveState() {
|
||||
|
@ -227,7 +252,8 @@ exports.getModule = class NewScanModule extends MenuModule {
|
|||
});
|
||||
break;
|
||||
|
||||
default : return cb(null);
|
||||
default:
|
||||
return cb(null);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -243,7 +269,9 @@ exports.getModule = class NewScanModule 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,
|
||||
}));
|
||||
|
||||
// :TODO: display scan step/etc.
|
||||
|
||||
|
@ -260,11 +288,14 @@ exports.getModule = class NewScanModule extends MenuModule {
|
|||
},
|
||||
function performCurrentStepScan(callback) {
|
||||
return self.performScanCurrentStep(callback);
|
||||
}
|
||||
},
|
||||
],
|
||||
err => {
|
||||
if (err) {
|
||||
self.client.log.error( { error : err.toString() }, 'Error during new scan');
|
||||
self.client.log.error(
|
||||
{ error: err.toString() },
|
||||
'Error during new scan'
|
||||
);
|
||||
}
|
||||
return cb(err);
|
||||
}
|
||||
|
|
|
@ -37,13 +37,15 @@ const MciViewIds = {
|
|||
preview: 3,
|
||||
|
||||
customRangeStart: 10,
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
exports.getModule = class NodeMessageModule 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 = {
|
||||
sendMessage: (formData, extraArgs, cb) => {
|
||||
|
@ -65,7 +67,10 @@ exports.getModule = class NodeMessageModule extends MenuModule {
|
|||
}
|
||||
}
|
||||
|
||||
Events.emit(Events.getSystemEvents().UserSendNodeMsg, { user : this.client.user, global : -1 === nodeId } );
|
||||
Events.emit(Events.getSystemEvents().UserSendNodeMsg, {
|
||||
user: this.client.user,
|
||||
global: -1 === nodeId,
|
||||
});
|
||||
|
||||
return this.prevMenu(cb);
|
||||
});
|
||||
|
@ -81,18 +86,28 @@ exports.getModule = class NodeMessageModule extends MenuModule {
|
|||
|
||||
series(
|
||||
[
|
||||
(callback) => {
|
||||
return this.prepViewController('sendMessage', FormIds.sendMessage, mciData.menu, callback);
|
||||
},
|
||||
(callback) => {
|
||||
return this.validateMCIByViewIds(
|
||||
callback => {
|
||||
return this.prepViewController(
|
||||
'sendMessage',
|
||||
[ MciViewIds.sendMessage.nodeSelect, MciViewIds.sendMessage.message ],
|
||||
FormIds.sendMessage,
|
||||
mciData.menu,
|
||||
callback
|
||||
);
|
||||
},
|
||||
(callback) => {
|
||||
const nodeSelectView = this.viewControllers.sendMessage.getView(MciViewIds.sendMessage.nodeSelect);
|
||||
callback => {
|
||||
return this.validateMCIByViewIds(
|
||||
'sendMessage',
|
||||
[
|
||||
MciViewIds.sendMessage.nodeSelect,
|
||||
MciViewIds.sendMessage.message,
|
||||
],
|
||||
callback
|
||||
);
|
||||
},
|
||||
callback => {
|
||||
const nodeSelectView = this.viewControllers.sendMessage.getView(
|
||||
MciViewIds.sendMessage.nodeSelect
|
||||
);
|
||||
this.prepareNodeList();
|
||||
|
||||
nodeSelectView.on('index update', idx => {
|
||||
|
@ -104,23 +119,32 @@ exports.getModule = class NodeMessageModule extends MenuModule {
|
|||
this.nodeListSelectionIndexUpdate(0);
|
||||
return callback(null);
|
||||
},
|
||||
(callback) => {
|
||||
const previewView = this.viewControllers.sendMessage.getView(MciViewIds.sendMessage.preview);
|
||||
callback => {
|
||||
const previewView = this.viewControllers.sendMessage.getView(
|
||||
MciViewIds.sendMessage.preview
|
||||
);
|
||||
if (!previewView) {
|
||||
return callback(null); // preview is optional
|
||||
}
|
||||
|
||||
const messageView = this.viewControllers.sendMessage.getView(MciViewIds.sendMessage.message);
|
||||
const messageView = this.viewControllers.sendMessage.getView(
|
||||
MciViewIds.sendMessage.message
|
||||
);
|
||||
let timerId;
|
||||
messageView.on('key press', () => {
|
||||
messageView.on(
|
||||
'key press',
|
||||
() => {
|
||||
clearTimeout(timerId);
|
||||
const focused = this.viewControllers.sendMessage.getFocusedView();
|
||||
const focused =
|
||||
this.viewControllers.sendMessage.getFocusedView();
|
||||
if (focused === messageView) {
|
||||
previewView.setText(messageView.getData());
|
||||
focused.setFocus(true);
|
||||
}
|
||||
}, 500);
|
||||
}
|
||||
},
|
||||
500
|
||||
);
|
||||
},
|
||||
],
|
||||
err => {
|
||||
return cb(err);
|
||||
|
@ -130,7 +154,9 @@ exports.getModule = class NodeMessageModule extends MenuModule {
|
|||
}
|
||||
|
||||
createInterruptItem(message, cb) {
|
||||
const dateTimeFormat = this.config.dateTimeFormat || this.client.currentTheme.helpers.getDateTimeFormat();
|
||||
const dateTimeFormat =
|
||||
this.config.dateTimeFormat ||
|
||||
this.client.currentTheme.helpers.getDateTimeFormat();
|
||||
|
||||
const textFormatObj = {
|
||||
fromUserName: this.client.user.username,
|
||||
|
@ -167,7 +193,7 @@ exports.getModule = class NodeMessageModule extends MenuModule {
|
|||
|
||||
async.waterfall(
|
||||
[
|
||||
(callback) => {
|
||||
callback => {
|
||||
getArt('header', headerArt => {
|
||||
return callback(null, headerArt);
|
||||
});
|
||||
|
@ -179,10 +205,12 @@ exports.getModule = class NodeMessageModule extends MenuModule {
|
|||
},
|
||||
(headerArt, footerArt, callback) => {
|
||||
if (headerArt || footerArt) {
|
||||
item.contents = `${headerArt || ''}\r\n${pipeToAnsi(item.text)}\r\n${footerArt || ''}`;
|
||||
item.contents = `${headerArt || ''}\r\n${pipeToAnsi(
|
||||
item.text
|
||||
)}\r\n${footerArt || ''}`;
|
||||
}
|
||||
return callback(null);
|
||||
}
|
||||
},
|
||||
],
|
||||
err => {
|
||||
return cb(err, item);
|
||||
|
@ -192,7 +220,8 @@ exports.getModule = class NodeMessageModule extends MenuModule {
|
|||
|
||||
prepareNodeList() {
|
||||
// standard node list with {text} field added for compliance
|
||||
this.nodeList = [{
|
||||
this.nodeList = [
|
||||
{
|
||||
text: '-ALL-',
|
||||
// dummy fields:
|
||||
node: -1,
|
||||
|
@ -204,9 +233,16 @@ exports.getModule = class NodeMessageModule extends MenuModule {
|
|||
location: 'N/A',
|
||||
affils: 'N/A',
|
||||
timeOn: 'N/A',
|
||||
}].concat(getActiveConnectionList(true)
|
||||
.map(node => Object.assign(node, { text : -1 == node.node ? '-ALL-' : node.node.toString() } ))
|
||||
).filter(node => node.node !== this.client.node); // remove our client's node
|
||||
},
|
||||
]
|
||||
.concat(
|
||||
getActiveConnectionList(true).map(node =>
|
||||
Object.assign(node, {
|
||||
text: -1 == node.node ? '-ALL-' : node.node.toString(),
|
||||
})
|
||||
)
|
||||
)
|
||||
.filter(node => node.node !== this.client.node); // remove our client's node
|
||||
this.nodeList.sort((a, b) => a.node - b.node); // sort by node
|
||||
}
|
||||
|
||||
|
@ -215,6 +251,10 @@ exports.getModule = class NodeMessageModule extends MenuModule {
|
|||
if (!node) {
|
||||
return;
|
||||
}
|
||||
this.updateCustomViewTextsWithFilter('sendMessage', MciViewIds.sendMessage.customRangeStart, node);
|
||||
this.updateCustomViewTextsWithFilter(
|
||||
'sendMessage',
|
||||
MciViewIds.sendMessage.customRangeStart,
|
||||
node
|
||||
);
|
||||
}
|
||||
};
|
||||
|
|
50
core/nua.js
50
core/nua.js
|
@ -8,9 +8,7 @@ const theme = require('./theme.js');
|
|||
const login = require('./system_menu_method.js').login;
|
||||
const Config = require('./config.js').get;
|
||||
const messageArea = require('./message_area.js');
|
||||
const {
|
||||
getISOTimestampString
|
||||
} = require('./database.js');
|
||||
const { getISOTimestampString } = require('./database.js');
|
||||
const UserProps = require('./user_property.js');
|
||||
|
||||
// deps
|
||||
|
@ -29,7 +27,6 @@ const MciViewIds = {
|
|||
};
|
||||
|
||||
exports.getModule = class NewUserAppModule extends MenuModule {
|
||||
|
||||
constructor(options) {
|
||||
super(options);
|
||||
|
||||
|
@ -40,8 +37,14 @@ exports.getModule = class NewUserAppModule extends MenuModule {
|
|||
// Validation stuff
|
||||
//
|
||||
validatePassConfirmMatch: function (data, cb) {
|
||||
const passwordView = self.viewControllers.menu.getView(MciViewIds.password);
|
||||
return cb(passwordView.getData() === data ? null : new Error('Passwords do not match'));
|
||||
const passwordView = self.viewControllers.menu.getView(
|
||||
MciViewIds.password
|
||||
);
|
||||
return cb(
|
||||
passwordView.getData() === data
|
||||
? null
|
||||
: new Error('Passwords do not match')
|
||||
);
|
||||
},
|
||||
|
||||
viewValidationListener: function (err, cb) {
|
||||
|
@ -54,7 +57,9 @@ exports.getModule = class NewUserAppModule extends MenuModule {
|
|||
|
||||
if (err.view.getId() === MciViewIds.confirm) {
|
||||
newFocusId = MciViewIds.password;
|
||||
self.viewControllers.menu.getView(MciViewIds.password).clearText();
|
||||
self.viewControllers.menu
|
||||
.getView(MciViewIds.password)
|
||||
.clearText();
|
||||
}
|
||||
} else {
|
||||
errMsgView.clearText();
|
||||
|
@ -63,7 +68,6 @@ exports.getModule = class NewUserAppModule extends MenuModule {
|
|||
return cb(newFocusId);
|
||||
},
|
||||
|
||||
|
||||
//
|
||||
// Submit handlers
|
||||
//
|
||||
|
@ -76,8 +80,15 @@ exports.getModule = class NewUserAppModule extends MenuModule {
|
|||
//
|
||||
// We have to disable ACS checks for initial default areas as the user is not yet ready
|
||||
//
|
||||
let confTag = messageArea.getDefaultMessageConferenceTag(self.client, true); // true=disableAcsCheck
|
||||
let areaTag = messageArea.getDefaultMessageAreaTagByConfTag(self.client, confTag, true); // true=disableAcsCheck
|
||||
let confTag = messageArea.getDefaultMessageConferenceTag(
|
||||
self.client,
|
||||
true
|
||||
); // true=disableAcsCheck
|
||||
let areaTag = messageArea.getDefaultMessageAreaTagByConfTag(
|
||||
self.client,
|
||||
confTag,
|
||||
true
|
||||
); // true=disableAcsCheck
|
||||
|
||||
// can't store undefined!
|
||||
confTag = confTag || '';
|
||||
|
@ -85,7 +96,9 @@ exports.getModule = class NewUserAppModule extends MenuModule {
|
|||
|
||||
newUser.properties = {
|
||||
[UserProps.RealName]: formData.value.realName,
|
||||
[ UserProps.Birthdate ] : getISOTimestampString(formData.value.birthdate),
|
||||
[UserProps.Birthdate]: getISOTimestampString(
|
||||
formData.value.birthdate
|
||||
),
|
||||
[UserProps.Sex]: formData.value.sex,
|
||||
[UserProps.Location]: formData.value.location,
|
||||
[UserProps.Affiliations]: formData.value.affils,
|
||||
|
@ -117,7 +130,10 @@ exports.getModule = class NewUserAppModule extends MenuModule {
|
|||
};
|
||||
newUser.create(createUserInfo, err => {
|
||||
if (err) {
|
||||
self.client.log.info( { error : err, username : formData.value.username }, 'New user creation failed');
|
||||
self.client.log.info(
|
||||
{ error: err, username: formData.value.username },
|
||||
'New user creation failed'
|
||||
);
|
||||
|
||||
self.gotoMenu(extraArgs.error, err => {
|
||||
if (err) {
|
||||
|
@ -126,7 +142,10 @@ exports.getModule = class NewUserAppModule extends MenuModule {
|
|||
return cb(null);
|
||||
});
|
||||
} else {
|
||||
self.client.log.info( { username : formData.value.username, userId : newUser.userId }, 'New user created');
|
||||
self.client.log.info(
|
||||
{ username: formData.value.username, userId: newUser.userId },
|
||||
'New user created'
|
||||
);
|
||||
|
||||
// Cache SysOp information now
|
||||
// :TODO: Similar to bbs.js. DRY
|
||||
|
@ -137,7 +156,10 @@ exports.getModule = class NewUserAppModule extends MenuModule {
|
|||
};
|
||||
}
|
||||
|
||||
if(User.AccountStatus.inactive === self.client.user.properties[UserProps.AccountStatus]) {
|
||||
if (
|
||||
User.AccountStatus.inactive ===
|
||||
self.client.user.properties[UserProps.AccountStatus]
|
||||
) {
|
||||
return self.gotoMenu(extraArgs.inactive, cb);
|
||||
} else {
|
||||
//
|
||||
|
|
|
@ -4,10 +4,7 @@
|
|||
// ENiGMA½
|
||||
const MenuModule = require('./menu_module.js').MenuModule;
|
||||
|
||||
const {
|
||||
getModDatabasePath,
|
||||
getTransactionDatabase
|
||||
} = require('./database.js');
|
||||
const { getModDatabasePath, getTransactionDatabase } = require('./database.js');
|
||||
|
||||
// deps
|
||||
const sqlite3 = require('sqlite3');
|
||||
|
@ -36,7 +33,7 @@ const MciViewIds = {
|
|||
newEntry: 1,
|
||||
entryPreview: 2,
|
||||
addPrompt: 3,
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
const FormIds = {
|
||||
|
@ -56,18 +53,23 @@ exports.getModule = class OnelinerzModule extends MenuModule {
|
|||
},
|
||||
|
||||
addEntry: function (formData, extraArgs, cb) {
|
||||
if(_.isString(formData.value.oneliner) && formData.value.oneliner.length > 0) {
|
||||
if (
|
||||
_.isString(formData.value.oneliner) &&
|
||||
formData.value.oneliner.length > 0
|
||||
) {
|
||||
const oneliner = formData.value.oneliner.trim(); // remove any trailing ws
|
||||
|
||||
self.storeNewOneliner(oneliner, err => {
|
||||
if (err) {
|
||||
self.client.log.warn( { error : err.message }, 'Failed saving oneliner');
|
||||
self.client.log.warn(
|
||||
{ error: err.message },
|
||||
'Failed saving oneliner'
|
||||
);
|
||||
}
|
||||
|
||||
self.clearAddForm();
|
||||
return self.displayViewScreen(true, cb); // true=cls
|
||||
});
|
||||
|
||||
} else {
|
||||
// empty message - treat as if cancel was hit
|
||||
return self.displayViewScreen(true, cb); // true=cls
|
||||
|
@ -77,7 +79,7 @@ exports.getModule = class OnelinerzModule extends MenuModule {
|
|||
cancelAdd: function (formData, extraArgs, cb) {
|
||||
self.clearAddForm();
|
||||
return self.displayViewScreen(true, cb); // true=cls
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -90,7 +92,7 @@ exports.getModule = class OnelinerzModule extends MenuModule {
|
|||
},
|
||||
function display(callback) {
|
||||
return self.displayViewScreen(false, callback);
|
||||
}
|
||||
},
|
||||
],
|
||||
err => {
|
||||
if (err) {
|
||||
|
@ -116,19 +118,23 @@ exports.getModule = class OnelinerzModule extends MenuModule {
|
|||
FormIds.view,
|
||||
{
|
||||
clearScreen,
|
||||
trailingLF : false
|
||||
trailingLF: false,
|
||||
},
|
||||
(err, artInfo, wasCreated) => {
|
||||
if (!err && !wasCreated) {
|
||||
self.viewControllers.view.setFocus(true);
|
||||
self.viewControllers.view.getView(MciViewIds.view.addPrompt).redraw();
|
||||
self.viewControllers.view
|
||||
.getView(MciViewIds.view.addPrompt)
|
||||
.redraw();
|
||||
}
|
||||
return callback(err);
|
||||
}
|
||||
);
|
||||
},
|
||||
function fetchEntries(callback) {
|
||||
const entriesView = self.viewControllers.view.getView(MciViewIds.view.entries);
|
||||
const entriesView = self.viewControllers.view.getView(
|
||||
MciViewIds.view.entries
|
||||
);
|
||||
const limit = entriesView.dimens.height;
|
||||
let entries = [];
|
||||
|
||||
|
@ -158,7 +164,8 @@ exports.getModule = class OnelinerzModule extends MenuModule {
|
|||
self.menuConfig.config.timestampFormat || // deprecated
|
||||
self.client.currentTheme.helpers.getDateFormat('short');
|
||||
|
||||
entriesView.setItems(entries.map( e => {
|
||||
entriesView.setItems(
|
||||
entries.map(e => {
|
||||
return {
|
||||
text: e.oneliner, // standard
|
||||
userId: e.user_id,
|
||||
|
@ -166,16 +173,19 @@ exports.getModule = class OnelinerzModule extends MenuModule {
|
|||
oneliner: e.oneliner,
|
||||
ts: e.timestamp.format(tsFormat),
|
||||
};
|
||||
}));
|
||||
})
|
||||
);
|
||||
|
||||
entriesView.redraw();
|
||||
return callback(null);
|
||||
},
|
||||
function finalPrep(callback) {
|
||||
const promptView = self.viewControllers.view.getView(MciViewIds.view.addPrompt);
|
||||
const promptView = self.viewControllers.view.getView(
|
||||
MciViewIds.view.addPrompt
|
||||
);
|
||||
promptView.setFocusItemIndex(1); // default to NO
|
||||
return callback(null);
|
||||
}
|
||||
},
|
||||
],
|
||||
err => {
|
||||
if (cb) {
|
||||
|
@ -198,21 +208,27 @@ exports.getModule = class OnelinerzModule extends MenuModule {
|
|||
FormIds.add,
|
||||
{
|
||||
clearScreen: true,
|
||||
trailingLF : false
|
||||
trailingLF: false,
|
||||
},
|
||||
(err, artInfo, wasCreated) => {
|
||||
if (!wasCreated) {
|
||||
self.viewControllers.add.setFocus(true);
|
||||
self.viewControllers.add.redrawAll();
|
||||
self.viewControllers.add.switchFocus(MciViewIds.add.newEntry);
|
||||
self.viewControllers.add.switchFocus(
|
||||
MciViewIds.add.newEntry
|
||||
);
|
||||
}
|
||||
return callback(err);
|
||||
}
|
||||
);
|
||||
},
|
||||
function initPreviewUpdates(callback) {
|
||||
const previewView = self.viewControllers.add.getView(MciViewIds.add.entryPreview);
|
||||
const entryView = self.viewControllers.add.getView(MciViewIds.add.newEntry);
|
||||
const previewView = self.viewControllers.add.getView(
|
||||
MciViewIds.add.entryPreview
|
||||
);
|
||||
const entryView = self.viewControllers.add.getView(
|
||||
MciViewIds.add.newEntry
|
||||
);
|
||||
if (previewView) {
|
||||
let timerId;
|
||||
entryView.on('key press', () => {
|
||||
|
@ -227,7 +243,7 @@ exports.getModule = class OnelinerzModule extends MenuModule {
|
|||
});
|
||||
}
|
||||
return callback(null);
|
||||
}
|
||||
},
|
||||
],
|
||||
err => {
|
||||
if (cb) {
|
||||
|
@ -249,12 +265,14 @@ exports.getModule = class OnelinerzModule extends MenuModule {
|
|||
[
|
||||
function openDatabase(callback) {
|
||||
const dbSuffix = self.menuConfig.config.dbSuffix;
|
||||
self.db = getTransactionDatabase(new sqlite3.Database(
|
||||
self.db = getTransactionDatabase(
|
||||
new sqlite3.Database(
|
||||
getModDatabasePath(exports.moduleInfo, dbSuffix),
|
||||
err => {
|
||||
return callback(err);
|
||||
}
|
||||
));
|
||||
)
|
||||
);
|
||||
},
|
||||
function createTables(callback) {
|
||||
self.db.run(
|
||||
|
@ -264,12 +282,12 @@ exports.getModule = class OnelinerzModule extends MenuModule {
|
|||
user_name VARCHAR NOT NULL,
|
||||
oneliner VARCHAR NOT NULL,
|
||||
timestamp DATETIME NOT NULL
|
||||
);`
|
||||
,
|
||||
);`,
|
||||
err => {
|
||||
return callback(err);
|
||||
});
|
||||
}
|
||||
);
|
||||
},
|
||||
],
|
||||
err => {
|
||||
return cb(err);
|
||||
|
@ -287,7 +305,12 @@ exports.getModule = class OnelinerzModule extends MenuModule {
|
|||
self.db.run(
|
||||
`INSERT INTO onelinerz (user_id, user_name, oneliner, timestamp)
|
||||
VALUES (?, ?, ?, ?);`,
|
||||
[ self.client.user.userId, self.client.user.username, oneliner, ts ],
|
||||
[
|
||||
self.client.user.userId,
|
||||
self.client.user.username,
|
||||
oneliner,
|
||||
ts,
|
||||
],
|
||||
callback
|
||||
);
|
||||
},
|
||||
|
@ -304,7 +327,7 @@ exports.getModule = class OnelinerzModule extends MenuModule {
|
|||
);`,
|
||||
callback
|
||||
);
|
||||
}
|
||||
},
|
||||
],
|
||||
err => {
|
||||
return cb(err);
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue