var FRAME_VIEWDATA = (1<<2); var VIEWDATA_FRAME_LENGTH = 22; /* Length of a frame */ var VIEWDATA_FRAME_WIDTH = 40; /* Width of a frame */ var VIEWDATA_FRAME_HEADER = 23; /* Size of page owner (length) */ var VIEWDATA_FRAME_PAGENUM = 11; /* Size of page number (length with a-z) */ var VIEWDATA_FRAME_COST = 3; /* Size of cost (length without unit) */ var VIEWDATA_HOME = "\x1e"; var VIEWDATA_CLS = "\x0c"; var VIEWDATA_UP = "\x0b"; var VIEWDATA_DOWN = "\x0a"; var VIEWDATA_COFF = "\x14"; var VIEWDATA_CON = "\x11"; var VIEWDATA_RIGHT = "\x09"; var VIEWDATA_LEFT = "\x08"; var VIEWDATA_MSG_SENDORNOT = ascii(27)+'BKEY 1 TO SEND, 2 NOT TO SEND'; var VIEWDATA_MSG_LOGON = ascii(27)+'BKEY 1 TO LOGON, 2 TO RETURN'; var VIEWDATA_MSG_SENT = ascii(27)+'BMESSAGE SENT - KEY _ TO CONTINUE'; var VIEWDATA_MSG_NOTSENT = ascii(27)+'BMESSAGE NOT SENT - KEY _ TO CONTINUE'; var VIEWDATA_ERR_NO_PARENT = ascii(27)+'APARENT FRAME DOESNT EXIST'; var VIEWDATA_ERR_NOT_IMPLEMENTED = ascii(27)+'ANOT IMPLEMENTED YET?'; var VIEWDATA_ERR_ROUTE = ascii(27)+'GMISTAKE?'+ascii(27)+'BTRY AGAIN OR TELL US ON *08'; var VIEWDATA_ERR_METHOD_NOT_EXIST = ascii(27)+'GMISTAKE?'+ascii(27)+'BTRY AGAIN OR TELL US ON *08'; var VIEWDATA_ACCESS_DENIED = ascii(27)+'ACCESS DENIED.'; var VIEWDATA_ALREADY_MEMBER = ascii(27)+'AALREADY MEMBER OF CUG' var VIEWDATA_INACTIVITY = ascii(27)+'AINACTIVITY ALERT, DISCONNECT PENDING...'; var VIEWDATA_INACTIVE = ascii(27)+'AINACTIVITY DISCONNECT'; var VIEWDATA_NOACTION = ascii(27)+'ANO ACTION PERFORMED'; var VIEWDATA_BASESTAR = ascii(27)+'B*'; var VIEWDATA_INVALID_CODE = ascii(27)+'AINVAID CODE, PLEASE TRY AGAIN *00'; var VIEWDATA_TOKEN_EMAIL = ascii(27)+'ATOKEN EMAILED TO YOU...'; var VIEWDATA_TOKEN_SENT = ascii(27)+'ATOKEN SENT, PLEASE ENTER TOKEN'; var VIEWDATA_INVALID_EMAIL = ascii(27)+'AINVAID EMAIL, PLEASE TRY AGAIN *00'; var VIEWDATA_INVALID_UID = ascii(27)+'AINVAID USER ID, PLEASE TRY AGAIN *00'; var VIEWDATA_CANNOT_SEND_TOKEN = ascii(27)+'ACANNOT SEND VALIDATION CODE, PLEASE TRY AGAIN *00'; var VIEWDATA_USER_EXISTS = ascii(27)+'AERROR USER EXISTS, PLEASE TRY AGAIN *00'; var VIEWDATA_USER_CREATE_ERROR = ascii(27)+'AERROR CREATING USER, PLEASE TRY AGAIN *00'; var VIEWDATA_LOGIN_ERROR = ascii(27)+'AERROR LOGGING IN, PLEASE TRY AGAIN *00'; var VIEWDATA_CANCEL_MSG = ascii(27)+'BPRESS 2 TO CANCEL'; // Our frame object function VIEWDATAFrame() { 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 var blp=0; // Length of data on the bottom line // 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] this.attr=function(field) { //NOOP } /** * Turn off the cursor */ this.cursorOff=function() { write_raw(VIEWDATA_COFF); this.gotoxy(1,1); } this.cursorOn=function(x,y) { write_raw(VIEWDATA_CON); this.gotoxy(x,y); } // Field backspace, that leaves the field filler char this.fieldbs=function(char) { console.write(VIEWDATA_LEFT+char+VIEWDATA_LEFT); } this.gotoxy=function(x,y) { // @todo This could be optimised to go the shortest route write_raw(VIEWDATA_HOME); if (x>0) write_raw(VIEWDATA_RIGHT.repeat(x)); if (y>0) write_raw(VIEWDATA_DOWN.repeat(y)); } this.strlen=function(str) { return str.replace(/\x1b/g,'').length; }; // Render the frame to the user this.render=function(withHeader) { log(LOG_DEBUG,'- VIEWDATA FRAME'); owner = base64_decode(this.owner); header = VIEWDATA_DOWN; //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+'] ('+this.strlen(videotex(this.pageownerlogo))+')'); cost = (this.isAccessible ? this.cost+FRAME_COSTUNIT : ' -'); header = videotex(this.pageownerlogo)+' '.repeat(VIEWDATA_FRAME_HEADER-this.strlen(videotex(this.pageownerlogo)))+ (this.isAccessible ? ascii(27)+'G' : ascii(27)+'A')+this.page+' '.repeat(VIEWDATA_FRAME_PAGENUM-this.page.length)+ ascii(27)+'B'+' '.repeat(VIEWDATA_FRAME_COST-cost.toString().length+1)+cost; } //console.status |= CON_RAW_IN; write_raw(VIEWDATA_CLS); write_raw(header); return write_raw(videotex(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 VIEWDATA frame from: '+filename); f = new File(system.mods_dir+'ansitex/text/'+filename+'.vtx'); 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); 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] 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 = []; // Default Attributes f = 39; b = 49; i = 0; for(p=0;p= 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; // SOS 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 \ sos = matches[0].substr(0,matches[0].length-2).split(';'); //log(LOG_DEBUG,'SOS ['+r+'x'+c+'] ADVANCE: '+advance+', SOS: '+sos); var num = 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>VIEWDATA_FRAME_WIDTH) { c = 1; r++; } /* // @todo - If we are longer than FRAME_LENGTH, move the output into the next frame. if (r>FRAME_LENGTH) { break; } */ } return output; }; this.qrcode = function(qr,subframe) { var offset = 18/2; // @todo For some reason RIGHT movements results in 2 spaces // Render the body var qrcode = VIEWDATA_HOME+VIEWDATA_DOWN.repeat(5)+VIEWDATA_RIGHT.repeat(offset); //var line = ''; for (var x = -1; x < qr.size; x=x+3) { var line = VIEWDATA_RIGHT.repeat(offset)+ascii(0x97); for (var y = -1; y < qr.size; y=y+2) { var char = 0; //TL char |= ((x==-1) || (y==-1) || ! qr.getModule(x,y)) ? (1<<0) : (0<<0); //TR char |= ((x==-1) || (y == qr.size-1) || ! qr.getModule(x,y+1)) ? (1<<1) : (0<<1); //ML char |= ((y==-1) || ! qr.getModule(x+1,y)) ? (1<<2) : (0<<2); //MR char |= ((y == qr.size-1) || ! qr.getModule(x+1,y+1)) ? (1<<3) : (0<<3); //BL char |= ((x==qr.size-2) || (y==-1) || ! qr.getModule(x+2,y)) ? (1<<4) : (0<<4); //BR char |= ((x==qr.size-2) || (y == qr.size-1) || ! qr.getModule(x+2,y+1)) ? (1<<5) : (0<<5); char += 0x20; if (char > 0x3f) char += 0x20; line += ascii(char); } // Render the right column if (y%2) line += ascii(0x35); qrcode += line+' '.repeat(VIEWDATA_FRAME_WIDTH-Math.ceil(qr.size/2)-2-offset*2-(y%2 ? 0 : 1))+VIEWDATA_RIGHT.repeat(offset); } log(LOG_DEBUG,'WIDTH:'+VIEWDATA_FRAME_WIDTH); log(LOG_DEBUG,'QR :'+(Math.ceil(qr.size/2)+1)); log(LOG_DEBUG,'OFF :'+offset); log(LOG_DEBUG,'Y :'+(y%2 ? 0 : 1)); log(LOG_DEBUG,'X :'+(x%3 ? 0 : 1)); // Render the bottom if (x%3) { line = VIEWDATA_RIGHT.repeat(offset)+ascii(0x97); for (var y = 0; y < qr.size; y=y+2) { line += ascii(0x23); } // Render the right column if (y%2 == 0) { line += ascii(0x21); } qrcode += line+' '.repeat(VIEWDATA_FRAME_WIDTH-Math.ceil(qr.size/2)-2-offset-(y%2 ? 0 : 1)); } write_raw(qrcode+VIEWDATA_DOWN); }; 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 = VIEWDATA_'+text+';'); var x = this.strlen(msg); write_raw(VIEWDATA_HOME+VIEWDATA_UP+msg+ ((blp > x) ? (' '.repeat(blp-x)+(reposition ? VIEWDATA_HOME+VIEWDATA_UP+VIEWDATA_RIGHT.repeat(x) : '')) : '') ); blp = x; } this.clearBaseline=function(reposition) { msg = ''; write_raw(VIEWDATA_HOME+VIEWDATA_UP+msg+ ((blp > msg.length) ? (' '.repeat(blp-msg.length)+(reposition ? VIEWDATA_HOME+VIEWDATA_UP+VIEWDATA_RIGHT.repeat(msg.length) : '')) : '') ); blp = msg.length; } 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() { return base64_decode(pageOwner(this.frame).logovtx); } }) } function videotex(data) { var output = ''; //$output .= ($byte < 32) ? ESC.chr($byte+64) : chr($byte); for (var i = 0; i < data.length; i++) { output += (data.charCodeAt(i) < 32) ? "\x1b"+String.fromCharCode(data.charCodeAt(i)+64) : String.fromCharCode(data.charCodeAt(i)); } return output; }