595 lines
14 KiB
JavaScript
595 lines
14 KiB
JavaScript
const SESSION_ANSITEX = (1<<1);
|
|
const SESSION_EXT = 'tex';
|
|
|
|
const ANSI_FRAME_WIDTH = 80;
|
|
const ANSI_FRAME_HEIGHT = 22;
|
|
const ANSI_FRAME_PROVIDER_LENGTH = 55;
|
|
const ANSI_FRAME_PAGE_LENGTH = 13;
|
|
const ANSI_FRAME_COST_LENGTH = 10;
|
|
const ANSI_FRAME_ATTR_LENGTH = 0; // Space that an attribute takes
|
|
|
|
/**
|
|
* This function converts ANSI text into an array of attributes
|
|
*
|
|
* We include the attribute for every character, so that if a window is placed on top of this window, the edges
|
|
* render correctly.
|
|
*
|
|
* @param contents - Our ANSI content to convert
|
|
* @param width - The width before wrapping to the next line
|
|
* @param yoffset - fields offset as discovered
|
|
* @param xoffset - fields offset as discovered
|
|
* @param debug - Enable debug mode
|
|
*/
|
|
function anstoattrs(contents,width,yoffset,xoffset,debug) {
|
|
if (debug)
|
|
writeln('DEBUG active: '+debug);
|
|
|
|
lines = (''+contents).split(/\r\n/);
|
|
|
|
var i = 0;
|
|
var bg = BG_BLACK;
|
|
var fg = LIGHTGRAY;
|
|
var attr = fg + bg + i;
|
|
|
|
var y = 0;
|
|
// Saving cursor positions
|
|
var saved = {};
|
|
|
|
var frame = {
|
|
content: [],
|
|
dynamic_fields: [],
|
|
input_fields: [],
|
|
};
|
|
|
|
// @todo temp hack, rework ansi variable - perhaps have a function that converts an attribute back into an ANSI sequence
|
|
var ansi = { i: 0, f: 37, b: 40 };
|
|
|
|
while (lines.length > 0) {
|
|
var x = 0;
|
|
var line = lines.shift();
|
|
|
|
if ((debug !== undefined) && (y > debug)) {
|
|
exit(1);
|
|
}
|
|
|
|
if (debug) {
|
|
log(LOG_DEBUG,'y:'+y);
|
|
log(LOG_DEBUG,'line:'+line);
|
|
}
|
|
|
|
while (line.length > 0) {
|
|
/* parse an attribute sequence*/
|
|
var m = line.match(/^\x1b\[((\d+)(;?(\d+))*)+m/);
|
|
if (m !== null) {
|
|
line = line.substr(m.shift().length);
|
|
m = m.shift().split(';').sort(function(a,b) { return Number(a) < Number(b) ? -1 : 1; });
|
|
|
|
if ((debug !== undefined) && debug === y) {
|
|
//writeln();
|
|
log(LOG_DEBUG,'m:'+JSON.stringify(m));
|
|
}
|
|
|
|
// Reset
|
|
if (Number(m[0]) === 0) {
|
|
bg = BG_BLACK;
|
|
fg = LIGHTGRAY;
|
|
i = 0;
|
|
ansi = { i:0, b:37, f:40};
|
|
m.shift();
|
|
|
|
if (debug)
|
|
log(LOG_DEBUG,' - RESET');
|
|
}
|
|
|
|
// High Intensity
|
|
if (Number(m[0]) === 1) {
|
|
i += (((i === 0) || (i === BLINK)) ? HIGH : 0);
|
|
m.shift();
|
|
ansi.i = 1;
|
|
|
|
if (debug && (debug === y))
|
|
writeln('fg:'+fg+', bg:'+bg+' i:'+i);
|
|
}
|
|
|
|
// Blink
|
|
if (Number(m[0]) === 5) {
|
|
i += (((i === 0) || (i === HIGH)) ? BLINK : 0);
|
|
m.shift();
|
|
}
|
|
|
|
// Foreground
|
|
if ((Number(m[0]) >= 30) && (Number(m[0]) <= 37)) {
|
|
ansi.f = Number(m[0]);
|
|
|
|
switch(Number(m.shift())) {
|
|
case 30:
|
|
fg = BLACK;
|
|
break;
|
|
|
|
case 31:
|
|
fg = RED;
|
|
break;
|
|
|
|
case 32:
|
|
fg = GREEN;
|
|
break;
|
|
|
|
case 33:
|
|
fg = BROWN;
|
|
break;
|
|
|
|
case 34:
|
|
fg = BLUE;
|
|
break;
|
|
|
|
case 35:
|
|
fg = MAGENTA;
|
|
break;
|
|
|
|
case 36:
|
|
fg = CYAN;
|
|
break;
|
|
|
|
case 37:
|
|
fg = LIGHTGRAY;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Background
|
|
if ((Number(m[0]) >= 40) && (Number(m[0]) <= 47)) {
|
|
ansi.b = Number(m[0]);
|
|
|
|
switch (Number(m.shift())) {
|
|
case 40:
|
|
bg = BG_BLACK;
|
|
break;
|
|
|
|
case 41:
|
|
bg = BG_RED;
|
|
break;
|
|
|
|
case 42:
|
|
bg = BG_GREEN;
|
|
break;
|
|
|
|
case 43:
|
|
bg = BG_BROWN;
|
|
break;
|
|
|
|
case 44:
|
|
bg = BG_BLUE;
|
|
break;
|
|
|
|
case 45:
|
|
bg = BG_MAGENTA;
|
|
break;
|
|
|
|
case 46:
|
|
bg = BG_CYAN;
|
|
break;
|
|
|
|
case 47:
|
|
bg = BG_LIGHTGRAY;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (debug) {
|
|
log(LOG_DEBUG,'fg:'+fg+', bg:'+bg+' i:'+i);
|
|
log(LOG_DEBUG,'ansi:'+JSON.stringify(ansi));
|
|
}
|
|
|
|
attr = bg + fg + i;
|
|
continue;
|
|
}
|
|
|
|
if (debug)
|
|
log(LOG_DEBUG,'= Current attr:'+attr);
|
|
|
|
/* parse absolute character position */
|
|
var m = line.match(/^\x1b\[(\d*);?(\d*)[Hf]/);
|
|
if (m !== null) {
|
|
line = line.substr(m.shift().length);
|
|
|
|
if (m.length === 0) {
|
|
x = 0;
|
|
y = 0;
|
|
|
|
} else {
|
|
if(m[0])
|
|
y = Number(m.shift())-1;
|
|
|
|
if(m[0])
|
|
x = Number(m.shift())-1;
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
/* ignore a bullshit sequence */
|
|
var n = line.match(/^\x1b\[\?7h/);
|
|
if (n !== null) {
|
|
line = line.substr(n.shift().length);
|
|
continue;
|
|
}
|
|
|
|
/* parse an up positional sequence */
|
|
var n = line.match(/^\x1b\[(\d*)A/);
|
|
if (n !== null) {
|
|
line = line.substr(n.shift().length);
|
|
|
|
var chars = n.shift();
|
|
|
|
y -= (chars < 1) ? 0 : Number(chars);
|
|
|
|
continue;
|
|
}
|
|
|
|
/* parse a down positional sequence */
|
|
var n = line.match(/^\x1b\[(\d*)B/);
|
|
if (n !== null) {
|
|
line = line.substr(n.shift().length);
|
|
|
|
var chars = n.shift();
|
|
|
|
y += (chars < 1) ? 0 : Number(chars);
|
|
|
|
continue;
|
|
}
|
|
|
|
/* parse a forward positional sequence */
|
|
var n = line.match(/^\x1b\[(\d*)C/);
|
|
if (n !== null) {
|
|
line = line.substr(n.shift().length);
|
|
|
|
var chars = n.shift();
|
|
|
|
x += (chars < 1) ? 0 : Number(chars);
|
|
|
|
continue;
|
|
}
|
|
|
|
/* parse a backward positional sequence */
|
|
var n = line.match(/^\x1b\[(\d*)D/);
|
|
if (n !== null) {
|
|
line = line.substr(n.shift().length);
|
|
|
|
var chars = n.shift()
|
|
|
|
x -= (chars < 1) ? 0 : Number(chars);
|
|
|
|
continue;
|
|
}
|
|
|
|
/* parse a clear screen sequence */
|
|
var n = line.match(/^\x1b\[2J/);
|
|
if (n !== null) {
|
|
line = line.substr(n.shift().length);
|
|
|
|
continue;
|
|
}
|
|
|
|
/* parse save cursor sequence */
|
|
var n = line.match(/^\x1b\[s/);
|
|
if (n !== null) {
|
|
line = line.substr(n.shift().length);
|
|
saved.x = x;
|
|
saved.y = y;
|
|
|
|
continue;
|
|
}
|
|
|
|
/* parse restore cursor sequence */
|
|
var n = line.match(/^\x1b\[u/);
|
|
if (n !== null) {
|
|
line = line.substr(n.shift().length);
|
|
x = saved.x;
|
|
y = saved.y;
|
|
|
|
continue;
|
|
}
|
|
|
|
/* parse an input field */
|
|
// Input field 'FIELD;valueTYPE;input char'
|
|
// @todo remove the trailing ESC \ to end the field, just use a control code ^B \x02 (Start of Text) and ^C \x03
|
|
var m = line.match(/^\x1b_(([A-Z]+;[0-9a-z]+)([;]?.+)?)\x1b\\/);
|
|
if (m !== null) {
|
|
log(LOG_DEBUG,'Got input field: '+JSON.stringify(m));
|
|
log(LOG_DEBUG,'ansi:'+JSON.stringify(ansi));
|
|
// full string that matched
|
|
match = m.shift();
|
|
|
|
// thus, the rest of the line
|
|
line = line.substr(match.length);
|
|
//writeln('rest of line:'+JSON.stringify(line));
|
|
|
|
// We are interested in our field match
|
|
var sos = m.shift().split(';');
|
|
//writeln('sos:'+JSON.stringify(sos));
|
|
|
|
for (var num in sos) {
|
|
switch (num) {
|
|
// First value is the field name
|
|
case '0':
|
|
field = sos[num];
|
|
break;
|
|
|
|
// Second value is the length/type of the field, nnX nn=size in chars, T=type (lower case)
|
|
case '1':
|
|
var c = sos[num].match(/([0-9]+)([a-z])/);
|
|
if (! c) {
|
|
log(LOG_ERROR,'SOS FAILED PARSING FIELD LENGTH/TYPE. ['+r+'x'+c+'] '+sos[num]);
|
|
break;
|
|
}
|
|
|
|
//log(LOG_DEBUG,'SOS ['+r+'x'+c+'] NUM CHARS: '+x[1]+', TYPE: '+x[2]);
|
|
fieldlen = c[1];
|
|
fieldtype = c[2];
|
|
break;
|
|
|
|
// Third field is the char to to use
|
|
case '2':
|
|
fieldchar = sos[num];
|
|
break;
|
|
|
|
default:
|
|
log(LOG_ERROR,'IGNORING ADDITIONAL SOS FIELDS. ['+r+'x'+c+'] '+sos[num]);
|
|
}
|
|
}
|
|
|
|
// If we are padding our field with a char, we need to add that back to line
|
|
// @todo validate if this goes beyond our width (and if scrolling not enabled)
|
|
if (fieldlen)
|
|
line = fieldchar.repeat(fieldlen)+line;
|
|
|
|
frame.input_fields.push({
|
|
type: fieldtype,
|
|
length: Number(fieldlen),
|
|
char: fieldchar,
|
|
name: field,
|
|
attribute: JSON.parse(JSON.stringify(ansi)),
|
|
x: Number(x+(xoffset !== undefined ? xoffset : 0)),
|
|
y: Number(y+(yoffset !== undefined ? yoffset : 0)),
|
|
value: '',
|
|
});
|
|
|
|
log(LOG_DEBUG,'input_field:'+JSON.stringify(frame.input_fields.last));
|
|
}
|
|
|
|
/* parse dynamic value field */
|
|
// @todo remove the trailing ESC \ to end the field, just use a control code ie: ^E \x05 (Enquiry) or ^Z \x26 (Substitute)
|
|
var m = line.match(/^\x1bX(([a-zA-Z._:^;]+[0-9]?;-?[0-9^;]+)([;]?[^;]+)?)\x1b\\/);
|
|
if (m !== null) {
|
|
// full string that matched
|
|
match = m.shift();
|
|
|
|
// thus, the rest of the line
|
|
line = line.substr(match.length);
|
|
//writeln('rest of line:'+JSON.stringify(line));
|
|
|
|
// We are interested in our field match
|
|
var df = m.shift().split(';');
|
|
|
|
log(LOG_DEBUG,'- DF found at ['+x+'x'+y+'], Field: '+df[0]+', Length: '+df[1]+', Pad:'+df[2]);
|
|
// If we are padding our field with a char, we need to add that back to line
|
|
// @todo validate if this goes beyond our width (and if scrolling not enabled)
|
|
line = (df[2] ? df[2] : '_').repeat(Math.abs(df[1]))+line;
|
|
|
|
frame.dynamic_fields.push({
|
|
name: df[0],
|
|
length: df[1],
|
|
pad: df[2],
|
|
x: x+(xoffset !== undefined ? xoffset : 0),
|
|
y: y+(yoffset !== undefined ? yoffset : 0),
|
|
value: undefined,
|
|
});
|
|
}
|
|
|
|
/* set character and attribute */
|
|
var ch = line[0];
|
|
line = line.substr(1);
|
|
|
|
if (debug && debug === y) {
|
|
log(LOG_DEBUG,'Got char: '+ch);
|
|
//write(ch);
|
|
}
|
|
|
|
/* validate position */
|
|
if (y < 0)
|
|
y = 0;
|
|
if (x < 0)
|
|
x = 0;
|
|
|
|
if (x >= width) {
|
|
x = 0;
|
|
y++;
|
|
}
|
|
|
|
/* set character and attribute */
|
|
if (! frame.content[y+1])
|
|
frame.content[y+1]=[];
|
|
|
|
if (attr === null)
|
|
throw new Error('Attribute is null?');
|
|
|
|
frame.content[y+1][x+1] = new Char(ch,attr);
|
|
|
|
x++;
|
|
}
|
|
|
|
// If we got a BG_BLACK|LIGHTGRAY ESC [0m, but not character, we include it as it resets any background that was going on
|
|
if ((attr === BG_BLACK|LIGHTGRAY) && frame.content[y+1] && (frame.content[y+1][x].__properties__.attr !== attr))
|
|
frame.content[y+1][x+1] = new Char(undefined,attr);
|
|
|
|
y++;
|
|
}
|
|
|
|
return frame;
|
|
}
|
|
|
|
load('ansitex/load/session.js');
|
|
|
|
// Our frame object
|
|
function SessionAnsitex() {
|
|
Session.apply(this,arguments);
|
|
|
|
this.settings.MSG_SENDORNOT = '\1n\1h\1GKEY 1 TO SEND, 2 NOT TO SEND';
|
|
this.settings.MSG_LOGON = '\1n\1h\1GKEY 1 TO LOGON, 2 TO RETURN';
|
|
this.settings.MSG_SENT = '\1n\1h\1GMESSAGE SENT - KEY # TO CONTINUE';
|
|
this.settings.MSG_NOTSENT = '\1n\1h\1GMESSAGE NOT SENT - KEY # TO CONTINUE';
|
|
this.settings.ERR_NO_PARENT = '\1n\1h\1RPARENT FRAME DOESNT EXIST';
|
|
this.settings.ERR_NOT_IMPLEMENTED = '\1n\1h\1RNOT IMPLEMENTED YET?';
|
|
this.settings.ERR_ROUTE = '\1n\1h\1WMISTAKE? \1GTRY AGAIN OR TELL US ON *08';
|
|
this.settings.ERR_METHOD_NOT_EXIST = '\1n\1h\1WMISTAKE? \1GTRY AGAIN OR TELL US ON *08';
|
|
this.settings.ACCESS_DENIED = '\1n\1h\1RACCESS DENIED. MISTAKE? TRY AGAIN OR TELL US *08';
|
|
this.settings.ALREADY_MEMBER = '\1n\1h\1RALREADY MEMBER OF CUG'
|
|
this.settings.INACTIVITY = '\1n\1h\1RINACTIVITY ALERT, DISCONNECT PENDING...';
|
|
this.settings.INACTIVE = '\1n\1h\1RINACTIVITY DISCONNECT';
|
|
this.settings.NOACTION = '\1n\1h\1RNO ACTION PERFORMED';
|
|
this.settings.BASESTAR = '\1N\1G\1H*';
|
|
this.settings.INVALID_CODE = '\1n\1h\1RINVAID CODE, PLEASE TRY AGAIN *00';
|
|
this.settings.TOKEN_EMAIL = '\1n\1h\1RTOKEN EMAILED TO YOU...';
|
|
this.settings.TOKEN_SENT = '\1n\1h\1RTOKEN SENT, PLEASE ENTER TOKEN';
|
|
this.settings.INVALID_EMAIL = '\1n\1h\1RINVAID EMAIL, PLEASE TRY AGAIN *00';
|
|
this.settings.INVALID_UID = '\1n\1h\1RINVAID USER ID, PLEASE TRY AGAIN *00';
|
|
this.settings.CANNOT_SEND_TOKEN = '\1n\1h\1RCANNOT SEND VALIDATION CODE, PLEASE TRY AGAIN *00';
|
|
this.settings.USER_EXISTS = '\1n\1h\1RERROR USER EXISTS, PLEASE TRY AGAIN *00';
|
|
this.settings.USER_CREATE_ERROR = '\1n\1h\1RERROR CREATING USER, PLEASE TRY AGAIN *00';
|
|
this.settings.LOGIN_ERROR = '\1n\1h\1RERROR LOGGING IN, PLEASE TRY AGAIN *00';
|
|
this.settings.CANCEL_MSG = '\1n\1h\1GPRESS 2 TO CANCEL';
|
|
this.settings.SYS_ERROR = '\1n\1h\1RSYSTEM ERROR DETECTED - TRY AGAIN OR TELL US *08';
|
|
this.settings.LOADING = '\1n\1h\1Kloading...';
|
|
this.settings.PROCESSING = '\1n\1h\1Kprocessing...';
|
|
|
|
/**
|
|
* Set the attribute at the current position
|
|
*/
|
|
this.attr = function(field) {
|
|
write('\x1b['+field.i+';'+field.f+';'+field.b+'m');
|
|
}
|
|
|
|
this.baselineClear = function(reposition) {
|
|
this.cursorSave();
|
|
this.gotoxy(0,24);
|
|
this.cleareol();
|
|
|
|
if (! reposition)
|
|
this.cursorRestore();
|
|
}
|
|
|
|
/**
|
|
* Send a message to the baseline.
|
|
*
|
|
* @param text
|
|
* @param reposition
|
|
*/
|
|
this.baselineSend = function(text,reposition) {
|
|
this.cursorSave();
|
|
this.gotoxy(0,24);
|
|
write(this.getMessage(text));
|
|
this.cleareol();
|
|
|
|
if (! reposition)
|
|
this.cursorRestore();
|
|
}
|
|
|
|
/**
|
|
* Turn off the cursor
|
|
*/
|
|
this.cursorOff = function() {
|
|
write('\x1b[?25l');
|
|
write('\x1b[24;0H');
|
|
}
|
|
|
|
/**
|
|
* Turn on cursor
|
|
* @param x
|
|
* @param y
|
|
*/
|
|
this.cursorOn = function(x,y) {
|
|
write('\x1b[?25h');
|
|
|
|
if (x && y)
|
|
this.gotoxy(x,y);
|
|
}
|
|
|
|
this.cursorRestore = function() {
|
|
write('\x1b[u');
|
|
}
|
|
|
|
this.cursorSave = function() {
|
|
write('\x1b[s');
|
|
}
|
|
|
|
this.cleareol = function() {
|
|
write('\x1b[0K');
|
|
}
|
|
|
|
// Field backspace, that leaves the field filler char
|
|
this.fieldbs = function(char) {
|
|
write('\x1b[D'+char+'\x1b[D');
|
|
}
|
|
|
|
/**
|
|
* Position the cursor in a specific location
|
|
*
|
|
* @param x
|
|
* @param y
|
|
*/
|
|
this.gotoxy = function(x,y) {
|
|
write('\x1b['+y+';'+x+'H');
|
|
}
|
|
|
|
this.qrcode = function(qr,subframe) {
|
|
// SMALL Image
|
|
var full = ascii(0xdb);
|
|
var top = ascii(0xdf);
|
|
var bot = ascii(0xdc);
|
|
var blank = ' ';
|
|
|
|
var qrcode = '';
|
|
|
|
/*
|
|
// Render the top line
|
|
var line = ascii(27)+'[1;37m'+bot;
|
|
for (var y = 0; y < qr.size; y++) {
|
|
line += bot;
|
|
}
|
|
qrcode += line+bot+bot+ascii(27)+'[0m'+"\r\n";
|
|
*/
|
|
|
|
// Render the body
|
|
for (var x = -1; x < qr.size; x=x+2) {
|
|
line = ascii(27)+'[1;37m'+full;
|
|
|
|
for (var y = 0; y < qr.size; y++) {
|
|
// Top is white
|
|
if (! ((x===-1)? 0 : qr.getModule(x, y))) {
|
|
line += (qr.getModule(x+1, y)) ? top : full;
|
|
|
|
// Top is black
|
|
} else {
|
|
line += (qr.getModule(x+1, y)) ? blank : bot;
|
|
}
|
|
}
|
|
|
|
qrcode += line+full+ascii(27)+'[0m'+"\r\n";
|
|
}
|
|
|
|
// Render the bottom
|
|
line = ascii(27)+'[1;37m'+top;
|
|
for (var y = 0; y < qr.size; y++) {
|
|
line += top;
|
|
}
|
|
qrcode += line+top+ascii(27)+'[0m'+"\r\n";
|
|
|
|
ans2bin(fo.parse(qrcode),subframe);
|
|
subframe.open();
|
|
subframe.cycle();
|
|
};
|
|
}
|
|
|
|
SessionAnsitex.prototype = Session.prototype;
|
|
SessionAnsitex.prototype.constructor = SessionAnsitex;
|