sbbs/load/ansiframe.js

579 lines
16 KiB
JavaScript
Raw Normal View History

2020-07-30 13:47:40 +00:00
var FRAME_ANSI = (1<<1);
2020-07-30 13:47:40 +00:00
var ANSI_FRAME_LENGTH = 22; /* Length of a frame */
var ANSI_FRAME_WIDTH = 80; /* Width of a frame */
var ANSI_FRAME_HEADER = 56; /* Size of page owner (length) */
var ANSI_FRAME_PAGENUM = 12; /* Size of page number (length with a-z) */
var ANSI_FRAME_COST = 9; /* Size of cost (length without unit) */
2020-07-30 13:47:40 +00:00
var ANSI_MSG_SENDORNOT = '\1n\1h\1GKEY 1 TO SEND, 2 NOT TO SEND';
var ANSI_MSG_LOGON = '\1n\1h\1GKEY 1 TO LOGON, 2 TO RETURN';
var ANSI_MSG_SENT = '\1n\1h\1GMESSAGE SENT - KEY # TO CONTINUE';
var ANSI_MSG_NOTSENT = '\1n\1h\1GMESSAGE NOT SENT - KEY # TO CONTINUE';
var ANSI_ERR_NO_PARENT = '\1n\1h\1RPARENT FRAME DOESNT EXIST';
var ANSI_ERR_NOT_IMPLEMENTED = '\1n\1h\1RNOT IMPLEMENTED YET?';
var ANSI_ERR_ROUTE = '\1n\1h\1WMISTAKE? \1GTRY AGAIN OR TELL US ON *08';
var ANSI_ERR_METHOD_NOT_EXIST = '\1n\1h\1WMISTAKE? \1GTRY AGAIN OR TELL US ON *08';
var ANSI_ACCESS_DENIED = '\1n\1h\1RACCESS DENIED. MISTAKE? TRY AGAIN OR TELL US *08';
var ANSI_ALREADY_MEMBER = '\1n\1h\1RALREADY MEMBER OF CUG'
var ANSI_INACTIVITY = '\1n\1h\1RINACTIVITY ALERT, DISCONNECT PENDING...';
var ANSI_INACTIVE = '\1n\1h\1RINACTIVITY DISCONNECT';
var ANSI_NOACTION = '\1n\1h\1RNO ACTION PERFORMED';
var ANSI_BASESTAR = '\1N\1G\1H*';
2020-07-31 14:01:48 +00:00
var ANSI_INVALID_CODE = '\1n\1h\1RINVAID CODE, PLEASE TRY AGAIN *00';
var ANSI_TOKEN_EMAIL = '\1n\1h\1RTOKEN EMAILED TO YOU...';
var ANSI_TOKEN_SENT = '\1n\1h\1RTOKEN SENT, PLEASE ENTER TOKEN';
var ANSI_INVALID_EMAIL = '\1n\1h\1RINVAID EMAIL, PLEASE TRY AGAIN *00';
var ANSI_INVALID_UID = '\1n\1h\1RINVAID USER ID, PLEASE TRY AGAIN *00';
var ANSI_CANNOT_SEND_TOKEN = '\1n\1h\1RCANNOT SEND VALIDATION CODE, PLEASE TRY AGAIN *00';
var ANSI_USER_EXISTS = '\1n\1h\1RERROR USER EXISTS, PLEASE TRY AGAIN *00';
var ANSI_USER_CREATE_ERROR = '\1n\1h\1RERROR CREATING USER, PLEASE TRY AGAIN *00';
var ANSI_LOGIN_ERROR = '\1n\1h\1RERROR LOGGING IN, PLEASE TRY AGAIN *00';
2020-08-12 13:23:26 +00:00
var ANSI_CANCEL_MSG = '\1n\1h\1GPRESS 2 TO CANCEL';
// Our frame object
function ANSIFrame() {
this.version=1; // The version of this frame - in case we update functionality and we need to be
// backwards compatible
this.frame=null; // Frame Number [0-9]+
this.index=null; // Frame Index [a-z]
this.owner=0; // The Service Provider owning the frame.
this.cost=0; // The cost to view the frame @TODO
this.content=''; // The frame content, base64 encoded
// Frame's owned by the system where:
// isPublic is FALSE - the user must be logged in to view it
// isPublic is TRUE - can be viewed by non-logged in users
// Frame's owned by Service Providers where:
// isPublic is FALSE - can only be viewed if a user is
// a member of the Service Providers CUG
// isPublic is TRUE - can be viewed by users (if logged in)
this.isPublic=false; // Is this frame accessible to non CUG users
// If FALSE user must be a member of the CUG to view the frame
// All users, including unauthenticated, are members of 'system' (owner = 0)
this.isAccessible=false; // Is this frame available to be accessed
// If FALSE, only the SP can view/edit the frame
this.type = FRAME_TYPE_INFO; // The frame type - see FRAME_TYPES above
this.key=[ null,null,null,null,null,null,null,null,null,null ]; // Key actions [0-9]
2020-07-30 13:47:40 +00:00
/**
* Set the attribute at the current position
*/
this.attr=function(field) {
2020-08-12 13:23:26 +00:00
console.write(ascii(27)+'['+field.i+';'+field.f+';'+field.b+'m');
2020-07-30 13:47:40 +00:00
}
/**
* Turn off the cursor
*/
this.cursorOff=function() {
ansi.send('ext_mode','clear','cursor');
2020-07-30 13:47:40 +00:00
this.gotoxy(0,24);
}
this.cursorOn=function(x,y) {
ansi.send('ext_mode','set','cursor');
2020-07-30 13:47:40 +00:00
this.gotoxy(x,y);
}
2020-07-31 14:01:48 +00:00
// Field backspace, that leaves the field filler char
this.fieldbs=function(char) {
2020-08-12 13:23:26 +00:00
console.write(ascii(27)+'[D'+char+ascii(27)+'[D');
2020-07-31 14:01:48 +00:00
}
2020-07-30 13:47:40 +00:00
this.gotoxy=function(x,y) {
console.gotoxy(x,y);
}
// Render the frame to the user
this.render=function(withHeader) {
log(LOG_DEBUG,'- ANSI FRAME');
owner = base64_decode(this.owner);
header = '\n\r';
2020-08-12 13:23:26 +00:00
//log(LOG_DEBUG,' - FRAME User: ['+JSON.stringify(user)+']');
// Dont show the page number on system login page
if (user.number || (this.type != FRAME_TYPE_LOGIN && NO_HISTORY_FRAMES.indexOf(this.page) == -1)) {
log(LOG_DEBUG,' - Owner: ['+this.pageowner+']');
2020-07-30 13:47:40 +00:00
cost = (this.isAccessible ? this.cost+FRAME_COSTUNIT : ' -');
header = '\1n'+this.pageownerlogo+' '.repeat(ANSI_FRAME_HEADER-console.strlen(this.pageownerlogo))+'\1n '+
(this.isAccessible ? '\1W' : '\1R')+'\1H'+this.page+' '.repeat(ANSI_FRAME_PAGENUM-this.page.length)+' '+
'\1G\1H'+' '.repeat(ANSI_FRAME_COST-cost.toString().length+1)+cost+'\1n'+
(console.screen_columns > 80 ? '\n\r' : '');
}
console.clear();
2020-11-01 10:55:32 +00:00
return console.putmsg(header+this.parse(base64_decode(this.content)));
};
this.fieldValue=function(key) {
for each (var k in this.frame_fields) {
log(LOG_DEBUG,' - k:'+JSON.stringify(k));
if (k.fname == key) {
return k.fvalue;
}
}
return null;
}
// Load a frame from disk (.tex file)
this.load = function(filename) {
log(LOG_DEBUG,'Loading ANSI frame from: '+filename);
f = new File(system.mods_dir+'ansitex/text/'+filename+'.tex');
if (! f.exists || ! f.open('r')) {
return null;
}
try {
var load = JSON.parse(f.read());
for (property in load) {
this[property] = load[property];
}
} catch (error) {
log(LOG_ERROR,'Frame error: '+error);
// Load our system error frame.
this.load('998a');
return null;
}
log(LOG_DEBUG,'Loaded frame: ['+this.frame+']['+this.index+'] ('+this.page+')');
};
/**
* Parse the page text, and return the frame as 2 arrays:
* + First array is all the characters and the position on the frame
* + Second array is the array of the control codes that changes the color of the character
*
* The purpose of this function is to convert any special char sequences there are interpreted directly by Ansitex
* Currently they are:
* + ESC _ <value>[;value] ESC \
*
* Additionally, for response frames, if the cursor is moved to a field, its to determine what attributes (eg: color)
* should apply for that field.
*
* @param text
*/
this.parse = function(text) {
var c = 1; // column
var r = 2; // row (row 1 is the header)
var output = '';
this.frame_fields = [];
2020-08-12 13:23:26 +00:00
// If there is no text return
if (! text)
return output;
// Default Attributes
f = 39;
b = 49;
i = 0;
for(p=0;p<text.length;p++) {
// Look for a special character until the end of the frame width
cte = (r*ANSI_FRAME_WIDTH - ((r-1)*ANSI_FRAME_WIDTH+c));
match = text.substr(p,cte).match(/[\r\n\x1b]/);
//log(LOG_DEBUG,'SPECIAL CHAR ['+r+'x'+c+'] ['+p+'-'+cte+'] for: '+match);
if (match == null || match.index) {
advance = match ? match.index : cte;
//log(LOG_DEBUG,'INCLUDE: '+text.substr(p,advance));
output += text.substr(p,advance);
p += advance;
c += advance;
advance = 0;
}
// If the frame is not big enough, fill it with spaces.
byte = text.charAt(p);
advance = 0;
switch (byte) {
// Carriage Return
case '\r':
// log(LOG_DEBUG,'CR');
c=1;
break;
// New line
case '\n':
// log(LOG_DEBUG,'LF');
r++;
break;
// ESC
case KEY_ESC:
advance = 1;
// Is the next byte something we know about
nextbyte = text.charAt(p+advance);
//log(LOG_DEBUG,'ESC ['+r+'x'+c+'] NEXT: '+nextbyte);
switch (nextbyte) {
// CSI
case '[':
advance++;
chars = '';
// Find our end CSI param in the next 50 chars
matches = text.substr(p+advance,50).match(/([0-9]+[;]?)+([a-zA-Z])/);
//log(LOG_DEBUG,'CSI ['+r+'x'+c+'] ADVANCE: '+advance+', MATCHES: '+matches+', STRING: '+text.substr(p+advance,50));
if (! matches) {
chars += nextbyte;
break;
}
advance += matches[0].length-1;
chars += nextbyte+matches[0];
//log(LOG_DEBUG,'CSI ['+r+'x'+c+'] ADVANCE: '+advance+', CHARS: '+chars+', CHARSLEN: '+chars.length);
switch (matches[2]) {
// Color CSIs
case 'm':
//log(LOG_DEBUG,'CSI m ['+r+'x'+c+'] MATCHES: '+matches[0]+', LENGTH : '+matches[0].length);
csi = matches[0].substr(0,matches[0].length-1).split(';');
var num = null;
for (num in csi) {
//log(LOG_DEBUG,'CSI m ['+r+'x'+c+'] NUM: '+num+', CSI: '+csi[num]);
// Intensity
if (csi[num] >= 0 && csi[num] <= 8) {
i = csi[num];
f = 39;
b = 49;
// Forground Color
} else if (csi[num] >= 30 && csi[num] <= 39) {
f = csi[num];
// Background Color
} else if (csi[num] >= 40 && csi[num] <= 49) {
b = num;
}
}
break;
// Advance characters
case 'C':
//log(LOG_DEBUG,'CSI C ['+r+'x'+c+'] CHARS: '+matches[1]);
c += parseInt(matches[1]); // Advance our position
break;
default:
log(LOG_DEBUG,'? CSI: '+matches[2]);
}
break;
case ' ':
log(LOG_DEBUG,'LOOSE ESC? ['+r+'x'+c+'] '+advance);
break;
2020-11-01 10:55:32 +00:00
// X - dynamic fields, terminated with ESC
case 'X':
//log(LOG_DEBUG,'PU ['+r+'x'+c+'] '+advance);
advance++;
// Find our end ST param in the next 50 chars
matches = text.substr(p+advance,50).match(/(([a-z]+;[0-9]+)([;]?.+)?)\x1b\\/);
//log(LOG_DEBUG,'PU ['+r+'x'+c+'] ADVANCE: '+advance+', MATCHES: '+JSON.stringify(matches)+', LENGTH: '+(matches ? matches[0].length : 0)+', STRING: '+text.substr(p+advance,50));
if (! matches) {
chars += nextbyte;
break;
}
advance += matches[0].length-1;
var pu = matches[0].substr(0,matches[0].length-2).split(';');
//log(LOG_DEBUG,'PU ['+r+'x'+c+'] ADVANCE: '+advance+', PU: '+pu);
chars = renderfield(pu[0],pu[1]);
byte = '';
log(LOG_DEBUG,'PU Field found at ['+r+'x'+(c-1)+'], Field: '+pu[0]+', Length: '+pu[1]+', Attrs: '+JSON.stringify({i:i,f:f,b:b}));
break;
// _ - has our fields that take input
case '_':
//log(LOG_DEBUG,'SOS ['+r+'x'+c+'] '+advance);
advance++;
// Find our end ST param in the next 50 chars
matches = text.substr(p+advance,50).match(/(([A-Z]+;[0-9a-z]+)([;]?.+)?)\x1b\\/);
//log(LOG_DEBUG,'SOS ['+r+'x'+c+'] ADVANCE: '+advance+', MATCHES: '+matches+', LENGTH: '+matches[0].length+', STRING: '+text.substr(p+advance,50));
if (! matches) {
chars += nextbyte;
break;
}
advance += matches[0].length-1;
// The last 2 chars of matches[0] are the ESC \
2020-11-01 10:55:32 +00:00
var sos = matches[0].substr(0,matches[0].length-2).split(';');
//log(LOG_DEBUG,'SOS ['+r+'x'+c+'] ADVANCE: '+advance+', SOS: '+sos);
var num = null;
2020-11-01 10:55:32 +00:00
var field = null;
var fieldlen = null;
for (num in sos) {
//log(LOG_DEBUG,'SOS ['+r+'x'+c+'] NUM: '+num+', SOS: '+sos[num]);
switch (num) {
// First value is the field name
case '0':
field = sos[num];
break;
// Second value is the length/type of the field
case '1':
x = sos[num].match(/([0-9]+)([a-z])/);
if (! x) {
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 = x[1];
fieldtype = x[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 (fieldlen) {
chars = fieldchar.repeat(fieldlen);
}
byte = '';
this.frame_fields.push({
ftype: fieldtype,
flength: fieldlen,
fchar: fieldchar,
fname: field,
r: r,
c: c,
attribute: {i:i,f:f,b:b},
fvalue: '',
});
log(LOG_DEBUG,'SOS Field found at ['+r+'x'+(c-1)+'], Type: '+fieldtype+', Length: '+fieldlen+', Attrs: '+JSON.stringify({i:i,f:f,b:b}));
break;
default:
log(LOG_DEBUG,'DEFAULT ['+r+'x'+c+'] '+advance);
}
break;
default:
c++;
}
output += byte;
if (advance) {
//log(LOG_DEBUG,'ADVANCE P ['+r+'x'+c+'] '+advance+', NEXT CHAR: '+text.charAt(p+advance)+' ('+text.charCodeAt(p+advance)+')');
output += chars;
p += advance;
}
if (c>ANSI_FRAME_WIDTH) {
c = 1;
r++;
}
/*
// @todo - If we are longer than ANSI_FRAME_LENGTH, move the output into the next frame.
if (r>ANSI_FRAME_LENGTH) {
break;
}
*/
}
return output;
};
2020-08-16 15:03:31 +00:00
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)) == 0) {
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();
};
this.save=function() {
file = system.mods_dir+'ansitex/text/'+this.page+'.tex';
w = new File(file);
if (! w.open('w')) {
log(LOG_ERROR,'! ERROR: Unable to create TEX file for '+this.page);
exit(1);
}
w.write(JSON.stringify(this));
w.close();
log(LOG_DEBUG,'Saved file: '+this.page+'.tex');
}
/**
* Send a message to the baseline.
*
* @param text
* @param reposition
*/
this.sendBaseline=function(text,reposition) {
eval('var msg = ANSI_'+text+';');
console.pushxy();
console.gotoxy(0,24);
console.print(msg);
console.cleartoeol();
if (! reposition) {
console.popxy();
}
}
this.clearBaseline=function(reposition) {
console.pushxy();
console.gotoxy(0,24);
console.print('');
console.cleartoeol();
if (! reposition) {
console.popxy();
}
}
Object.defineProperty(this,'accessible',{
get: function() {
log(LOG_DEBUG,'- Checking if user can access frame: '+this.page);
log(LOG_DEBUG,' - User: '+JSON.stringify(user.number));
log(LOG_DEBUG,' - Frame Owner: '+JSON.stringify(this.owner)+', System Frame: '+(this.pageowner == SYSTEM_OWNER));
// user.number 0 is unidentified user.
if (user.number) {
return (
(this.isAccessible && this.pageowner == SYSTEM_OWNER && ! this.isPublic) ||
(this.isAccessible && this.isPublic) ||
(this.isAccessible && ! this.isPublic && this.isMember) ||
(pageEditor(this.frame))
);
} else {
return (this.isAccessible && this.pageowner == SYSTEM_OWNER && this.isPublic);
}
}
});
Object.defineProperty(this,'fields', {
get: function() {
return this.frame_fields;
}
});
// Check if the user is already a member of the CUG
Object.defineProperty(this,'isMember',{
get: function() {
log(LOG_DEBUG,'- Checking if user is a member of frame: '+this.page);
if (user.number) {
return (
(this.pageowner == SYSTEM_OWNER)
);
} else {
return false;
}
}
})
Object.defineProperty(this,'page', {
get: function() {
if (this.frame == null || this.index == null) return null;
return this.frame.toString()+this.index;
}
});
Object.defineProperty(this,'pageowner', {
get: function() {
return pageOwner(this.frame).prefix;
}
})
Object.defineProperty(this,'pageownerlogo', {
get: function() {
2020-07-30 13:47:40 +00:00
return base64_decode(pageOwner(this.frame).logoans);
}
})
2020-11-01 10:55:32 +00:00
}