Synchronet now requires the libarchive development package (e.g. libarchive-dev on Debian-based Linux distros, libarchive.org for more info) to build successfully.

Commits (1)
......@@ -6,341 +6,326 @@ require('fido_syscfg.js', 'FTNDomains');
require('ftn_nodelist.js', 'NodeList');
function node_info_popup(node, frame, settings) {
const fmt = "%10s: \1+\1h\1c%s\1-\r\n";
const popup = new Frame(
frame.x + 1,
Math.floor((frame.y + frame.height - 17) / 2),
frame.width - 2,
17,
BG_BLUE|WHITE,
frame
);
popup.putmsg('\r\n Node Details');
popup.gotoxy(1, 4);
popup.putmsg(format(fmt, 'Address', node.addr));
popup.putmsg(format(fmt, 'Name', node.name));
popup.putmsg(format(fmt, 'Sysop', node.sysop));
popup.putmsg(format(fmt, 'Location', node.location));
popup.putmsg(format(fmt, 'Hub', node.hub));
if (node.flags.INA || node.flags.IP) {
popup.putmsg(format(fmt, 'Internet', node.flags.INA || node.flags.IP));
}
const protocol = [];
if (node.flags.IBN) protocol.push('Binkp');
if (node.flags.IFC) protocol.push('ifcico');
if (node.flags.ITN) protocol.push('Telnet');
if (node.flags.IVM) protocol.push('Vmodem');
if (protocol.length) {
popup.putmsg(format(fmt, 'Protocol', protocol.join(', ')));
}
protocol = undefined;
const email = [];
if (node.flags.IEM) email.push(node.flags.IEM);
if (node.flags.ITX) email.push('TransX');
if (node.flags.IUC) email.push('UUEncode');
if (node.flags.IMI) email.push('MIME');
if (node.flags.ISE) email.push('SEAT');
if (email.length) popup.putmsg(format(fmt, 'Email', email.join(', ')));
email = undefined;
if (node.flags.PING) popup.putmsg('Accepts PING\r\n');
var other = [
'ZEC', 'REC', 'NEC', 'NC', 'SDS', 'SMH', 'RPK', 'NPK', 'ENC', 'CDP'
].filter(function (e) {
return typeof node.flags[e] != 'undefined';
});
if (other.length) popup.putmsg(format(fmt, 'Flags', other.join(', ')));
other = ['Private', 'Hold', 'Down'].reduce(function (a, c) {
if (node[c.toLowerCase()]) a.push(c);
return a;
}, []);
if (other.length) popup.putmsg(format(fmt, 'Status', other.join(', ')));
other = undefined;
popup.gotoxy(1, 16);
popup.putmsg(' \1h\1cS\1h\1wend netmail \1h\1cC\1h\1wlose');
popup.open();
frame.cycle();
console.gotoxy(console.screen_columns, console.screen_rows);
var user_input;
while((user_input = console.inkey(K_NONE).toLowerCase()) == '') {
yield();
}
popup.close();
if (user_input == 's') {
console.clear(LIGHTGRAY);
bbs.netmail(node.sysop + '@' + node.addr, WM_NETMAIL);
console.clear(LIGHTGRAY);
settings.frame.invalidate();
}
const fmt = "%10s: \1+\1h\1c%s\1-\r\n";
const popup = new Frame(
frame.x + 1,
Math.floor((frame.y + frame.height - 17) / 2),
frame.width - 2,
17,
BG_BLUE|WHITE,
frame
);
popup.putmsg('\r\n Node Details');
popup.gotoxy(1, 4);
popup.putmsg(format(fmt, 'Address', node.addr));
popup.putmsg(format(fmt, 'Name', node.name));
popup.putmsg(format(fmt, 'Sysop', node.sysop));
popup.putmsg(format(fmt, 'Location', node.location));
popup.putmsg(format(fmt, 'Hub', node.hub));
if (node.flags.INA || node.flags.IP) {
popup.putmsg(format(fmt, 'Internet', node.flags.INA || node.flags.IP));
}
const protocol = [];
if (node.flags.IBN) protocol.push('Binkp');
if (node.flags.IFC) protocol.push('ifcico');
if (node.flags.ITN) protocol.push('Telnet');
if (node.flags.IVM) protocol.push('Vmodem');
if (protocol.length) {
popup.putmsg(format(fmt, 'Protocol', protocol.join(', ')));
}
protocol = undefined;
const email = [];
if (node.flags.IEM) email.push(node.flags.IEM);
if (node.flags.ITX) email.push('TransX');
if (node.flags.IUC) email.push('UUEncode');
if (node.flags.IMI) email.push('MIME');
if (node.flags.ISE) email.push('SEAT');
if (email.length) popup.putmsg(format(fmt, 'Email', email.join(', ')));
email = undefined;
if (node.flags.PING) popup.putmsg('Accepts PING\r\n');
var other = [
'ZEC', 'REC', 'NEC', 'NC', 'SDS', 'SMH', 'RPK', 'NPK', 'ENC', 'CDP'
].filter(function (e) {
return typeof node.flags[e] != 'undefined';
});
if (other.length) popup.putmsg(format(fmt, 'Flags', other.join(', ')));
other = ['Private', 'Hold', 'Down'].reduce(function (a, c) {
if (node[c.toLowerCase()]) a.push(c);
return a;
}, []);
if (other.length) popup.putmsg(format(fmt, 'Status', other.join(', ')));
other = undefined;
popup.gotoxy(1, 16);
popup.putmsg(' \1h\1cS\1h\1wend netmail \1h\1cC\1h\1wlose');
popup.open();
frame.cycle();
console.gotoxy(console.screen_columns, console.screen_rows);
var user_input;
while((user_input = console.inkey(K_NONE).toLowerCase()) == '') {
yield();
}
popup.close();
if (user_input == 's') {
console.clear(LIGHTGRAY);
bbs.netmail(node.sysop + '@' + node.addr, WM_NETMAIL);
console.clear(LIGHTGRAY);
settings.frame.invalidate();
}
}
function populate_node_tree(filename, zone, net, tree, settings) {
const nodelist = new NodeList(filename);
nodelist.entries.forEach(function (e) {
const z = e.addr.split(':')[0];
if (z != zone) return;
const n = e.addr.match(/\d+:(\d+)\//)[1];
if (n != net) return;
const w = Math.floor(((tree.frame.width - 15 - 3) / 2) - 1);
tree.addItem(format(
'%-15s%-' + w + 's%-' + w + 's',
e.addr, e.name.substr(0, w), e.location.substr(0, w)
), node_info_popup, e, tree.frame, settings);
});
// Not sure if it's worth sorting this tree
// Nodes probably could be out of order but they generally appear to be in
// sequence by the time we get here.
const nodelist = new NodeList(filename);
nodelist.entries.forEach(function (e) {
const z = e.addr.split(':')[0];
if (z != zone) return;
const n = e.addr.match(/\d+:(\d+)\//)[1];
if (n != net) return;
const w = Math.floor(((tree.frame.width - 15 - 3) / 2) - 1);
tree.addItem(format(
'%-15s%-' + w + 's%-' + w + 's',
e.addr, e.name.substr(0, w), e.location.substr(0, w)
), node_info_popup, e, tree.frame, settings);
});
// Not sure if it's worth sorting this tree
// Nodes probably could be out of order but they generally appear to be in
// sequence by the time we get here.
}
function populate_net_tree(filename, zone, tree, settings) {
const nodelist = new NodeList(filename);
const nets = [];
nodelist.entries.forEach(function (e) {
const zn = e.addr.match(/(\d+):(\d+)\//);
if (zn[1] != zone) return;
if (nets.indexOf(zn[2]) > -1) return;
nets.push(zn[2]);
const net_tree = tree.addTree('Net ' + zn[2]);
const idx = tree.items.length - 1;
net_tree.onOpen = function () {
populate_node_tree(filename, zone, zn[2], net_tree, settings);
if (settings.auto_close_net) {
tree.items.forEach(function (e) {
if (e.text != net_tree.text) {
e.close();
e.index = -1;
}
});
}
tree.refresh();
}
net_tree.onClose = function () {
net_tree.items = [];
}
});
tree.items.sort(function (a, b) {
if (parseInt(a.text.split(' ')[1]) < parseInt(b.text.split(' ')[1])) {
return -1;
} else {
return 1;
}
});
const nodelist = new NodeList(filename);
const nets = [];
nodelist.entries.forEach(function (e) {
const zn = e.addr.match(/(\d+):(\d+)\//);
if (zn[1] != zone) return;
if (nets.indexOf(zn[2]) > -1) return;
nets.push(zn[2]);
const net_tree = tree.addTree('Net ' + zn[2]);
const idx = tree.items.length - 1;
net_tree.onOpen = function () {
populate_node_tree(filename, zone, zn[2], net_tree, settings);
if (settings.auto_close_net) {
tree.items.forEach(function (e) {
if (e.text != net_tree.text) {
e.close();
e.index = -1;
}
});
}
tree.refresh();
}
net_tree.onClose = function () {
net_tree.items = [];
}
});
tree.items.sort(function (a, b) {
if (parseInt(a.text.split(' ')[1]) < parseInt(b.text.split(' ')[1])) {
return -1;
} else {
return 1;
}
});
}
function populate_zone_tree(filename, tree, settings) {
const nodelist = new NodeList(filename);
const zones = [];
nodelist.entries.forEach(function (e) {
const zone = e.addr.split(':')[0];
if (zones.indexOf(zone) > -1) return;
zones.push(zone);
const zone_tree = tree.addTree('Zone ' + zone);
const idx = tree.items.length - 1;
zone_tree.onOpen = function () {
populate_net_tree(filename, zone, zone_tree, settings);
if (settings.auto_close_zone) {
tree.items.forEach(function (e) {
if (e.text != zone_tree.text) {
e.close();
e.index = -1;
}
});
}
tree.refresh();
}
zone_tree.onClose = function () {
zone_tree.items = [];
}
});
tree.items.sort(function (a, b) {
if (parseInt(a.text.split(' ')[1]) < parseInt(b.text.split(' ')[1])) {
return -1;
} else {
return 1;
}
});
const nodelist = new NodeList(filename);
const zones = [];
nodelist.entries.forEach(function (e) {
const zone = e.addr.split(':')[0];
if (zones.indexOf(zone) > -1) return;
zones.push(zone);
const zone_tree = tree.addTree('Zone ' + zone);
const idx = tree.items.length - 1;
zone_tree.onOpen = function () {
populate_net_tree(filename, zone, zone_tree, settings);
if (settings.auto_close_zone) {
tree.items.forEach(function (e) {
if (e.text != zone_tree.text) {
e.close();
e.index = -1;
}
});
}
tree.refresh();
}
zone_tree.onClose = function () {
zone_tree.items = [];
}
});
tree.items.sort(function (a, b) {
if (parseInt(a.text.split(' ')[1]) < parseInt(b.text.split(' ')[1])) {
return -1;
} else {
return 1;
}
});
}
function populate_domain_tree(filename, domain, tree, settings) {
try {
const nodelist = new NodeList(filename);
} catch (err) {
log(LOG_ERR, format('Error parsing %s: %s', filename, err));
return;
}
const domain_tree = tree.addTree(domain);
const idx = tree.items.length - 1;
domain_tree.onOpen = function () {
populate_zone_tree(filename, domain_tree, settings);
if (settings.auto_close_domain) {
tree.items.forEach(function (e) {
if (e.text != domain_tree.text) {
e.close();
e.index = -1;
}
});
}
tree.refresh();
}
domain_tree.onClose = function () {
domain_tree.items = [];
}
function populate_domain_tree(filename, domain, tree, settings, frame) {
try {
const nodelist = new NodeList(filename);
} catch (err) {
log(LOG_ERR, format('Error parsing %s: %s', filename, err));
return;
}
const domain_tree = tree.addTree(domain);
const idx = tree.items.length - 1;
var sframe;
domain_tree.onOpen = function () {
populate_zone_tree(filename, domain_tree, settings);
if (settings.auto_close_domain) {
tree.items.forEach(function (e) {
if (e.text != domain_tree.text) {
e.close();
e.index = -1;
}
});
}
if (nodelist.domain && nodelist.date) {
sframe = new Frame(frame.x, frame.y + 1, frame.width, 1, frame.attr, frame);
sframe.open();
sframe.putmsg(nodelist.domain + ' nodelist for ' + nodelist.date);
}
tree.refresh();
}
domain_tree.onClose = function () {
domain_tree.items = [];
if (sframe instanceof Frame) sframe.delete();
}
}
function populate_tree(tree, settings) {
const ftn_domains = new FTNDomains();
Object.keys(ftn_domains.nodeListFN).forEach(function (e) {
if (!file_exists(ftn_domains.nodeListFN[e])) return;
const dn = settings['domain_' + e] || e;
populate_domain_tree(ftn_domains.nodeListFN[e], dn, tree, settings);
});
Object.keys(settings).forEach(function (e) {
if (e.search(/^nodelist_/) > -1 && file_exists(settings[e])) {
const dn = e.replace(/^nodelist_/, '');
populate_domain_tree(settings[e], dn, tree, settings);
}
});
tree.items.sort(function (a, b) {
return a.text.toLowerCase() < b.text.toLowerCase() ? -1 : 1;
});
tree.refresh();
function populate_tree(tree, settings, frame) {
const ftn_domains = new FTNDomains();
Object.keys(ftn_domains.nodeListFN).forEach(function (e) {
if (!file_exists(ftn_domains.nodeListFN[e])) return;
const dn = settings['domain_' + e] || e;
populate_domain_tree(ftn_domains.nodeListFN[e], dn, tree, settings, frame);
});
Object.keys(settings).forEach(function (e) {
if (e.search(/^nodelist_/) > -1 && file_exists(settings[e])) {
const dn = e.replace(/^nodelist_/, '');
populate_domain_tree(settings[e], dn, tree, settings, frame);
}
});
tree.items.sort(function (a, b) {
return a.text.toLowerCase() < b.text.toLowerCase() ? -1 : 1;
});
tree.refresh();
}
function search(frame, settings) {
const sframe = new Frame(frame.x, frame.y + 4, frame.width, 3, BG_BLUE|WHITE, frame);
sframe.putmsg('Search by address, system name, sysop, or location:');
sframe.gotoxy(1, 3);
sframe.putmsg("Type a query and wait for results, don't hit enter.");
sframe.open();
const typeahead = new Typeahead({
x : 1,
y : sframe.y + 1,
bg : 0,
prompt: '',
frame: sframe,
datasources: [
function (str) {
const w = (Math.floor((frame.width - 9 - 15) / 3) - 1);
const ret = [];
const ftn_domains = new FTNDomains();
const files = Object.keys(ftn_domains.nodeListFN).map(function (e) {
return ftn_domains.nodeListFN[e];
});
Object.keys(settings).forEach(function (e) {
if (e.search(/^nodelist_/) > -1) files.push(settings[e]);
});
files.forEach(function (e) {
if (!file_exists(e)) return
const nodelist = new NodeList(e);
nodelist.entries.forEach(function (e) {
var r = ['addr', 'sysop', 'name', 'location'].some(function (ee) {
if (e[ee].search(new RegExp(str, 'i')) > -1) {
ret.push({
node : e,
text : format(
'%-15s%-' + w + 's %-' + w + 's %-' + w + 's',
e.addr,
e.name.substr(0, w),
e.location.substr(0, w),
e.sysop.substr(0, w)
)
});
return true;
}
return false;
});
});
});
return ret;
}
]
});
var user_input;
while (typeof user_input == 'boolean' || typeof user_input == 'undefined') {
user_input = typeahead.inkey(console.inkey(K_NONE));
typeahead.cycle();
if (frame.cycle()) {
console.gotoxy(console.screen_columns, console.screen_rows);
typeahead.updateCursor();
}
yield();
}
typeahead.close();
sframe.close();
if (typeof user_input == 'object') {
node_info_popup(user_input.node, frame, settings);
}
const sframe = new Frame(frame.x, frame.y + 4, frame.width, 3, BG_BLUE|WHITE, frame);
sframe.putmsg('Search by address, system name, sysop, or location:');
sframe.gotoxy(1, 3);
sframe.putmsg("Type a query and wait for results, don't hit enter.");
sframe.open();
const typeahead = new Typeahead({
x: 1,
y: sframe.y + 1,
bg: 0,
prompt: '',
frame: sframe,
datasources: [
function (str) {
const w = (Math.floor((frame.width - 9 - 15) / 3) - 1);
const ret = [];
const ftn_domains = new FTNDomains();
const files = Object.keys(ftn_domains.nodeListFN).map(function (e) {
return ftn_domains.nodeListFN[e];
});
Object.keys(settings).forEach(function (e) {
if (e.search(/^nodelist_/) > -1) files.push(settings[e]);
});
files.forEach(function (e) {
if (!file_exists(e)) return
const nodelist = new NodeList(e);
nodelist.entries.forEach(function (e) {
var r = ['addr', 'sysop', 'name', 'location'].some(function (ee) {
if (e[ee].search(new RegExp(str, 'i')) > -1) {
ret.push({
node: e,
text: format(
'%-15s%-' + w + 's %-' + w + 's %-' + w + 's',
e.addr, e.name.substr(0, w), e.location.substr(0, w), e.sysop.substr(0, w)
),
});
return true;
}
return false;
});
});
});
return ret;
}
],
});
var user_input;
while (typeof user_input == 'boolean' || typeof user_input == 'undefined') {
user_input = typeahead.inkey(console.inkey(K_NONE));
typeahead.cycle();
if (frame.cycle()) {
console.gotoxy(console.screen_columns, console.screen_rows);
typeahead.updateCursor();
}
yield();
}
typeahead.close();
sframe.close();
if (typeof user_input == 'object') {
node_info_popup(user_input.node, frame, settings);
}
}
function main() {
const ca = console.attributes;
const bss = bbs.sys_status;
bbs.sys_status|=SS_MOFF;
const frame = new Frame(
1, 1, console.screen_columns, console.screen_rows, BG_BLUE|WHITE
);
const tree_frame = new Frame(
frame.x, frame.y + 2, frame.width, frame.height - 4, BG_BLACK, frame
);
const tree = new Tree(tree_frame);
tree.colors.lfg = WHITE;
tree.colors.lbg = BG_CYAN;
tree.colors.tfg = LIGHTCYAN;
tree.colors.kfg = WHITE;
tree.colors.xfg = LIGHTCYAN;
const scrollbar = new ScrollBar(tree, {autohide : true});
// Valid settings:
// auto_close_domain t/f
// auto_close_zone t/f
// auto_close_net t/f
// nodelist_xxx /path/to/some/nodelist
const defaults = {
auto_close_domain: true,
auto_close_zone: true,
auto_close_net: true
};
const settings = load({}, 'modopts.js', 'fido_nodelist_browser') || {};
Object.keys(defaults).forEach(function (e) {
if (typeof settings[e] == 'undefined') settings[e] = defaults[e];
});
settings.frame = frame;
frame.putmsg('Nodelist Browser');
frame.gotoxy(1, frame.height);
frame.putmsg('\1h\1cS\1h\1wearch \1h\1cQ\1h\1wuit');
populate_tree(tree, settings);
console.clear(LIGHTGRAY);
frame.open();
tree.open();
frame.cycle();
scrollbar.cycle();
var user_input;
while ((user_input = console.getkey(K_NOSPIN).toLowerCase()) != 'q') {
if (user_input == 's') {
search(frame, settings);
} else {
tree.getcmd(user_input);
scrollbar.cycle();
}
if (frame.cycle()) {
console.gotoxy(console.screen_columns, console.screen_rows);
}
yield();
}
frame.close();
bbs.sys_status = bss;
console.clear(ca);
const ca = console.attributes;
const bss = bbs.sys_status;
bbs.sys_status|=SS_MOFF;
const frame = new Frame(1, 1, console.screen_columns, console.screen_rows, BG_BLUE|WHITE);
const tree_frame = new Frame(frame.x, frame.y + 2, frame.width, frame.height - 4, BG_BLACK, frame);
const tree = new Tree(tree_frame);
tree.colors.lfg = WHITE;
tree.colors.lbg = BG_CYAN;
tree.colors.tfg = LIGHTCYAN;
tree.colors.kfg = WHITE;
tree.colors.xfg = LIGHTCYAN;
const scrollbar = new ScrollBar(tree, { autohide: true });
// Valid settings:
// auto_close_domain t/f
// auto_close_zone t/f
// auto_close_net t/f
// nodelist_xxx /path/to/some/nodelist
const defaults = {
auto_close_domain: true,
auto_close_zone: true,
auto_close_net: true
};
const settings = load({}, 'modopts.js', 'fido_nodelist_browser') || {};
Object.keys(defaults).forEach(function (e) {
if (typeof settings[e] == 'undefined') settings[e] = defaults[e];
});
settings.frame = frame;
frame.putmsg('Nodelist Browser');
frame.gotoxy(1, frame.height);
frame.putmsg('\1h\1cS\1h\1wearch \1h\1cQ\1h\1wuit');
populate_tree(tree, settings, frame);
console.clear(LIGHTGRAY);
frame.open();
tree.open();
frame.cycle();
scrollbar.cycle();
var user_input;
while ((user_input = console.getkey(K_NOSPIN).toLowerCase()) != 'q') {
if (user_input == 's') {
search(frame, settings);
} else {
tree.getcmd(user_input);
scrollbar.cycle();
}
if (frame.cycle()) console.gotoxy(console.screen_columns, console.screen_rows);
}
frame.close();
bbs.sys_status = bss;
console.clear(ca);
}
main();