Merge branch 'master' of ssh://numinibsd/git/base/enigma-bbs

This commit is contained in:
Bryan Ashby
2020-06-03 20:53:46 -06:00
455 changed files with 69758 additions and 44066 deletions

0
mods/.keep Normal file
View File

View File

@@ -1,197 +0,0 @@
/* jslint node: true */
'use strict';
const MenuModule = require('../core/menu_module.js').MenuModule;
const DropFile = require('../core/dropfile.js').DropFile;
const door = require('../core/door.js');
const theme = require('../core/theme.js');
const ansi = require('../core/ansi_term.js');
const async = require('async');
const assert = require('assert');
const paths = require('path');
const _ = require('lodash');
const mkdirs = require('fs-extra').mkdirs;
// :TODO: This should really be a system module... needs a little work to allow for such
const activeDoorNodeInstances = {};
exports.moduleInfo = {
name : 'Abracadabra',
desc : 'External BBS Door Module',
author : 'NuSkooler',
};
/*
Example configuration for LORD under DOSEMU:
{
config: {
name: PimpWars
dropFileType: DORINFO
cmd: qemu-system-i386
args: [
"-localtime",
"freedos.img",
"-chardev",
"socket,port={srvPort},nowait,host=localhost,id=s0",
"-device",
"isa-serial,chardev=s0"
]
io: socket
}
}
listen: socket | stdio
{
"config" : {
"name" : "LORD",
"dropFileType" : "DOOR",
"cmd" : "/usr/bin/dosemu",
"args" : [ "-quiet", "-f", "/etc/dosemu/dosemu.conf", "X:\\PW\\START.BAT {dropfile} {node}" ] ],
"nodeMax" : 32,
"tooManyArt" : "toomany-lord.ans"
}
}
:TODO: See Mystic & others for other arg options that we may need to support
*/
exports.getModule = class AbracadabraModule extends MenuModule {
constructor(options) {
super(options);
this.config = options.menuConfig.config;
// :TODO: MenuModule.validateConfig(cb) -- validate config section gracefully instead of asserts! -- { key : type, key2 : type2, ... }
assert(_.isString(this.config.name, 'Config \'name\' is required'));
assert(_.isString(this.config.dropFileType, 'Config \'dropFileType\' is required'));
assert(_.isString(this.config.cmd, 'Config \'cmd\' is required'));
this.config.nodeMax = this.config.nodeMax || 0;
this.config.args = this.config.args || [];
}
/*
:TODO:
* disconnecting wile door is open leaves dosemu
* http://bbslink.net/sysop.php support
* Font support ala all other menus... or does this just work?
*/
initSequence() {
const self = this;
async.series(
[
function validateNodeCount(callback) {
if(self.config.nodeMax > 0 &&
_.isNumber(activeDoorNodeInstances[self.config.name]) &&
activeDoorNodeInstances[self.config.name] + 1 > self.config.nodeMax)
{
self.client.log.info(
{
name : self.config.name,
activeCount : activeDoorNodeInstances[self.config.name]
},
'Too many active instances');
if(_.isString(self.config.tooManyArt)) {
theme.displayThemeArt( { client : self.client, name : self.config.tooManyArt }, function displayed() {
self.pausePrompt( () => {
callback(new Error('Too many active instances'));
});
});
} else {
self.client.term.write('\nToo many active instances. Try again later.\n');
// :TODO: Use MenuModule.pausePrompt()
self.pausePrompt( () => {
callback(new Error('Too many active instances'));
});
}
} else {
// :TODO: JS elegant way to do this?
if(activeDoorNodeInstances[self.config.name]) {
activeDoorNodeInstances[self.config.name] += 1;
} else {
activeDoorNodeInstances[self.config.name] = 1;
}
callback(null);
}
},
function generateDropfile(callback) {
self.dropFile = new DropFile(self.client, self.config.dropFileType);
var fullPath = self.dropFile.fullPath;
mkdirs(paths.dirname(fullPath), function dirCreated(err) {
if(err) {
callback(err);
} else {
self.dropFile.createFile(function created(err) {
callback(err);
});
}
});
}
],
function complete(err) {
if(err) {
self.client.log.warn( { error : err.toString() }, 'Could not start door');
self.lastError = err;
self.prevMenu();
} else {
self.finishedLoading();
}
}
);
}
runDoor() {
const exeInfo = {
cmd : this.config.cmd,
args : this.config.args,
io : this.config.io || 'stdio',
encoding : this.config.encoding || this.client.term.outputEncoding,
dropFile : this.dropFile.fileName,
node : this.client.node,
//inhSocket : this.client.output._handle.fd,
};
const doorInstance = new door.Door(this.client, exeInfo);
doorInstance.once('finished', () => {
//
// Try to clean up various settings such as scroll regions that may
// have been set within the door
//
this.client.term.rawWrite(
ansi.normal() +
ansi.goto(this.client.term.termHeight, this.client.term.termWidth) +
ansi.setScrollRegion() +
ansi.goto(this.client.term.termHeight, 0) +
'\r\n\r\n'
);
this.prevMenu();
});
this.client.term.write(ansi.resetScreen());
doorInstance.run();
}
leave() {
super.leave();
if(!this.lastError) {
activeDoorNodeInstances[this.config.name] -= 1;
}
}
finishedLoading() {
this.runDoor();
}
};

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -1,19 +0,0 @@
 <31><6D><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ܲ<6D><DCB2>  <33>  <31><6D> 
 <31><6D><EFBFBD><31><31><6D> <31><6D><EFBFBD><EFBFBD><EFBFBD>۲<37><31><6D><30><6D><EFBFBD>  <20><>  <33><6D><EFBFBD> <33><6D><EFBFBD><EFBFBD> <20> <31><6D><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ܲ<EFBFBD><DCB2><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
<31><31><6D><EFBFBD><EFBFBD><EFBFBD><31><6D><37><6D><EFBFBD><31><6D><30><6D> <30><6D>  <20><><EFBFBD> <31><6D> <33><37><6D><EFBFBD><EFBFBD><EFBFBD><31><31><6D><30><6D><EFBFBD>
<31><6D><EFBFBD><EFBFBD><EFBFBD><37><6D><30><6D>  <20><31><37>۲<EFBFBD><DBB2><EFBFBD><EFBFBD><31><6D><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><31><6D><30>۲<37><6D><EFBFBD><EFBFBD><30><6D>ܲ<37><6D><EFBFBD><30><6D> <31><6D><31><6D><EFBFBD><EFBFBD><EFBFBD>۲
 <20><37><31><6D><37><6D><EFBFBD><30><31><6D><37><6D><EFBFBD><EFBFBD><EFBFBD><EFBFBD><30> <37><6D><30><6D><37><6D><30> <31><37><6D><EFBFBD><EFBFBD><EFBFBD><30> <20><37><6D><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><31><37><6D><30><31><30><6D>
<37><6D><EFBFBD>۲<EFBFBD><DBB2>     <37><6D><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><30><6D><37> <20><><EFBFBD><30><30>  <37><6D><EFBFBD><EFBFBD><EFBFBD> <37><6D><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <37><6D><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><37><6D><EFBFBD><EFBFBD><EFBFBD><EFBFBD><30>  
<31><6D><EFBFBD><EFBFBD><37><6D><EFBFBD><EFBFBD><EFBFBD><30><6D> <20>  <20><><EFBFBD><37><6D><30><6D><EFBFBD><EFBFBD><37><6D><30><6D><EFBFBD><EFBFBD><30><6D> <31><6D> <31><37> <37><6D><EFBFBD><EFBFBD> <30> <20><><EFBFBD><30><6D><EFBFBD><30> <20><><30><6D><EFBFBD><30><6D> <31><6D> <20> <20><><37><6D><EFBFBD><EFBFBD><EFBFBD><EFBFBD><30><6D>   <31><6D><37><6D><EFBFBD><EFBFBD><EFBFBD> <30><6D>    <37><6D><EFBFBD><EFBFBD><EFBFBD><30>  <20><37><6D><EFBFBD><EFBFBD><37>  <20><37><30><6D><EFBFBD> <30><37><30><6D><EFBFBD><EFBFBD><30> <20> <20><> <20><><EFBFBD><EFBFBD> 
<34> <30><37><6D><EFBFBD> <20><><30>  <20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><37><6D><EFBFBD><30><6D><EFBFBD><34>ܲ<EFBFBD> <20><><37><6D><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <34> <37><6D><EFBFBD><EFBFBD><EFBFBD><30> <34><6D><EFBFBD><EFBFBD>۲<EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><37>  <34><6D> 
<34><34> <37> <30><6D><EFBFBD><EFBFBD><30> <34><6D><34><6D> <30><6D><37><6D><30><6D><EFBFBD><EFBFBD><EFBFBD><EFBFBD><37><30>۲<EFBFBD> <34><6D> <30><6D><EFBFBD><EFBFBD><EFBFBD><37><6D><EFBFBD><30><6D><37><6D><EFBFBD><EFBFBD> <34><34><34> <30><37><6D><EFBFBD><30><6D> <34><34><6D><34><6D><EFBFBD> <37><6D><EFBFBD><EFBFBD><30><6D><EFBFBD> <20><><34>
<34>  <37><6D><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <34>߲<EFBFBD> <30><6D><EFBFBD><37><6D><30>۲<EFBFBD><DBB2><EFBFBD><EFBFBD><EFBFBD>۲ <34><6D>߲ <20><30> <37><6D><30><6D> ߲<6D> <30>۲۲<DBB2> <20><><EFBFBD> <34> <20><37><6D><EFBFBD><EFBFBD><EFBFBD><30> ߲<6D>
<30><37><6D><EFBFBD><EFBFBD><EFBFBD><30><6D>  <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><30>۲<30><6D><EFBFBD> <20><><EFBFBD><30><6D> <20><37><6D><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
<30><37><6D><EFBFBD><EFBFBD><EFBFBD><30> <20><><EFBFBD><EFBFBD><EFBFBD>۲<EFBFBD> <20><><EFBFBD> ޲<30><30><6D><37><6D><EFBFBD><30><6D>
 <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD>۲<EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><30><6D>۲<30><6D> 
 <30><6D> <20><><EFBFBD><EFBFBD><EFBFBD><31><6D><EFBFBD> <31><6D> enigma<6D>bbs soft<30><6D><EFBFBD><37><6D><30><6D><31> <20><><EFBFBD><EFBFBD>۲ <31><6D>
dangermouse <20><>۲<6D><DBB2><EFBFBD><EFBFBD><30><37><6D><30><6D> <31>
  <20><>  ۲<><DBB2><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
<30><6D><EFBFBD><EFBFBD><EFBFBD><31><31><31><6D><EFBFBD>
 <20> <30> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>


Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -1,52 +0,0 @@
You should never see this!
... nor this
[?33h
 fONT tEST
 ~~~~~~~~~
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B | C | D | E | F
---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---
0 |NUL|  |  |  |  |  |  |  |BS |HT |LF | | |CR |  |  
1 |  |  |  |  |  |  |  |  |  |  |EOF|ESC|  |  |  |  
2 | | ! | " | # | $ | % | & | ' | ( | ) | * | + | , | - | . | / 
3 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | : | ; | < | = | > | ? 
---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---
4 | @ | A | B | C | D | E | F | G | H | I | J | K | L | M | N | O 
5 | P | Q | R | S | T | U | V | W | X | Y | Z | [ | \ | ] | ^ | _ 
6 | ` | a | b | c | d | e | f | g | h | i | j | k | l | m | n | o 
7 | p | q | r | s | t | u | v | w | x | y | z | { | | | } | ~ |  
---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---
8 | <20> | <20> | <20> | <20> | <20> | <20> | <20> | <20> | <20> | <20> | <20> | <20> | <20> | <20> | <20> | <20> 
9 | <20> | <20> | <20> | <20> | <20> | <20> | <20> | <20> | <20> | <20> | <20> | <20> | <20> | <20> | <20> | <20> 
A | <20> | <20> | <20> | <20> | <20> | <20> | <20> | <20> | <20> | <20> | <20> | <20> | <20> | <20> | <20> | <20> 
B | <20> | <20> | <20> | <20> | <20> | <20> | <20> | <20> | <20> | <20> | <20> | <20> | <20> | <20> | <20> | <20> 
---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---
C | <20> | <20> | <20> | <20> | <20> | <20> | <20> | <20> | <20> | <20> | <20> | <20> | <20> | <20> | <20> | <20> 
D | <20> | <20> | <20> | <20> | <20> | <20> | <20> | <20> | <20> | <20> | <20> | <20> | <20> | <20> | <20> | <20> 
E | <20> | <20> | <20> | <20> | <20> | <20> | <20> | <20> | <20> | <20> | <20> | <20> | <20> | <20> | <20> | <20> 
F | <20> | <20> | <20> | <20> | <20> | <20> | <20> | <20> | <20> | <20> | <20> | <20> | <20> | <20> | <20> | <20> 
 cOLOR tEST
 ~~~~~~~~~~
<30><6D><EFBFBD><EFBFBD><31><6D><EFBFBD><EFBFBD><32><6D><EFBFBD><EFBFBD><33><6D><EFBFBD><EFBFBD><34><6D><EFBFBD><EFBFBD><35><6D><EFBFBD><EFBFBD><36><6D><EFBFBD><EFBFBD><37><6D><EFBFBD><EFBFBD><30><6D><EFBFBD><EFBFBD><31><6D><EFBFBD><EFBFBD><32><6D><EFBFBD><EFBFBD><33><6D><EFBFBD><EFBFBD><34><6D><EFBFBD><EFBFBD><35><6D><EFBFBD><EFBFBD><36><6D><EFBFBD><EFBFBD><37><6D><EFBFBD><EFBFBD>
<30><6D><EFBFBD><EFBFBD><31><6D><EFBFBD><EFBFBD><32><6D><EFBFBD><EFBFBD><33><6D><EFBFBD><EFBFBD><34><6D><EFBFBD><EFBFBD><35><6D><EFBFBD><EFBFBD><36><6D><EFBFBD><EFBFBD><37><6D><EFBFBD><EFBFBD><30><6D><EFBFBD><EFBFBD><31><6D><EFBFBD><EFBFBD><32><6D><EFBFBD><EFBFBD><33><6D><EFBFBD><EFBFBD><34><6D><EFBFBD><EFBFBD><35><6D><EFBFBD><EFBFBD><36><6D><EFBFBD><EFBFBD><37><6D><EFBFBD><EFBFBD>
<30><6D><EFBFBD><EFBFBD><31><6D><EFBFBD><EFBFBD><32><6D><EFBFBD><EFBFBD><33><6D><EFBFBD><EFBFBD><34><6D><EFBFBD><EFBFBD><35><6D><EFBFBD><EFBFBD><36><6D><EFBFBD><EFBFBD><37><6D><EFBFBD><EFBFBD><30><6D><EFBFBD><EFBFBD><31><6D><EFBFBD><EFBFBD><32><6D><EFBFBD><EFBFBD><33><6D><EFBFBD><EFBFBD><34><6D><EFBFBD><EFBFBD><35><6D><EFBFBD><EFBFBD><36><6D><EFBFBD><EFBFBD><37><6D><EFBFBD><EFBFBD>
<30><6D><EFBFBD><EFBFBD><31><6D><EFBFBD><EFBFBD><32><6D><EFBFBD><EFBFBD><33><6D><EFBFBD><EFBFBD><34><6D><EFBFBD><EFBFBD><35><6D><EFBFBD><EFBFBD><36><6D><EFBFBD><EFBFBD><37><6D><EFBFBD><EFBFBD><30><6D><EFBFBD><EFBFBD><31><6D><EFBFBD><EFBFBD><32><6D><EFBFBD><EFBFBD><33><6D><EFBFBD><EFBFBD><34><6D><EFBFBD><EFBFBD><35><6D><EFBFBD><EFBFBD><36><6D><EFBFBD><EFBFBD><37><6D><EFBFBD><EFBFBD>
<30><6D><EFBFBD><EFBFBD><31><6D><EFBFBD><EFBFBD><32><6D><EFBFBD><EFBFBD><33><6D><EFBFBD><EFBFBD><34><6D><EFBFBD><EFBFBD><35><6D><EFBFBD><EFBFBD><36><6D><EFBFBD><EFBFBD><37><6D><EFBFBD><EFBFBD><30><6D><EFBFBD><EFBFBD><31><6D><EFBFBD><EFBFBD><32><6D><EFBFBD><EFBFBD><33><6D><EFBFBD><EFBFBD><34><6D><EFBFBD><EFBFBD><35><6D><EFBFBD><EFBFBD><36><6D><EFBFBD><EFBFBD><37><6D><EFBFBD><EFBFBD>
<30><6D><EFBFBD><EFBFBD><31><6D><EFBFBD><EFBFBD><32><6D><EFBFBD><EFBFBD><33><6D><EFBFBD><EFBFBD><34><6D><EFBFBD><EFBFBD><35><6D><EFBFBD><EFBFBD><36><6D><EFBFBD><EFBFBD><37><6D><EFBFBD><EFBFBD><30><6D><EFBFBD><EFBFBD><31><6D><EFBFBD><EFBFBD><32><6D><EFBFBD><EFBFBD><33><6D><EFBFBD><EFBFBD><34><6D><EFBFBD><EFBFBD><35><6D><EFBFBD><EFBFBD><36><6D><EFBFBD><EFBFBD><37><6D><EFBFBD><EFBFBD>
<30><6D><EFBFBD><EFBFBD><31><6D><EFBFBD><EFBFBD><32><6D><EFBFBD><EFBFBD><33><6D><EFBFBD><EFBFBD><34><6D><EFBFBD><EFBFBD><35><6D><EFBFBD><EFBFBD><36><6D><EFBFBD><EFBFBD><37><6D><EFBFBD><EFBFBD><30><6D><EFBFBD><EFBFBD><31><6D><EFBFBD><EFBFBD><32><6D><EFBFBD><EFBFBD><33><6D><EFBFBD><EFBFBD><34><6D><EFBFBD><EFBFBD><35><6D><EFBFBD><EFBFBD><36><6D><EFBFBD><EFBFBD><37><6D><EFBFBD><EFBFBD>
<30><6D><EFBFBD><EFBFBD><31><6D><EFBFBD><EFBFBD><32><6D><EFBFBD><EFBFBD><33><6D><EFBFBD><EFBFBD><34><6D><EFBFBD><EFBFBD><35><6D><EFBFBD><EFBFBD><36><6D><EFBFBD><EFBFBD><37><6D><EFBFBD><EFBFBD><30><6D><EFBFBD><EFBFBD><31><6D><EFBFBD><EFBFBD><32><6D><EFBFBD><EFBFBD><33><6D><EFBFBD><EFBFBD><34><6D><EFBFBD><EFBFBD><35><6D><EFBFBD><EFBFBD><36><6D><EFBFBD><EFBFBD><37><6D><EFBFBD><EFBFBD>
<30><6D><EFBFBD><EFBFBD><31><6D><EFBFBD><EFBFBD><32><6D><EFBFBD><EFBFBD><33><6D><EFBFBD><EFBFBD><34><6D><EFBFBD><EFBFBD><35><6D><EFBFBD><EFBFBD><36><6D><EFBFBD><EFBFBD><37><6D><EFBFBD><EFBFBD><30><6D><EFBFBD><EFBFBD><31><6D><EFBFBD><EFBFBD><32><6D><EFBFBD><EFBFBD><33><6D><EFBFBD><EFBFBD><34><6D><EFBFBD><EFBFBD><35><6D><EFBFBD><EFBFBD><36><6D><EFBFBD><EFBFBD><37><6D><EFBFBD><EFBFBD>
<30><6D><EFBFBD><EFBFBD><31><6D><EFBFBD><EFBFBD><32><6D><EFBFBD><EFBFBD><33><6D><EFBFBD><EFBFBD><34><6D><EFBFBD><EFBFBD><35><6D><EFBFBD><EFBFBD><36><6D><EFBFBD><EFBFBD><37><6D><EFBFBD><EFBFBD><30><6D><EFBFBD><EFBFBD><31><6D><EFBFBD><EFBFBD><32><6D><EFBFBD><EFBFBD><33><6D><EFBFBD><EFBFBD><34><6D><EFBFBD><EFBFBD><35><6D><EFBFBD><EFBFBD><36><6D><EFBFBD><EFBFBD><37><6D><EFBFBD><EFBFBD>
<30><6D><EFBFBD><EFBFBD><31><6D><EFBFBD><EFBFBD><32><6D><EFBFBD><EFBFBD><33><6D><EFBFBD><EFBFBD><34><6D><EFBFBD><EFBFBD><35><6D><EFBFBD><EFBFBD><36><6D><EFBFBD><EFBFBD><37><6D><EFBFBD><EFBFBD><30><6D><EFBFBD><EFBFBD><31><6D><EFBFBD><EFBFBD><32><6D><EFBFBD><EFBFBD><33><6D><EFBFBD><EFBFBD><34><6D><EFBFBD><EFBFBD><35><6D><EFBFBD><EFBFBD><36><6D><EFBFBD><EFBFBD><37><6D><EFBFBD><EFBFBD>
<30><6D><EFBFBD><EFBFBD><31><6D><EFBFBD><EFBFBD><32><6D><EFBFBD><EFBFBD><33><6D><EFBFBD><EFBFBD><34><6D><EFBFBD><EFBFBD><35><6D><EFBFBD><EFBFBD><36><6D><EFBFBD><EFBFBD><37><6D><EFBFBD><EFBFBD><30><6D><EFBFBD><EFBFBD><31><6D><EFBFBD><EFBFBD><32><6D><EFBFBD><EFBFBD><33><6D><EFBFBD><EFBFBD><34><6D><EFBFBD><EFBFBD><35><6D><EFBFBD><EFBFBD><36><6D><EFBFBD><EFBFBD><37><6D><EFBFBD><EFBFBD>
<30><6D><EFBFBD><EFBFBD><31><6D><EFBFBD><EFBFBD><32><6D><EFBFBD><EFBFBD><33><6D><EFBFBD><EFBFBD><34><6D><EFBFBD><EFBFBD><35><6D><EFBFBD><EFBFBD><36><6D><EFBFBD><EFBFBD><37><6D><EFBFBD><EFBFBD><30><6D><EFBFBD><EFBFBD><31><6D><EFBFBD><EFBFBD><32><6D><EFBFBD><EFBFBD><33><6D><EFBFBD><EFBFBD><34><6D><EFBFBD><EFBFBD><35><6D><EFBFBD><EFBFBD><36><6D><EFBFBD><EFBFBD><37><6D><EFBFBD><EFBFBD>
<30><6D><EFBFBD><EFBFBD><31><6D><EFBFBD><EFBFBD><32><6D><EFBFBD><EFBFBD><33><6D><EFBFBD><EFBFBD><34><6D><EFBFBD><EFBFBD><35><6D><EFBFBD><EFBFBD><36><6D><EFBFBD><EFBFBD><37><6D><EFBFBD><EFBFBD><30><6D><EFBFBD><EFBFBD><31><6D><EFBFBD><EFBFBD><32><6D><EFBFBD><EFBFBD><33><6D><EFBFBD><EFBFBD><34><6D><EFBFBD><EFBFBD><35><6D><EFBFBD><EFBFBD><36><6D><EFBFBD><EFBFBD><37><6D><EFBFBD><EFBFBD>
<30><6D><EFBFBD><EFBFBD><31><6D><EFBFBD><EFBFBD><32><6D><EFBFBD><EFBFBD><33><6D><EFBFBD><EFBFBD><34><6D><EFBFBD><EFBFBD><35><6D><EFBFBD><EFBFBD><36><6D><EFBFBD><EFBFBD><37><6D><EFBFBD><EFBFBD><30><6D><EFBFBD><EFBFBD><31><6D><EFBFBD><EFBFBD><32><6D><EFBFBD><EFBFBD><33><6D><EFBFBD><EFBFBD><34><6D><EFBFBD><EFBFBD><35><6D><EFBFBD><EFBFBD><36><6D><EFBFBD><EFBFBD><37><6D><EFBFBD><EFBFBD>
<30><6D><EFBFBD><EFBFBD><31><6D><EFBFBD><EFBFBD><32><6D><EFBFBD><EFBFBD><33><6D><EFBFBD><EFBFBD><34><6D><EFBFBD><EFBFBD><35><6D><EFBFBD><EFBFBD><36><6D><EFBFBD><EFBFBD><37><6D><EFBFBD><EFBFBD><30><6D><EFBFBD><EFBFBD><31><6D><EFBFBD><EFBFBD><32><6D><EFBFBD><EFBFBD><33><6D><EFBFBD><EFBFBD><34><6D><EFBFBD><EFBFBD><35><6D><EFBFBD><EFBFBD><36><6D><EFBFBD><EFBFBD><37><6D><EFBFBD><EFBFBD>


View File

@@ -1,207 +0,0 @@
/* jslint node: true */
'use strict';
const MenuModule = require('../core/menu_module.js').MenuModule;
const resetScreen = require('../core/ansi_term.js').resetScreen;
const async = require('async');
const _ = require('lodash');
const http = require('http');
const net = require('net');
const crypto = require('crypto');
const packageJson = require('../package.json');
/*
Expected configuration block:
{
module: bbs_link
...
config: {
sysCode: XXXXX
authCode: XXXXX
schemeCode: XXXX
door: lord
// default hoss: games.bbslink.net
host: games.bbslink.net
// defualt port: 23
port: 23
}
}
*/
// :TODO: BUG: When a client disconnects, it's not handled very well -- the log is spammed with tons of errors
// :TODO: ENH: Support nodeMax and tooManyArt
exports.moduleInfo = {
name : 'BBSLink',
desc : 'BBSLink Access Module',
author : 'NuSkooler',
};
exports.getModule = class BBSLinkModule extends MenuModule {
constructor(options) {
super(options);
this.config = options.menuConfig.config;
this.config.host = this.config.host || 'games.bbslink.net';
this.config.port = this.config.port || 23;
}
initSequence() {
let token;
let randomKey;
let clientTerminated;
const self = this;
async.series(
[
function validateConfig(callback) {
if(_.isString(self.config.sysCode) &&
_.isString(self.config.authCode) &&
_.isString(self.config.schemeCode) &&
_.isString(self.config.door))
{
callback(null);
} else {
callback(new Error('Configuration is missing option(s)'));
}
},
function acquireToken(callback) {
//
// Acquire an authentication token
//
crypto.randomBytes(16, function rand(ex, buf) {
if(ex) {
callback(ex);
} else {
randomKey = buf.toString('base64').substr(0, 6);
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');
callback(null);
}
});
}
});
},
function authenticateToken(callback) {
//
// Authenticate the token we acquired previously
//
var 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-Rows' : self.client.term.termHeight.toString(),
'X-Key' : randomKey,
'X-Door' : self.config.door,
'X-Token' : token,
'X-Type' : 'enigma-bbs',
'X-Version' : packageJson.version,
};
self.simpleHttpRequest('/auth.php?key=' + randomKey, headers, function resp(err, body) {
var status = body.trim();
if('complete' === status) {
callback(null);
} else {
callback(new Error('Bad authentication status: ' + status));
}
});
},
function createTelnetBridge(callback) {
//
// Authentication with BBSLink successful. Now, we need to create a telnet
// bridge from us to them
//
var connectOpts = {
port : self.config.port,
host : self.config.host,
};
var clientTerminated;
self.client.term.write(resetScreen());
self.client.term.write(' Connecting to BBSLink.net, please wait...\n');
var bridgeConnection = net.createConnection(connectOpts, function connected() {
self.client.log.info(connectOpts, 'BBSLink bridge connection established');
self.client.term.output.pipe(bridgeConnection);
self.client.once('end', function clientEnd() {
self.client.log.info('Connection ended. Terminating BBSLink connection');
clientTerminated = true;
bridgeConnection.end();
});
});
var restorePipe = function() {
self.client.term.output.unpipe(bridgeConnection);
self.client.term.output.resume();
};
bridgeConnection.on('data', function incomingData(data) {
// pass along
// :TODO: just pipe this as well
self.client.term.rawWrite(data);
});
bridgeConnection.on('end', function connectionEnd() {
restorePipe();
callback(clientTerminated ? new Error('Client connection terminated') : null);
});
bridgeConnection.on('error', function error(err) {
self.client.log.info('BBSLink bridge connection error: ' + err.message);
restorePipe();
callback(err);
});
}
],
function complete(err) {
if(err) {
self.client.log.warn( { error : err.toString() }, 'BBSLink connection error');
}
if(!clientTerminated) {
self.prevMenu();
}
}
);
}
simpleHttpRequest(path, headers, cb) {
const getOpts = {
host : this.config.host,
path : path,
headers : headers,
};
const req = http.get(getOpts, function response(resp) {
let data = '';
resp.on('data', function chunk(c) {
data += c;
});
resp.on('end', function respEnd() {
cb(null, data);
req.end();
});
});
req.on('error', function reqErr(err) {
cb(err);
});
}
};

View File

@@ -1,430 +0,0 @@
/* jslint node: true */
'use strict';
// ENiGMA½
const MenuModule = require('../core/menu_module.js').MenuModule;
const getModDatabasePath = require('../core/database.js').getModDatabasePath;
const ViewController = require('../core/view_controller.js').ViewController;
const ansi = require('../core/ansi_term.js');
const theme = require('../core/theme.js');
const User = require('../core/user.js');
const stringFormat = require('../core/string_format.js');
// deps
const async = require('async');
const sqlite3 = require('sqlite3');
const _ = require('lodash');
// :TODO: add notes field
const moduleInfo = exports.moduleInfo = {
name : 'BBS List',
desc : 'List of other BBSes',
author : 'Andrew Pamment',
packageName : 'com.magickabbs.enigma.bbslist'
};
const MciViewIds = {
view : {
BBSList : 1,
SelectedBBSName : 2,
SelectedBBSSysOp : 3,
SelectedBBSTelnet : 4,
SelectedBBSWww : 5,
SelectedBBSLoc : 6,
SelectedBBSSoftware : 7,
SelectedBBSNotes : 8,
SelectedBBSSubmitter : 9,
},
add : {
BBSName : 1,
Sysop : 2,
Telnet : 3,
Www : 4,
Location : 5,
Software : 6,
Notes : 7,
Error : 8,
}
};
const FormIds = {
View : 0,
Add : 1,
};
const SELECTED_MCI_NAME_TO_ENTRY = {
SelectedBBSName : 'bbsName',
SelectedBBSSysOp : 'sysOp',
SelectedBBSTelnet : 'telnet',
SelectedBBSWww : 'www',
SelectedBBSLoc : 'location',
SelectedBBSSoftware : 'software',
SelectedBBSSubmitter : 'submitter',
SelectedBBSSubmitterId : 'submitterUserId',
SelectedBBSNotes : 'notes',
};
exports.getModule = class BBSListModule extends MenuModule {
constructor(options) {
super(options);
const self = this;
this.menuMethods = {
//
// Validators
//
viewValidationListener : function(err, cb) {
const errMsgView = self.viewControllers.add.getView(MciViewIds.add.Error);
if(errMsgView) {
if(err) {
errMsgView.setText(err.message);
} else {
errMsgView.clearText();
}
}
return cb(null);
},
//
// Key & submit handlers
//
addBBS : function(formData, extraArgs, cb) {
self.displayAddScreen(cb);
},
deleteBBS : function(formData, extraArgs, cb) {
const entriesView = self.viewControllers.view.getView(MciViewIds.view.BBSList);
if(self.entries[self.selectedBBS].submitterUserId !== self.client.user.userId && !self.client.user.isSysOp()) {
// must be owner or +op
return cb(null);
}
const entry = self.entries[self.selectedBBS];
if(!entry) {
return cb(null);
}
self.database.run(
`DELETE FROM bbs_list
WHERE id=?;`,
[ entry.id ],
err => {
if (err) {
self.client.log.error( { err : err }, 'Error deleting from BBS list');
} else {
self.entries.splice(self.selectedBBS, 1);
self.setEntries(entriesView);
if(self.entries.length > 0) {
entriesView.focusPrevious();
}
self.viewControllers.view.redrawAll();
}
return cb(null);
}
);
},
submitBBS : function(formData, extraArgs, cb) {
let ok = true;
[ 'BBSName', 'Sysop', 'Telnet' ].forEach( mciName => {
if('' === self.viewControllers.add.getView(MciViewIds.add[mciName]).getData()) {
ok = false;
}
});
if(!ok) {
// validators should prevent this!
return cb(null);
}
self.database.run(
`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 ],
err => {
if(err) {
self.client.log.error( { err : err }, 'Error adding to BBS list');
}
self.clearAddForm();
self.displayBBSList(true, cb);
}
);
},
cancelSubmit : function(formData, extraArgs, cb) {
self.clearAddForm();
self.displayBBSList(true, cb);
}
};
}
initSequence() {
const self = this;
async.series(
[
function beforeDisplayArt(callback) {
self.beforeArt(callback);
},
function display(callback) {
self.displayBBSList(false, callback);
}
],
err => {
if(err) {
// :TODO: Handle me -- initSequence() should really take a completion callback
}
self.finishedLoading();
}
);
}
drawSelectedEntry(entry) {
if(!entry) {
Object.keys(SELECTED_MCI_NAME_TO_ENTRY).forEach(mciName => {
this.setViewText('view', MciViewIds.view[mciName], '');
});
} else {
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));
} else {
this.setViewText('view',MciViewIds.view[mciName], t);
}
}
});
}
}
setEntries(entriesView) {
const config = this.menuConfig.config;
const listFormat = config.listFormat || '{bbsName}';
const focusListFormat = config.focusListFormat || '{bbsName}';
entriesView.setItems(this.entries.map( e => stringFormat(listFormat, e) ) );
entriesView.setFocusItems(this.entries.map( e => stringFormat(focusListFormat, e) ) );
}
displayBBSList(clearScreen, cb) {
const self = this;
async.waterfall(
[
function clearAndDisplayArt(callback) {
if(self.viewControllers.add) {
self.viewControllers.add.setFocus(false);
}
if (clearScreen) {
self.client.term.rawWrite(ansi.resetScreen());
}
theme.displayThemedAsset(
self.menuConfig.config.art.entries,
self.client,
{ font : self.menuConfig.font, trailingLF : false },
(err, artData) => {
return callback(err, artData);
}
);
},
function initOrRedrawViewController(artData, callback) {
if(_.isUndefined(self.viewControllers.add)) {
const vc = self.addViewController(
'view',
new ViewController( { client : self.client, formId : FormIds.View } )
);
const loadOpts = {
callingMenu : self,
mciMap : artData.mciMap,
formId : FormIds.View,
};
return vc.loadFromMenuConfig(loadOpts, callback);
} else {
self.viewControllers.view.setFocus(true);
self.viewControllers.view.getView(MciViewIds.view.BBSList).redraw();
return callback(null);
}
},
function fetchEntries(callback) {
const entriesView = self.viewControllers.view.getView(MciViewIds.view.BBSList);
self.entries = [];
self.database.each(
`SELECT id, bbs_name, sysop, telnet, www, location, software, submitter_user_id, notes
FROM bbs_list;`,
(err, row) => {
if (!err) {
self.entries.push({
id : row.id,
bbsName : row.bbs_name,
sysOp : row.sysop,
telnet : row.telnet,
www : row.www,
location : row.location,
software : row.software,
submitterUserId : row.submitter_user_id,
notes : row.notes,
});
}
},
err => {
return callback(err, entriesView);
}
);
},
function getUserNames(entriesView, callback) {
async.each(self.entries, (entry, next) => {
User.getUserName(entry.submitterUserId, (err, username) => {
if(username) {
entry.submitter = username;
} else {
entry.submitter = 'N/A';
}
return next();
});
}, () => {
return callback(null, entriesView);
});
},
function populateEntries(entriesView, callback) {
self.setEntries(entriesView);
entriesView.on('index update', idx => {
const entry = self.entries[idx];
self.drawSelectedEntry(entry);
if(!entry) {
self.selectedBBS = -1;
} else {
self.selectedBBS = idx;
}
});
if (self.selectedBBS >= 0) {
entriesView.setFocusItemIndex(self.selectedBBS);
self.drawSelectedEntry(self.entries[self.selectedBBS]);
} else if (self.entries.length > 0) {
entriesView.setFocusItemIndex(0);
self.drawSelectedEntry(self.entries[0]);
}
entriesView.redraw();
return callback(null);
}
],
err => {
if(cb) {
return cb(err);
}
}
);
}
displayAddScreen(cb) {
const self = this;
async.waterfall(
[
function clearAndDisplayArt(callback) {
self.viewControllers.view.setFocus(false);
self.client.term.rawWrite(ansi.resetScreen());
theme.displayThemedAsset(
self.menuConfig.config.art.add,
self.client,
{ font : self.menuConfig.font },
(err, artData) => {
return callback(err, artData);
}
);
},
function initOrRedrawViewController(artData, callback) {
if(_.isUndefined(self.viewControllers.add)) {
const vc = self.addViewController(
'add',
new ViewController( { client : self.client, formId : FormIds.Add } )
);
const loadOpts = {
callingMenu : self,
mciMap : artData.mciMap,
formId : FormIds.Add,
};
return vc.loadFromMenuConfig(loadOpts, callback);
} else {
self.viewControllers.add.setFocus(true);
self.viewControllers.add.redrawAll();
self.viewControllers.add.switchFocus(MciViewIds.add.BBSName);
return callback(null);
}
}
],
err => {
if(cb) {
return cb(err);
}
}
);
}
clearAddForm() {
[ 'BBSName', 'Sysop', 'Telnet', 'Www', 'Location', 'Software', 'Error', 'Notes' ].forEach( mciName => {
this.setViewText('add', MciViewIds.add[mciName], '');
});
}
initDatabase(cb) {
const self = this;
async.series(
[
function openDatabase(callback) {
self.database = new sqlite3.Database(
getModDatabasePath(moduleInfo),
callback
);
},
function createTables(callback) {
self.database.serialize( () => {
self.database.run(
`CREATE TABLE IF NOT EXISTS bbs_list (
id INTEGER PRIMARY KEY,
bbs_name VARCHAR NOT NULL,
sysop VARCHAR NOT NULL,
telnet VARCHAR NOT NULL,
www VARCHAR,
location VARCHAR,
software VARCHAR,
submitter_user_id INTEGER NOT NULL,
notes VARCHAR
);`
);
});
callback(null);
}
],
err => {
return cb(err);
}
);
}
beforeArt(cb) {
super.beforeArt(err => {
return err ? cb(err) : this.initDatabase(cb);
});
}
};

View File

@@ -1,179 +0,0 @@
/* jslint node: true */
'use strict';
const MenuModule = require('../core/menu_module.js').MenuModule;
const stringFormat = require('../core/string_format.js');
// deps
const async = require('async');
const _ = require('lodash');
const net = require('net');
/*
Expected configuration block example:
config: {
host: 192.168.1.171
port: 5001
bbsTag: SOME_TAG
}
*/
exports.getModule = ErcClientModule;
exports.moduleInfo = {
name : 'ENiGMA Relay Chat Client',
desc : 'Chat with other ENiGMA BBSes',
author : 'Andrew Pamment',
};
var MciViewIds = {
ChatDisplay : 1,
InputArea : 3,
};
// :TODO: needs converted to ES6 MenuModule subclass
function ErcClientModule(options) {
MenuModule.prototype.ctorShim.call(this, options);
const self = this;
this.config = options.menuConfig.config;
this.chatEntryFormat = this.config.chatEntryFormat || '[{bbsTag}] {userName}: {message}';
this.systemEntryFormat = this.config.systemEntryFormat || '[*SYSTEM*] {message}';
this.finishedLoading = function() {
async.waterfall(
[
function validateConfig(callback) {
if(_.isString(self.config.host) &&
_.isNumber(self.config.port) &&
_.isString(self.config.bbsTag))
{
return callback(null);
} else {
return callback(new Error('Configuration is missing required option(s)'));
}
},
function connectToServer(callback) {
const connectOpts = {
port : self.config.port,
host : self.config.host,
};
const chatMessageView = self.viewControllers.menu.getView(MciViewIds.ChatDisplay);
chatMessageView.setText('Connecting to server...');
chatMessageView.redraw();
self.viewControllers.menu.switchFocus(MciViewIds.InputArea);
// :TODO: Track actual client->enig connection for optional prevMenu @ final CB
self.chatConnection = net.createConnection(connectOpts.port, connectOpts.host);
self.chatConnection.on('data', data => {
data = data.toString();
if(data.startsWith('ERCHANDSHAKE')) {
self.chatConnection.write(`ERCMAGIC|${self.config.bbsTag}|${self.client.user.username}\r\n`);
} else if(data.startsWith('{')) {
try {
data = JSON.parse(data);
} catch(e) {
return self.client.log.warn( { error : e.message }, 'ERC: Error parsing ERC data from server');
}
let text;
try {
if(data.userName) {
// user message
text = stringFormat(self.chatEntryFormat, data);
} else {
// system message
text = stringFormat(self.systemEntryFormat, data);
}
} catch(e) {
return self.client.log.warn( { error : e.message }, 'ERC: chatEntryFormat error');
}
chatMessageView.addText(text);
if(chatMessageView.getLineCount() > 30) { // :TODO: should probably be ChatDisplay.height?
chatMessageView.deleteLine(0);
chatMessageView.scrollDown();
}
chatMessageView.redraw();
self.viewControllers.menu.switchFocus(MciViewIds.InputArea);
}
});
self.chatConnection.once('end', () => {
return callback(null);
});
self.chatConnection.once('error', err => {
self.client.log.info(`ERC connection error: ${err.message}`);
return callback(new Error('Failed connecting to ERC server!'));
});
}
],
err => {
if(err) {
self.client.log.warn( { error : err.message }, 'ERC error');
}
self.prevMenu();
}
);
};
this.scrollHandler = function(keyName) {
const inputAreaView = self.viewControllers.menu.getView(MciViewIds.InputArea);
const chatDisplayView = self.viewControllers.menu.getView(MciViewIds.ChatDisplay);
if('up arrow' === keyName) {
chatDisplayView.scrollUp();
} else {
chatDisplayView.scrollDown();
}
chatDisplayView.redraw();
inputAreaView.setFocus(true);
};
this.menuMethods = {
inputAreaSubmit : function(formData, extraArgs, cb) {
const inputAreaView = self.viewControllers.menu.getView(MciViewIds.InputArea);
const inputData = inputAreaView.getData();
if('/quit' === inputData.toLowerCase()) {
self.chatConnection.end();
} else {
try {
self.chatConnection.write(`${inputData}\r\n`);
} catch(e) {
self.client.log.warn( { error : e.message }, 'ERC error');
}
inputAreaView.clearText();
}
return cb(null);
},
scrollUp : function(formData, extraArgs, cb) {
self.scrollHandler(formData.key.name);
return cb(null);
},
scrollDown : function(formData, extraArgs, cb) {
self.scrollHandler(formData.key.name);
return cb(null);
}
};
}
require('util').inherits(ErcClientModule, MenuModule);
ErcClientModule.prototype.mciReady = function(mciData, cb) {
this.standardMCIReadyHandler(mciData, cb);
};

View File

@@ -1,339 +0,0 @@
/* jslint node: true */
'use strict';
// ENiGMA½
const MenuModule = require('../core/menu_module.js').MenuModule;
const ViewController = require('../core/view_controller.js').ViewController;
const getSortedAvailableFileAreas = require('../core/file_base_area.js').getSortedAvailableFileAreas;
const FileBaseFilters = require('../core/file_base_filter.js');
const stringFormat = require('../core/string_format.js');
// deps
const async = require('async');
exports.moduleInfo = {
name : 'File Area Filter Editor',
desc : 'Module for adding, deleting, and modifying file base filters',
author : 'NuSkooler',
};
const MciViewIds = {
editor : {
searchTerms : 1,
tags : 2,
area : 3,
sort : 4,
order : 5,
filterName : 6,
navMenu : 7,
// :TODO: use the customs new standard thing - filter obj can have active/selected, etc.
selectedFilterInfo : 10, // { ...filter object ... }
activeFilterInfo : 11, // { ...filter object ... }
error : 12, // validation errors
}
};
exports.getModule = class FileAreaFilterEdit extends MenuModule {
constructor(options) {
super(options);
this.filtersArray = new FileBaseFilters(this.client).toArray(); // ordered, such that we can index into them
this.currentFilterIndex = 0; // into |filtersArray|
//
// Lexical sort + keep currently active filter (if any) as the first item in |filtersArray|
//
const activeFilter = FileBaseFilters.getActiveFilter(this.client);
this.filtersArray.sort( (filterA, filterB) => {
if(activeFilter) {
if(filterA.uuid === activeFilter.uuid) {
return -1;
}
if(filterB.uuid === activeFilter.uuid) {
return 1;
}
}
return filterA.name.localeCompare(filterB.name, { sensitivity : false, numeric : true } );
});
this.menuMethods = {
saveFilter : (formData, extraArgs, cb) => {
return this.saveCurrentFilter(formData, cb);
},
prevFilter : (formData, extraArgs, cb) => {
this.currentFilterIndex -= 1;
if(this.currentFilterIndex < 0) {
this.currentFilterIndex = this.filtersArray.length - 1;
}
this.loadDataForFilter(this.currentFilterIndex);
return cb(null);
},
nextFilter : (formData, extraArgs, cb) => {
this.currentFilterIndex += 1;
if(this.currentFilterIndex >= this.filtersArray.length) {
this.currentFilterIndex = 0;
}
this.loadDataForFilter(this.currentFilterIndex);
return cb(null);
},
makeFilterActive : (formData, extraArgs, cb) => {
const filters = new FileBaseFilters(this.client);
filters.setActive(this.filtersArray[this.currentFilterIndex].uuid);
this.updateActiveLabel();
return cb(null);
},
newFilter : (formData, extraArgs, cb) => {
this.currentFilterIndex = this.filtersArray.length; // next avail slot
this.clearForm(MciViewIds.editor.searchTerms);
return cb(null);
},
deleteFilter : (formData, extraArgs, cb) => {
const selectedFilter = this.filtersArray[this.currentFilterIndex];
const filterUuid = selectedFilter.uuid;
// cannot delete built-in/system filters
if(true === selectedFilter.system) {
this.showError('Cannot delete built in filters!');
return cb(null);
}
this.filtersArray.splice(this.currentFilterIndex, 1); // remove selected entry
// remove from stored properties
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.file_base_filter_active_uuid) {
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');
}
}
// update UI
this.updateActiveLabel();
if(this.filtersArray.length > 0) {
this.loadDataForFilter(this.currentFilterIndex);
} else {
this.clearForm();
}
return cb(null);
});
},
viewValidationListener : (err, cb) => {
const errorView = this.viewControllers.editor.getView(MciViewIds.editor.error);
let newFocusId;
if(errorView) {
if(err) {
errorView.setText(err.message);
err.view.clearText(); // clear out the invalid data
} else {
errorView.clearText();
}
}
return cb(newFocusId);
},
};
}
showError(errMsg) {
const errorView = this.viewControllers.editor.getView(MciViewIds.editor.error);
if(errorView) {
if(errMsg) {
errorView.setText(errMsg);
} else {
errorView.clearText();
}
}
}
mciReady(mciData, cb) {
super.mciReady(mciData, err => {
if(err) {
return cb(err);
}
const self = this;
const vc = self.addViewController( 'editor', new ViewController( { client : this.client } ) );
async.series(
[
function loadFromConfig(callback) {
return vc.loadFromMenuConfig( { callingMenu : self, mciMap : mciData.menu }, callback);
},
function populateAreas(callback) {
self.availAreas = [ { name : '-ALL-' } ].concat(getSortedAvailableFileAreas(self.client) || []);
const areasView = vc.getView(MciViewIds.editor.area);
if(areasView) {
areasView.setItems( self.availAreas.map( a => a.name ) );
}
self.updateActiveLabel();
self.loadDataForFilter(self.currentFilterIndex);
self.viewControllers.editor.resetInitialFocus();
return callback(null);
}
],
err => {
return cb(err);
}
);
});
}
getCurrentFilter() {
return this.filtersArray[this.currentFilterIndex];
}
setText(mciId, text) {
const view = this.viewControllers.editor.getView(mciId);
if(view) {
view.setText(text);
}
}
updateActiveLabel() {
const activeFilter = FileBaseFilters.getActiveFilter(this.client);
if(activeFilter) {
const activeFormat = this.menuConfig.config.activeFormat || '{name}';
this.setText(MciViewIds.editor.activeFilterInfo, stringFormat(activeFormat, activeFilter));
}
}
setFocusItemIndex(mciId, index) {
const view = this.viewControllers.editor.getView(mciId);
if(view) {
view.setFocusItemIndex(index);
}
}
clearForm(newFocusId) {
[ 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 => {
this.setFocusItemIndex(mciId, 0);
});
if(newFocusId) {
this.viewControllers.editor.switchFocus(newFocusId);
} else {
this.viewControllers.editor.resetInitialFocus();
}
}
getSelectedAreaTag(index) {
if(0 === index) {
return ''; // -ALL-
}
const area = this.availAreas[index];
if(!area) {
return '';
}
return area.areaTag;
}
getOrderBy(index) {
return FileBaseFilters.OrderByValues[index] || FileBaseFilters.OrderByValues[0];
}
setAreaIndexFromCurrentFilter() {
let index;
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;
} else {
index = 0;
}
this.setFocusItemIndex(MciViewIds.editor.area, index);
}
setOrderByFromCurrentFilter() {
let index;
const filter = this.getCurrentFilter();
if(filter) {
index = FileBaseFilters.OrderByValues.findIndex( ob => filter.order === ob ) || 0;
} else {
index = 0;
}
this.setFocusItemIndex(MciViewIds.editor.order, index);
}
setSortByFromCurrentFilter() {
let index;
const filter = this.getCurrentFilter();
if(filter) {
index = FileBaseFilters.SortByValues.findIndex( sb => filter.sort === sb ) || 0;
} else {
index = 0;
}
this.setFocusItemIndex(MciViewIds.editor.sort, index);
}
getSortBy(index) {
return FileBaseFilters.SortByValues[index] || FileBaseFilters.SortByValues[0];
}
setFilterValuesFromFormData(filter, formData) {
filter.name = formData.value.name;
filter.areaTag = this.getSelectedAreaTag(formData.value.areaIndex);
filter.terms = formData.value.searchTerms;
filter.tags = formData.value.tags;
filter.order = this.getOrderBy(formData.value.orderByIndex);
filter.sort = this.getSortBy(formData.value.sortByIndex);
}
saveCurrentFilter(formData, cb) {
const filters = new FileBaseFilters(this.client);
const selectedFilter = this.filtersArray[this.currentFilterIndex];
if(selectedFilter) {
// *update* currently selected filter
this.setFilterValuesFromFormData(selectedFilter, formData);
filters.replace(selectedFilter.uuid, selectedFilter);
} else {
// add a new entry; note that UUID will be generated
const newFilter = {};
this.setFilterValuesFromFormData(newFilter, formData);
// set current to what we just saved
newFilter.uuid = filters.add(newFilter);
// add to our array (at current index position)
this.filtersArray[this.currentFilterIndex] = newFilter;
}
return filters.persist(cb);
}
loadDataForFilter(filterIndex) {
const filter = this.filtersArray[filterIndex];
if(filter) {
this.setText(MciViewIds.editor.searchTerms, filter.terms);
this.setText(MciViewIds.editor.tags, filter.tags);
this.setText(MciViewIds.editor.filterName, filter.name);
this.setAreaIndexFromCurrentFilter();
this.setSortByFromCurrentFilter();
this.setOrderByFromCurrentFilter();
}
}
};

View File

@@ -1,684 +0,0 @@
/* jslint node: true */
'use strict';
// ENiGMA½
const MenuModule = require('../core/menu_module.js').MenuModule;
const ViewController = require('../core/view_controller.js').ViewController;
const ansi = require('../core/ansi_term.js');
const theme = require('../core/theme.js');
const FileEntry = require('../core/file_entry.js');
const stringFormat = require('../core/string_format.js');
const FileArea = require('../core/file_base_area.js');
const Errors = require('../core/enig_error.js').Errors;
const ErrNotEnabled = require('../core/enig_error.js').ErrorReasons.NotEnabled;
const ArchiveUtil = require('../core/archive_util.js');
const Config = require('../core/config.js').config;
const DownloadQueue = require('../core/download_queue.js');
const FileAreaWeb = require('../core/file_area_web.js');
const FileBaseFilters = require('../core/file_base_filter.js');
const resolveMimeType = require('../core/mime_util.js').resolveMimeType;
const isAnsi = require('../core/string_util.js').isAnsi;
// deps
const async = require('async');
const _ = require('lodash');
const moment = require('moment');
exports.moduleInfo = {
name : 'File Area List',
desc : 'Lists contents of file an file area',
author : 'NuSkooler',
};
const FormIds = {
browse : 0,
details : 1,
detailsGeneral : 2,
detailsNfo : 3,
detailsFileList : 4,
};
const MciViewIds = {
browse : {
desc : 1,
navMenu : 2,
customRangeStart : 10, // 10+ = customs
},
details : {
navMenu : 1,
infoXyTop : 2, // %XY starting position for info area
infoXyBottom : 3,
customRangeStart : 10, // 10+ = customs
},
detailsGeneral : {
customRangeStart : 10, // 10+ = customs
},
detailsNfo : {
nfo : 1,
customRangeStart : 10, // 10+ = customs
},
detailsFileList : {
fileList : 1,
customRangeStart : 10, // 10+ = customs
},
};
exports.getModule = class FileAreaList extends MenuModule {
constructor(options) {
super(options);
this.filterCriteria = _.get(options, 'extraArgs.filterCriteria');
this.fileList = _.get(options, 'extraArgs.fileList');
if(this.fileList) {
// we'll need to adjust position as well!
this.fileListPosition = 0;
}
this.dlQueue = new DownloadQueue(this.client);
if(!this.filterCriteria) {
this.filterCriteria = FileBaseFilters.getActiveFilter(this.client);
}
if(_.isString(this.filterCriteria)) {
this.filterCriteria = JSON.parse(this.filterCriteria);
}
if(_.has(options, 'lastMenuResult.value')) {
this.lastMenuResultValue = options.lastMenuResult.value;
}
this.menuMethods = {
nextFile : (formData, extraArgs, cb) => {
if(this.fileListPosition + 1 < this.fileList.length) {
this.fileListPosition += 1;
return this.displayBrowsePage(true, cb); // true=clerarScreen
}
return cb(null);
},
prevFile : (formData, extraArgs, cb) => {
if(this.fileListPosition > 0) {
--this.fileListPosition;
return this.displayBrowsePage(true, cb); // true=clearScreen
}
return cb(null);
},
viewDetails : (formData, extraArgs, cb) => {
this.viewControllers.browse.setFocus(false);
return this.displayDetailsPage(cb);
},
detailsQuit : (formData, extraArgs, cb) => {
[ 'detailsNfo', 'detailsFileList', 'details' ].forEach(n => {
const vc = this.viewControllers[n];
if(vc) {
vc.detachClientEvents();
}
});
return this.displayBrowsePage(true, cb); // true=clearScreen
},
toggleQueue : (formData, extraArgs, cb) => {
this.dlQueue.toggle(this.currentFileEntry);
this.updateQueueIndicator();
return cb(null);
},
showWebDownloadLink : (formData, extraArgs, cb) => {
return this.fetchAndDisplayWebDownloadLink(cb);
},
displayHelp : (formData, extraArgs, cb) => {
return this.displayHelpPage(cb);
}
};
}
enter() {
super.enter();
}
leave() {
super.leave();
}
getSaveState() {
return {
fileList : this.fileList,
fileListPosition : this.fileListPosition,
};
}
restoreSavedState(savedState) {
if(savedState) {
this.fileList = savedState.fileList;
this.fileListPosition = savedState.fileListPosition;
}
}
updateFileEntryWithMenuResult(cb) {
if(!this.lastMenuResultValue) {
return cb(null);
}
if(_.isNumber(this.lastMenuResultValue.rating)) {
const fileId = this.fileList[this.fileListPosition];
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' );
}
return cb(null);
});
} else {
return cb(null);
}
}
initSequence() {
const self = this;
async.series(
[
function preInit(callback) {
return self.updateFileEntryWithMenuResult(callback);
},
function beforeArt(callback) {
return self.beforeArt(callback);
},
function display(callback) {
return self.displayBrowsePage(false, err => {
if(err && 'NORESULTS' === err.reasonCode) {
self.gotoMenu(self.menuConfig.config.noResultsMenu || 'fileBaseListEntriesNoResults');
}
return callback(err);
});
}
],
() => {
self.finishedLoading();
}
);
}
populateCurrentEntryInfo(cb) {
const config = this.menuConfig.config;
const currEntry = this.currentFileEntry;
const uploadTimestampFormat = config.browseUploadTimestampFormat || config.uploadTimestampFormat || 'YYYY-MMM-DD';
const area = FileArea.getFileAreaByTag(currEntry.areaTag);
const hashTagsSep = config.hashTagsSep || ', ';
const isQueuedIndicator = config.isQueuedIndicator || 'Y';
const isNotQueuedIndicator = config.isNotQueuedIndicator || 'N';
const entryInfo = currEntry.entryInfo = {
fileId : currEntry.fileId,
areaTag : currEntry.areaTag,
areaName : _.get(area, 'name') || 'N/A',
areaDesc : _.get(area, 'desc') || 'N/A',
fileSha256 : currEntry.fileSha256,
fileName : currEntry.fileName,
desc : currEntry.desc || '',
descLong : currEntry.descLong || '',
userRating : currEntry.userRating,
uploadTimestamp : moment(currEntry.uploadTimestamp).format(uploadTimestampFormat),
hashTags : Array.from(currEntry.hashTags).join(hashTagsSep),
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
// consumers may very likely attempt to use them
//
const metaValues = FileEntry.WellKnownMetaValues;
metaValues.forEach(name => {
const value = !_.isUndefined(currEntry.meta[name]) ? currEntry.meta[name] : 'N/A';
entryInfo[_.camelCase(name)] = value;
});
if(entryInfo.archiveType) {
const mimeType = resolveMimeType(entryInfo.archiveType);
entryInfo.archiveTypeDesc = mimeType ? _.get(Config, [ 'fileTypes', mimeType, 'desc' ] ) || mimeType : entryInfo.archiveType;
} else {
entryInfo.archiveTypeDesc = 'N/A';
}
entryInfo.uploadByUsername = entryInfo.uploadByUsername || 'N/A'; // may be imported
entryInfo.hashTags = entryInfo.hashTags || '(none)';
// create a rating string, e.g. "**---"
const userRatingTicked = config.userRatingTicked || '*';
const userRatingUnticked = config.userRatingUnticked || '';
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) );
}
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';
} else {
entryInfo.webDlLink = config.webDlLinkNeedsGenerated || 'Not yet generated';
}
} else {
const webDlExpireTimeFormat = config.webDlExpireTimeFormat || 'YYYY-MMM-DD @ h:mm';
entryInfo.webDlLink = serveItem.url;
entryInfo.webDlExpire = moment(serveItem.expireTimestamp).format(webDlExpireTimeFormat);
}
return cb(null);
});
}
populateCustomLabels(category, startId) {
return this.updateCustomViewTextsWithFilter(category, startId, this.currentFileEntry.entryInfo);
}
displayArtAndPrepViewController(name, options, cb) {
const self = this;
const config = this.menuConfig.config;
async.waterfall(
[
function readyAndDisplayArt(callback) {
if(options.clearScreen) {
self.client.term.rawWrite(ansi.resetScreen());
}
theme.displayThemedAsset(
config.art[name],
self.client,
{ font : self.menuConfig.font, trailingLF : false },
(err, artData) => {
return callback(err, artData);
}
);
},
function prepeareViewController(artData, callback) {
if(_.isUndefined(self.viewControllers[name])) {
const vcOpts = {
client : self.client,
formId : FormIds[name],
};
if(!_.isUndefined(options.noInput)) {
vcOpts.noInput = options.noInput;
}
const vc = self.addViewController(name, new ViewController(vcOpts));
if('details' === name) {
try {
self.detailsInfoArea = {
top : artData.mciMap.XY2.position,
bottom : artData.mciMap.XY3.position,
};
} catch(e) {
return callback(Errors.DoesNotExist('Missing XY2 and XY3 position indicators!'));
}
}
const loadOpts = {
callingMenu : self,
mciMap : artData.mciMap,
formId : FormIds[name],
};
return vc.loadFromMenuConfig(loadOpts, callback);
}
self.viewControllers[name].setFocus(true);
return callback(null);
},
],
err => {
return cb(err);
}
);
}
displayBrowsePage(clearScreen, cb) {
const self = this;
async.series(
[
function fetchEntryData(callback) {
if(self.fileList) {
return callback(null);
}
return self.loadFileIds(false, callback); // false=do not force
},
function checkEmptyResults(callback) {
if(0 === self.fileList.length) {
return callback(Errors.General('No results for criteria', 'NORESULTS'));
}
return callback(null);
},
function prepArtAndViewController(callback) {
return self.displayArtAndPrepViewController('browse', { clearScreen : clearScreen }, callback);
},
function loadCurrentFileInfo(callback) {
self.currentFileEntry = new FileEntry();
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);
if(descView) {
if(isAnsi(self.currentFileEntry.desc)) {
descView.setAnsi(
self.currentFileEntry.desc,
{
prepped : false,
forceLineTerm : true
},
() => {
return callback(null);
}
);
} else {
descView.setText(self.currentFileEntry.desc);
return callback(null);
}
}
} else {
return callback(null);
}
},
function populateAdditionalViews(callback) {
self.updateQueueIndicator();
self.populateCustomLabels('browse', MciViewIds.browse.customRangeStart);
return callback(null);
}
],
err => {
if(cb) {
return cb(err);
}
}
);
}
displayDetailsPage(cb) {
const self = this;
async.series(
[
function prepArtAndViewController(callback) {
return self.displayArtAndPrepViewController('details', { clearScreen : true }, callback);
},
function populateViews(callback) {
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);
navMenu.setFocusItemIndex(0);
navMenu.on('index update', index => {
const sectionName = {
0 : 'general',
1 : 'nfo',
2 : 'fileList',
}[index];
if(sectionName) {
self.displayDetailsSection(sectionName, true);
}
});
return callback(null);
}
],
err => {
return cb(err);
}
);
}
displayHelpPage(cb) {
this.displayAsset(
this.menuConfig.config.art.help,
{ clearScreen : true },
() => {
this.client.waitForKeyPress( () => {
return this.displayBrowsePage(true, cb);
});
}
);
}
fetchAndDisplayWebDownloadLink(cb) {
const self = this;
async.series(
[
function generateLinkIfNeeded(callback) {
if(self.currentFileEntry.webDlExpireTime < moment()) {
return callback(null);
}
const expireTime = moment().add(Config.fileBase.web.expireMinutes, 'minutes');
FileAreaWeb.createAndServeTempDownload(
self.client,
self.currentFileEntry,
{ expireTime : expireTime },
(err, url) => {
if(err) {
return callback(err);
}
self.currentFileEntry.webDlExpireTime = expireTime;
const webDlExpireTimeFormat = self.menuConfig.config.webDlExpireTimeFormat || 'YYYY-MMM-DD @ h:mm';
self.currentFileEntry.entryInfo.webDlLink = url;
self.currentFileEntry.entryInfo.webDlExpire = expireTime.format(webDlExpireTimeFormat);
return callback(null);
}
);
},
function updateActiveViews(callback) {
self.updateCustomViewTextsWithFilter(
'browse',
MciViewIds.browse.customRangeStart, self.currentFileEntry.entryInfo,
{ filter : [ '{webDlLink}', '{webDlExpire}' ] }
);
return callback(null);
}
],
err => {
return cb(err);
}
);
}
updateQueueIndicator() {
const isQueuedIndicator = this.menuConfig.config.isQueuedIndicator || 'Y';
const isNotQueuedIndicator = this.menuConfig.config.isNotQueuedIndicator || 'N';
this.currentFileEntry.entryInfo.isQueued = stringFormat(
this.dlQueue.isQueued(this.currentFileEntry) ?
isQueuedIndicator :
isNotQueuedIndicator
);
this.updateCustomViewTextsWithFilter(
'browse',
MciViewIds.browse.customRangeStart,
this.currentFileEntry.entryInfo,
{ filter : [ '{isQueued}' ] }
);
}
cacheArchiveEntries(cb) {
// check cache
if(this.currentFileEntry.archiveEntries) {
return cb(null, 'cache');
}
const areaInfo = FileArea.getFileAreaByTag(this.currentFileEntry.areaTag);
if(!areaInfo) {
return cb(Errors.Invalid('Invalid area tag'));
}
const filePath = this.currentFileEntry.filePath;
const archiveUtil = ArchiveUtil.getInstance();
archiveUtil.listEntries(filePath, this.currentFileEntry.entryInfo.archiveType, (err, entries) => {
if(err) {
return cb(err);
}
this.currentFileEntry.archiveEntries = entries;
return cb(null, 're-cached');
});
}
populateFileListing() {
const fileListView = this.viewControllers.detailsFileList.getView(MciViewIds.detailsFileList.fileList);
if(this.currentFileEntry.entryInfo.archiveType) {
this.cacheArchiveEntries( (err, cacheStatus) => {
if(err) {
// :TODO: Handle me!!!
fileListView.setItems( [ 'Failed getting file listing' ] ); // :TODO: make this not suck
return;
}
if('re-cached' === cacheStatus) {
const fileListEntryFormat = this.menuConfig.config.fileListEntryFormat || '{fileName} {fileSize}'; // :TODO: use byteSize here?
const focusFileListEntryFormat = this.menuConfig.config.focusFileListEntryFormat || fileListEntryFormat;
fileListView.setItems( this.currentFileEntry.archiveEntries.map( entry => stringFormat(fileListEntryFormat, entry) ) );
fileListView.setFocusItems( this.currentFileEntry.archiveEntries.map( entry => stringFormat(focusFileListEntryFormat, entry) ) );
fileListView.redraw();
}
});
} else {
fileListView.setItems( [ stringFormat(this.menuConfig.config.notAnArchiveFormat || 'Not an archive', { fileName : this.currentFileEntry.fileName } ) ] );
}
}
displayDetailsSection(sectionName, clearArea, cb) {
const self = this;
const name = `details${_.upperFirst(sectionName)}`;
async.series(
[
function detachPrevious(callback) {
if(self.lastDetailsViewController) {
self.lastDetailsViewController.detachClientEvents();
}
return callback(null);
},
function prepArtAndViewController(callback) {
function gotoTopPos() {
self.client.term.rawWrite(ansi.goto(self.detailsInfoArea.top[0], 1));
}
gotoTopPos();
if(clearArea) {
self.client.term.rawWrite(ansi.reset());
let pos = self.detailsInfoArea.top[0];
const bottom = self.detailsInfoArea.bottom[0];
while(pos++ <= bottom) {
self.client.term.rawWrite(ansi.eraseLine() + ansi.down());
}
gotoTopPos();
}
return self.displayArtAndPrepViewController(name, { clearScreen : false, noInput : true }, callback);
},
function populateViews(callback) {
self.lastDetailsViewController = self.viewControllers[name];
switch(sectionName) {
case 'nfo' :
{
const nfoView = self.viewControllers.detailsNfo.getView(MciViewIds.detailsNfo.nfo);
if(!nfoView) {
return callback(null);
}
if(isAnsi(self.currentFileEntry.entryInfo.descLong)) {
nfoView.setAnsi(
self.currentFileEntry.entryInfo.descLong,
{
prepped : false,
forceLineTerm : true,
},
() => {
return callback(null);
}
);
} else {
nfoView.setText(self.currentFileEntry.entryInfo.descLong);
return callback(null);
}
}
break;
case 'fileList' :
self.populateFileListing();
return callback(null);
default :
return callback(null);
}
},
function setLabels(callback) {
self.populateCustomLabels(name, MciViewIds[name].customRangeStart);
return callback(null);
}
],
err => {
if(cb) {
return cb(err);
}
}
);
}
loadFileIds(force, cb) {
if(force || (_.isUndefined(this.fileList) || _.isUndefined(this.fileListPosition))) {
this.fileListPosition = 0;
FileEntry.findFiles(this.filterCriteria, (err, fileIds) => {
this.fileList = fileIds;
return cb(err);
});
}
}
};

View File

@@ -1,84 +0,0 @@
/* jslint node: true */
'use strict';
// enigma-bbs
const MenuModule = require('../core/menu_module.js').MenuModule;
const Config = require('../core/config.js').config;
const stringFormat = require('../core/string_format.js');
const ViewController = require('../core/view_controller.js').ViewController;
const getSortedAvailableFileAreas = require('../core/file_base_area.js').getSortedAvailableFileAreas;
// deps
const async = require('async');
const _ = require('lodash');
exports.moduleInfo = {
name : 'File Area Selector',
desc : 'Select from available file areas',
author : 'NuSkooler',
};
const MciViewIds = {
areaList : 1,
};
exports.getModule = class FileAreaSelectModule extends MenuModule {
constructor(options) {
super(options);
this.config = this.menuConfig.config || {};
this.loadAvailAreas();
this.menuMethods = {
selectArea : (formData, extraArgs, cb) => {
const area = this.availAreas[formData.value.areaSelect] || 0;
const filterCriteria = {
areaTag : area.areaTag,
};
const menuOpts = {
extraArgs : {
filterCriteria : filterCriteria,
},
menuFlags : [ 'noHistory' ],
};
return this.gotoMenu(this.menuConfig.config.fileBaseListEntriesMenu || 'fileBaseListEntries', menuOpts, cb);
}
};
}
loadAvailAreas() {
this.availAreas = getSortedAvailableFileAreas(this.client);
}
mciReady(mciData, cb) {
super.mciReady(mciData, err => {
if(err) {
return cb(err);
}
this.prepViewController('allViews', 0, { mciMap : mciData.menu }, (err, vc) => {
if(err) {
return cb(err);
}
const areaListView = vc.getView(MciViewIds.areaList);
const areaListFormat = this.config.areaListFormat || '{name}';
areaListView.setItems(this.availAreas.map(a => stringFormat(areaListFormat, a) ) );
if(this.config.areaListFocusFormat) {
areaListView.setFocusItems(this.availAreas.map(a => stringFormat(this.config.areaListFocusFormat, a) ) );
}
areaListView.redraw();
return cb(null);
});
});
}
};

View File

@@ -1,218 +0,0 @@
/* jslint node: true */
'use strict';
// ENiGMA½
const MenuModule = require('../core/menu_module.js').MenuModule;
const ViewController = require('../core/view_controller.js').ViewController;
const DownloadQueue = require('../core/download_queue.js');
const theme = require('../core/theme.js');
const ansi = require('../core/ansi_term.js');
const Errors = require('../core/enig_error.js').Errors;
const stringFormat = require('../core/string_format.js');
// deps
const async = require('async');
const _ = require('lodash');
exports.moduleInfo = {
name : 'File Base Download Queue Manager',
desc : 'Module for interacting with download queue/batch',
author : 'NuSkooler',
};
const FormIds = {
queueManager : 0,
details : 1,
};
const MciViewIds = {
queueManager : {
queue : 1,
navMenu : 2,
},
details : {
}
};
exports.getModule = class FileBaseDownloadQueueManager extends MenuModule {
constructor(options) {
super(options);
this.dlQueue = new DownloadQueue(this.client);
if(_.has(options, 'lastMenuResult.sentFileIds')) {
this.sentFileIds = options.lastMenuResult.sentFileIds;
}
this.fallbackOnly = options.lastMenuResult ? true : false;
this.menuMethods = {
downloadAll : (formData, extraArgs, cb) => {
const modOpts = {
extraArgs : {
sendQueue : this.dlQueue.items,
direction : 'send',
}
};
return this.gotoMenu(this.menuConfig.config.fileTransferProtocolSelection || 'fileTransferProtocolSelection', modOpts, cb);
},
viewItemInfo : (formData, extraArgs, cb) => {
},
removeItem : (formData, extraArgs, cb) => {
const selectedItem = this.dlQueue.items[formData.value.queueItem];
if(!selectedItem) {
return cb(null);
}
this.dlQueue.removeItems(selectedItem.fileId);
// :TODO: broken: does not redraw menu properly - needs fixed!
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);
}
};
}
initSequence() {
if(0 === this.dlQueue.items.length) {
if(this.sendFileIds) {
// we've finished everything up - just fall back
return this.prevMenu();
}
// Simply an empty D/L queue: Present a specialized "empty queue" page
return this.gotoMenu(this.menuConfig.config.emptyQueueMenu || 'fileBaseDownloadManagerEmptyQueue');
}
const self = this;
async.series(
[
function beforeArt(callback) {
return self.beforeArt(callback);
},
function display(callback) {
return self.displayQueueManagerPage(false, callback);
}
],
() => {
return self.finishedLoading();
}
);
}
removeItemsFromDownloadQueueView(itemIndex, cb) {
const queueView = this.viewControllers.queueManager.getView(MciViewIds.queueManager.queue);
if(!queueView) {
return cb(Errors.DoesNotExist('Queue view does not exist'));
}
if('all' === itemIndex) {
queueView.setItems([]);
queueView.setFocusItems([]);
} else {
queueView.removeItem(itemIndex);
}
queueView.redraw();
return cb(null);
}
updateDownloadQueueView(cb) {
const queueView = this.viewControllers.queueManager.getView(MciViewIds.queueManager.queue);
if(!queueView) {
return cb(Errors.DoesNotExist('Queue view does not exist'));
}
const queueListFormat = this.menuConfig.config.queueListFormat || '{fileName} {byteSize}';
const focusQueueListFormat = this.menuConfig.config.focusQueueListFormat || queueListFormat;
queueView.setItems(this.dlQueue.items.map( queueItem => stringFormat(queueListFormat, queueItem) ) );
queueView.setFocusItems(this.dlQueue.items.map( queueItem => stringFormat(focusQueueListFormat, queueItem) ) );
queueView.redraw();
return cb(null);
}
displayQueueManagerPage(clearScreen, cb) {
const self = this;
async.series(
[
function prepArtAndViewController(callback) {
return self.displayArtAndPrepViewController('queueManager', { clearScreen : clearScreen }, callback);
},
function populateViews(callback) {
return self.updateDownloadQueueView(callback);
}
],
err => {
if(cb) {
return cb(err);
}
}
);
}
displayArtAndPrepViewController(name, options, cb) {
const self = this;
const config = this.menuConfig.config;
async.waterfall(
[
function readyAndDisplayArt(callback) {
if(options.clearScreen) {
self.client.term.rawWrite(ansi.resetScreen());
}
theme.displayThemedAsset(
config.art[name],
self.client,
{ font : self.menuConfig.font, trailingLF : false },
(err, artData) => {
return callback(err, artData);
}
);
},
function prepeareViewController(artData, callback) {
if(_.isUndefined(self.viewControllers[name])) {
const vcOpts = {
client : self.client,
formId : FormIds[name],
};
if(!_.isUndefined(options.noInput)) {
vcOpts.noInput = options.noInput;
}
const vc = self.addViewController(name, new ViewController(vcOpts));
const loadOpts = {
callingMenu : self,
mciMap : artData.mciMap,
formId : FormIds[name],
};
return vc.loadFromMenuConfig(loadOpts, callback);
}
self.viewControllers[name].setFocus(true);
return callback(null);
},
],
err => {
return cb(err);
}
);
}
};

View File

@@ -1,120 +0,0 @@
/* jslint node: true */
'use strict';
// ENiGMA½
const MenuModule = require('../core/menu_module.js').MenuModule;
const ViewController = require('../core/view_controller.js').ViewController;
const getSortedAvailableFileAreas = require('../core/file_base_area.js').getSortedAvailableFileAreas;
const FileBaseFilters = require('../core/file_base_filter.js');
// deps
const async = require('async');
exports.moduleInfo = {
name : 'File Base Search',
desc : 'Module for quickly searching the file base',
author : 'NuSkooler',
};
const MciViewIds = {
search : {
searchTerms : 1,
search : 2,
tags : 3,
area : 4,
orderBy : 5,
sort : 6,
advSearch : 7,
}
};
exports.getModule = class FileBaseSearch extends MenuModule {
constructor(options) {
super(options);
this.menuMethods = {
search : (formData, extraArgs, cb) => {
const isAdvanced = formData.submitId === MciViewIds.search.advSearch;
return this.searchNow(formData, isAdvanced, cb);
},
};
}
mciReady(mciData, cb) {
super.mciReady(mciData, err => {
if(err) {
return cb(err);
}
const self = this;
const vc = self.addViewController( 'search', new ViewController( { client : this.client } ) );
async.series(
[
function loadFromConfig(callback) {
return vc.loadFromMenuConfig( { callingMenu : self, mciMap : mciData.menu }, callback);
},
function populateAreas(callback) {
self.availAreas = [ { name : '-ALL-' } ].concat(getSortedAvailableFileAreas(self.client) || []);
const areasView = vc.getView(MciViewIds.search.area);
areasView.setItems( self.availAreas.map( a => a.name ) );
areasView.redraw();
vc.switchFocus(MciViewIds.search.searchTerms);
return callback(null);
}
],
err => {
return cb(err);
}
);
});
}
getSelectedAreaTag(index) {
if(0 === index) {
return ''; // -ALL-
}
const area = this.availAreas[index];
if(!area) {
return '';
}
return area.areaTag;
}
getOrderBy(index) {
return FileBaseFilters.OrderByValues[index] || FileBaseFilters.OrderByValues[0];
}
getSortBy(index) {
return FileBaseFilters.SortByValues[index] || FileBaseFilters.SortByValues[0];
}
getFilterValuesFromFormData(formData, isAdvanced) {
const areaIndex = isAdvanced ? formData.value.areaIndex : 0;
const orderByIndex = isAdvanced ? formData.value.orderByIndex : 0;
const sortByIndex = isAdvanced ? formData.value.sortByIndex : 0;
return {
areaTag : this.getSelectedAreaTag(areaIndex),
terms : formData.value.searchTerms,
tags : isAdvanced ? formData.value.tags : '',
order : this.getOrderBy(orderByIndex),
sort : this.getSortBy(sortByIndex),
};
}
searchNow(formData, isAdvanced, cb) {
const filterCriteria = this.getFilterValuesFromFormData(formData, isAdvanced);
const menuOpts = {
extraArgs : {
filterCriteria : filterCriteria,
},
menuFlags : [ 'noHistory' ],
};
return this.gotoMenu(this.menuConfig.config.fileBaseListEntriesMenu || 'fileBaseListEntries', menuOpts, cb);
}
};

View File

@@ -1,158 +0,0 @@
/* jslint node: true */
'use strict';
// enigma-bbs
const MenuModule = require('../core/menu_module.js').MenuModule;
const Config = require('../core/config.js').config;
const stringFormat = require('../core/string_format.js');
const ViewController = require('../core/view_controller.js').ViewController;
// deps
const async = require('async');
const _ = require('lodash');
exports.moduleInfo = {
name : 'File transfer protocol selection',
desc : 'Select protocol / method for file transfer',
author : 'NuSkooler',
};
const MciViewIds = {
protList : 1,
};
exports.getModule = class FileTransferProtocolSelectModule extends MenuModule {
constructor(options) {
super(options);
this.config = this.menuConfig.config || {};
if(options.extraArgs) {
if(options.extraArgs.direction) {
this.config.direction = options.extraArgs.direction;
}
}
this.config.direction = this.config.direction || 'send';
this.extraArgs = options.extraArgs;
if(_.has(options, 'lastMenuResult.sentFileIds')) {
this.sentFileIds = options.lastMenuResult.sentFileIds;
}
if(_.has(options, 'lastMenuResult.recvFilePaths')) {
this.recvFilePaths = options.lastMenuResult.recvFilePaths;
}
this.fallbackOnly = options.lastMenuResult ? true : false;
this.loadAvailProtocols();
this.menuMethods = {
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 );
const modOpts = {
extraArgs : finalExtraArgs,
};
if('send' === this.config.direction) {
return this.gotoMenu(this.config.downloadFilesMenu || 'sendFilesToUser', modOpts, cb);
} else {
return this.gotoMenu(this.config.uploadFilesMenu || 'recvFilesFromUser', modOpts, cb);
}
},
};
}
getMenuResult() {
if(this.sentFileIds) {
return { sentFileIds : this.sentFileIds };
}
if(this.recvFilePaths) {
return { recvFilePaths : this.recvFilePaths };
}
}
initSequence() {
if(this.sentFileIds || this.recvFilePaths) {
// nothing to do here; move along (we're just falling through)
this.prevMenu();
} else {
super.initSequence();
}
}
mciReady(mciData, cb) {
super.mciReady(mciData, err => {
if(err) {
return cb(err);
}
const self = this;
const vc = self.viewControllers.allViews = new ViewController( { client : self.client } );
async.series(
[
function loadFromConfig(callback) {
const loadOpts = {
callingMenu : self,
mciMap : mciData.menu
};
return vc.loadFromMenuConfig(loadOpts, callback);
},
function populateList(callback) {
const protListView = vc.getView(MciViewIds.protList);
const protListFormat = self.config.protListFormat || '{name}';
const protListFocusFormat = self.config.protListFocusFormat || protListFormat;
protListView.setItems(self.protocols.map(p => stringFormat(protListFormat, p) ) );
protListView.setFocusItems(self.protocols.map(p => stringFormat(protListFocusFormat, p) ) );
protListView.redraw();
return callback(null);
}
],
err => {
return cb(err);
}
);
});
}
loadAvailProtocols() {
this.protocols = _.map(Config.fileTransferProtocols, (protInfo, protocol) => {
return {
protocol : protocol,
name : protInfo.name,
hasBatch : _.has(protInfo, 'external.recvArgs'),
hasNonBatch : _.has(protInfo, 'external.recvArgsNonBatch'),
sort : protInfo.sort,
};
});
// Filter out batch vs non-batch only protocols
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 );
}
// natural sort taking explicit orders into consideration
this.protocols.sort( (a, b) => {
if(_.isNumber(a.sort) && _.isNumber(b.sort)) {
return a.sort - b.sort;
} else {
return a.name.localeCompare(b.name, { sensitivity : false, numeric : true } );
}
});
}
};

View File

@@ -1,151 +0,0 @@
/* jslint node: true */
'use strict';
// ENiGMA½
const MenuModule = require('../core/menu_module.js').MenuModule;
const ViewController = require('../core/view_controller.js').ViewController;
const StatLog = require('../core/stat_log.js');
const User = require('../core/user.js');
const stringFormat = require('../core/string_format.js');
// deps
const moment = require('moment');
const async = require('async');
const _ = require('lodash');
/*
Available listFormat object members:
userId
userName
location
affiliation
ts
*/
exports.moduleInfo = {
name : 'Last Callers',
desc : 'Last callers to the system',
author : 'NuSkooler',
packageName : 'codes.l33t.enigma.lastcallers'
};
const MciCodeIds = {
CallerList : 1,
};
exports.getModule = class LastCallersModule extends MenuModule {
constructor(options) {
super(options);
}
mciReady(mciData, cb) {
super.mciReady(mciData, err => {
if(err) {
return cb(err);
}
const self = this;
const vc = self.viewControllers.allViews = new ViewController( { client : self.client } );
let loginHistory;
let callersView;
async.series(
[
function loadFromConfig(callback) {
const loadOpts = {
callingMenu : self,
mciMap : mciData.menu,
noInput : true,
};
vc.loadFromMenuConfig(loadOpts, callback);
},
function fetchHistory(callback) {
callersView = vc.getView(MciCodeIds.CallerList);
// fetch up
StatLog.getSystemLogEntries('user_login_history', StatLog.Order.TimestampDesc, 200, (err, lh) => {
loginHistory = lh;
if(self.menuConfig.config.hideSysOpLogin) {
const noOpLoginHistory = loginHistory.filter(lh => {
return false === User.isRootUserId(parseInt(lh.log_value)); // log_value=userId
});
//
// If we have enough items to display, or hideSysOpLogin is set to 'always',
// then set loginHistory to our filtered list. Else, we'll leave it be.
//
if(noOpLoginHistory.length >= callersView.dimens.height || 'always' === self.menuConfig.config.hideSysOpLogin) {
loginHistory = noOpLoginHistory;
}
}
//
// Finally, we need to trim up the list to the needed size
//
loginHistory = loginHistory.slice(0, callersView.dimens.height);
return callback(err);
});
},
function getUserNamesAndProperties(callback) {
const getPropOpts = {
names : [ 'location', 'affiliation' ]
};
const dateTimeFormat = self.menuConfig.config.dateTimeFormat || 'ddd MMM DD';
async.each(
loginHistory,
(item, next) => {
item.userId = parseInt(item.log_value);
item.ts = moment(item.timestamp).format(dateTimeFormat);
User.getUserName(item.userId, (err, userName) => {
if(err) {
item.deleted = true;
return next(null);
} else {
item.userName = userName || 'N/A';
User.loadProperties(item.userId, getPropOpts, (err, props) => {
if(!err && props) {
item.location = props.location || 'N/A';
item.affiliation = item.affils = (props.affiliation || 'N/A');
} else {
item.location = 'N/A';
item.affiliation = item.affils = 'N/A';
}
return next(null);
});
}
});
},
err => {
loginHistory = loginHistory.filter(lh => true !== lh.deleted);
return callback(err);
}
);
},
function populateList(callback) {
const listFormat = self.menuConfig.config.listFormat || '{userName} - {location} - {affiliation} - {ts}';
callersView.setItems(_.map(loginHistory, ce => stringFormat(listFormat, ce) ) );
callersView.redraw();
return callback(null);
}
],
(err) => {
if(err) {
self.client.log.error( { error : err.toString() }, 'Error loading last callers');
}
cb(err);
}
);
});
}
};

File diff suppressed because it is too large Load Diff

View File

@@ -1,177 +0,0 @@
/* jslint node: true */
'use strict';
// ENiGMA½
const MenuModule = require('../core/menu_module.js').MenuModule;
const ViewController = require('../core/view_controller.js').ViewController;
const messageArea = require('../core/message_area.js');
const displayThemeArt = require('../core/theme.js').displayThemeArt;
const resetScreen = require('../core/ansi_term.js').resetScreen;
const stringFormat = require('../core/string_format.js');
// deps
const async = require('async');
const _ = require('lodash');
exports.moduleInfo = {
name : 'Message Area List',
desc : 'Module for listing / choosing message areas',
author : 'NuSkooler',
};
/*
:TODO:
Obv/2 has the following:
CHANGE .ANS - Message base changing ansi
|SN Current base name
|SS Current base sponsor
|NM Number of messages in current base
|UP Number of posts current user made (total)
|LR Last read message by current user
|DT Current date
|TI Current time
*/
const MciViewIds = {
AreaList : 1,
SelAreaInfo1 : 2,
SelAreaInfo2 : 3,
};
exports.getModule = class MessageAreaListModule extends MenuModule {
constructor(options) {
super(options);
this.messageAreas = messageArea.getSortedAvailMessageAreasByConfTag(
this.client.user.properties.message_conf_tag,
{ client : this.client }
);
const self = this;
this.menuMethods = {
changeArea : function(formData, extraArgs, cb) {
if(1 === formData.submitId) {
let area = self.messageAreas[formData.value.area];
const areaTag = area.areaTag;
area = area.area; // what we want is actually embedded
messageArea.changeMessageArea(self.client, areaTag, err => {
if(err) {
self.client.term.pipeWrite(`\n|00Cannot change area: ${err.message}\n`);
self.prevMenuOnTimeout(1000, cb);
} else {
if(_.isString(area.art)) {
const dispOptions = {
client : self.client,
name : area.art,
};
self.client.term.rawWrite(resetScreen());
displayThemeArt(dispOptions, () => {
// pause by default, unless explicitly told not to
if(_.has(area, 'options.pause') && false === area.options.pause) {
return self.prevMenuOnTimeout(1000, cb);
} else {
self.pausePrompt( () => {
return self.prevMenu(cb);
});
}
});
} else {
return self.prevMenu(cb);
}
}
});
} else {
return cb(null);
}
}
};
}
prevMenuOnTimeout(timeout, cb) {
setTimeout( () => {
return this.prevMenu(cb);
}, timeout);
}
updateGeneralAreaInfoViews(areaIndex) {
// :TODO: these concepts have been replaced with the {someKey} style formatting - update me!
/* experimental: not yet avail
const areaInfo = self.messageAreas[areaIndex];
[ MciViewIds.SelAreaInfo1, MciViewIds.SelAreaInfo2 ].forEach(mciId => {
const v = self.viewControllers.areaList.getView(mciId);
if(v) {
v.setFormatObject(areaInfo.area);
}
});
*/
}
mciReady(mciData, cb) {
super.mciReady(mciData, err => {
if(err) {
return cb(err);
}
const self = this;
const vc = self.viewControllers.areaList = new ViewController( { client : self.client } );
async.series(
[
function loadFromConfig(callback) {
const loadOpts = {
callingMenu : self,
mciMap : mciData.menu,
formId : 0,
};
vc.loadFromMenuConfig(loadOpts, function startingViewReady(err) {
callback(err);
});
},
function populateAreaListView(callback) {
const listFormat = self.menuConfig.config.listFormat || '{index} ) - {name}';
const focusListFormat = self.menuConfig.config.focusListFormat || listFormat;
const areaListView = vc.getView(MciViewIds.AreaList);
let i = 1;
areaListView.setItems(_.map(self.messageAreas, v => {
return stringFormat(listFormat, {
index : i++,
areaTag : v.area.areaTag,
name : v.area.name,
desc : v.area.desc,
});
}));
i = 1;
areaListView.setFocusItems(_.map(self.messageAreas, v => {
return stringFormat(focusListFormat, {
index : i++,
areaTag : v.area.areaTag,
name : v.area.name,
desc : v.area.desc,
});
}));
areaListView.on('index update', areaIndex => {
self.updateGeneralAreaInfoViews(areaIndex);
});
areaListView.redraw();
callback(null);
}
],
function complete(err) {
return cb(err);
}
);
});
}
};

View File

@@ -1,67 +0,0 @@
/* jslint node: true */
'use strict';
const FullScreenEditorModule = require('../core/fse.js').FullScreenEditorModule;
const persistMessage = require('../core/message_area.js').persistMessage;
const _ = require('lodash');
const async = require('async');
exports.moduleInfo = {
name : 'Message Area Post',
desc : 'Module for posting a new message to an area',
author : 'NuSkooler',
};
exports.getModule = class AreaPostFSEModule extends FullScreenEditorModule {
constructor(options) {
super(options);
const self = this;
// we're posting, so always start with 'edit' mode
this.editorMode = 'edit';
this.menuMethods.editModeMenuSave = function(formData, extraArgs, cb) {
var msg;
async.series(
[
function getMessageObject(callback) {
self.getMessage(function gotMsg(err, msgObj) {
msg = msgObj;
return callback(err);
});
},
function saveMessage(callback) {
return persistMessage(msg, callback);
},
function updateStats(callback) {
self.updateUserStats(callback);
}
],
function complete(err) {
if(err) {
// :TODO:... sooooo now what?
} 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.uuid },
'Message persisted'
);
}
return self.nextMenu(cb);
}
);
};
}
enter() {
if(_.isString(this.client.user.properties.message_area_tag) && !_.isString(this.messageAreaTag)) {
this.messageAreaTag = this.client.user.properties.message_area_tag;
}
super.enter();
}
};

View File

@@ -1,18 +0,0 @@
/* jslint node: true */
'use strict';
var FullScreenEditorModule = require('../core/fse.js').FullScreenEditorModule;
exports.getModule = AreaReplyFSEModule;
exports.moduleInfo = {
name : 'Message Area Reply',
desc : 'Module for replying to an area message',
author : 'NuSkooler',
};
function AreaReplyFSEModule(options) {
FullScreenEditorModule.call(this, options);
}
require('util').inherits(AreaReplyFSEModule, FullScreenEditorModule);

View File

@@ -1,135 +0,0 @@
/* jslint node: true */
'use strict';
// ENiGMA½
const FullScreenEditorModule = require('../core/fse.js').FullScreenEditorModule;
const Message = require('../core/message.js');
// deps
const _ = require('lodash');
exports.moduleInfo = {
name : 'Message Area View',
desc : 'Module for viewing an area message',
author : 'NuSkooler',
};
exports.getModule = class AreaViewFSEModule extends FullScreenEditorModule {
constructor(options) {
super(options);
this.editorType = 'area';
this.editorMode = 'view';
if(_.isObject(options.extraArgs)) {
this.messageList = options.extraArgs.messageList;
this.messageIndex = options.extraArgs.messageIndex;
this.lastMessageNextExit = options.extraArgs.lastMessageNextExit;
}
this.messageList = this.messageList || [];
this.messageIndex = this.messageIndex || 0;
this.messageTotal = this.messageList.length;
const self = this;
// assign *additional* menuMethods
Object.assign(this.menuMethods, {
nextMessage : (formData, extraArgs, cb) => {
if(self.messageIndex + 1 < self.messageList.length) {
self.messageIndex++;
return self.loadMessageByUuid(self.messageList[self.messageIndex].messageUuid, cb);
}
// auto-exit if no more to go?
if(self.lastMessageNextExit) {
self.lastMessageReached = true;
return self.prevMenu(cb);
}
return cb(null);
},
prevMessage : (formData, extraArgs, cb) => {
if(self.messageIndex > 0) {
self.messageIndex--;
return self.loadMessageByUuid(self.messageList[self.messageIndex].messageUuid, cb);
}
return cb(null);
},
movementKeyPressed : (formData, extraArgs, cb) => {
const bodyView = self.viewControllers.body.getView(1); // :TODO: use const here vs magic #
// :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;
}
// :TODO: need to stop down/page down if doing so would push the last
// visible page off the screen at all .... this should be handled by MLTEV though...
return cb(null);
},
replyMessage : (formData, extraArgs, cb) => {
if(_.isString(extraArgs.menu)) {
const modOpts = {
extraArgs : {
messageAreaTag : self.messageAreaTag,
replyToMessage : self.message,
}
};
return self.gotoMenu(extraArgs.menu, modOpts, cb);
}
self.client.log(extraArgs, 'Missing extraArgs.menu');
return cb(null);
}
});
}
loadMessageByUuid(uuid, cb) {
const msg = new Message();
msg.load( { uuid : uuid, user : this.client.user }, () => {
this.setMessage(msg);
if(cb) {
return cb(null);
}
});
}
finishedLoading() {
this.loadMessageByUuid(this.messageList[this.messageIndex].messageUuid);
}
getSaveState() {
return {
messageList : this.messageList,
messageIndex : this.messageIndex,
messageTotal : this.messageList.length,
};
}
restoreSavedState(savedState) {
this.messageList = savedState.messageList;
this.messageIndex = savedState.messageIndex;
this.messageTotal = savedState.messageTotal;
}
getMenuResult() {
return {
messageIndex : this.messageIndex,
lastMessageReached : this.lastMessageReached,
};
}
};

View File

@@ -1,148 +0,0 @@
/* jslint node: true */
'use strict';
// ENiGMA½
const MenuModule = require('../core/menu_module.js').MenuModule;
const ViewController = require('../core/view_controller.js').ViewController;
const messageArea = require('../core/message_area.js');
const displayThemeArt = require('../core/theme.js').displayThemeArt;
const resetScreen = require('../core/ansi_term.js').resetScreen;
const stringFormat = require('../core/string_format.js');
// deps
const async = require('async');
const _ = require('lodash');
exports.moduleInfo = {
name : 'Message Conference List',
desc : 'Module for listing / choosing message conferences',
author : 'NuSkooler',
};
const MciViewIds = {
ConfList : 1,
// :TODO:
// # areas in conf .... see Obv/2, iNiQ, ...
//
};
exports.getModule = class MessageConfListModule extends MenuModule {
constructor(options) {
super(options);
this.messageConfs = messageArea.getSortedAvailMessageConferences(this.client);
const self = this;
this.menuMethods = {
changeConference : function(formData, extraArgs, cb) {
if(1 === formData.submitId) {
let conf = self.messageConfs[formData.value.conf];
const confTag = conf.confTag;
conf = conf.conf; // what we want is embedded
messageArea.changeMessageConference(self.client, confTag, err => {
if(err) {
self.client.term.pipeWrite(`\n|00Cannot change conference: ${err.message}\n`);
setTimeout( () => {
return self.prevMenu(cb);
}, 1000);
} else {
if(_.isString(conf.art)) {
const dispOptions = {
client : self.client,
name : conf.art,
};
self.client.term.rawWrite(resetScreen());
displayThemeArt(dispOptions, () => {
// pause by default, unless explicitly told not to
if(_.has(conf, 'options.pause') && false === conf.options.pause) {
return self.prevMenuOnTimeout(1000, cb);
} else {
self.pausePrompt( () => {
return self.prevMenu(cb);
});
}
});
} else {
return self.prevMenu(cb);
}
}
});
} else {
return cb(null);
}
}
};
}
prevMenuOnTimeout(timeout, cb) {
setTimeout( () => {
return this.prevMenu(cb);
}, timeout);
}
mciReady(mciData, cb) {
super.mciReady(mciData, err => {
if(err) {
return cb(err);
}
const self = this;
const vc = self.viewControllers.areaList = new ViewController( { client : self.client } );
async.series(
[
function loadFromConfig(callback) {
let loadOpts = {
callingMenu : self,
mciMap : mciData.menu,
formId : 0,
};
vc.loadFromMenuConfig(loadOpts, callback);
},
function populateConfListView(callback) {
const listFormat = self.menuConfig.config.listFormat || '{index} ) - {name}';
const focusListFormat = self.menuConfig.config.focusListFormat || listFormat;
const confListView = vc.getView(MciViewIds.ConfList);
let i = 1;
confListView.setItems(_.map(self.messageConfs, v => {
return stringFormat(listFormat, {
index : i++,
confTag : v.conf.confTag,
name : v.conf.name,
desc : v.conf.desc,
});
}));
i = 1;
confListView.setFocusItems(_.map(self.messageConfs, v => {
return stringFormat(focusListFormat, {
index : i++,
confTag : v.conf.confTag,
name : v.conf.name,
desc : v.conf.desc,
});
}));
confListView.redraw();
callback(null);
},
function populateTextViews(callback) {
// :TODO: populate other avail MCI, e.g. current conf name
callback(null);
}
],
function complete(err) {
cb(err);
}
);
});
}
};

View File

@@ -1,259 +0,0 @@
/* jslint node: true */
'use strict';
// ENiGMA½
const MenuModule = require('../core/menu_module.js').MenuModule;
const ViewController = require('../core/view_controller.js').ViewController;
const messageArea = require('../core/message_area.js');
const stringFormat = require('../core/string_format.js');
const MessageAreaConfTempSwitcher = require('../core/mod_mixins.js').MessageAreaConfTempSwitcher;
// deps
const async = require('async');
const _ = require('lodash');
const moment = require('moment');
/*
Available listFormat/focusListFormat members (VM1):
msgNum : Message number
to : To username/handle
from : From username/handle
subj : Subject
ts : Message mod timestamp (format with config.dateTimeFormat)
newIndicator : New mark/indicator (config.newIndicator)
MCI codes:
VM1 : Message list
TL2 : Message info 1: { msgNumSelected, msgNumTotal }
*/
exports.moduleInfo = {
name : 'Message List',
desc : 'Module for listing/browsing available messages',
author : 'NuSkooler',
};
const MCICodesIDs = {
MsgList : 1, // VM1
MsgInfo1 : 2, // TL2
};
exports.getModule = class MessageListModule extends MessageAreaConfTempSwitcher(MenuModule) {
constructor(options) {
super(options);
const self = this;
const config = this.menuConfig.config;
this.messageAreaTag = config.messageAreaTag;
this.lastMessageReachedExit = _.get(options, 'lastMenuResult.lastMessageReached', false);
if(options.extraArgs) {
//
// |extraArgs| can override |messageAreaTag| provided by config
// as well as supply a pre-defined message list
//
if(options.extraArgs.messageAreaTag) {
this.messageAreaTag = options.extraArgs.messageAreaTag;
}
if(options.extraArgs.messageList) {
this.messageList = options.extraArgs.messageList;
}
}
this.menuMethods = {
selectMessage : function(formData, extraArgs, cb) {
if(1 === formData.submitId) {
self.initialFocusIndex = formData.value.message;
const modOpts = {
extraArgs : {
messageAreaTag : self.messageAreaTag,
messageList : self.messageList,
messageIndex : formData.value.message,
lastMessageNextExit : true,
}
};
//
// Provide a serializer so we don't dump *huge* bits of information to the log
// due to the size of |messageList|. See https://github.com/trentm/node-bunyan/issues/189
//
modOpts.extraArgs.toJSON = function() {
const logMsgList = (this.messageList.length <= 4) ?
this.messageList :
this.messageList.slice(0, 2).concat(this.messageList.slice(-2));
return {
messageAreaTag : this.messageAreaTag,
apprevMessageList : logMsgList,
messageCount : this.messageList.length,
messageIndex : formData.value.message,
};
};
return self.gotoMenu(config.menuViewPost || 'messageAreaViewPost', modOpts, cb);
} else {
return cb(null);
}
},
fullExit : function(formData, extraArgs, cb) {
self.menuResult = { fullExit : true };
return self.prevMenu(cb);
}
};
}
enter() {
if(this.lastMessageReachedExit) {
return this.prevMenu();
}
super.enter();
//
// Config can specify |messageAreaTag| else it comes from
// the user's current area
//
if(this.messageAreaTag) {
this.tempMessageConfAndAreaSwitch(this.messageAreaTag);
} else {
this.messageAreaTag = this.client.user.properties.message_area_tag;
}
}
leave() {
this.tempMessageConfAndAreaRestore();
super.leave();
}
mciReady(mciData, cb) {
super.mciReady(mciData, err => {
if(err) {
return cb(err);
}
const self = this;
const vc = self.viewControllers.allViews = new ViewController( { client : self.client } );
async.series(
[
function loadFromConfig(callback) {
const loadOpts = {
callingMenu : self,
mciMap : mciData.menu
};
return vc.loadFromMenuConfig(loadOpts, callback);
},
function fetchMessagesInArea(callback) {
//
// Config can supply messages else we'll need to populate the list now
//
if(_.isArray(self.messageList)) {
return callback(0 === self.messageList.length ? new Error('No messages in area') : null);
}
messageArea.getMessageListForArea( { client : self.client }, self.messageAreaTag, function msgs(err, msgList) {
if(!msgList || 0 === msgList.length) {
return callback(new Error('No messages in area'));
}
self.messageList = msgList;
return callback(err);
});
},
function getLastReadMesageId(callback) {
messageArea.getMessageAreaLastReadId(self.client.user.userId, self.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 || 'ddd MMM Do';
const newIndicator = self.menuConfig.config.newIndicator || '*';
const regIndicator = new Array(newIndicator.length + 1).join(' '); // fill with space to avoid draw issues
let msgNum = 1;
self.messageList.forEach( (listItem, index) => {
listItem.msgNum = msgNum++;
listItem.ts = moment(listItem.modTimestamp).format(dateTimeFormat);
listItem.newIndicator = listItem.messageId > self.lastReadId ? newIndicator : regIndicator;
if(_.isUndefined(self.initialFocusIndex) && listItem.messageId > self.lastReadId) {
self.initialFocusIndex = index;
}
});
return callback(null);
},
function populateList(callback) {
const msgListView = vc.getView(MCICodesIDs.MsgList);
const listFormat = self.menuConfig.config.listFormat || '{msgNum} - {subject} - {toUserName}';
const focusListFormat = self.menuConfig.config.focusListFormat || listFormat; // :TODO: default change color here
const messageInfo1Format = self.menuConfig.config.messageInfo1Format || '{msgNumSelected} / {msgNumTotal}';
// :TODO: This can take a very long time to load large lists. What we need is to implement the "owner draw" concept in
// which items are requested (e.g. their format at least) *as-needed* vs trying to get the format for all of them at once
msgListView.setItems(_.map(self.messageList, listEntry => {
return stringFormat(listFormat, listEntry);
}));
msgListView.setFocusItems(_.map(self.messageList, listEntry => {
return stringFormat(focusListFormat, listEntry);
}));
msgListView.on('index update', idx => {
self.setViewText(
'allViews',
MCICodesIDs.MsgInfo1,
stringFormat(messageInfo1Format, { msgNumSelected : (idx + 1), msgNumTotal : self.messageList.length } ));
});
if(self.initialFocusIndex > 0) {
// note: causes redraw()
msgListView.setFocusItemIndex(self.initialFocusIndex);
} else {
msgListView.redraw();
}
return callback(null);
},
function drawOtherViews(callback) {
const messageInfo1Format = self.menuConfig.config.messageInfo1Format || '{msgNumSelected} / {msgNumTotal}';
self.setViewText(
'allViews',
MCICodesIDs.MsgInfo1,
stringFormat(messageInfo1Format, { msgNumSelected : self.initialFocusIndex + 1, msgNumTotal : self.messageList.length } ));
return callback(null);
},
],
err => {
if(err) {
self.client.log.error( { error : err.message }, 'Error loading message list');
}
return cb(err);
}
);
});
}
getSaveState() {
return { initialFocusIndex : this.initialFocusIndex };
}
restoreSavedState(savedState) {
if(savedState) {
this.initialFocusIndex = savedState.initialFocusIndex;
}
}
getMenuResult() {
return this.menuResult;
}
};

View File

@@ -1,144 +0,0 @@
/* jslint node: true */
'use strict';
// ENiGMA½
const MenuModule = require('../core/menu_module.js').MenuModule;
const User = require('../core/user.js');
const theme = require('../core/theme.js');
const login = require('../core/system_menu_method.js').login;
const Config = require('../core/config.js').config;
const messageArea = require('../core/message_area.js');
exports.moduleInfo = {
name : 'NUA',
desc : 'New User Application',
};
const MciViewIds = {
userName : 1,
password : 9,
confirm : 10,
errMsg : 11,
};
exports.getModule = class NewUserAppModule extends MenuModule {
constructor(options) {
super(options);
const self = this;
this.menuMethods = {
//
// 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'));
},
viewValidationListener : function(err, cb) {
const errMsgView = self.viewControllers.menu.getView(MciViewIds.errMsg);
let newFocusId;
if(err) {
errMsgView.setText(err.message);
err.view.clearText();
if(err.view.getId() === MciViewIds.confirm) {
newFocusId = MciViewIds.password;
self.viewControllers.menu.getView(MciViewIds.password).clearText();
}
} else {
errMsgView.clearText();
}
return cb(newFocusId);
},
//
// Submit handlers
//
submitApplication : function(formData, extraArgs, cb) {
const newUser = new User();
newUser.username = formData.value.username;
//
// 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
// can't store undefined!
confTag = confTag || '';
areaTag = areaTag || '';
newUser.properties = {
real_name : formData.value.realName,
birthdate : new Date(Date.parse(formData.value.birthdate)).toISOString(), // :TODO: Use moment & explicit ISO string format
sex : formData.value.sex,
location : formData.value.location,
affiliation : formData.value.affils,
email_address : formData.value.email,
web_address : formData.value.web,
account_created : new Date().toISOString(), // :TODO: Use moment & explicit ISO string format
message_conf_tag : confTag,
message_area_tag : areaTag,
term_height : self.client.term.termHeight,
term_width : self.client.term.termWidth,
// :TODO: Other defaults
// :TODO: should probably have a place to create defaults/etc.
};
if('*' === Config.defaults.theme) {
newUser.properties.theme_id = theme.getRandomTheme();
} else {
newUser.properties.theme_id = Config.defaults.theme;
}
// :TODO: User.create() should validate email uniqueness!
newUser.create(formData.value.password, err => {
if(err) {
self.client.log.info( { error : err, username : formData.value.username }, 'New user creation failed');
self.gotoMenu(extraArgs.error, err => {
if(err) {
return self.prevMenu(cb);
}
return cb(null);
});
} else {
self.client.log.info( { username : formData.value.username, userId : newUser.userId }, 'New user created');
// Cache SysOp information now
// :TODO: Similar to bbs.js. DRY
if(newUser.isSysOp()) {
Config.general.sysOp = {
username : formData.value.username,
properties : newUser.properties,
};
}
if(User.AccountStatus.inactive === self.client.user.properties.account_status) {
return self.gotoMenu(extraArgs.inactive, cb);
} else {
//
// If active now, we need to call login() to authenticate
//
return login(self, formData, extraArgs, cb);
}
}
});
},
};
}
mciReady(mciData, cb) {
return this.standardMCIReadyHandler(mciData, cb);
}
};

View File

@@ -1,333 +0,0 @@
/* jslint node: true */
'use strict';
// ENiGMA½
const MenuModule = require('../core/menu_module.js').MenuModule;
const getModDatabasePath = require('../core/database.js').getModDatabasePath;
const ViewController = require('../core/view_controller.js').ViewController;
const theme = require('../core/theme.js');
const ansi = require('../core/ansi_term.js');
const stringFormat = require('../core/string_format.js');
// deps
const sqlite3 = require('sqlite3');
const async = require('async');
const _ = require('lodash');
const moment = require('moment');
/*
Module :TODO:
* Add pipe code support
- override max length & monitor *display* len as user types in order to allow for actual display len with color
* Add preview control: Shows preview with pipe codes resolved
* Add ability to at least alternate formatStrings -- every other
*/
exports.moduleInfo = {
name : 'Onelinerz',
desc : 'Standard local onelinerz',
author : 'NuSkooler',
packageName : 'codes.l33t.enigma.onelinerz',
};
const MciViewIds = {
ViewForm : {
Entries : 1,
AddPrompt : 2,
},
AddForm : {
NewEntry : 1,
EntryPreview : 2,
AddPrompt : 3,
}
};
const FormIds = {
View : 0,
Add : 1,
};
exports.getModule = class OnelinerzModule extends MenuModule {
constructor(options) {
super(options);
const self = this;
this.menuMethods = {
viewAddScreen : function(formData, extraArgs, cb) {
return self.displayAddScreen(cb);
},
addEntry : function(formData, extraArgs, cb) {
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.clearAddForm();
return self.displayViewScreen(true, cb); // true=cls
});
} else {
// empty message - treat as if cancel was hit
return self.displayViewScreen(true, cb); // true=cls
}
},
cancelAdd : function(formData, extraArgs, cb) {
self.clearAddForm();
return self.displayViewScreen(true, cb); // true=cls
}
};
}
initSequence() {
const self = this;
async.series(
[
function beforeDisplayArt(callback) {
return self.beforeArt(callback);
},
function display(callback) {
return self.displayViewScreen(false, callback);
}
],
err => {
if(err) {
// :TODO: Handle me -- initSequence() should really take a completion callback
}
self.finishedLoading();
}
);
}
displayViewScreen(clearScreen, cb) {
const self = this;
async.waterfall(
[
function clearAndDisplayArt(callback) {
if(self.viewControllers.add) {
self.viewControllers.add.setFocus(false);
}
if(clearScreen) {
self.client.term.rawWrite(ansi.resetScreen());
}
theme.displayThemedAsset(
self.menuConfig.config.art.entries,
self.client,
{ font : self.menuConfig.font, trailingLF : false },
(err, artData) => {
return callback(err, artData);
}
);
},
function initOrRedrawViewController(artData, callback) {
if(_.isUndefined(self.viewControllers.add)) {
const vc = self.addViewController(
'view',
new ViewController( { client : self.client, formId : FormIds.View } )
);
const loadOpts = {
callingMenu : self,
mciMap : artData.mciMap,
formId : FormIds.View,
};
return vc.loadFromMenuConfig(loadOpts, callback);
} else {
self.viewControllers.view.setFocus(true);
self.viewControllers.view.getView(MciViewIds.ViewForm.AddPrompt).redraw();
return callback(null);
}
},
function fetchEntries(callback) {
const entriesView = self.viewControllers.view.getView(MciViewIds.ViewForm.Entries);
const limit = entriesView.dimens.height;
let entries = [];
self.db.each(
`SELECT *
FROM (
SELECT *
FROM onelinerz
ORDER BY timestamp DESC
LIMIT ${limit}
)
ORDER BY timestamp ASC;`,
(err, row) => {
if(!err) {
row.timestamp = moment(row.timestamp); // convert -> moment
entries.push(row);
}
},
err => {
return callback(err, entriesView, entries);
}
);
},
function populateEntries(entriesView, entries, callback) {
const listFormat = self.menuConfig.config.listFormat || '{username}@{ts}: {oneliner}';// :TODO: should be userName to be consistent
const tsFormat = self.menuConfig.config.timestampFormat || 'ddd h:mma';
entriesView.setItems(entries.map( e => {
return stringFormat(listFormat, {
userId : e.user_id,
username : e.user_name,
oneliner : e.oneliner,
ts : e.timestamp.format(tsFormat),
} );
}));
entriesView.redraw();
return callback(null);
},
function finalPrep(callback) {
const promptView = self.viewControllers.view.getView(MciViewIds.ViewForm.AddPrompt);
promptView.setFocusItemIndex(1); // default to NO
return callback(null);
}
],
err => {
if(cb) {
return cb(err);
}
}
);
}
displayAddScreen(cb) {
const self = this;
async.waterfall(
[
function clearAndDisplayArt(callback) {
self.viewControllers.view.setFocus(false);
self.client.term.rawWrite(ansi.resetScreen());
theme.displayThemedAsset(
self.menuConfig.config.art.add,
self.client,
{ font : self.menuConfig.font },
(err, artData) => {
return callback(err, artData);
}
);
},
function initOrRedrawViewController(artData, callback) {
if(_.isUndefined(self.viewControllers.add)) {
const vc = self.addViewController(
'add',
new ViewController( { client : self.client, formId : FormIds.Add } )
);
const loadOpts = {
callingMenu : self,
mciMap : artData.mciMap,
formId : FormIds.Add,
};
return vc.loadFromMenuConfig(loadOpts, callback);
} else {
self.viewControllers.add.setFocus(true);
self.viewControllers.add.redrawAll();
self.viewControllers.add.switchFocus(MciViewIds.AddForm.NewEntry);
return callback(null);
}
}
],
err => {
if(cb) {
return cb(err);
}
}
);
}
clearAddForm() {
this.setViewText('add', MciViewIds.AddForm.NewEntry, '');
this.setViewText('add', MciViewIds.AddForm.EntryPreview, '');
}
initDatabase(cb) {
const self = this;
async.series(
[
function openDatabase(callback) {
self.db = new sqlite3.Database(
getModDatabasePath(exports.moduleInfo),
err => {
return callback(err);
}
);
},
function createTables(callback) {
self.db.run(
`CREATE TABLE IF NOT EXISTS onelinerz (
id INTEGER PRIMARY KEY,
user_id INTEGER_NOT NULL,
user_name VARCHAR NOT NULL,
oneliner VARCHAR NOT NULL,
timestamp DATETIME NOT NULL
);`
,
err => {
return callback(err);
});
}
],
err => {
return cb(err);
}
);
}
storeNewOneliner(oneliner, cb) {
const self = this;
const ts = moment().format('YYYY-MM-DDTHH:mm:ss.SSSZ');
async.series(
[
function addRec(callback) {
self.db.run(
`INSERT INTO onelinerz (user_id, user_name, oneliner, timestamp)
VALUES (?, ?, ?, ?);`,
[ self.client.user.userId, self.client.user.username, oneliner, ts ],
callback
);
},
function removeOld(callback) {
// keep 25 max most recent items - remove the older ones
self.db.run(
`DELETE FROM onelinerz
WHERE id IN (
SELECT id
FROM onelinerz
ORDER BY id DESC
LIMIT -1 OFFSET 25
);`,
callback
);
}
],
err => {
return cb(err);
}
);
}
beforeArt(cb) {
super.beforeArt(err => {
return err ? cb(err) : this.initDatabase(cb);
});
}
};

View File

@@ -1,267 +0,0 @@
{
/*
./\/\.' ENiGMA½ Prompt Configuration -/--/-------- - -- -
_____________________ _____ ____________________ __________\_ /
\__ ____/\_ ____ \ /____/ / _____ __ \ / ______/ // /___jp!
// __|___// | \// |// | \// | | \// \ /___ /_____
/____ _____| __________ ___|__| ____| \ / _____ \
---- \______\ -- |______\ ------ /______/ ---- |______\ - |______\ /__/ // ___/
/__ _\
<*> ENiGMA½ // HTTPS://GITHUB.COM/NUSKOOLER/ENIGMA-BBS <*> /__/
-------------------------------------------------------------------------------
This configuration is in HJSON (http://hjson.org/) format. Strict to-spec
JSON is also perfectly valid. Use 'hjson' from npm to convert to/from JSON.
See http://hjson.org/ for more information and syntax.
If you haven't yet, copy the conents of this file to something like
sick_board_prompt.hjson. Point to it via config.hjson using the
'general.promptFile' key:
general: { promptFile: "sick_board_prompt.hjson" }
*/
// :TODO: this entire file needs cleaned up a LOT
// :TODO: Convert all of this to HJSON
prompts: {
userCredentials: {
"art" : "usercred",
"mci" : {
"ET1" : {
"argName" : "username",
"maxLength" : "@config:users.usernameMax"
},
"ET2" : {
"submit" : true,
"argName" : "password",
"password" : true,
"maxLength" : "@config:users.passwordMax"
}
}
},
"userLoginCredentials" : {
"art" : "USRCRED",
"mci" : {
"ET1" : {
"argName" : "username",
"maxLength" : "@config:users.usernameMax"
},
"ET2" : {
"submit" : true,
"argName" : "password",
"password" : true,
"maxLength" : "@config:users.passwordMax"
}
}
},
logoffConfirmation: {
art: LOGPMPT
mci: {
TM1: {
argName: promptValue
items: [ "yes", "no" ]
focus: true
hotKeys: { Y: 0, N: 1 }
hotKeySubmit: true
}
}
}
loginGlobalNewScan: {
art: GNSPMPT
mci: {
TM1: {
argName: promptValue
items: [ "yes", "no" ]
focus: true
hotKeys: { Y: 0, N: 1 }
hotKeySubmit: true
}
}
}
menuCommand: {
art: MNUPRMT
mci: {
TL1: {
// theme me!
}
ET2: {
argName: command
width: 20
maxLength: 20
submit: true
textStyle: upper
focus: true
}
}
},
messageMenuCommand: {
art: MSGPMPT
mci: {
TL1: {
// theme me!
}
ET2: {
argName: command
width: 20
maxLength: 20
submit: true
textStyle: upper
focus: true
}
}
},
"newAreaPostPrompt" : {
"art" : "message_area_new_post",
"mci" : {
"ET1" : {
"argName" : "to",
"width" : 20
},
"ET2" : {
"argName" : "subject",
"width" : 20
}
}
},
forgotPasswordPrompt: {
art: FORGOTPW
mci: {
ET1: {
argName: username
maxLength: @config:users.usernameMax
width: 32
focus: true
}
}
actionKeys: [
{
keys: [ "escape" ]
action: @systemMethod:prevMenu
}
]
}
///////////////////////////////////////////////////////////////////////
// File Base Related
///////////////////////////////////////////////////////////////////////
fileMenuCommand: {
art: FILPMPT
mci: {
TL1: {}
ET2: {
argName: menuOption
width: 20
maxLength: 20
textStyle: upper
focus: true
}
}
}
fileBaseRateEntryPrompt: {
art: RATEFILE
mci: {
SM1: {
argName: rating
items: [ "-----", "*----", "**---", "***--", "****-", "*****" ]
}
}
actionKeys: [
{
keys: [ "escape" ]
action: @systemMethod:prevMenu
}
]
}
fileBaseTagEntryPrompt: {
art: TAGFILE
mci: {
ET1: {
argName: tags
}
}
}
///////////////////////////////////////////////////////////////////////
// Standard / Required
//
// Prompts in this section are considered "standard" and are required
// to be present
//
///////////////////////////////////////////////////////////////////////
pause: {
//
// Any menu 'pause' will use this prompt
//
art: pause
options: {
trailingLF: no
}
/*
"mci" : {
// :TODO: Need special pause for a key MCI
// e.g. %PA -> themed prompt
}
...or maybe pause should just be special:
{
...
"pause" true
// uses theme pause which can be art/inline/etc.
}
... better, a special prompt
GetKeyView
* echoKey : false
*/
}
/*,
"standard" : {
// any menu 'pause' will display this, pause for a key, then erase and move on
"pause" : {
"art" : "pause"
// :TODO: support mci mappings
}
},
"custom" : {
}*/
/*
see notes in menu_module.js also
...how to allow for this to come from the theme first???
same as custom vc drawing/etc.? ...
{
"theme" : {
"inlineArt" : {
"something" : "%MC and |01Pipe codes here"
}
}
}
"pause" : {
"art" : "@inline:simplePrompt",
// support pipe codes & MCI
"simplePrompt" : "--------/ Pause /----------------",
"mci" : {
}
}
*/
}
}

View File

@@ -1,247 +0,0 @@
/* jslint node: true */
'use strict';
// ENiGMA½
const MenuModule = require('../core/menu_module.js').MenuModule;
const ViewController = require('../core/view_controller.js').ViewController;
const theme = require('../core/theme.js');
const resetScreen = require('../core/ansi_term.js').resetScreen;
const StatLog = require('../core/stat_log.js');
const renderStringLength = require('../core/string_util.js').renderStringLength;
const stringFormat = require('../core/string_format.js');
// deps
const async = require('async');
const _ = require('lodash');
exports.moduleInfo = {
name : 'Rumorz',
desc : 'Standard local rumorz',
author : 'NuSkooler',
packageName : 'codes.l33t.enigma.rumorz',
};
const STATLOG_KEY_RUMORZ = 'system_rumorz';
const FormIds = {
View : 0,
Add : 1,
};
const MciCodeIds = {
ViewForm : {
Entries : 1,
AddPrompt : 2,
},
AddForm : {
NewEntry : 1,
EntryPreview : 2,
AddPrompt : 3,
}
};
exports.getModule = class RumorzModule extends MenuModule {
constructor(options) {
super(options);
this.menuMethods = {
viewAddScreen : (formData, extraArgs, cb) => {
return this.displayAddScreen(cb);
},
addEntry : (formData, extraArgs, cb) => {
if(_.isString(formData.value.rumor) && renderStringLength(formData.value.rumor) > 0) {
const rumor = formData.value.rumor.trim(); // remove any trailing ws
StatLog.appendSystemLogEntry(STATLOG_KEY_RUMORZ, rumor, StatLog.KeepDays.Forever, StatLog.KeepType.Forever, () => {
this.clearAddForm();
return this.displayViewScreen(true, cb); // true=cls
});
} else {
// empty message - treat as if cancel was hit
return this.displayViewScreen(true, cb); // true=cls
}
},
cancelAdd : (formData, extraArgs, cb) => {
this.clearAddForm();
return this.displayViewScreen(true, cb); // true=cls
}
};
}
get config() { return this.menuConfig.config; }
clearAddForm() {
const newEntryView = this.viewControllers.add.getView(MciCodeIds.AddForm.NewEntry);
const previewView = this.viewControllers.add.getView(MciCodeIds.AddForm.EntryPreview);
newEntryView.setText('');
// preview is optional
if(previewView) {
previewView.setText('');
}
}
initSequence() {
const self = this;
async.series(
[
function beforeDisplayArt(callback) {
self.beforeArt(callback);
},
function display(callback) {
self.displayViewScreen(false, callback);
}
],
err => {
if(err) {
// :TODO: Handle me -- initSequence() should really take a completion callback
}
self.finishedLoading();
}
);
}
displayViewScreen(clearScreen, cb) {
const self = this;
async.waterfall(
[
function clearAndDisplayArt(callback) {
if(self.viewControllers.add) {
self.viewControllers.add.setFocus(false);
}
if(clearScreen) {
self.client.term.rawWrite(resetScreen());
}
theme.displayThemedAsset(
self.config.art.entries,
self.client,
{ font : self.menuConfig.font, trailingLF : false },
(err, artData) => {
return callback(err, artData);
}
);
},
function initOrRedrawViewController(artData, callback) {
if(_.isUndefined(self.viewControllers.add)) {
const vc = self.addViewController(
'view',
new ViewController( { client : self.client, formId : FormIds.View } )
);
const loadOpts = {
callingMenu : self,
mciMap : artData.mciMap,
formId : FormIds.View,
};
return vc.loadFromMenuConfig(loadOpts, callback);
} else {
self.viewControllers.view.setFocus(true);
self.viewControllers.view.getView(MciCodeIds.ViewForm.AddPrompt).redraw();
return callback(null);
}
},
function fetchEntries(callback) {
const entriesView = self.viewControllers.view.getView(MciCodeIds.ViewForm.Entries);
StatLog.getSystemLogEntries(STATLOG_KEY_RUMORZ, StatLog.Order.Timestamp, (err, entries) => {
return callback(err, entriesView, entries);
});
},
function populateEntries(entriesView, entries, callback) {
const config = self.config;
const listFormat = config.listFormat || '{rumor}';
const focusListFormat = config.focusListFormat || listFormat;
entriesView.setItems(entries.map( e => stringFormat(listFormat, { rumor : e.log_value } ) ) );
entriesView.setFocusItems(entries.map(e => stringFormat(focusListFormat, { rumor : e.log_value } ) ) );
entriesView.redraw();
return callback(null);
},
function finalPrep(callback) {
const promptView = self.viewControllers.view.getView(MciCodeIds.ViewForm.AddPrompt);
promptView.setFocusItemIndex(1); // default to NO
return callback(null);
}
],
err => {
if(cb) {
return cb(err);
}
}
);
}
displayAddScreen(cb) {
const self = this;
async.waterfall(
[
function clearAndDisplayArt(callback) {
self.viewControllers.view.setFocus(false);
self.client.term.rawWrite(resetScreen());
theme.displayThemedAsset(
self.config.art.add,
self.client,
{ font : self.menuConfig.font },
(err, artData) => {
return callback(err, artData);
}
);
},
function initOrRedrawViewController(artData, callback) {
if(_.isUndefined(self.viewControllers.add)) {
const vc = self.addViewController(
'add',
new ViewController( { client : self.client, formId : FormIds.Add } )
);
const loadOpts = {
callingMenu : self,
mciMap : artData.mciMap,
formId : FormIds.Add,
};
return vc.loadFromMenuConfig(loadOpts, callback);
} else {
self.viewControllers.add.setFocus(true);
self.viewControllers.add.redrawAll();
self.viewControllers.add.switchFocus(MciCodeIds.AddForm.NewEntry);
return callback(null);
}
},
function initPreviewUpdates(callback) {
const previewView = self.viewControllers.add.getView(MciCodeIds.AddForm.EntryPreview);
const entryView = self.viewControllers.add.getView(MciCodeIds.AddForm.NewEntry);
if(previewView) {
let timerId;
entryView.on('key press', () => {
clearTimeout(timerId);
timerId = setTimeout( () => {
const focused = self.viewControllers.add.getFocusedView();
if(focused === entryView) {
previewView.setText(entryView.getData());
focused.setFocus(true);
}
}, 500);
});
}
return callback(null);
}
],
err => {
if(cb) {
return cb(err);
}
}
);
}
};

View File

@@ -1,198 +0,0 @@
/* jslint node: true */
'use strict';
// ENiGMA½
const MenuModule = require('../core/menu_module.js').MenuModule;
const resetScreen = require('../core/ansi_term.js').resetScreen;
const setSyncTermFontWithAlias = require('../core/ansi_term.js').setSyncTermFontWithAlias;
// deps
const async = require('async');
const _ = require('lodash');
const net = require('net');
const EventEmitter = require('events');
const buffers = require('buffers');
/*
Expected configuration block:
{
module: telnet_bridge
...
config: {
host: somehost.net
port: 23
}
}
*/
// :TODO: ENH: Support nodeMax and tooManyArt
exports.moduleInfo = {
name : 'Telnet Bridge',
desc : 'Connect to other Telnet Systems',
author : 'Andrew Pamment',
};
const IAC_DO_TERM_TYPE = new Buffer( [ 255, 253, 24 ] );
class TelnetClientConnection extends EventEmitter {
constructor(client) {
super();
this.client = client;
}
restorePipe() {
if(!this.pipeRestored) {
this.pipeRestored = true;
// client may have bailed
if(_.has(this, 'client.term.output')) {
if(this.bridgeConnection) {
this.client.term.output.unpipe(this.bridgeConnection);
}
this.client.term.output.resume();
}
}
}
connect(connectOpts) {
this.bridgeConnection = net.createConnection(connectOpts, () => {
this.emit('connected');
this.pipeRestored = false;
this.client.term.output.pipe(this.bridgeConnection);
});
this.bridgeConnection.on('data', data => {
this.client.term.rawWrite(data);
//
// Wait for a terminal type request, and send it eactly once.
// This is enough (in additional to other negotiations handled in telnet.js)
// to get us in on most systems
//
if(!this.termSent && data.indexOf(IAC_DO_TERM_TYPE) > -1) {
this.termSent = true;
this.bridgeConnection.write(this.getTermTypeNegotiationBuffer());
}
});
this.bridgeConnection.once('end', () => {
this.restorePipe();
this.emit('end');
});
this.bridgeConnection.once('error', err => {
this.restorePipe();
this.emit('end', err);
});
}
disconnect() {
if(this.bridgeConnection) {
this.bridgeConnection.end();
}
}
getTermTypeNegotiationBuffer() {
//
// Create a TERMINAL-TYPE sub negotiation buffer using the
// actual/current terminal type.
//
let bufs = buffers();
bufs.push(new Buffer(
[
255, // IAC
250, // SB
24, // TERMINAL-TYPE
0, // IS
]
));
bufs.push(
new Buffer(this.client.term.termType), // e.g. "ansi"
new Buffer( [ 255, 240 ] ) // IAC, SE
);
return bufs.toBuffer();
}
}
exports.getModule = class TelnetBridgeModule extends MenuModule {
constructor(options) {
super(options);
this.config = options.menuConfig.config;
// defaults
this.config.port = this.config.port || 23;
}
initSequence() {
let clientTerminated;
const self = this;
async.series(
[
function validateConfig(callback) {
if(_.isString(self.config.host) &&
_.isNumber(self.config.port))
{
callback(null);
} else {
callback(new Error('Configuration is missing required option(s)'));
}
},
function createTelnetBridge(callback) {
const connectOpts = {
port : self.config.port,
host : self.config.host,
};
let clientTerminated;
self.client.term.write(resetScreen());
self.client.term.write(` Connecting to ${connectOpts.host}, please wait...\n`);
const telnetConnection = new TelnetClientConnection(self.client);
telnetConnection.on('connected', () => {
self.client.log.info(connectOpts, 'Telnet bridge connection established');
if(self.config.font) {
self.client.term.rawWrite(setSyncTermFontWithAlias(self.config.font));
}
self.client.once('end', () => {
self.client.log.info('Connection ended. Terminating connection');
clientTerminated = true;
telnetConnection.disconnect();
});
});
telnetConnection.on('end', err => {
if(err) {
self.client.log.info(`Telnet bridge connection error: ${err.message}`);
}
callback(clientTerminated ? new Error('Client connection terminated') : null);
});
telnetConnection.connect(connectOpts);
}
],
err => {
if(err) {
self.client.log.warn( { error : err.message }, 'Telnet connection error');
}
if(!clientTerminated) {
self.prevMenu();
}
}
);
}
};

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