sbbs/main.js
2020-05-27 21:56:12 +10:00

672 lines
17 KiB
JavaScript

log(LOG_DEBUG,'- INIT: ANSITEX');
// Load many SBBS definitions
require('sbbsdefs.js','SS_UNUSED');
// Load text.dat definitions
require('text.js','TOTAL_TEXT');
// Key definitions
require('key_defs.js','KEY_ESC');
ansi = load({},'ansiterm_lib.js');
load('ansitex/load/funcs.js');
// Ansitex specific includes
require('ansitex/load/defs.js','ACTION_EXIT');
while(bbs.online) {
var mode = false; // Initial mode
// If the user is already on, our start page is 98b
var next_page = { frame: 98,index: user.number ? 'b' : 'a' }; // Start Frame
var action = ACTION_GOTO; // Initial action
var inkey_timeout = 10000; // Timeout waiting for input @todo required? check if idle timeout occurs
var fo = null; // Current Frame
var fn = null; // Current Field Number for an Input Frame
var history = []; // Page history
var control = []; // Methods that need to process input
ansi.send('ext_mode','clear','cursor');
while (action != ACTION_TERMINATE && action !=ACTION_EXIT) {
bbs.nodesync(false); // @todo Stop the display of telegrams
read = '';
// If we have no action, read from the terminal
if (action == false) {
read = console.inkey(K_NONE,inkey_timeout);
}
log(LOG_DEBUG,'READ: ['+read+']');
inkey_timeout = 60000; // Set our key timeout to 60s
system.node_list[bbs.node_num-1].action=0xff; // to ensure our node status is updated correctly
log(LOG_DEBUG,'MODE START: ['+read+']');
switch (mode) {
case false:
log(LOG_DEBUG,'- false: ['+read+']');
cmd='';
switch (read) {
case '*': action = ACTION_STAR
break;
// Frame Routing
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
if (fo.key[read]) {
next_page = { frame: fo.key[read] };
action = ACTION_GOTO;
log(LOG_DEBUG,'- false: Key ['+read+'] ['+pageStr(next_page)+']');
} else {
sendBaseline(ERR_ROUTE,false);
}
break;
case '#':
log(LOG_DEBUG,'- false: Key ['+read+'] ['+pageStr(fo)+']');
if (fo.index !== 'z') {
next_page = { frame: fo.frame, index: String.fromCharCode(fo.index.charCodeAt(0)+1) };
action = ACTION_GOTO;
} else {
sendBaseline(ERR_ROUTE,false);
}
break;
}
break;
// Command input on bottom line
case MODE_BL:
log(LOG_DEBUG,'- MODE_BL: ['+read+']');
if (read.match(/[0-9]/)) {
cmd += read;
console.write(read);
}
// @todo check if CTRL_H is required?
if ((read == CTRL_H || read == KEY_DEL) && cmd.length) {
console.backspace();
cmd = cmd.substring(0,cmd.length-1);
}
if (cmd == '00') {
action = ACTION_RELOAD;
cmd = '';
cursorOff();
break;
}
// Edit specific frame
if (cmd.match(/^04/) && read.match(/[a-z]/)) {
// @todo
mode = action = false;
cmd = '';
cursorOff();
sendBaseline(ERR_NOT_IMPLEMENTED,false);
break;
}
// Bookmark frame
if (cmd == '05') {
// @todo
mode = action = false;
cmd = '';
cursorOff();
sendBaseline(ERR_NOT_IMPLEMENTED,false);
break;
}
// Report Problem
if (cmd == '08') {
// @todo
mode = action = false;
cmd = '';
cursorOff();
sendBaseline(ERR_NOT_IMPLEMENTED,false);
break;
}
// Reload frame
if (cmd == '09') {
// @todo
action = ACTION_GOTO;
cmd = '';
cursorOff();
next_page = { frame: fo.frame, index: fo.index};
break;
}
// Another star aborts the command
if (read == '*') {
mode = action = false;
sendBaseline('',false);
cmd = '';
cursorOff();
// @todo If we are editing a field...
}
if (read == '#' || read == '\r') {
// Nothing typed between * and #
// *# means go back
if (cmd == '') {
mode = false;
cursorOff();
sendBaseline('',false);
action = ACTION_BACKUP;
} else if (cmd == '0') {
next_page = { frame: 1 }; // @todo specify home page in config
action = ACTION_GOTO;
// Edit frame
} else if (cmd == '04') {
sendBaseline(ERR_NOT_IMPLEMENTED,false);
} else {
next_page = { frame: cmd };
action = ACTION_GOTO;
}
// Clear the command we are finished processing...
cursorOff();
cmd = '';
mode = false;
}
break;
// Key presses during field input.
case MODE_FIELD:
//$cmd = '';
action = false;
switch (fo.type) {
// Login frame.
case FRAME_TYPE_LOGIN:
switch (read) {
case '#':
case '\r':
log(LOG_DEBUG,'- MODE_FIELD:FRAME_TYPE_LOGIN: ['+read+'] A');
// If we are the main login screen, see if it is a new user
//if ($this->fo->isCUG(0)) {
if (fo.frame_fields[fn].ftype == 't' && fo.frame_fields[fn].fvalue == 'NEW') {
action = ACTION_GOTO;
next_page = { frame: 981,index: 'a' }; // @todo This should be in the INI.
break;
}
//}
break;
}
// Response frame.
case FRAME_TYPE_RESPONSE:
// If we came from FRAME_TYPE_LOGIN and the user typed NEW to register
if (action == ACTION_GOTO)
break;
switch (read) {
// End of field entry.
case '#':
case '\r':
log(LOG_DEBUG,'- MODE_FIELD:FRAME_TYPE_RESPONSE: ['+read+'] A');
// Next Field
fn++;
cf = fo.frame_fields[fn];
log(LOG_DEBUG,'fn:'+fn+', cf'+JSON.stringify(cf));
if (cf) {
mode = MODE_FIELD;
console.gotoxy(cf.c,cf.r);
console.write(KEY_ESC+'['+cf.attribute.i+';'+cf.attribute.f+';'+cf.attribute.b+'m');
// Finished all editable fields.
} else {
action = ACTION_SUBMITRF;
}
break;
case '*':
log(LOG_DEBUG,'- MODE_FIELD:FRAME_TYPE_RESPONSE: ['+read+'] B');
//$current['prevmode'] = MODE_FIELD;
action = ACTION_STAR;
break;
case KEY_DEL:
log(LOG_DEBUG,'- MODE_FIELD:FRAME_TYPE_RESPONSE: ['+read+'] C');
/*
if ($this->fo->setFieldCurrentInputDelete())
$client->send(LEFT.$this->fo::$if_filler.LEFT);
*/
break;
case KEY_ESC:
log(LOG_DEBUG,'- MODE_FIELD:FRAME_TYPE_RESPONSE: ['+read+'] D');
break;
// Record Data Entry
default:
log(LOG_DEBUG,'- MODE_FIELD:FRAME_TYPE_RESPONSE: ['+read+'] E:'+read.charCodeAt(0)+' cf:'+(cf ? cf.flength : '{}'));
if (read.charCodeAt(0) > 31 && cf.fvalue.length < cf.flength) {
cf.fvalue += read;
console.write((cf.ftype == 't') ? read : 'x');
}
}
break;
// Other Frame Types - Shouldnt get here.
default:
log(LOG_DEBUG,'- SHOULDNT GET HERE: ['+read+']');
action = ACTION_TERMINATE;
}
break;
// Form submission: 1 to send, 2 not to send.
case MODE_SUBMITRF:
switch (read) {
case '1':
log(LOG_DEBUG,'- MODE_SUBMITRF: Key ['+read+'] ['+pageStr(fo)+']');
log(LOG_DEBUG,' - Frame fields: '+JSON.stringify(fo.frame_fields));
log(LOG_DEBUG,' - Key 1 is:'+JSON.stringify(fo.key[1]));
// If we are in a control method, complete it
if (control.count) {
log(LOG_DEBUG,'Last control method is:'+JSON.stringify(control[control.length-1]));
control[control.length-1].process();
} else if (fo.key[1] == '*' || fo.key[1].match(/[0-9]/)) {
sendBaseline('\1n\1h\1RNO ACTION PERFORMED',false);
mode = MODE_RFSENT;
} else {
log(LOG_DEBUG,' - Key 1 is a METHOD check it exists:'+JSON.stringify(fo.key[1]));
switch(fo.key[1]) {
// User is logging in to system or CUG
case 'login':
log(LOG_DEBUG,' - User:'+JSON.stringify(fo.frame_fields[0].fvalue));
// If login is successful, we'll exit here
if (bbs.login(fo.frame_fields[0].fvalue,null,fo.frame_fields[1].fvalue)) {
log(LOG_DEBUG,' - User:'+JSON.stringify(user.number));
bbs.logon();
log(LOG_DEBUG,' - SEND TO EXIT:');
action = ACTION_EXIT;
break;
}
log(LOG_DEBUG,' ! Login failed for User:'+JSON.stringify(fo.frame_fields[0].fvalue));
break;
}
/*
} elseif ($ao = FrameClass\Action::factory($this->fo->route(1),$this,$user,$action,$mode)) {
$ao->handle();
$mode = $ao->mode;
$action = $ao->action;
if ($ao->page)
$next_page = $ao->page;
} else {
sendBaseline(ERR_METHOD_NOT_EXIST,false);
mode = MODE_RFSENT;
}
*/
}
break;
case '2':
log(LOG_DEBUG,'- MODE_SUBMITRF: Key ['+read+'] ['+pageStr(fo)+']');
// @todo Check if HASH is a valid next destination
if (fo.type == 'l') {
action = ACTION_RELOAD;
mode = false;
} else {
sendBaseline(MSG_NOTSENT,false);
mode = MODE_RFNOTSENT;
}
/*
// If a Control method was rejected, we can clear it
if ($control AND $method->count()) {
$save = $method->pop();
if ($method->count()) {
$control = $method->last()->state['control'];
} else {
$mode = $save->state['mode'];
$action = $save->state['action'];
$control = FALSE;
}
}
*/
break;
case '*':
action = ACTION_STAR;
break;
}
break;
// Response form after Sent processing
case MODE_RFSENT:
ansi.send('ext_mode','clear','cursor');
switch (read) {
case '*':
action = ACTION_STAR;
break;
}
/*
if ($read == HASH) {
if ($x = $this->fo->route(2) AND $x !== '*' AND is_numeric($x)) {
$next_page = ['frame'=>$x];
} elseif (FrameModel::where('frame',$this->fo->frame())->where('index',$this->fo->index_next())->exists()) {
$next_page = ['frame'=>$this->fo->frame(),'index'=>$this->fo->index_next()];
} elseif ($x = $this->fo->route(0) AND $x !== '*' AND is_numeric($x)) {
$next_page = ['frame'=>$x];
// No further routes defined, go home.
} else {
$next_page = ['frame'=>0];
}
$action = ACTION_GOTO;
}
*/
break;
// Response form after NOT sending
case MODE_RFNOTSENT:
// Response form ERROR
case MODE_RFERROR:
cursorOff();
if (read == '#') {
/*
if ($x = $this->fo->route(2) AND $x !== '*' AND is_numeric($x)) {
$next_page = ['frame'=>$x];
} elseif (FrameModel::where('frame',$this->fo->frame())->where('index',$this->fo->index_next())->exists()) {
$next_page = ['frame'=>$this->fo->frame(),'index'=>$this->fo->index_next()];
} elseif ($x = $this->fo->route(0) AND $x !== '*' AND is_numeric($x)) {
$next_page = ['frame'=>$x];
// No further routes defined, go home.
} else {
$next_page = ['frame'=>0];
}
*/
action = ACTION_GOTO;
} else if (read == '*') {
action = ACTION_STAR;
break;
}
break;
// @todo MODE_CONTROL
default:
log(LOG_DEBUG,'- SHOULDNT GET HERE: ['+read+']');
action = ACTION_TERMINATE;
}
log(LOG_DEBUG,'MODE END: ['+read+']');
log(LOG_DEBUG,'ACTION START: ['+read+']');
switch (action) {
// Start command entry
case ACTION_STAR:
log(LOG_DEBUG,'- ACTION_STAR: ['+(next_page ? pageStr(next_page) : '')+']');
// @todo If something on the baseline preserve it
ansi.send('ext_mode','set','cursor');
sendBaseline('\1N\1G\1H*',true);
action = false;
mode = MODE_BL;
bbs.replace_text(NodeActionRetrieving,'\1h%s \1n\1gJumping to page');
bbs.node_action=NODE_RFSD;
break;
// Submitting forms
case ACTION_SUBMITRF:
action = false;
cursorOff();
log(LOG_DEBUG,'- ACTION_SUBMITRF: ['+fo.type+']');
sendBaseline((fo.type == 'l' ? MSG_LOGON : MSG_SENDORNOT),true);
mode = MODE_SUBMITRF;
break;
// GO Backwards
case ACTION_BACKUP:
// Do we have anywhere to go, drop the current page from the history.
if (history.length > 1)
history.pop();
// @todo If in control...
next_page = (history.length > 0) ? history[history.length-1] : null;
log(LOG_DEBUG,'- ACTION_BACKUP: Backing up to ['+(next_page ? pageStr(next_page) : '')+'] current ['+fo.page+']');
// If there is no next page, we'll ignore the request.
if (! next_page || (pageStr(next_page) == fo.page)) {
action = false;
break;
}
// Goto specific page
case ACTION_GOTO:
log(LOG_DEBUG,'- ACTION_GOTO: ['+(next_page ? pageStr(next_page) : '')+']');
if (next_page !== null) {
current = fo;
fo = new Frame();
fo.load(pageStr(next_page));
if (fo.page == null) {
fo = current;
// sendbaseline ERR_PAGE
sendBaseline(ERR_ROUTE,false);
mode = action = false;
break;
}
current = null;
}
// If the user has access to the frame
if (fo.accessible) {
// @todo if its a login frame and the user is already member of CUG, display error
if (false) {
// baseline USER_ALREADY_MEMBER
break;
}
// Check if the frame exists, and the user is the Service Provider
} else {
sendBaseline(ACCESS_DENIED,false);
mode = action = false;
break;
}
log(LOG_DEBUG,'- ACTION_GOTO: next_page ['+JSON.stringify(next_page)+'] last history ['+JSON.stringify(history[history.length-1])+']');
// Record our history
if (next_page && (! history.length || (pageStr(history[history.length-1]) != pageStr(next_page))) && (fo.type != FRAME_TYPE_LOGIN)) {
// Ignore the login frames
if (NO_HISTORY_FRAMES.indexOf(pageStr(next_page)) == -1) {
history.push(next_page);
log(LOG_DEBUG,'- ACTION_GOTO: Added to history ['+(next_page ? pageStr(next_page) : '')+'] now ['+history.length+']');
}
}
next_page = null;
// Load frame
case ACTION_RELOAD:
log(LOG_DEBUG,'- ACTION_RELOAD: ['+(next_page ? pageStr(next_page) : '')+']');
// Clear the baseline history
// $this->sendBaseline($client,'');
// $current['baseline'] = '';
console.line_counter=0; // @todo fix to suppress a pause that is occurring before clear()
console.clear();
bbs.replace_text(NodeActionMain,'\1h%s \1nViewing \1h*'+fo.frame+'#\1n ['+fo.index+']');
bbs.log_str(fo.page+'|');
bbs.node_action=NODE_MAIN;
switch(fo.type) {
// Terminate frame
case FRAME_TYPE_TERMINATE:
console.putmsg(fo.render());
mode = false;
action = ACTION_TERMINATE;
break;
// External Frame
// @todo returning from the frame, go to the 0 key if it is set
case FRAME_TYPE_EXTERNAL:
log(LOG_DEBUG,'- ACTION_GOTO: EXTERNAL ['+JSON.stringify(fo.raw())+']');
switch(fo.raw()) {
case 'bbs.user_config()':
case 'bbs.read_mail(MAIL_YOUR)':
case 'bbs.scan_subs(SCAN_NEW)':
case 'bbs.scan_posts()':
case 'bbs.post_msg()':
eval(fo.raw());
// Check and see if our shell was changed
if (user.command_shell != 'ansitex') {
exit();
}
action = ACTION_BACKUP;
break;
default:
console.putmsg(JSON.stringify(fo.raw()));
sendBaseline(ERR_ROUTE,false);
action = false;
break;
}
mode = false;
break;
case FRAME_TYPE_LOGIN:
action = false;
/*
// If this is the registration page
// @todo Should be evaluated out of the DB
if ($this->fo->page() == '981a') {
$control = CONTROL_METHOD;
$method->push(Control::factory('register',$this));
$method->last()->state['control'] = $control;
$method->last()->state['action'] = FALSE;
$method->last()->state['mode'] = MODE_FIELD;
}
*/
case FRAME_TYPE_RESPONSE:
//log(LOG_DEBUG,'FRAME_TYPE_RESPONSE :'+fo.page+', FIELDS: '+fo.frame_fields.count);
fn = 0;
cf = null;
console.putmsg(fo.render());
if (fo.frame_fields.length) {
cf = fo.frame_fields[fn];
log(LOG_DEBUG,'cf'+JSON.stringify(cf));
if (cf) {
mode = MODE_FIELD;
ansi.send('ext_mode','set','cursor');
console.gotoxy(cf.c,cf.r);
console.write(KEY_ESC+'['+cf.attribute.i+';'+cf.attribute.f+';'+cf.attribute.b+'m');
// There were no editable fields.
} else {
mode = MODE_COMPLETE;
cursorOff();
}
} else {
mode = false;
}
break;
// Standard Frame
case FRAME_TYPE_INFO:
default:
console.putmsg(fo.render());
mode = action = false;
break;
// Active frame
}
break;
}
log(LOG_DEBUG,'ACTION END: ['+read+']');
}
log(LOG_DEBUG,'- FINISHED');
if (action == ACTION_TERMINATE) {
log(LOG_DEBUG,'! Hangup');
bbs.hangup();
}
exit();
}