'use strict'; require('ansitex/load/defs.js','ANSITEX_HOME'); // Our message bases that are enabled to render Videotex Messages /* * If a message area doesnt have: * + a zone_id, then the group isnt Videotex enabled * + a area_id, then the area isnt Videotex enabled */ function MsgAreas() { this.areas = []; // Message areas var zone_id; var zone_name; var ma; var cfg = []; // Load the message areas for (var g in msg_area.grp_list) { for (var a in msg_area.grp_list[g].sub_list) { //writeln('group:'+g+' name:'+msg_area.grp_list[g].name+' FIDO:'+JSON.stringify(msg_area.grp_list[g].sub_list[a].fidonet_addr)+' INDEX:'+JSON.stringify(msg_area.grp_list[g].sub_list[a].ptridx)); ma = new MsgArea(); ma.group_id = g; ma.sub_id = a; // Work out the zone by the FTN address of the area if (msg_area.grp_list[g].name.indexOf(':') === -1) { // If the sub is enabled for FTN, and the zone < 9999, then we'll us that if zone_id is undefined if ((msg_area.grp_list[g].sub_list[a].settings & SUB_FIDO) && msg_area.grp_list[g].sub_list[a].fidonet_addr) { var x = msg_area.grp_list[g].sub_list[a].fidonet_addr.split(/([0-9]+):/)[1]; if ((x > 0) && (x < 9999)) { zone_id = x.padStart(4,'0'); zone_name = msg_area.grp_list[g].name; } else { zone_id = undefined; zone_name = undefined; } } else { zone_id = undefined; zone_name = undefined; } // Work out the zone by the name, where name is zone_id:name } else { zone_id = msg_area.grp_list[g].name.split(':')[0]; zone_name = msg_area.grp_list[g].name.split(':')[1]; } if (zone_id) { if (cfg[zone_id] === undefined) { log(LOG_DEBUG,'Opening internal:'+FRAMES_MSG_BASE+' to work out subs for zone: '+zone_id); var internal = new MsgArea(); internal.code = FRAMES_MSG_BASE; var conf = internal.get('1'+zone_id+'99999a'); // @todo Quick hack for scripts if (SESSION_EXT === undefined) var SESSION_EXT = 'tex'; if (conf !== undefined) cfg[zone_id] = conf.content[SESSION_EXT].content.map(function(item) { return item.toLowerCase(); }); else cfg[zone_id] = []; } ma.zone_id = zone_id; ma.zone_name = zone_name; ma.code = msg_area.grp_list[g].sub_list[a].code; // Our area_id can be embedded in the name of the area if (msg_area.grp_list[g].sub_list[a].name.indexOf(':') !== -1) { var sublist = msg_area.grp_list[g].sub_list[a].name.split(':'); ma.area_id = sublist[0]; /* writeln(' code:'+ma.code); writeln(' fullname:'+ma.full_name); writeln(' pageprefix:'+ma.page_prefix) writeln(); */ log(LOG_DEBUG,'Hard set index to ['+ma.area_id+'] for: '+ma.code); // Otherwise we get our area_id from the configuration - page 999 of the zone } else { var index = (cfg[zone_id].indexOf(msg_area.grp_list[g].sub_list[a].code)+1).toString().padStart(2,'0'); // Make sure that index isnt already defined if (this.areas.filter(function(item) { return (item.zone_id === zone_id) && (item.area_id === index); }).length) { log(LOG_ERROR,'! ERROR: Prefix ['+index+'] already defined in ['+zone_id+'], ignoring ['+ma.code+']'); } else if ((cfg[zone_id] !== undefined) && cfg[zone_id].length) { if (index === '00') ma.area_id = undefined; else ma.area_id = index; } } this.areas.push(ma); } } } /* List areas that we do and dont manage */ Object.defineProperty(this,'list',{ get: function() { var areas = this.managed; writeln('Areas that we ARE managing mail:'+areas.length); for (var x in areas.sort(function(a,b) { return a.page_prefix > b.page_prefix; })) { writeln(x+':'+areas[x].code+' prefix:'+areas[x].page_prefix); } areas = this.unmanaged; writeln('Areas that we are NOT managing mail:'+areas.length); for (var x in areas) writeln(x+':'+areas[x].code); } }); /* List of message areas managed */ Object.defineProperty(this,'managed',{ get: function() { return this.areas.filter(function(item) { return item.managed; }); } }); /* List of message areas unmanaged */ Object.defineProperty(this,'unmanaged',{ get: function() { return this.areas.filter(function(item) { return ! item.managed; }) } }); /* Fetch a specific message area */ MsgAreas.prototype.getArea = function(area) { if (area === undefined) return undefined; return (((area.length === 6) && (NUMERIC_REGEX.test(area))) ? this.areas.filter(function(x) { return x.page_prefix === area; }) : this.areas.filter(function(x) { return x.code === area; })).pop(); } // @todo review MsgAreas.prototype.getMessage = function(page) { var area = this.getArea(page); log(LOG_DEBUG,' - msg:'+JSON.stringify(page.substr(7,4))); return area ? area.getMessage(page.substr(7,4)) : undefined; } // @todo review MsgAreas.prototype.getUserStats = function(page) { var area = this.getArea(page); return area ? msg_area.grp_list[area.msgbase.cfg.grp_number].sub_list[msg_area.sub[area.msgbase.cfg.code].index] : undefined; } } function MsgArea() { this.zone_id = undefined; // Zone this message area belongs to, eg: 0516 this.zone_name = undefined; // Zone Name, eg: VIDEOTEX this.area_id = undefined; // Sub Area ID for this message area, eg: 01 this.area_name = undefined; // Sub Area Name for this message area, eg: CHAT this.group_id = undefined; // SBBS Message Group ID this.__properties__ = { code: undefined, // SBBS Message Sub Internal Code }; // MSG Base Code Object.defineProperty(this,'code',{ get: function() { return this.__properties__.code; }, set: function(code) { this.__properties__.code = code; } }); // Get all frames that are tagged Object.defineProperty(this,'frames',{ get: function() { var msgbase; var regex = false; if (this.code === FRAMES_MSG_BASE) { msgbase = this.msgbase; } else if (this.managed) { msgbase = new MsgBase(FRAMES_MSG_BASE); regex = this.page_prefix_regex; } else return undefined; var frames = []; try { if (msgbase.open()) { var headers = msgbase.get_all_msg_headers(false,false) || []; for (var i in headers) { if ((! (headers[i].attr&MSG_DELETE)) && (headers[i].from === 'SYSTEM') && ((! regex) || regex.test(headers[i].to))) frames.push(headers[i]); } msgbase.close(); } else { writeln('*** NOPE WONT OPEN msgbase:'+JSON.stringify(msgbase)); } } catch (e) { log(LOG_ERROR,this.code+' cannot be opened (frames):'+e.message); return undefined; } return frames.sort(function(a,b) { return (a.when_imported_time !== b.when_imported_time) ? a.when_imported_time > b.when_imported_time : a.number > b.number; }); } }); // Get Area's full name Object.defineProperty(this,'full_name',{ get: function() { return this.zone_name+':'+this.area_name; } }); // @deprecated use frames instead // Total tagged messages Object.defineProperty(this,'list_tagged',{ get: function() { if (this.tagged_list === undefined) { this.tagged_list = []; if (! this.headers) return this.tagged_list; for(var x in this.headers) { if (this.headers[x].tags && (this.headers[x].tags.length === PAGE_LENGTH)) { this.tagged_list.push(this.headers[x]); write(); // @todo This is needed for this to work? } } } return this.tagged_list; } }); // Retrieve the last tagged frame Object.defineProperty(this,'last_tagged_message',{ get: function() { var last_tag = this.frames.sort(function(a,b) { return (a.when_imported_time !== b.when_imported_time) ? a.when_imported_time > b.when_imported_time : a.number > b.number; }).pop(); if (last_tag === undefined) return undefined; var msgbase = new MsgBase(FRAMES_MSG_BASE); try { if (msgbase.open()) { var body = JSON.parse(LZString.decompressFromBase64(msgbase.get_msg_body(last_tag.number))); msgbase.close(); } else { writeln('*** NOPE WONT OPEN msgbase:'+JSON.stringify(msgbase)); } } catch (e) { log(LOG_ERROR,this.code+' cannot be opened (last_tagged_message):'+e.message); return undefined; } msgbase = this.msgbase; try { if (msgbase.open()) { var index = msgbase.get_msg_header(body.id); msgbase.close(); } else { writeln('*** NOPE WONT OPEN msgbase:'+JSON.stringify(msgbase)); } } catch (e) { log(LOG_ERROR,this.code+' cannot be opened (last_tagged_message_base):'+e.message); return undefined; } return index; } }) // Is this area defined for ansitex messages Object.defineProperty(this,'managed',{ get: function() { return (this.zone_id !== undefined) && (this.area_id !== undefined); } }); Object.defineProperty(this,'max_page',{ get: function() { return parseInt('9'.repeat(PAGE_LENGTH),10); } }); Object.defineProperty(this,'msgbase',{ get: function() { return new MsgBase(this.code); } }); // Get Next page number Object.defineProperty(this,'page_next',{ get: function() { if (! this.managed) return undefined; var next_tag = this.frames.pop(); if (next_tag !== undefined) { next_tag = next_tag.tags; if (next_tag.indexOf('1'+this.page_prefix) === 0) next_tag = next_tag.slice(this.page_prefix.length+1); next_tag = parseInt(next_tag,10)+1; if (next_tag > this.max_page) next_tag = 0; } else { next_tag = 0; } return (''+next_tag).padStart(4,'0'); }, /* set: function(page) { var f = new File(file_cfgname(system.mods_dir,'ansitex/ctrl/videotex.ini')); if (! f.open('r+')) { writeln('Unable to open ini file'); exit(2); } f.iniSetValue('prefix:'+this.page_prefix,PAGE_LAST_KEY,(''+page).padStart(4,'0')); f.close(); } */ }); // Our page prefix for this msg area Object.defineProperty(this,'page_prefix',{ get: function() { return ''+this.zone_id+this.area_id; }, }); // Return a REG to test if this frame is part of this msgbase Object.defineProperty(this,'page_prefix_regex',{ get: function() { return new RegExp( '^1'+this.page_prefix); }, }); // Total Messages in a msgbase Object.defineProperty(this,'total_msgs',{ get: function() { if (! this.managed) return undefined; var msgbase = this.msgbase; try { if (msgbase.open()) { var index = msgbase.get_index() || []; msgbase.close(); } else { writeln('*** NOPE WONT OPEN msgbase:'+JSON.stringify(msgbase)); } } catch (e) { log(LOG_ERROR,this.code+' cannot be opened (total_msgs):'+e.message); return undefined; } return index.length; } }); // List untagged messages Object.defineProperty(this,'untagged', { get: function() { if (! this.managed) return undefined; var msgbase = this.msgbase; var last_tag = this.last_tagged_message; var msgs = []; try { if (msgbase.open()) { var headers = msgbase.get_all_msg_headers(false,false); for (var x in headers) { if (last_tag && ((headers[x].when_imported_time < last_tag.when_imported_time) || ((headers[x].when_imported_time === last_tag.when_imported_time) && (headers[x].number <= last_tag.number)))) continue; msgs.push(headers[x]); } msgbase.close(); } else { writeln('*** NOPE WONT OPEN msgbase:'+JSON.stringify(msgbase)); } } catch (e) { log(LOG_ERROR,this.code+' cannot be opened (untagged):'+e.message); return undefined; } return msgs.sort(function(a,b) { return (a.when_imported_time !== b.when_imported_time) ? a.when_imported_time > b.when_imported_time : a.number > b.number; }); } }); // Get message header for page MsgArea.prototype.get = function(page) { if (this.code !== FRAMES_MSG_BASE) return undefined; var msgbase = this.msgbase; var msgs = []; try { if (msgbase.open()) { log(LOG_DEBUG,'Looking for ['+page+'] in ['+this.code+']'); var index = msgbase .get_all_msg_headers(); for (var x in index) if ((index[x].from === 'SYSTEM') && (index[x].to === page)) msgs.push(index[x]); if (msgs.length) { var msg = msgs.sort(function(a,b) { return (a.when_imported_time !== b.when_imported_time) ? a.when_imported_time > b.when_imported_time : a.number > b.number; }).pop(); return this.getContent(msg.number); } else { return undefined; } } } catch (e) { log(LOG_ERROR,this.code+' cannot be opened (frames):'+e.message); return undefined; } return content; } // Get frame content MsgArea.prototype.getContent = function(id) { // @todo If this is for a echomail/netmail content, then we need to switch message bases if (this.code !== FRAMES_MSG_BASE) return undefined; var msgbase = this.msgbase; try { if (msgbase.open()) { var raw = msgbase.get_msg_body(false,id,false,false,true,true); //log(LOG_DEBUG,'RAW:'+JSON.stringify(raw)); // Our messages are terminated with FRAMES_EOF_MARKER var regex = new RegExp('^(.*)'+FRAMES_EOF_MARKER); //log(LOG_DEBUG,'MARKER:'+regex.test(raw)); if (! regex.test(raw)) { msgbase.close(); return undefined; } var regex = new RegExp(FRAMES_EOF_MARKER+'[.\\s\\S]*$'); var content = JSON.parse(LZString.decompressFromBase64(raw.replace(regex,''))); //log(LOG_DEBUG,'CONTENT:'+JSON.stringify(content)); msgbase.close(); } else { writeln('*** NOPE WONT OPEN msgbase:'+JSON.stringify(msgbase)); } } catch (e) { log(LOG_ERROR,this.code+' cannot be opened (frames):'+e.message); return undefined; } return content; }; // @todo review /** * Unread messages [1..] * Array key 0 returns the last read message * * @returns {*[]} */ MsgArea.prototype.newMsgs = function() { var msgs = []; var stats = this.getUserStats(); //log(LOG_DEBUG,'Users last_read pointer: '+JSON.stringify(stats.last_read)); for(var x in this.list_tagged) { // Advance past our last scan_ptr if (this.list_tagged[x].number <= stats.last_read) continue; msgs.push(this.list_tagged[x]); write(); // @todo This is needed for this to work? } return msgs; } // @todo review /** * New Messages for the logged in user */ MsgArea.prototype.newMsgsToMe = function() { var msgs = []; var stats = this.getUserStats(); var last = null; //log(LOG_DEBUG,'Users scan_ptr pointer: '+JSON.stringify(stats.scan_ptr)); for(var x in this.list_tagged) { // Advance past our last scan_ptr if (this.list_tagged[x].number <= stats.scan_ptr) { if ((this.list_tagged[x].to === user.name) || (this.list_tagged[x].to === user.alias)) last = x; continue; } // Add our previous to me message if (msgs.length === 0) msgs.push(last !== null ? this.list_tagged[last] : []); if ((this.list_tagged[x].to === user.name) || (this.list_tagged[x].to === user.alias)) msgs.push(this.list_tagged[x]); write(); // @todo This is needed for this to work? } return msgs; } // @todo review /** * Get a specific message with a tag */ MsgArea.prototype.getMessage = function(page) { var msg = undefined; for(var x in this.list_tagged) { if (this.list_tagged[x].tags === page) { msg = this.list_tagged[x]; break; } write(); // @todo This is needed for this to work? } if (! msg) return undefined; if (! this.msgbase.open()) { writeln(code+' cannot be opened (getMessage):'+this.msgbase.error); return undefined; } msg.grp_number = this.msgbase.cfg.grp_number; var cfg = this.msgbase.cfg; msg.subnum = msg_area.grp_list[cfg.grp_number].sub_list.filter(function(x) { return x.number === cfg.number; }).pop().index; msg.content = this.msgbase.get_msg_body(false,msg.number,false,false,true,true); this.msgbase.close(); return msg; } // @todo review /** * Get a message page by pointer * * @param number * @returns {string} */ MsgArea.prototype.getMessagePage = function(number) { log(LOG_DEBUG,'Get Message Page with number ['+number+']'); var r; for (var x in this.list_tagged) { if (this.list_tagged[x].number === number) { r = this.list_tagged[x]; break; } } if (! r || r.tags === undefined) return null; return '1'+this.zone_id+this.area_id+r.tags; } // @todo review MsgArea.prototype.getUserStats = function() { return this.msgbase.cfg ? msg_area.grp_list[this.msgbase.cfg.grp_number].sub_list[msg_area.sub[this.msgbase.cfg.code].index] : []; } // @todo review MsgArea.prototype.MessageNext = function(page) { var x = null; if (! page) return undefined; var msgid = page.substr(7,4); for(var x in this.list_tagged) { if (this.list_tagged[x].tags === msgid) { break; } write(); // @todo This is needed for this to work? } //log(LOG_DEBUG,'- Next Message is:'+JSON.stringify(this.list_tagged[(parseInt(x)+1)])+', msgid:'+msgid+', page:'+page+', x:'+x); /* = Our next message is either + x+1 if x < this.list_tagged.length + x=0 if x == this.list_tagged.length (-1) + null if this.list_tagged.length == null; (thus no messages) */ return x === null ? null : this.list_tagged[(parseInt(x) === this.list_tagged.length-1) ? 0 : (parseInt(x)+1)]; } // @todo review MsgArea.prototype.MessagePrev = function(page) { var prev = null; var x = null; if (! page) return undefined; var msgid = page.substr(7,4); for(var x in this.list_tagged) { if (this.list_tagged[x].tags === msgid) { break; } else { prev = x; } write(); // @todo This is needed for this to work? } /* = Our previous message is either + prev if a tag was found, unless + prev is null, in which case it is this.list_tagged.length -1 + null if x is still null (thus no messages) */ // If prev is still null, then our last message must be the last one, unless x is null then there are no messages return x === null ? null : this.list_tagged[(prev === null) ? this.list_tagged.length-1 : parseInt(prev)]; } /** * Tag messages with a frame number * @note: May need to run jsexec with -m 32MB to overcome memory issues * * @returns {boolean} */ MsgArea.prototype.tag_msgs = function() { var msgs = this.untagged; var msgbase = new MsgBase(FRAMES_MSG_BASE); var page_next = this.page_next; writeln('* We have '+msgs.length+' messages to tag, starting at '+page_next); // See if we need to tag something if (! msgs.length) return; if (! msgbase.open()) { writeln(code+' cannot be opened (tag_msgs):'+msgbase.error); return false; } writeln('Starting at:'+page_next+' (max:'+this.max_page+')'); for (var x in msgs) { //writeln('- '+msgs[x].when_imported_time+', #:'+msgs[x].number); var frame = '1'+this.page_prefix+page_next; var hdr = { to: frame+'a', from: 'SYSTEM', tags: frame, date: msgs[x].date, subject: this.code+':'+msgs[x].id, }; var page = { id: msgs[x].number, area_id: this.area_id, group_id: this.group_id, date: msgs[x].date, msgid: msgs[x].id, imported: msgs[x].when_imported_time, }; var body = LZString.compressToBase64(JSON.stringify(page)); writeln('Tagging:'+page.id+' Tag:'+page_next+' MSGID:'+page.msgid); if (! msgbase.save_msg(hdr,body)) { writeln('! ERROR: Failed to tag '+msgs[x].number); exit(1); } page_next++; if (page_next > this.max_page) page_next = 0; page_next = (''+page_next).padStart(4,'0'); } msgbase.close(); return true; } // @todo review MsgArea.prototype.page = function(msgid) { return '1'+this.page_prefix+msgid; } }