var SESSION_ANSITEX = (1<<1); var SESSION_EXT = 'tex'; var ANSI_FRAME_WIDTH = 80; var ANSI_FRAME_HEIGHT = 22; var ANSI_FRAME_PROVIDER_LENGTH = 55; var ANSI_FRAME_PAGE_LENGTH = 13; var ANSI_FRAME_COST_LENGTH = 10; /** * 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); /* File Extension used for frames */ this.settings.ext = 'tex'; /* Length of a frame */ this.settings.FRAME_LENGTH = 22; /* Width of a frame */ this.settings.FRAME_WIDTH = 80; /* Size of page owner (length) */ this.settings.FRAME_HEADER = 56; /* Size of page number (length with a-z) */ this.settings.FRAME_PAGENUM = 12; /* Size of cost (length without unit) */ this.settings.FRAME_COST = 9; 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'); } /* // Render the frame to the user this.render=function(context,withoutHeader) { log(LOG_DEBUG,'- ANSI FRAME'); owner = base64_decode(this.owner); const frame = new Frame(1,1,this.settings.FRAME_WIDTH,this.settings.FRAME_LENGTH+2,LIGHTGRAY); frame.open(); // Dont show the page number on system login page if ((! withoutHeader) && (user.number || (this.type !== FRAME_TYPE_LOGIN && NO_HISTORY_FRAMES.indexOf(this.page) === -1))) { log(LOG_DEBUG,' - Owner: ['+this.pageowner+']'); cost = (this.isAccessible ? this.cost+FRAME_COSTUNIT : ' -'); header = '\1n'+this.pageownerlogo+' '.repeat(this.settings.FRAME_HEADER-console.strlen(this.pageownerlogo))+'\1n '+ (this.isAccessible ? '\1W' : '\1R')+'\1H'+this.page+' '.repeat(this.settings.FRAME_PAGENUM-this.page.length)+' '+ '\1G\1H'+' '.repeat(this.settings.FRAME_COST-cost.toString().length+1)+cost+'\1n'+ (console.screen_columns > 80 ? '\n\r' : ''); frame.putmsg(header); } contentgraphic = new Graphic(this.settings.FRAME_WIDTH); contentgraphic.auto_extend = true; contentgraphic.atcodes = false; contentgraphic.ANSI = this.parse(base64_decode(this.content),context); var contentframe = new Frame(1,2,this.settings.FRAME_WIDTH,this.settings.FRAME_LENGTH,LIGHTGRAY,frame); contentframe.open(); contentframe.lf_strict = false; contentframe.atcodes = false; contentframe.word_wrap = false contentframe.putmsg(contentgraphic.MSG) contentframe.scrollTo(0,0); frame.cycle(); return contentframe; }; */ 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(); }; /* 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'); } */ } SessionAnsitex.prototype = Session.prototype; SessionAnsitex.prototype.constructor = SessionAnsitex;