sbbs/load/msgbases.js

802 lines
20 KiB
JavaScript

'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;
}
}
ma.area_name = msg_area.grp_list[g].sub_list[a].description;
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;
}
}