Multiple updates to tools and msgbase.js/page.js

This commit is contained in:
Deon George 2024-12-03 15:54:17 +11:00
parent 4cbd37f880
commit 991a31e957
7 changed files with 730 additions and 420 deletions

View File

@ -1,3 +1,8 @@
require('smbdefs.js','MSG_DELETE');
require('sbbsdefs.js','SUB_FIDO');
load('string.js');
load('lz-string.js');
/** /**
* ANSItex definitions * ANSItex definitions
*/ */
@ -11,6 +16,9 @@ const FRAMES_MSG_BASE = 'vtx_data';
/* Load frames from files */ /* Load frames from files */
const FRAMES_MSG_FILES = true; const FRAMES_MSG_FILES = true;
const PAGE_LENGTH = 4; // The size of our page tag as stored in the msgbase for echomail/netmail
const NUMERIC_REGEX = /^[0-9]*$/;
/* Unit of cost */ /* Unit of cost */
const FRAME_COSTUNIT = 'c'; const FRAME_COSTUNIT = 'c';
@ -51,6 +59,7 @@ const MODE_RFSENT = 5;
const MODE_RFERROR = 6; const MODE_RFERROR = 6;
/** FRAME TYPES **/ /** FRAME TYPES **/
// @todo Change these to bits
/* Information Frame, requires no response after viewed */ /* Information Frame, requires no response after viewed */
const FRAME_TYPE_INFO = 'i'; const FRAME_TYPE_INFO = 'i';
@ -120,14 +129,16 @@ const FRAME_SYSTEM_ERROR = {frame: '998',index: 'a'};
const FRAME_SAVE_ATTRS = [ const FRAME_SAVE_ATTRS = [
'content', // raw source content of a frame (as stored in vtx/tex files) 'content', // raw source content of a frame (as stored in vtx/tex files)
'cost', // integer, frame cost 'cost', // integer, frame cost
'dynamic_fields', // array of fields 'date', // date, date frame created
'dynamic_fields', // array of fields // @todo deprecate and work these out when loading and parsing a frame
'frame', // Page ID, 'frame', // Page ID,
'index', // Page index, 'index', // Page index,
'input_fields', // array of fields 'input_fields', // array of fields // @todo deprecate and work these out when loading and parsing a frame
'isAccessible', // boolean 'isAccessible', // boolean // @todo deprecate and merge into 'state'
'isPublic', // boolean 'isPublic', // boolean // @todo deprecate and merge into 'state'
'key', // array, representing our key actions 'key', // array, representing our key actions (0-9)
'type', // frame type 'state', // integer, representing type, accessible and public status
'type', // frame type // @todo deprecate and merge into 'state'
'version', // frame version (1) 'version', // frame version (1)
'window' // processed frame data 'window' // processed frame data
]; ];
@ -138,7 +149,7 @@ const MAIL_TEMPLATE_FRAME = {frame: '199',index: 'a'};
/* The page that has our echomail area summary template */ /* The page that has our echomail area summary template */
const MAIL_TEMPLATE_AREA_SUMMARY = {frame: '198',index: 'a'}; const MAIL_TEMPLATE_AREA_SUMMARY = {frame: '198',index: 'a'};
// The maximum size of embedded dynamic fields in frames // The maximum size (length) of embedded dynamic fields in frames
const DYNAMIC_FIELD_SIZE_MAX = 50; const DYNAMIC_FIELD_SIZE_MAX = 50;
/** ESCAPE CODES **/ /** ESCAPE CODES **/

View File

@ -1,98 +1,181 @@
const PAGE_LENGTH = 4; // The size of our page tag as stored in the msgbase for echomail/netmail 'use strict';
const PAGE_LAST_KEY = 'last_page'; // Last page which has the latest message require('ansitex/load/defs.js','ANSITEX_HOME');
const MAX_PAGE_NUM = 9999; // Maximum page number. @todo Can this be changed to '9'.repeat(PAGE_LENGTH)?
// Our message bases // 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() { function MsgAreas() {
'use strict'; this.areas = []; // Message areas
this.areas = [];
this.areas_excluded = [];
var zone_id; var zone_id;
var zone_name; var zone_name;
var ma; var ma;
for(var g in msg_area.grp_list) { // Load the message areas
if (msg_area.grp_list[g].name.indexOf(':') !== -1) { for (var g in msg_area.grp_list) {
zone_id = msg_area.grp_list[g].name.split(':')[0]; for (var a in msg_area.grp_list[g].sub_list) {
zone_name = msg_area.grp_list[g].name.split(':')[1]; //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;
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 = msg_area.grp_list[g].name.split(':')[0];
zone_name = msg_area.grp_list[g].name.split(':')[1];
}
if (zone_id) {
ma.zone_id = zone_id;
ma.zone_name = zone_name;
for (var a in msg_area.grp_list[g].sub_list) {
if (msg_area.grp_list[g].sub_list[a].name.indexOf(':') !== -1) { if (msg_area.grp_list[g].sub_list[a].name.indexOf(':') !== -1) {
ma = new MsgArea(); var sublist = msg_area.grp_list[g].sub_list[a].name.split(':');
ma.zone_id = zone_id; ma.area_id = sublist[0];
ma.zone_name = zone_name; ma.area_name = sublist[1];
ma.area_id = msg_area.grp_list[g].sub_list[a].name.split(':')[0];
ma.area_name = msg_area.grp_list[g].sub_list[a].name.split(':')[1];
ma.code = msg_area.grp_list[g].sub_list[a].code;
this.areas.push(ma); /*
} else { writeln(' code:'+ma.code);
this.areas_excluded.push(zone_name+':'+msg_area.grp_list[g].sub_list[a].name); writeln(' fullname:'+ma.full_name);
writeln(' pageprefix:'+ma.page_prefix)
writeln();
*/
} }
} }
} else { this.areas.push(ma);
zone_name = msg_area.grp_list[g].name;
for (var a in msg_area.grp_list[g].sub_list) {
this.areas_excluded.push(zone_name+':'+msg_area.grp_list[g].sub_list[a].name);
}
} }
} }
/* List areas that we do and dont manage */
Object.defineProperty(this,'list',{ Object.defineProperty(this,'list',{
get: function() { get: function() {
writeln('Areas that we are NOT managing mail:'+this.areas_excluded.length); var areas = this.managed;
writeln('Areas that we ARE managing mail:'+this.areas.length); 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; })) {
for(var x in this.areas_excluded) { writeln(x+':'+areas[x].code+' prefix:'+areas[x].page_prefix);
writeln(x+':'+((this.areas_excluded[x].area_name === undefined)
? this.areas_excluded[x]
: JSON.stringify(this.areas_excluded[x])));
} }
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 this.areas.filter(function(x) {
// If the area is a 6 digits, then its a page prefix, otherwise its an area code name
if ((area.length === 6) && (NUMERIC_REGEX.test(area)))
return x.page_prefix === area;
else
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() { function MsgArea() {
this.zone_id = undefined; this.zone_id = undefined; // Zone this message area belongs to, eg: 0516
this.zone_name = undefined; this.zone_name = undefined; // Zone Name, eg: VIDEOTEX
this.area_id = undefined; this.area_id = undefined; // Sub Area ID for this message area, eg: 01
this.area_name = undefined; this.area_name = undefined; // Sub Area Name for this message area, eg: CHAT
this.msgbase = undefined; this.group_id = undefined; // SBBS Message Group ID
this.headers = undefined; this.sub_id = undefined; // SBBS Message Sub ID
this.tagged_list = undefined;
this.untagged_list = undefined;
this.grp_number = undefined;
this.subnum = undefined;
/** // MSG Base Code
* Build a MsgArea once we are given the code
*/
Object.defineProperty(this,'code',{ Object.defineProperty(this,'code',{
set: function(code) { get: function() {
this.msgbase = new MsgBase(code); //writeln('group_id:'+this.group_id+' sub_id:'+this.sub_id);
return msg_area.grp_list[this.group_id].sub_list[this.sub_id].code;
}
});
// Get all frames that are tagged
Object.defineProperty(this,'frames',{
get: function() {
if (! this.managed)
return undefined;
var msgbase = new MsgAreas().getArea(FRAMES_MSG_BASE).msgbase;
var frames = [];
var regex = this.page_prefix_regex;
try { try {
if (this.msgbase.open()) { if (msgbase.open()) {
headers = this.msgbase.get_all_msg_headers(false,false) || []; var headers = msgbase.get_all_msg_headers(false,false) || [];
// Just take the last MAX_MESSAGES for (var i in headers) {
this.headers = Object.keys(headers).slice(-(MAX_PAGE_NUM+1)).map(function(key) { return headers[key]; }); if ((! (headers[i].attr&MSG_DELETE)) && headers[i].tags && regex.test(headers[i].tags))
headers = undefined; frames.push(headers[i]);
}
this.msgbase.close(); msgbase.close();
} else { } else {
log(LOG_ERROR,code+' cannot be opened:'+this.msgbase.error); writeln('*** NOPE WONT OPEN msgbase:'+JSON.stringify(msgbase));
this.headers = [];
} }
} catch (e) { } catch (e) {
log(LOG_ERROR,code+' cannot be opened:'+e.message); log(LOG_ERROR,this.code+' cannot be opened (frames):'+e.message);
this.headers = [];
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;
});
} }
}); });
@ -103,6 +186,7 @@ function MsgArea() {
} }
}); });
// @deprecated use frames instead
// Total tagged messages // Total tagged messages
Object.defineProperty(this,'list_tagged',{ Object.defineProperty(this,'list_tagged',{
get: function() { get: function() {
@ -124,42 +208,101 @@ function MsgArea() {
} }
}); });
// List untagged messages Object.defineProperty(this,'last_tagged',{
Object.defineProperty(this,'list_untagged',{
get: function() { get: function() {
if (this.untagged_list === undefined) { var last_tag = this.frames.sort(function(a,b) {
this.untagged_list = []; if (a.when_imported_time === b.when_imported_time)
return a.number > b.number
else
return (a.when_imported_time > b.when_imported_time);
}).pop();
if (! this.headers) var msgbase = new MsgAreas().getArea(FRAMES_MSG_BASE).msgbase;
return this.untagged_list;
for(var x in this.headers) { try {
if ((! this.headers[x].tags) || (this.headers[x].tags.length !== PAGE_LENGTH)) { if (msgbase.open()) {
this.untagged_list.push(this.headers[x]); var body = JSON.parse(LZString.decompressFromBase64(msgbase.get_msg_body(last_tag.number)));
write(); // @todo This is needed for this to work?
} msgbase.close();
} else {
writeln('*** NOPE WONT OPEN msgbase:'+JSON.stringify(msgbase));
} }
} catch (e) {
log(LOG_ERROR,this.code+' cannot be opened (last_tagged):'+e.message);
return undefined;
} }
return this.untagged_list; 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):'+e.message);
return undefined;
}
return index;
}
})
// Is this area defined for videotex 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 // Get Next page number
Object.defineProperty(this,'page_next',{ Object.defineProperty(this,'page_next',{
get: function() { get: function() {
var f = new File(file_cfgname(system.mods_dir,'ansitex/ctrl/videotex.ini')); if (! this.managed)
if (! f.open('r')) { return undefined;
writeln('Unable to open ini file');
exit(2); 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;
} }
var page = f.iniGetValue('prefix:'+this.page_prefix,PAGE_LAST_KEY) return (''+next_tag).padStart(4,'0');
f.close();
return page ? page : '0'.repeat(PAGE_LENGTH);
}, },
/*
set: function(page) { set: function(page) {
var f = new File(file_cfgname(system.mods_dir,'ansitex/ctrl/videotex.ini')); var f = new File(file_cfgname(system.mods_dir,'ansitex/ctrl/videotex.ini'));
if (! f.open('r+')) { if (! f.open('r+')) {
@ -170,6 +313,7 @@ function MsgArea() {
f.iniSetValue('prefix:'+this.page_prefix,PAGE_LAST_KEY,(''+page).padStart(4,'0')); f.iniSetValue('prefix:'+this.page_prefix,PAGE_LAST_KEY,(''+page).padStart(4,'0'));
f.close(); f.close();
} }
*/
}); });
// Our page prefix for this msg area // Our page prefix for this msg area
@ -178,251 +322,335 @@ function MsgArea() {
return ''+this.zone_id+this.area_id; return ''+this.zone_id+this.area_id;
}, },
}); });
}
/** // Return a REG to test if this frame is part of this msgbase
* Unread messages [1..] Object.defineProperty(this,'page_prefix_regex',{
* Array key 0 returns the last read message get: function() {
* return new RegExp( '^1'+this.page_prefix);
* @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) { // Total Messages in a msgbase
// Advance past our last scan_ptr Object.defineProperty(this,'total_msgs',{
if (this.list_tagged[x].number <= stats.last_read) get: function() {
continue; if (! this.managed)
return undefined;
msgs.push(this.list_tagged[x]); var msgbase = this.msgbase;
write(); // @todo This is needed for this to work? try {
} if (msgbase.open()) {
var index = msgbase.get_index() || [];
return msgs; msgbase.close();
}
/** } else {
* New Messages for the logged in user writeln('*** NOPE WONT OPEN msgbase:'+JSON.stringify(msgbase));
*/ }
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) { } catch (e) {
// Advance past our last scan_ptr log(LOG_ERROR,this.code+' cannot be opened (total_msgs):'+e.message);
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; return undefined;
}
return index.length;
} }
});
// Add our previous to me message // List untagged messages
if (msgs.length === 0) Object.defineProperty(this,'untagged', {
msgs.push(last !== null ? this.list_tagged[last] : []); get: function() {
if (! this.managed)
return undefined;
var msgbase = this.msgbase;
var last_tag = this.last_tagged;
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;
});
}
});
// @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;
if ((this.list_tagged[x].to === user.name) || (this.list_tagged[x].to === user.alias))
msgs.push(this.list_tagged[x]); msgs.push(this.list_tagged[x]);
write(); // @todo This is needed for this to work? write(); // @todo This is needed for this to work?
}
return msgs;
}
/**
* 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:'+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;
}
/**
* 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;
}
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] : [];
}
MsgArea.prototype.MessageNext = function(page) {
var x = null;
if (! page)
return undefined;
var msgid = page.substr(7,4);
for(x in this.list_tagged) {
if (this.list_tagged[x].tags === msgid) {
break;
} }
write(); // @todo This is needed for this to work? return msgs;
} }
//log(LOG_DEBUG,'- Next Message is:'+JSON.stringify(this.list_tagged[(parseInt(x)+1)])+', msgid:'+msgid+', page:'+page+', x:'+x); // @todo review
/**
/* * New Messages for the logged in user
= 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)
*/ */
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));
return x === null ? null : this.list_tagged[(parseInt(x) === this.list_tagged.length-1) ? 0 : (parseInt(x)+1)]; 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;
MsgArea.prototype.MessagePrev = function(page) { continue;
var prev = null; }
var x = null;
if (! page) // Add our previous to me message
return undefined; if (msgs.length === 0)
msgs.push(last !== null ? this.list_tagged[last] : []);
var msgid = page.substr(7,4); if ((this.list_tagged[x].to === user.name) || (this.list_tagged[x].to === user.alias))
msgs.push(this.list_tagged[x]);
for(x in this.list_tagged) { write(); // @todo This is needed for this to work?
if (this.list_tagged[x].tags === msgid) {
break;
} else {
prev = x;
} }
write(); // @todo This is needed for this to work? return msgs;
} }
/* // @todo review
= Our previous message is either /**
+ prev if a tag was found, unless * Get a specific message with a tag
+ prev is null, in which case it is this.list_tagged.length -1
+ null if x is still null (thus no messages)
*/ */
MsgArea.prototype.getMessage = function(page) {
var msg = undefined;
// If prev is still null, then our last message must be the last one, unless x is null then there are no messages for(var x in this.list_tagged) {
return x === null ? null : this.list_tagged[(prev === null) ? this.list_tagged.length-1 : parseInt(prev)]; if (this.list_tagged[x].tags === page) {
} msg = this.list_tagged[x];
break;
}
write(); // @todo This is needed for this to work?
}
/** if (! msg)
* Tag messages with a frame number return undefined;
* @note: May need to run jsexec with -m 32MB to overcome memory issues
*
* @returns {boolean}
*/
MsgArea.prototype.tag_msgs = function() {
var msgs = this.list_untagged;
writeln("We have "+msgs.length+" messages to tag."); if (! this.msgbase.open()) {
writeln(code+' cannot be opened (getMessage):'+this.msgbase.error);
return undefined;
}
// See if we need to tag something msg.grp_number = this.msgbase.cfg.grp_number;
if (! msgs.length) var cfg = this.msgbase.cfg;
return; 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();
if (! this.msgbase.open()) { return msg;
writeln(code+' cannot be opened:'+this.msgbase.error);
return false;
} }
var page_next = this.page_next; // @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+']');
for(var x in msgs) { var r;
msgs[x].tags = (''+(page_next)).padStart(4,'0');
if(! this.msgbase.put_msg_header(msgs[x].number,msgs[x])) { for (var x in this.list_tagged) {
writeln('ERROR:'+this.msgbase.error); 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 MsgAreas().getArea(FRAMES_MSG_BASE).msgbase;
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);
}
} else {
page_next++; page_next++;
if (page_next > MAX_PAGE_NUM) if (page_next > this.max_page)
page_next = 0; page_next = 0;
page_next = (''+page_next).padStart(4,'0');
} }
msgbase.close();
return true;
} }
this.msgbase.close(); // @todo review
this.page_next = page_next; MsgArea.prototype.page = function(msgid) {
return '1'+this.page_prefix+msgid;
return true; }
}
MsgArea.prototype.page = function(msgid) {
return '1'+this.page_prefix+msgid;
}
MsgAreas.prototype.getArea = function(area) {
log(LOG_DEBUG,'- AREA:'+JSON.stringify(area));
if (area === undefined)
return undefined;
var zone = (''+area).substr(1,4);
var echo = (''+area).substr(5,2);
log(LOG_DEBUG,' - zone:'+zone);
log(LOG_DEBUG,' - echo:'+echo);
return this.areas.filter(function(x) {
return x.zone_id === zone && x.area_id === echo;
})[0]
}
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;
}
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;
} }

View File

@ -1,3 +1,4 @@
'use strict';
/** /**
PAGE.js handles ANSItex and ViewData page frames PAGE.js handles ANSItex and ViewData page frames
@ -76,7 +77,7 @@
*/ */
load('ansitex/load/windows.js'); // Our supporting window class load('ansitex/load/windows.js'); // Our supporting window class
require('ansitex/load/msgbases.js','MAX_PAGE_NUM'); // To read/write to message bases require('ansitex/load/msgbases.js','PAGE_LENGTH'); // To read/write to message bases
require('sbbsdefs.js','SS_USERON'); // Need for our ANSI colors eg: BG_* require('sbbsdefs.js','SS_USERON'); // Need for our ANSI colors eg: BG_*
/** /**
@ -196,7 +197,7 @@ function Page(debug) {
} }
} }
// @todo change this to Object.defineProperty() - see session.js // @todo change this to Object.defineProperty() - see page.js
/** /**
* Determine if this frame is accessible to the current user * Determine if this frame is accessible to the current user
*/ */
@ -294,6 +295,7 @@ function Page(debug) {
this.__properties__.isPublic = bool; this.__properties__.isPublic = bool;
}); });
// Key Array
Page.prototype.__defineGetter__('key',function() { Page.prototype.__defineGetter__('key',function() {
return this.__properties__.key; return this.__properties__.key;
}); });
@ -301,6 +303,9 @@ function Page(debug) {
if (typeof array !== 'object') if (typeof array !== 'object')
throw new Error('key must be an array :'+typeof array); throw new Error('key must be an array :'+typeof array);
if (array.length !== 10)
throw new Error('key must contain 10 items :'+array);
return this.__properties__.key = array; return this.__properties__.key = array;
}); });
@ -430,7 +435,7 @@ function Page(debug) {
var content = fields[i].value.split(''); var content = fields[i].value.split('');
for (x=fields[i].x;x<fields[i].x+Math.abs(fields[i].length);x++) { for (var x=fields[i].x;x<fields[i].x+Math.abs(fields[i].length);x++) {
var index = x-fields[i].x; var index = x-fields[i].x;
if (content[index]) if (content[index])
@ -513,14 +518,14 @@ function Page(debug) {
last = new_screen; last = new_screen;
// Check all our dynamic fields have been placed // Check all our dynamic fields have been placed
df = this.dynamic_fields.filter(function(item) { return item.value === undefined; }); var df = this.dynamic_fields.filter(function(item) { return item.value === undefined; });
// If our dynamic fields havent been filled in // If our dynamic fields havent been filled in
if (df.length > 0) if (df.length > 0)
throw new Error('Dynamic fields ['+df.length+'] without values:'+(df.map(function(item) { return item.name; }).join('|'))); throw new Error('Dynamic fields ['+df.length+'] without values:'+(df.map(function(item) { return item.name; }).join('|')));
// Render the display // Render the display
for (y=1;y<=this.height;y++) { for (var y=1;y<=this.height;y++) {
var line = ''; var line = '';
if (new_line) if (new_line)
@ -529,7 +534,7 @@ function Page(debug) {
if (debug) if (debug)
writeln('============== ['+y+'] ==============='); writeln('============== ['+y+'] ===============');
for (x=1;x<=this.width;x++) { for (var x=1;x<=this.width;x++) {
if (debug) if (debug)
log(LOG_DEBUG,'* CELL : y:'+y+', x:'+x); log(LOG_DEBUG,'* CELL : y:'+y+', x:'+x);
// The current char value // The current char value
@ -729,7 +734,7 @@ function Page(debug) {
// Dump Header // Dump Header
write('--:'); write('--:');
for (x=0;x<this.width;x+=10) { for (var x=0;x<this.width;x+=10) {
write('_'.repeat(4)+'.'+'_'.repeat(4)+'|'); write('_'.repeat(4)+'.'+'_'.repeat(4)+'|');
} }
writeln(); writeln();
@ -749,7 +754,7 @@ function Page(debug) {
// Dump Header // Dump Header
write('--:'); write('--:');
for (x=0;x<this.width;x+=10) { for (var x=0;x<this.width;x+=10) {
write('_'.repeat(4)+'.'+'_'.repeat(4)+'|'); write('_'.repeat(4)+'.'+'_'.repeat(4)+'|');
} }
writeln(); writeln();
@ -972,7 +977,7 @@ function Page(debug) {
var msg; var msg;
// Find existing message with the page number // Find existing message with the page number
for(var x in headers) { for (var x in headers) {
if ((!(headers[x].attr&MSG_DELETE)) && (headers[x].to === page.toString()) && (headers[x].from === SESSION_EXT)) { if ((!(headers[x].attr&MSG_DELETE)) && (headers[x].to === page.toString()) && (headers[x].from === SESSION_EXT)) {
msg = headers[x]; msg = headers[x];
//break; @todo We'll take the last one that matches, if there are more than one. //break; @todo We'll take the last one that matches, if there are more than one.
@ -990,7 +995,7 @@ function Page(debug) {
var contents = mb.get_msg_body(false,msg.number,false,false,true,true).split("\r\n"); var contents = mb.get_msg_body(false,msg.number,false,false,true,true).split("\r\n");
var i; var i;
for (i=0; i<contents.length; i++) { for (var i=0; i<contents.length; i++) {
// Echomail tag line // Echomail tag line
if (contents[i] === '---' || contents[i].substring(0,4) === '--- ') if (contents[i] === '---' || contents[i].substring(0,4) === '--- ')
break; break;
@ -1076,112 +1081,127 @@ function Page(debug) {
// ANSItex files // ANSItex files
case 'tex': case 'tex':
case 'vtx': case 'vtx':
log(LOG_DEBUG,'|-- Processing FRAME file'); log(LOG_DEBUG,'|-- Processing FRAME file. V:'+contents.version);
try { switch (contents.version) {
for (var index in contents) { case 1:
if (FRAME_SAVE_ATTRS.indexOf(index) === -1) { try {
log(LOG_ERROR,'|-! Unknown index ['+index+'] in input.'); for (var index in contents) {
continue; if (FRAME_SAVE_ATTRS.indexOf(index) === -1) {
} log(LOG_ERROR,'|-! Unknown index ['+index+'] in input.');
continue;
log(LOG_DEBUG,'|-* Processing ['+index+'] with value ['+JSON.stringify(contents[index])+'].');
switch (index) {
case 'content':
//if (ext === 'tex')
// var page = rawtoattrs(base64_decode(contents[index]).replace("\x0a\x0d\x0a\x0d","\x0a\x0d"),this.width,this.__window__.body.y,this.__window__.body.x);
//else if (ext === 'vtx')
var page = rawtoattrs(base64_decode(contents[index]),this.width,this.__window__.body.y,this.__window__.body.x);
this.__window__.body.__properties__.content = page.content;
this.dynamic_fields = page.dynamic_fields;
// Our fields are sorted in x descending order
if (page.input_fields.length)
this.input_fields = page.input_fields.sort(function(a,b) { return a.x < b.x ? 1 : -1; });
this.__properties__.raw = base64_decode(contents[index]);
break;
case 'cost':
this.cost = contents[index];
break;
case 'date':
log(LOG_INFO,'|-/ Frame date : '+contents[index]);
break;
case 'dynamic_fields':
this.dynamic_fields = contents[index];
break;
case 'frame':
this.name.frame = ''+contents[index];
break;
case 'index':
this.name.index = contents[index];
break;
case 'input_fields':
this.input_fields = contents[index];
break;
case 'isAccessible':
this.isAccessible = ((contents[index] === 1) || (contents[index] === true));
break;
case 'isPublic':
this.isPublic = ((contents[index] === 1) || (contents[index] === true));
break;
case 'key':
this.key = contents[index];
break;
case 'type':
this.type = contents[index];
break;
case 'version':
log(LOG_INFO,'|-/ Frame version : '+contents[index]);
break;
case 'window':
for (var y in contents[index]) {
//log(LOG_DEBUG,' - Y: '+y+', '+JSON.stringify(contents[index][y]));
if (contents[index][y] === null)
continue;
for (var x in contents[index][y]) {
//log(LOG_DEBUG,' - X: '+x+', '+JSON.stringify(contents[index][y][x]));
if (contents[index][y][x] === null)
continue;
this.__window__.body.__properties__.content[y][x] = new Char(
contents[index][y][x].__properties__.ch,
contents[index][y][x].__properties__.attr
);
}
} }
break; log(LOG_DEBUG,'|-* Processing ['+index+'] with value ['+JSON.stringify(contents[index])+'].');
switch (index) {
case 'content':
//if (ext === 'tex')
// var page = rawtoattrs(base64_decode(contents[index]).replace("\x0a\x0d\x0a\x0d","\x0a\x0d"),this.width,this.__window__.body.y,this.__window__.body.x);
//else if (ext === 'vtx')
var page = rawtoattrs(base64_decode(contents[index]),this.width,this.__window__.body.y,this.__window__.body.x);
default: this.__window__.body.__properties__.content = page.content;
log(LOG_ERROR,'|-! Frame property not handled: '+index+', value:'+contents[index]); this.dynamic_fields = page.dynamic_fields;
// Our fields are sorted in x descending order
if (page.input_fields.length)
this.input_fields = page.input_fields.sort(function(a,b) { return a.x < b.x ? 1 : -1; });
this.__properties__.raw = base64_decode(contents[index]);
break;
case 'cost':
this.cost = contents[index];
break;
case 'date':
log(LOG_INFO,'|-/ Frame date : '+contents[index]);
break;
case 'dynamic_fields':
this.dynamic_fields = contents[index];
break;
case 'frame':
this.name.frame = ''+contents[index];
break;
case 'index':
this.name.index = contents[index];
break;
case 'input_fields':
this.input_fields = contents[index];
break;
case 'isAccessible':
this.isAccessible = ((contents[index] === 1) || (contents[index] === true));
break;
case 'isPublic':
this.isPublic = ((contents[index] === 1) || (contents[index] === true));
break;
case 'key':
this.key = contents[index];
break;
case 'type':
this.type = contents[index];
break;
case 'version':
log(LOG_INFO,'|-/ Frame version : '+contents[index]);
break;
case 'window':
for (var y in contents[index]) {
//log(LOG_DEBUG,' - Y: '+y+', '+JSON.stringify(contents[index][y]));
if (contents[index][y] === null)
continue;
for (var x in contents[index][y]) {
//log(LOG_DEBUG,' - X: '+x+', '+JSON.stringify(contents[index][y][x]));
if (contents[index][y][x] === null)
continue;
this.__window__.body.__properties__.content[y][x] = new Char(
contents[index][y][x].__properties__.ch,
contents[index][y][x].__properties__.attr
);
}
}
break;
default:
log(LOG_ERROR,'|-! Frame property not handled: '+index+', value:'+contents[index]);
}
}
} catch (error) {
log(LOG_ERROR,'|-! Frame error : '+error);
// Load our system error frame.
// @todo If our system error page errors, then we go into a loop
this.get(new PageObject(FRAME_SYSTEM_ERROR));
} }
}
} catch (error) { break;
log(LOG_ERROR,'|-! Frame error : '+error);
// Load our system error frame. case 2:
// @todo If our system error page errors, then we go into a loop // Work out frame type
this.get(new PageObject(FRAME_SYSTEM_ERROR)); writeln(JSON.stringify(contents));
exit(2);
break;
return null; default:
writeln('here');
log(LOG_ERROR,'|-! Unknown frame version : '+contents.version);
this.get(new PageObject(FRAME_SYSTEM_ERROR));
return null;
} }
this.loadcomplete(); this.loadcomplete();
@ -1280,7 +1300,7 @@ function Page(debug) {
// Find existing message with the page number and delete it if defined // Find existing message with the page number and delete it if defined
var msg; var msg;
for(var x in headers) { for (var x in headers) {
if ((headers[x].tags === this.name.toString()) && (!(headers[x].attr&MSG_DELETE))) { if ((headers[x].tags === this.name.toString()) && (!(headers[x].attr&MSG_DELETE))) {
msg = headers[x]; msg = headers[x];
break; break;

37
tools/clear_tags.js Normal file
View File

@ -0,0 +1,37 @@
/** CLEAR ALL THE TAGS IN A MESSAGE BASE */
load('ansitex/load/msgbases.js');
if (argv.length !== 1) {
writeln('ERROR: Need a msgbase page prefix');
exit(1);
} else {
writeln('Showing frames in:'+argv[0]);
}
var ma = new MsgAreas();
var area = ma.getArea(argv[0]);
writeln('Opening ['+argv[0]+'] - ('+area.code+')');
var msgbase = area.msgbase;
msgbase.open();
writeln('- First:'+msgbase.first_msg);
writeln('- Last:'+msgbase.last_msg);
var msgs = msgbase.get_all_msg_headers(false,false) || [];
for (var x in msgs) {
if (! msgs[x].tags)
continue;
writeln('TAG was:'+msgs[x].tags);
msgs[x].tags = '';
if (! msgbase.put_msg_header(msgs[x].number,msgs[x]))
writeln('ERROR:'+msgbase.error);
}
msgbase.close();

View File

@ -1,19 +1,26 @@
load('load/string.js'); load('ansitex/load/funcs.js');
load('load/funcs.js');
load('ansitex/load/msgbases.js'); load('ansitex/load/msgbases.js');
if (argv.length !== 1) { if (argv.length !== 1) {
writeln('ERROR: Need a msgbase page prefix'); writeln('ERROR: Need a msgbase page prefix');
exit(1); exit(1);
} else {
writeln('Showing frames in:'+argv[0]);
} }
ma = new MsgAreas(); var ma = new MsgAreas();
area = ma.getArea(argv[0]); var area = ma.getArea(argv[0]);
writeln('Opening ['+argv[0]+'] - ('+area.msgbase.cfg.code+')'); writeln('Opening ['+argv[0]+'] - ('+area.code+')');
writeln('- First:'+area.msgbase.first_msg);
writeln('- Last:'+area.msgbase.last_msg);
for (var x in area.headers) { var msgbase = area.msgbase;
writeln(padright(area.headers[x].number,4,' ')+':'+area.headers[x].tags); msgbase.open();
} writeln('- First:'+msgbase.first_msg);
writeln('- Last:'+msgbase.last_msg);
writeln('- Next:'+area.page_next);
msgbase.close();
var frames = area.frames;
for (var x in frames)
writeln(padright(frames[x].number,4,' ')+':'+frames[x].tags);

View File

@ -1,20 +1,26 @@
load('ansitex/load/msgbases.js');
/** /**
* Go through our messages and tag a frame id for messages without one. * Go through our messages and tag a frame id for messages without one.
* @note: May need to run jsexec with -m 32MB to overcome memory issues * @note: May need to run jsexec with -m 32MB to overcome memory issues
*/ */
load('load/string.js');
load('ansitex/load/msgbases.js');
const ma = new MsgAreas() const ma = new MsgAreas()
for (var i=0;i<ma.areas.length;i++) { var areas = ma.managed;
if (argv[0] && (argv[0] !== ma.areas[i].msgbase.cfg.code))
for (var i=0;i<areas.length;i++) {
if (argv[0] && ((argv[0] !== areas[i].code) && (argv[0] !== areas[i].page_prefix)))
continue; continue;
writeln('Area : '+ma.areas[i].full_name); var total = areas[i].total_msgs;
writeln('Total Messages : '+ma.areas[i].headers.length); var frames = areas[i].frames.length;
writeln('- Tagged Messages : '+ma.areas[i].list_tagged.length);
writeln('- Untagged Messages: '+ma.areas[i].list_untagged.length);
ma.areas[i].tag_msgs(); writeln('Area : '+areas[i].full_name);
writeln('- Total Messages : '+total);
writeln('- Page Prefix : '+areas[i].page_prefix);
writeln('- Tagged Messages : '+frames);
writeln('- Untagged Messages: '+(total-frames));
writeln('- Next Tag : '+areas[i].page_next);
areas[i].tag_msgs();
} }

View File

@ -1,4 +1,5 @@
load('ansitex/load/msgbases.js'); load('ansitex/load/msgbases.js');
/* Show a list of our message bases */
var ma = new MsgAreas(); var ma = new MsgAreas();
ma.list; ma.list;