* Reworked FTN packet I/O (WIP)
* Detect FTN packet 2, 2.2, and 2+ * Various FTN utils (MSGID, Origin, PID, generation etc) * More work on message network readyness
This commit is contained in:
parent
317af8419a
commit
dec78e942d
|
@ -172,7 +172,10 @@ function getDefaultConfig() {
|
|||
paths : {
|
||||
mods : paths.join(__dirname, './../mods/'),
|
||||
servers : paths.join(__dirname, './servers/'),
|
||||
msgNetworks : paths.join(__dirname, './msg_networks/'),
|
||||
|
||||
scannerTossers : paths.join(__dirname, './scanner_tossers/'),
|
||||
mailers : paths.join(__dirname, './mailers/') ,
|
||||
|
||||
art : paths.join(__dirname, './../mods/art/'),
|
||||
themes : paths.join(__dirname, './../mods/themes/'),
|
||||
logs : paths.join(__dirname, './../logs/'), // :TODO: set up based on system, e.g. /var/logs/enigmabbs or such
|
||||
|
|
|
@ -307,9 +307,9 @@ function FullScreenEditorModule(options) {
|
|||
|
||||
// :TODO: We'd like to delete up to N rows, but this does not work
|
||||
// in NetRunner:
|
||||
//self.client.term.rawWrite(ansi.reset() + ansi.deleteLine(3));
|
||||
self.client.term.rawWrite(ansi.reset() + ansi.deleteLine(3));
|
||||
|
||||
self.client.term.rawWrite(ansi.reset() + ansi.eraseLine(2))
|
||||
//self.client.term.rawWrite(ansi.reset() + ansi.eraseLine(2))
|
||||
}
|
||||
callback(null);
|
||||
},
|
||||
|
|
|
@ -17,39 +17,37 @@ var buffers = require('buffers');
|
|||
var moment = require('moment');
|
||||
|
||||
/*
|
||||
:TODO: should probably be broken up
|
||||
FTNPacket
|
||||
FTNPacketImport: packet -> message(s)
|
||||
FTNPacketExport: message(s) -> packet
|
||||
*/
|
||||
:TODO: things
|
||||
* Read/detect packet types: 2, 2.2, and 2+
|
||||
* Write packet types: 2, 2.2, and 2+
|
||||
* Test SAUCE ignore/extraction
|
||||
* FSP-1010 for netmail (see SBBS)
|
||||
|
||||
/*
|
||||
Reader: file to ftn data
|
||||
Writer: ftn data to packet
|
||||
|
||||
Data to toMessage
|
||||
Data.fromMessage
|
||||
|
||||
FTNMessage.toMessage() => Message
|
||||
FTNMessage.fromMessage() => Create from Message
|
||||
|
||||
* read: header -> simple {} obj, msg -> Message object
|
||||
* read: read(..., iterator): iterator('header', ...), iterator('message', msg)
|
||||
* write: provide information to go into header
|
||||
|
||||
* Logic of "Is this for us"/etc. elsewhere
|
||||
*/
|
||||
|
||||
const FTN_PACKET_HEADER_SIZE = 58; // fixed header size
|
||||
const FTN_PACKET_HEADER_TYPE = 2;
|
||||
const FTN_PACKET_MESSAGE_TYPE = 2;
|
||||
const FTN_PACKET_BAUD_TYPE_2_2 = 2;
|
||||
|
||||
// EOF + SAUCE.id + SAUCE.version ('00')
|
||||
const FTN_MESSAGE_SAUCE_HEADER =
|
||||
new Buffer( [ 0x1a, 'S', 'A', 'U', 'C', 'E', '0', '0' ] );
|
||||
// SAUCE magic header + version ("00")
|
||||
const FTN_MESSAGE_SAUCE_HEADER = new Buffer('SAUCE00');
|
||||
|
||||
const FTN_MESSAGE_KLUDGE_PREFIX = '\x01';
|
||||
|
||||
//
|
||||
// Read/Write FTN packets with support for the following formats:
|
||||
//
|
||||
// * Type 1 FTS-0001 @ http://ftsc.org/docs/fts-0001.016 (Obsolete)
|
||||
// * Type 2.2 FSC-0045 @ http://ftsc.org/docs/fsc-0045.001
|
||||
// * Type 2+ FSC-0039 and FSC-0048 @ http://ftsc.org/docs/fsc-0039.004
|
||||
// and http://ftsc.org/docs/fsc-0048.002
|
||||
//
|
||||
// Additional resources:
|
||||
// * Writeup on differences between type 2, 2.2, and 2+:
|
||||
// http://walon.org/pub/fidonet/FTSC-nodelists-etc./pkt-types.txt
|
||||
//
|
||||
function FTNPacket() {
|
||||
|
||||
var self = this;
|
||||
|
@ -57,16 +55,14 @@ function FTNPacket() {
|
|||
this.parsePacketHeader = function(packetBuffer, cb) {
|
||||
assert(Buffer.isBuffer(packetBuffer));
|
||||
|
||||
//
|
||||
// See the following specs:
|
||||
// http://ftsc.org/docs/fts-0001.016
|
||||
// http://ftsc.org/docs/fsc-0048.002
|
||||
//
|
||||
if(packetBuffer.length < FTN_PACKET_HEADER_SIZE) {
|
||||
cb(new Error('Buffer too small'));
|
||||
return;
|
||||
}
|
||||
|
||||
//
|
||||
// Start out reading as if this is a FSC-0048 2+ packet
|
||||
//
|
||||
binary.parse(packetBuffer)
|
||||
.word16lu('origNode')
|
||||
.word16lu('destNode')
|
||||
|
@ -81,7 +77,7 @@ function FTNPacket() {
|
|||
.word16lu('origNet')
|
||||
.word16lu('destNet')
|
||||
.word8('prodCodeLo')
|
||||
.word8('revisionMajor') // aka serialNo
|
||||
.word8('prodRevLo') // aka serialNo
|
||||
.buffer('password', 8) // null padded C style string
|
||||
.word16lu('origZone')
|
||||
.word16lu('destZone')
|
||||
|
@ -89,9 +85,9 @@ function FTNPacket() {
|
|||
.word16lu('auxNet')
|
||||
.word16lu('capWordA')
|
||||
.word8('prodCodeHi')
|
||||
.word8('revisionMinor')
|
||||
.word8('prodRevHi')
|
||||
.word16lu('capWordB')
|
||||
.word16lu('originZone2')
|
||||
.word16lu('origZone2')
|
||||
.word16lu('destZone2')
|
||||
.word16lu('originPoint')
|
||||
.word16lu('destPoint')
|
||||
|
@ -105,6 +101,30 @@ function FTNPacket() {
|
|||
return;
|
||||
}
|
||||
|
||||
//
|
||||
// What kind of packet do we really have here?
|
||||
//
|
||||
if(FTN_PACKET_BAUD_TYPE_2_2 === packetHeader.baud) {
|
||||
packetHeader.packetVersion = '2.2';
|
||||
} else {
|
||||
//
|
||||
// See heuristics described in FSC-0048, "Receiving Type-2+ bundles"
|
||||
//
|
||||
const capWordASwapped =
|
||||
((packetHeader.capWordA & 0xff) << 8) |
|
||||
((packetHeader.capWordA >> 8) & 0xff);
|
||||
|
||||
if(capWordASwapped === packetHeader.capWordB &&
|
||||
0 != packetHeader.capWordB &&
|
||||
packetHeader.capWordB & 0x0001)
|
||||
{
|
||||
packetHeader.packetVersion = '2+';
|
||||
} else {
|
||||
packetHeader.packetVersion = '2';
|
||||
packetHeader.point
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Date/time components into something more reasonable
|
||||
// Note: The names above match up with object members moment() allows
|
||||
|
@ -131,7 +151,7 @@ function FTNPacket() {
|
|||
buffer.writeUInt16LE(headerInfo.origNet, 20);
|
||||
buffer.writeUInt16LE(headerInfo.destNet, 22);
|
||||
buffer.writeUInt8(headerInfo.prodCodeLo, 24);
|
||||
buffer.writeUInt8(headerInfo.revisionMajor, 25);
|
||||
buffer.writeUInt8(headerInfo.prodRevHi, 25);
|
||||
|
||||
const pass = ftn.stringToNullPaddedBuffer(headerInfo.password, 8);
|
||||
pass.copy(buffer, 26);
|
||||
|
@ -143,7 +163,7 @@ function FTNPacket() {
|
|||
buffer.writeUInt16LE(headerInfo.auxNet, 38);
|
||||
buffer.writeUInt16LE(headerInfo.capWordA, 40);
|
||||
buffer.writeUInt8(headerInfo.prodCodeHi, 42);
|
||||
buffer.writeUInt8(headerInfo.revisionMinor, 43);
|
||||
buffer.writeUInt8(headerInfo.prodRevLo, 43);
|
||||
buffer.writeUInt16LE(headerInfo.capWordB, 44);
|
||||
buffer.writeUInt16LE(headerInfo.origZone2, 46);
|
||||
buffer.writeUInt16LE(headerInfo.destZone2, 48);
|
||||
|
@ -209,11 +229,14 @@ function FTNPacket() {
|
|||
// has SEEN-BY, PATH, and other kludge information *appended*
|
||||
const sauceHeaderPosition = messageBodyBuffer.indexOf(FTN_MESSAGE_SAUCE_HEADER);
|
||||
if(sauceHeaderPosition > -1) {
|
||||
sauce.readSAUCE(messageBodyBuffer.slice(sauceHeaderPosition), (err, theSauce) => {
|
||||
sauce.readSAUCE(messageBodyBuffer.slice(sauceHeaderPosition, sauceHeaderPosition + sauce.SAUCE_SIZE), (err, theSauce) => {
|
||||
if(!err) {
|
||||
// we read some SAUCE - don't re-process that portion into the body
|
||||
messageBodyBuffer = messageBodyBuffer.slice(0, sauceHeaderPosition);
|
||||
messageBodyBuffer = messageBodyBuffer.slice(0, sauceHeaderPosition) + messageBodyBuffer.slice(sauceHeaderPosition + sauce.SAUCE_SIZE);
|
||||
// messageBodyBuffer = messageBodyBuffer.slice(0, sauceHeaderPosition);
|
||||
messageBodyData.sauce = theSauce;
|
||||
} else {
|
||||
console.log(err)
|
||||
}
|
||||
callback(null); // failure to read SAUCE is OK
|
||||
});
|
||||
|
@ -349,6 +372,19 @@ function FTNPacket() {
|
|||
msg.meta.FtnProperty.ftn_origin = messageBodyData.originLine;
|
||||
}
|
||||
|
||||
//
|
||||
// Update message UUID, if possible, based on MSGID and AREA
|
||||
//
|
||||
if(_.isString(msg.meta.FtnKludge.MSGID) &&
|
||||
_.isString(msg.meta.FtnProperty.ftn_area) &&
|
||||
msg.meta.FtnKludge.MSGID.length > 0 &&
|
||||
msg.meta.FtnProperty.ftn_area.length > 0)
|
||||
{
|
||||
msg.uuid = ftn.createMessageUuid(
|
||||
msg.meta.FtnKludge.MSGID,
|
||||
msg.meta.FtnProperty.area);
|
||||
}
|
||||
|
||||
iterator('message', msg);
|
||||
})
|
||||
});
|
||||
|
@ -367,21 +403,6 @@ function FTNPacket() {
|
|||
basicHeader.writeUInt8(message.meta.FtnProperty.ftn_attr_flags2, 11);
|
||||
basicHeader.writeUInt16LE(message.meta.FtnProperty.ftn_cost, 12);
|
||||
|
||||
//
|
||||
// From http://ftsc.org/docs/fts-0001.016:
|
||||
// DateTime = (* a character string 20 characters long *)
|
||||
// (* 01 Jan 86 02:34:56 *)
|
||||
// DayOfMonth " " Month " " Year " "
|
||||
// " " HH ":" MM ":" SS
|
||||
// Null
|
||||
//
|
||||
// DayOfMonth = "01" | "02" | "03" | ... | "31" (* Fido 0 fills *)
|
||||
// Month = "Jan" | "Feb" | "Mar" | "Apr" | "May" | "Jun" |
|
||||
// "Jul" | "Aug" | "Sep" | "Oct" | "Nov" | "Dec"
|
||||
// Year = "01" | "02" | .. | "85" | "86" | ... | "99" | "00"
|
||||
// HH = "00" | .. | "23"
|
||||
// MM = "00" | .. | "59"
|
||||
// SS = "00" | .. | "59"
|
||||
const dateTimeBuffer = new Buffer(ftn.getDateTimeString(message.modTimestamp) + '\0');
|
||||
dateTimeBuffer.copy(basicHeader, 14);
|
||||
|
||||
|
@ -423,12 +444,17 @@ function FTNPacket() {
|
|||
}
|
||||
}
|
||||
|
||||
// :TODO: is Area really any differnt (e.g. no space between AREA:the_area)
|
||||
//
|
||||
// FTN-0004.001 @ http://ftsc.org/docs/fts-0004.001
|
||||
// AREA:CONFERENCE
|
||||
// Should be first line in a message
|
||||
//
|
||||
if(message.meta.FtnProperty.ftn_area) {
|
||||
msgBody += `AREA:${message.meta.FtnProperty.ftn_area}\n`;
|
||||
}
|
||||
|
||||
Object.keys(message.meta.FtnKludge).forEach(k => {
|
||||
// we want PATH to be last
|
||||
if('PATH' !== k) {
|
||||
appendMeta(k, message.meta.FtnKludge[k]);
|
||||
}
|
||||
|
@ -436,9 +462,21 @@ function FTNPacket() {
|
|||
|
||||
msgBody += message.message;
|
||||
|
||||
//
|
||||
// FTN-0004.001 @ http://ftsc.org/docs/fts-0004.001
|
||||
// Origin line should be near the bottom of a message
|
||||
//
|
||||
appendMeta('', message.meta.FtnProperty.ftn_tear_line);
|
||||
|
||||
//
|
||||
// Tear line should be near the bottom of a message
|
||||
//
|
||||
appendMeta('', message.meta.FtnProperty.ftn_origin);
|
||||
|
||||
//
|
||||
// FTN-0004.001 @ http://ftsc.org/docs/fts-0004.001
|
||||
// SEEN-BY and PATH should be the last lines of a message
|
||||
//
|
||||
appendMeta('SEEN-BY', message.meta.FtnProperty.ftn_seen_by);
|
||||
appendMeta('PATH', message.meta.FtnKludge['PATH']);
|
||||
|
||||
|
@ -509,498 +547,6 @@ FTNPacket.prototype.write = function(path, headerInfo, messages, cb) {
|
|||
};
|
||||
|
||||
|
||||
|
||||
//
|
||||
// References
|
||||
// * http://ftsc.org/docs/fts-0001.016
|
||||
// * http://ftsc.org/docs/fsc-0048.002
|
||||
//
|
||||
// Other implementations:
|
||||
// * https://github.com/M-griffin/PyPacketMail/blob/master/PyPacketMail.py
|
||||
//
|
||||
function FTNMailPacket(options) {
|
||||
|
||||
//MailPacket.call(this, options);
|
||||
|
||||
var self = this;
|
||||
self.KLUDGE_PREFIX = '\x01';
|
||||
|
||||
this.getPacketHeaderAddress = function() {
|
||||
return {
|
||||
zone : self.packetHeader.destZone,
|
||||
net : self.packetHeader.destNet,
|
||||
node : self.packetHeader.destNode,
|
||||
point : self.packetHeader.destPoint,
|
||||
};
|
||||
};
|
||||
|
||||
this.getNetworkNameForAddress = function(addr) {
|
||||
var nodeAddr;
|
||||
for(var network in self.nodeAddresses) {
|
||||
nodeAddr = self.nodeAddresses[network];
|
||||
if(nodeAddr.zone === addr.zone &&
|
||||
nodeAddr.net === addr.net &&
|
||||
nodeAddr.node === addr.node &&
|
||||
nodeAddr.point === addr.point)
|
||||
{
|
||||
return network;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
this.parseFtnPacketHeader = function(packetBuffer, cb) {
|
||||
assert(Buffer.isBuffer(packetBuffer));
|
||||
|
||||
if(packetBuffer.length < 58) {
|
||||
cb(new Error('Buffer too small'));
|
||||
return;
|
||||
}
|
||||
|
||||
binary.parse(packetBuffer)
|
||||
.word16lu('origNode')
|
||||
.word16lu('destNode')
|
||||
.word16lu('year')
|
||||
.word16lu('month')
|
||||
.word16lu('day')
|
||||
.word16lu('hour')
|
||||
.word16lu('minute')
|
||||
.word16lu('second')
|
||||
.word16lu('baud')
|
||||
.word16lu('packetType')
|
||||
.word16lu('origNet')
|
||||
.word16lu('destNet')
|
||||
.word8('prodCodeLo')
|
||||
.word8('revisionMajor') // aka serialNo
|
||||
.buffer('password', 8) // null terminated C style string
|
||||
.word16lu('origZone')
|
||||
.word16lu('destZone')
|
||||
// Additions in FSC-0048.002 follow...
|
||||
.word16lu('auxNet')
|
||||
.word16lu('capWordA')
|
||||
.word8('prodCodeHi')
|
||||
.word8('revisionMinor')
|
||||
.word16lu('capWordB')
|
||||
.word16lu('originZone2')
|
||||
.word16lu('destZone2')
|
||||
.word16lu('originPoint')
|
||||
.word16lu('destPoint')
|
||||
.word32lu('prodData')
|
||||
.tap(function tapped(packetHeader) {
|
||||
packetHeader.password = ftn.stringFromFTN(packetHeader.password);
|
||||
|
||||
// :TODO: Don't hard code magic # here
|
||||
if(2 !== packetHeader.packetType) {
|
||||
console.log(packetHeader.packetType)
|
||||
cb(new Error('Packet is not Type-2'));
|
||||
return;
|
||||
}
|
||||
|
||||
// :TODO: convert date information -> .created
|
||||
|
||||
packetHeader.created = moment(packetHeader);
|
||||
/*
|
||||
packetHeader.year, packetHeader.month, packetHeader.day, packetHeader.hour,
|
||||
packetHeader.minute, packetHeader.second);*/
|
||||
|
||||
// :TODO: validate & pass error if failure
|
||||
cb(null, packetHeader);
|
||||
});
|
||||
};
|
||||
|
||||
this.getPacketHeaderBuffer = function(packetHeader, options) {
|
||||
options = options || {};
|
||||
|
||||
if(options.created) {
|
||||
options.created = moment(options.created); // ensure we have a moment obj
|
||||
} else {
|
||||
options.created = moment();
|
||||
}
|
||||
|
||||
let buffer = new Buffer(58);
|
||||
|
||||
buffer.writeUInt16LE(packetHeader.origNode, 0);
|
||||
buffer.writeUInt16LE(packetHeader.destNode, 2);
|
||||
buffer.writeUInt16LE(options.created.year(), 4);
|
||||
buffer.writeUInt16LE(options.created.month(), 6);
|
||||
buffer.writeUInt16LE(options.created.date(), 8);
|
||||
buffer.writeUInt16LE(options.created.hour(), 10);
|
||||
buffer.writeUInt16LE(options.created.minute(), 12);
|
||||
buffer.writeUInt16LE(options.created.second(), 14);
|
||||
buffer.writeUInt16LE(0x0000, 16);
|
||||
buffer.writeUInt16LE(0x0002, 18);
|
||||
buffer.writeUInt16LE(packetHeader.origNet, 20);
|
||||
buffer.writeUInt16LE(packetHeader.destNet, 22);
|
||||
buffer.writeUInt8(packetHeader.prodCodeLo, 24);
|
||||
buffer.writeUInt8(packetHeader.revisionMajor, 25);
|
||||
|
||||
const pass = ftn.stringToNullPaddedBuffer(packetHeader.password, 8);
|
||||
pass.copy(buffer, 26);
|
||||
|
||||
buffer.writeUInt16LE(packetHeader.origZone, 34);
|
||||
buffer.writeUInt16LE(packetHeader.destZone, 36);
|
||||
|
||||
// FSC-0048.002 additions...
|
||||
buffer.writeUInt16LE(packetHeader.auxNet, 38);
|
||||
buffer.writeUInt16LE(packetHeader.capWordA, 40);
|
||||
buffer.writeUInt8(packetHeader.prodCodeHi, 42);
|
||||
buffer.writeUInt8(packetHeader.revisionMinor, 43);
|
||||
buffer.writeUInt16LE(packetHeader.capWordB, 44);
|
||||
buffer.writeUInt16LE(packetHeader.origZone2, 46);
|
||||
buffer.writeUInt16LE(packetHeader.destZone2, 48);
|
||||
buffer.writeUInt16LE(packetHeader.origPoint, 50);
|
||||
buffer.writeUInt16LE(packetHeader.destPoint, 52);
|
||||
buffer.writeUInt32LE(packetHeader.prodData, 54);
|
||||
|
||||
return buffer;
|
||||
};
|
||||
|
||||
self.setOrAppend = function(value, dst) {
|
||||
if(dst) {
|
||||
if(!_.isArray(dst)) {
|
||||
dst = [ dst ];
|
||||
}
|
||||
|
||||
dst.push(value);
|
||||
} else {
|
||||
dst = value;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
self.getMessageMeta = function(msgBody, msgData) {
|
||||
var meta = {
|
||||
FtnKludge : msgBody.kludgeLines,
|
||||
FtnProperty : {},
|
||||
};
|
||||
|
||||
if(msgBody.tearLine) {
|
||||
meta.FtnProperty.ftn_tear_line = msgBody.tearLine;
|
||||
}
|
||||
if(msgBody.seenBy.length > 0) {
|
||||
meta.FtnProperty.ftn_seen_by = msgBody.seenBy;
|
||||
}
|
||||
if(msgBody.area) {
|
||||
meta.FtnProperty.ftn_area = msgBody.area;
|
||||
}
|
||||
if(msgBody.originLine) {
|
||||
meta.FtnProperty.ftn_origin = msgBody.originLine;
|
||||
}
|
||||
|
||||
meta.FtnProperty.ftn_orig_node = msgData.origNode;
|
||||
meta.FtnProperty.ftn_dest_node = msgData.destNode;
|
||||
meta.FtnProperty.ftn_orig_network = msgData.origNet;
|
||||
meta.FtnProperty.ftn_dest_network = msgData.destNet;
|
||||
meta.FtnProperty.ftn_attr_flags1 = msgData.attrFlags1;
|
||||
meta.FtnProperty.ftn_attr_flags2 = msgData.attrFlags2;
|
||||
meta.FtnProperty.ftn_cost = msgData.cost;
|
||||
|
||||
return meta;
|
||||
};
|
||||
|
||||
this.parseFtnMessageBody = function(msgBodyBuffer, cb) {
|
||||
//
|
||||
// From FTS-0001.16:
|
||||
// "Message text is unbounded and null terminated (note exception below).
|
||||
//
|
||||
// A 'hard' carriage return, 0DH, marks the end of a paragraph, and must
|
||||
// be preserved.
|
||||
//
|
||||
// So called 'soft' carriage returns, 8DH, may mark a previous
|
||||
// processor's automatic line wrap, and should be ignored. Beware that
|
||||
// they may be followed by linefeeds, or may not.
|
||||
//
|
||||
// All linefeeds, 0AH, should be ignored. Systems which display message
|
||||
// text should wrap long lines to suit their application."
|
||||
//
|
||||
// This is a bit tricky. Decoding the buffer to CP437 converts all 0x8d -> 0xec, so we'll
|
||||
// have to replace those characters if the buffer is left as CP437.
|
||||
// After decoding, we'll need to peek at the buffer for the various kludge lines
|
||||
// for charsets & possibly re-decode. Uggh!
|
||||
//
|
||||
|
||||
// :TODO: Use the proper encoding here. There appear to be multiple specs and/or
|
||||
// stuff people do with this... some specs kludge lines, which is kinda durpy since
|
||||
// to get to that point, one must read the file (and decode) to find said kludge...
|
||||
|
||||
|
||||
//var msgLines = msgBodyBuffer.toString().split(/\r\n|[\n\v\f\r\x85\u2028\u2029]/g);
|
||||
|
||||
//var msgLines = iconv.decode(msgBodyBuffer, 'CP437').replace(/\xec/g, '').split(/\r\n|[\r\n]/g);
|
||||
var msgLines = iconv.decode(msgBodyBuffer, 'CP437').replace(/[\xec\n]/g, '').split(/\r/g);
|
||||
|
||||
var msgBody = {
|
||||
message : [],
|
||||
kludgeLines : {}, // <KLUDGE> -> [ value1, value2, ... ]
|
||||
seenBy : [],
|
||||
};
|
||||
|
||||
var preOrigin = true;
|
||||
|
||||
function addKludgeLine(kl) {
|
||||
const kludgeParts = kl.split(':');
|
||||
kludgeParts[0] = kludgeParts[0].toUpperCase();
|
||||
kludgeParts[1] = kludgeParts[1].trim();
|
||||
|
||||
self.setOrAppend(kludgeParts[1], msgBody.kludgeLines[kludgeParts[0]]);
|
||||
}
|
||||
|
||||
var sauceBuffers;
|
||||
|
||||
msgLines.forEach(function nextLine(line) {
|
||||
if(0 === line.length) {
|
||||
msgBody.message.push('');
|
||||
return;
|
||||
}
|
||||
|
||||
if(preOrigin) {
|
||||
if(_.startsWith(line, 'AREA:')) {
|
||||
msgBody.area = line.substring(line.indexOf(':') + 1).trim();
|
||||
} else if(_.startsWith(line, '--- ')) {
|
||||
// Tag lines are tracked allowing for specialized display/etc.
|
||||
msgBody.tearLine = line;
|
||||
} else if(/[ ]{1,2}(\* )?Origin\: /.test(line)) { // To spec is " * Origin: ..."
|
||||
msgBody.originLine = line;
|
||||
preOrigin = false;
|
||||
} else if(self.KLUDGE_PREFIX === line.charAt(0)) {
|
||||
addKludgeLine(line.slice(1));
|
||||
} else if(!sauceBuffers || _.startsWith(line, '\x1aSAUCE00')) {
|
||||
sauceBuffers = sauceBuffers || buffers();
|
||||
sauceBuffers.push(new Buffer(line));
|
||||
} else {
|
||||
msgBody.message.push(line);
|
||||
}
|
||||
} else {
|
||||
if(_.startsWith(line, 'SEEN-BY:')) {
|
||||
msgBody.seenBy.push(line.substring(line.indexOf(':') + 1).trim());
|
||||
} else if(self.KLUDGE_PREFIX === line.charAt(0)) {
|
||||
addKludgeLine(line.slice(1));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if(sauceBuffers) {
|
||||
// :TODO: parse sauce -> sauce buffer. This needs changes to this method to return message & optional sauce
|
||||
}
|
||||
|
||||
cb(null, msgBody);
|
||||
};
|
||||
|
||||
this.extractMessages = function(buffer, iterator, cb) {
|
||||
assert(Buffer.isBuffer(buffer));
|
||||
assert(_.isFunction(iterator));
|
||||
|
||||
const NULL_TERM_BUFFER = new Buffer( [ 0 ] );
|
||||
|
||||
binary.stream(buffer).loop(function looper(end, vars) {
|
||||
this
|
||||
.word16lu('messageType')
|
||||
.word16lu('origNode')
|
||||
.word16lu('destNode')
|
||||
.word16lu('origNet')
|
||||
.word16lu('destNet')
|
||||
.word8('attrFlags1')
|
||||
.word8('attrFlags2')
|
||||
.word16lu('cost')
|
||||
.scan('modDateTime', NULL_TERM_BUFFER)
|
||||
.scan('toUserName', NULL_TERM_BUFFER)
|
||||
.scan('fromUserName', NULL_TERM_BUFFER)
|
||||
.scan('subject', NULL_TERM_BUFFER)
|
||||
.scan('message', NULL_TERM_BUFFER)
|
||||
.tap(function tapped(msgData) {
|
||||
if(!msgData.origNode) {
|
||||
end();
|
||||
cb(null);
|
||||
return;
|
||||
}
|
||||
|
||||
// buffer to string conversion
|
||||
[ 'modDateTime', 'toUserName', 'fromUserName', 'subject', ].forEach(function field(f) {
|
||||
msgData[f] = iconv.decode(msgData[f], 'CP437');
|
||||
});
|
||||
|
||||
self.parseFtnMessageBody(msgData.message, function msgBodyParsed(err, msgBody) {
|
||||
//
|
||||
// Now, create a Message object
|
||||
//
|
||||
var msg = new Message( {
|
||||
// AREA FTN -> local conf/area occurs elsewhere
|
||||
toUserName : msgData.toUserName,
|
||||
fromUserName : msgData.fromUserName,
|
||||
subject : msgData.subject,
|
||||
message : msgBody.message.join('\n'), // :TODO: \r\n is better?
|
||||
modTimestamp : ftn.getDateFromFtnDateTime(msgData.modDateTime),
|
||||
meta : self.getMessageMeta(msgBody, msgData),
|
||||
|
||||
|
||||
});
|
||||
|
||||
iterator(msg);
|
||||
//self.emit('message', msg); // :TODO: Placeholder
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
//this.getMessageHeaderBuffer = function(headerInfo)
|
||||
|
||||
this.parseFtnMessages = function(buffer, cb) {
|
||||
var nullTermBuf = new Buffer( [ 0 ] );
|
||||
var fidoMessages = [];
|
||||
|
||||
binary.stream(buffer).loop(function looper(end, vars) {
|
||||
this
|
||||
.word16lu('messageType')
|
||||
.word16lu('origNode')
|
||||
.word16lu('destNode')
|
||||
.word16lu('origNet')
|
||||
.word16lu('destNet')
|
||||
.word8('attrFlags1')
|
||||
.word8('attrFlags2')
|
||||
.word16lu('cost')
|
||||
.scan('modDateTime', nullTermBuf)
|
||||
.scan('toUserName', nullTermBuf)
|
||||
.scan('fromUserName', nullTermBuf)
|
||||
.scan('subject', nullTermBuf)
|
||||
.scan('message', nullTermBuf)
|
||||
.tap(function tapped(msgData) {
|
||||
if(!msgData.origNode) {
|
||||
end();
|
||||
cb(null, fidoMessages);
|
||||
return;
|
||||
}
|
||||
|
||||
// buffer to string conversion
|
||||
// :TODO: What is the real encoding here?
|
||||
[ 'modDateTime', 'toUserName', 'fromUserName', 'subject', ].forEach(function field(f) {
|
||||
msgData[f] = msgData[f].toString();
|
||||
});
|
||||
|
||||
self.parseFtnMessageBody(msgData.message, function msgBodyParsed(err, msgBody) {
|
||||
msgData.message = msgBody;
|
||||
fidoMessages.push(_.clone(msgData));
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
this.extractMesssagesFromPacketBuffer = function(packetBuffer, iterator, cb) {
|
||||
assert(Buffer.isBuffer(packetBuffer));
|
||||
assert(_.isFunction(iterator));
|
||||
|
||||
async.waterfall(
|
||||
[
|
||||
function parseHeader(callback) {
|
||||
self.parseFtnPacketHeader(packetBuffer, function headerParsed(err, packetHeader) {
|
||||
self.packetHeader = packetHeader;
|
||||
callback(err);
|
||||
});
|
||||
},
|
||||
function validateDesinationAddress(callback) {
|
||||
self.localNetworkName = self.getNetworkNameForAddress(self.getPacketHeaderAddress());
|
||||
self.localNetworkName = 'AllowAnyNetworkForDebugging';
|
||||
callback(self.localNetworkName ? null : new Error('Packet not addressed do this system'));
|
||||
},
|
||||
function extractEmbeddedMessages(callback) {
|
||||
// note: packet header is 58 bytes in length
|
||||
self.extractMessages(
|
||||
packetBuffer.slice(58), iterator, function extracted(err) {
|
||||
callback(err);
|
||||
});
|
||||
}
|
||||
],
|
||||
function complete(err) {
|
||||
cb(err);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
this.loadMessagesFromPacketBuffer = function(packetBuffer, cb) {
|
||||
async.waterfall(
|
||||
[
|
||||
function parseHeader(callback) {
|
||||
self.parseFtnPacketHeader(packetBuffer, function headerParsed(err, packetHeader) {
|
||||
self.packetHeader = packetHeader;
|
||||
callback(err);
|
||||
});
|
||||
},
|
||||
function validateDesinationAddress(callback) {
|
||||
self.localNetworkName = self.getNetworkNameForAddress(self.getPacketHeaderAddress());
|
||||
self.localNetworkName = 'AllowAnyNetworkForDebugging';
|
||||
callback(self.localNetworkName ? null : new Error('Packet not addressed do this system'));
|
||||
},
|
||||
function parseMessages(callback) {
|
||||
self.parseFtnMessages(packetBuffer.slice(58), function messagesParsed(err, fidoMessages) {
|
||||
callback(err, fidoMessages);
|
||||
});
|
||||
},
|
||||
function createMessageObjects(fidoMessages, callback) {
|
||||
fidoMessages.forEach(function msg(fmsg) {
|
||||
console.log(fmsg);
|
||||
});
|
||||
}
|
||||
],
|
||||
function complete(err) {
|
||||
cb(err);
|
||||
}
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
//require('util').inherits(FTNMailPacket, MailPacket);
|
||||
|
||||
FTNMailPacket.prototype.parse = function(path, cb) {
|
||||
var self = this;
|
||||
|
||||
async.waterfall(
|
||||
[
|
||||
function readFromFile(callback) {
|
||||
fs.readFile(path, function packetData(err, data) {
|
||||
callback(err, data);
|
||||
});
|
||||
},
|
||||
function extractMessages(data, callback) {
|
||||
self.loadMessagesFromPacketBuffer(data, function extracted(err, messages) {
|
||||
callback(err, messages);
|
||||
});
|
||||
}
|
||||
],
|
||||
function complete(err, messages) {
|
||||
cb(err, messages);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
FTNMailPacket.prototype.read = function(pathOrBuffer, iterator, cb) {
|
||||
var self = this;
|
||||
|
||||
if(_.isString(pathOrBuffer)) {
|
||||
async.waterfall(
|
||||
[
|
||||
function readPacketFile(callback) {
|
||||
fs.readFile(pathOrBuffer, function packetData(err, data) {
|
||||
callback(err, data);
|
||||
});
|
||||
},
|
||||
function extractMessages(data, callback) {
|
||||
self.extractMesssagesFromPacketBuffer(data, iterator, callback);
|
||||
}
|
||||
],
|
||||
cb
|
||||
);
|
||||
} else if(Buffer.isBuffer(pathOrBuffer)) {
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
FTNMailPacket.prototype.write = function(messages, fileName, options) {
|
||||
if(!_.isArray(messages)) {
|
||||
messages = [ messages ];
|
||||
}
|
||||
|
||||
|
||||
|
||||
};
|
||||
|
||||
var ftnPacket = new FTNPacket();
|
||||
var theHeader;
|
||||
var written = false;
|
||||
|
@ -1023,67 +569,23 @@ ftnPacket.read(
|
|||
});
|
||||
|
||||
}
|
||||
|
||||
let address = {
|
||||
zone : 46,
|
||||
net : 1,
|
||||
node : 232,
|
||||
domain : 'l33t.codes',
|
||||
};
|
||||
msg.areaTag = 'agn_bbs';
|
||||
msg.messageId = 1234;
|
||||
console.log(ftn.getMessageIdentifier(msg, address));
|
||||
console.log(ftn.getProductIdentifier())
|
||||
//console.log(ftn.getOrigin(address))
|
||||
console.log(ftn.parseAddress('46:1/232.4@l33t.codes'))
|
||||
console.log(ftn.getUTCTimeZoneOffset())
|
||||
}
|
||||
},
|
||||
function completion(err) {
|
||||
console.log(err);
|
||||
}
|
||||
);
|
||||
|
||||
/*
|
||||
var mailPacket = new FTNMailPacket(
|
||||
{
|
||||
nodeAddresses : {
|
||||
fidoNet : {
|
||||
zone : 46,
|
||||
net : 1,
|
||||
node : 140,
|
||||
point : 0,
|
||||
domain : ''
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
var didWrite = false;
|
||||
mailPacket.read(
|
||||
process.argv[2],
|
||||
//'/home/nuskooler/ownCloud/Projects/ENiGMA½ BBS/FTNPackets/mf/extracted/27000425.pkt',
|
||||
function packetIter(msg) {
|
||||
console.log(msg);
|
||||
if(_.has(msg, 'meta.FtnProperty.ftn_area')) {
|
||||
console.log('AREA: ' + msg.meta.FtnProperty.ftn_area);
|
||||
}
|
||||
|
||||
if(!didWrite) {
|
||||
console.log(mailPacket.packetHeader);
|
||||
console.log('-----------');
|
||||
|
||||
|
||||
didWrite = true;
|
||||
|
||||
let outTest = fs.createWriteStream('/home/nuskooler/Downloads/ftnout/test1.pkt');
|
||||
let buffer = mailPacket.getPacketHeaderBuffer(mailPacket.packetHeader);
|
||||
//mailPacket.write(buffer, msg.packetHeader);
|
||||
outTest.write(buffer);
|
||||
}
|
||||
},
|
||||
function complete(err) {
|
||||
console.log(err);
|
||||
}
|
||||
);
|
||||
*/
|
||||
/*
|
||||
Area Map
|
||||
networkName: {
|
||||
area_tag: conf_name:area_tag_name
|
||||
...
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
mailPacket.parse('/home/nuskooler/ownCloud/Projects/ENiGMA½ BBS/FTNPackets/BAD_BNDL.007', function parsed(err, messages) {
|
||||
console.log(err)
|
||||
});
|
||||
*/
|
233
core/ftn_util.js
233
core/ftn_util.js
|
@ -11,16 +11,37 @@ var fs = require('fs');
|
|||
var util = require('util');
|
||||
var iconv = require('iconv-lite');
|
||||
var moment = require('moment');
|
||||
var createHash = require('crypto').createHash;
|
||||
var uuid = require('node-uuid');
|
||||
var os = require('os');
|
||||
|
||||
var packageJson = require('../package.json');
|
||||
|
||||
// :TODO: Remove "Ftn" from most of these -- it's implied in the module
|
||||
exports.stringFromFTN = stringFromFTN;
|
||||
exports.stringToNullPaddedBuffer = stringToNullPaddedBuffer;
|
||||
exports.getFormattedFTNAddress = getFormattedFTNAddress;
|
||||
exports.createMessageUuid = createMessageUuid;
|
||||
exports.parseAddress = parseAddress;
|
||||
exports.formatAddress = formatAddress;
|
||||
exports.getDateFromFtnDateTime = getDateFromFtnDateTime;
|
||||
exports.getDateTimeString = getDateTimeString;
|
||||
|
||||
exports.getMessageIdentifier = getMessageIdentifier;
|
||||
exports.getProductIdentifier = getProductIdentifier;
|
||||
exports.getUTCTimeZoneOffset = getUTCTimeZoneOffset;
|
||||
exports.getOrigin = getOrigin;
|
||||
|
||||
exports.getQuotePrefix = getQuotePrefix;
|
||||
|
||||
//
|
||||
// Namespace for RFC-4122 name based UUIDs generated from
|
||||
// FTN kludges MSGID + AREA
|
||||
//
|
||||
const ENIGMA_FTN_MSGID_NAMESPACE = uuid.parse('a5c7ae11-420c-4469-a116-0e9a6d8d2654');
|
||||
|
||||
// Up to 5D FTN address RegExp
|
||||
const ENIGMA_FTN_ADDRESS_REGEXP = /^([0-9]+):([0-9]+)(\/[0-9]+)?(\.[0-9]+)?(@[a-z0-9\-\.]+)?$/i;
|
||||
|
||||
// See list here: https://github.com/Mithgol/node-fidonet-jam
|
||||
|
||||
// :TODO: proably move this elsewhere as a general method
|
||||
|
@ -48,6 +69,7 @@ function stringToNullPaddedBuffer(s, bufLen) {
|
|||
//
|
||||
// Convert a FTN style DateTime string to a Date object
|
||||
//
|
||||
// :TODO: Name the next couple methods better - for FTN *packets*
|
||||
function getDateFromFtnDateTime(dateTime) {
|
||||
//
|
||||
// Examples seen in the wild (Working):
|
||||
|
@ -83,42 +105,172 @@ function getDateTimeString(m) {
|
|||
return m.format('DD MMM YY HH:mm:ss');
|
||||
}
|
||||
|
||||
function getFormattedFTNAddress(address, dimensions) {
|
||||
//var addr = util.format('%d:%d', address.zone, address.net);
|
||||
var addr = '{0}:{1}'.format(address.zone, address.net);
|
||||
switch(dimensions) {
|
||||
case 2 :
|
||||
case '2D' :
|
||||
// above
|
||||
break;
|
||||
|
||||
case 3 :
|
||||
case '3D' :
|
||||
addr += '/{0}'.format(address.node);
|
||||
break;
|
||||
|
||||
case 4 :
|
||||
case '4D':
|
||||
addr += '.{0}'.format(address.point || 0); // missing and 0 are equiv for point
|
||||
break;
|
||||
|
||||
case 5 :
|
||||
case '5D' :
|
||||
if(address.domain) {
|
||||
addr += '@{0}'.format(address.domain);
|
||||
function createMessageUuid(ftnMsgId, ftnArea) {
|
||||
//
|
||||
// v5 UUID generation code based on the work here:
|
||||
// https://github.com/download13/uuidv5/blob/master/uuid.js
|
||||
//
|
||||
// Note: CrashMail uses MSGID + AREA, so we go with that as well:
|
||||
// https://github.com/larsks/crashmail/blob/master/crashmail/dupe.c
|
||||
//
|
||||
if(!Buffer.isBuffer(ftnMsgId)) {
|
||||
ftnMsgId = iconv.encode(ftnMsgId, 'CP437');
|
||||
}
|
||||
break;
|
||||
|
||||
ftnArea = ftnArea || ''; // AREA is optional
|
||||
if(!Buffer.isBuffer(ftnArea)) {
|
||||
ftnArea = iconv.encode(ftnArea, 'CP437');
|
||||
}
|
||||
|
||||
const ns = new Buffer(ENIGMA_FTN_MSGID_NAMESPACE);
|
||||
|
||||
let digest = createHash('sha1').update(
|
||||
Buffer.concat([ ns, ftnMsgId, ftnArea ])).digest();
|
||||
|
||||
let u = new Buffer(16);
|
||||
|
||||
// bbbb - bb - bb - bb - bbbbbb
|
||||
digest.copy(u, 0, 0, 4); // time_low
|
||||
digest.copy(u, 4, 4, 6); // time_mid
|
||||
digest.copy(u, 6, 6, 8); // time_hi_and_version
|
||||
|
||||
u[6] = (u[6] & 0x0f) | 0x50; // version, 4 most significant bits are set to version 5 (0101)
|
||||
u[8] = (digest[8] & 0x3f) | 0x80; // clock_seq_hi_and_reserved, 2msb are set to 10
|
||||
u[9] = digest[9];
|
||||
|
||||
digest.copy(u, 10, 10, 16);
|
||||
|
||||
return uuid.unparse(u); // to string
|
||||
}
|
||||
|
||||
function parseAddress(address) {
|
||||
const m = ENIGMA_FTN_ADDRESS_REGEXP.exec(address);
|
||||
|
||||
if(m) {
|
||||
let addr = {
|
||||
zone : parseInt(m[1]),
|
||||
net : parseInt(m[2]),
|
||||
};
|
||||
|
||||
//
|
||||
// substr(1) on the following to remove the
|
||||
// captured prefix
|
||||
//
|
||||
if(m[3]) {
|
||||
addr.node = parseInt(m[3].substr(1));
|
||||
}
|
||||
|
||||
if(m[4]) {
|
||||
addr.point = parseInt(m[4].substr(1));
|
||||
}
|
||||
|
||||
if(m[5]) {
|
||||
addr.domain = m[5].substr(1);
|
||||
}
|
||||
|
||||
return addr;
|
||||
}
|
||||
}
|
||||
|
||||
function formatAddress(address, dimensions) {
|
||||
let addr = `${address.zone}:${address.net}`;
|
||||
|
||||
// allow for e.g. '4D' or 5
|
||||
const dim = parseInt(dimensions.toString()[0]);
|
||||
|
||||
if(dim >= 3) {
|
||||
addr += `/${address.node}`;
|
||||
}
|
||||
|
||||
// missing & .0 are equiv for point
|
||||
if(dim >= 4 && address.point) {
|
||||
addr += `.${addresss.point}`;
|
||||
}
|
||||
|
||||
if(5 === dim && address.domain) {
|
||||
addr += `@${address.domain.toLowerCase()}`;
|
||||
}
|
||||
|
||||
return addr;
|
||||
}
|
||||
|
||||
function getFtnMessageSerialNumber(messageId) {
|
||||
return ((Math.floor((Date.now() - Date.UTC(2015, 1, 1)) / 1000) + messageId)).toString(16);
|
||||
function getMessageSerialNumber(message) {
|
||||
return ('00000000' + ((Math.floor((Date.now() - Date.UTC(2016, 1, 1)) / 1000) +
|
||||
message.messageId)).toString(16)).substr(-8);
|
||||
}
|
||||
|
||||
//
|
||||
// Return a FTS-0009.001 compliant MSGID value given a message
|
||||
// See http://ftsc.org/docs/fts-0009.001
|
||||
//
|
||||
// "A MSGID line consists of the string "^AMSGID:" (where ^A is a
|
||||
// control-A (hex 01) and the double-quotes are not part of the
|
||||
// string), followed by a space, the address of the originating
|
||||
// system, and a serial number unique to that message on the
|
||||
// originating system, i.e.:
|
||||
//
|
||||
// ^AMSGID: origaddr serialno
|
||||
//
|
||||
// The originating address should be specified in a form that
|
||||
// constitutes a valid return address for the originating network.
|
||||
// If the originating address is enclosed in double-quotes, the
|
||||
// entire string between the beginning and ending double-quotes is
|
||||
// considered to be the orginating address. A double-quote character
|
||||
// within a quoted address is represented by by two consecutive
|
||||
// double-quote characters. The serial number may be any eight
|
||||
// character hexadecimal number, as long as it is unique - no two
|
||||
// messages from a given system may have the same serial number
|
||||
// within a three years. The manner in which this serial number is
|
||||
// generated is left to the implementor."
|
||||
//
|
||||
//
|
||||
// Examples & Implementations
|
||||
//
|
||||
// Synchronet: <msgNum>.<conf+area>@<ftnAddr> <serial>
|
||||
// 2606.agora-agn_tst@46:1/142 19609217
|
||||
//
|
||||
// Mystic: <ftnAddress> <serial>
|
||||
// 46:3/102 46686263
|
||||
//
|
||||
// ENiGMA½: <messageId>.<areaTag>@<5dFtnAddress> <serial>
|
||||
//
|
||||
function getMessageIdentifier(message, address) {
|
||||
return `${message.messageId}.${message.areaTag.toLowerCase()}@${formatAddress(address, '5D')} ${getMessageSerialNumber(message)}`;
|
||||
}
|
||||
|
||||
//
|
||||
// Return a FSC-0046.005 Product Identifier or "PID"
|
||||
// http://ftsc.org/docs/fsc-0046.005
|
||||
//
|
||||
function getProductIdentifier() {
|
||||
const version = packageJson.version
|
||||
.replace(/\-/g, '.')
|
||||
.replace(/alpha/,'a')
|
||||
.replace(/beta/,'b');
|
||||
|
||||
const nodeVer = process.version.substr(1); // remove 'v' prefix
|
||||
|
||||
return `ENiGMA1/2 ${version} (${os.platform()}; ${os.arch()}; ${nodeVer})`;
|
||||
}
|
||||
|
||||
//
|
||||
// Return a FSC-0030.001 compliant (http://ftsc.org/docs/fsc-0030.001) MESSAGE-ID
|
||||
//
|
||||
// <unique-part@domain-name>
|
||||
//
|
||||
// :TODO: not implemented to spec at all yet :)
|
||||
function getFTNMessageID(messageId, areaId) {
|
||||
return messageId + '.' + areaId + '@' + getFTNAddress() + ' ' + getFTNMessageSerialNumber(messageId)
|
||||
return messageId + '.' + areaId + '@' + getFTNAddress() + ' ' + getMessageSerialNumber(messageId)
|
||||
}
|
||||
|
||||
//
|
||||
// Return a FRL-1004 style time zone offset for a
|
||||
// 'TZUTC' kludge line
|
||||
//
|
||||
// http://ftsc.org/docs/frl-1004.002
|
||||
//
|
||||
function getUTCTimeZoneOffset() {
|
||||
return moment().format('ZZ').replace(/\+/, '');
|
||||
}
|
||||
|
||||
// Get a FSC-0032 style quote prefixes
|
||||
|
@ -127,25 +279,14 @@ function getQuotePrefix(name) {
|
|||
return ' ' + name[0].toUpperCase() + name[1].toLowerCase() + '> ';
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Specs:
|
||||
// * http://ftsc.org/docs/fts-0009.001
|
||||
// *
|
||||
//
|
||||
function getFtnMsgIdKludgeLine(origAddress, messageId) {
|
||||
if(_.isObject(origAddress)) {
|
||||
origAddress = getFormattedFTNAddress(origAddress, '5D');
|
||||
}
|
||||
|
||||
return '\x01MSGID: ' + origAddress + ' ' + getFtnMessageSerialNumber(messageId);
|
||||
}
|
||||
|
||||
|
||||
function getFTNOriginLine() {
|
||||
//
|
||||
// Specs:
|
||||
// Return a FTS-0004 Origin line
|
||||
// http://ftsc.org/docs/fts-0004.001
|
||||
//
|
||||
return ' * Origin: ' + Config.general.boardName + '(' + getFidoNetAddress() + ')';
|
||||
function getOrigin(address) {
|
||||
const origin = _.has(Config.messageNetworks.originName) ?
|
||||
Config.messageNetworks.originName :
|
||||
Config.general.boardName;
|
||||
|
||||
return ` * Origin: ${origin} (${formatAddress(address, '5D')})`;
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ function Message(options) {
|
|||
|
||||
this.messageId = options.messageId || 0; // always generated @ persist
|
||||
this.areaTag = options.areaTag || Message.WellKnownAreaTags.Invalid;
|
||||
this.uuid = uuid.v1();
|
||||
this.uuid = options.uuid || uuid.v1();
|
||||
this.replyToMsgId = options.replyToMsgId || 0;
|
||||
this.toUserName = options.toUserName || '';
|
||||
this.fromUserName = options.fromUserName || '';
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
/* jslint node: true */
|
||||
'use strict';
|
||||
|
||||
// ENiGMA½
|
||||
var PluginModule = require('./plugin_module.js').PluginModule;
|
||||
|
||||
exports.MessageNetworkModule = MessageNetworkModule;
|
||||
|
||||
function MessageNetworkModule() {
|
||||
PluginModule.call(this);
|
||||
}
|
||||
|
||||
require('util').inherits(MessageNetworkModule, PluginModule);
|
||||
|
||||
MessageNetworkModule.prototype.startup = function(cb) {
|
||||
cb(null);
|
||||
};
|
||||
|
||||
MessageNetworkModule.prototype.shutdown = function(cb) {
|
||||
cb(null);
|
||||
};
|
||||
|
||||
MessageNetworkModule.prototype.record = function(message, cb) {
|
||||
cb(null);
|
||||
};
|
|
@ -1,27 +0,0 @@
|
|||
/* jslint node: true */
|
||||
'use strict';
|
||||
|
||||
// ENiGMA½
|
||||
var MessageNetworkModule = require('./msg_network_module.js').MessageNetworkModule;
|
||||
|
||||
function FTNMessageNetworkModule() {
|
||||
MessageNetworkModule.call(this);
|
||||
}
|
||||
|
||||
require('util').inherits(FTNMessageNetworkModule, MessageNetworkModule);
|
||||
|
||||
FTNMessageNetworkModule.prototype.startup = function(cb) {
|
||||
cb(null);
|
||||
};
|
||||
|
||||
FTNMessageNetworkModule.prototype.shutdown = function(cb) {
|
||||
cb(null);
|
||||
};
|
||||
|
||||
FTNMessageNetworkModule.prototype.record = function(message, cb) {
|
||||
cb(null);
|
||||
|
||||
// :TODO: should perhaps record in batches - e.g. start an event, record
|
||||
// to temp location until time is hit or N achieved such that if multiple
|
||||
// messages are being created a .FTN file is not made for each one
|
||||
};
|
|
@ -0,0 +1,25 @@
|
|||
/* jslint node: true */
|
||||
'use strict';
|
||||
|
||||
// ENiGMA½
|
||||
var PluginModule = require('./plugin_module.js').PluginModule;
|
||||
|
||||
exports.MessageScanTossModule = MessageScanTossModule;
|
||||
|
||||
function MessageScanTossModule() {
|
||||
PluginModule.call(this);
|
||||
}
|
||||
|
||||
require('util').inherits(MessageScanTossModule, PluginModule);
|
||||
|
||||
MessageScanTossModule.prototype.startup = function(cb) {
|
||||
cb(null);
|
||||
};
|
||||
|
||||
MessageScanTossModule.prototype.shutdown = function(cb) {
|
||||
cb(null);
|
||||
};
|
||||
|
||||
MessageScanTossModule.prototype.record = function(message, cb) {
|
||||
cb(null);
|
||||
};
|
|
@ -6,11 +6,11 @@ var iconv = require('iconv-lite');
|
|||
|
||||
exports.readSAUCE = readSAUCE;
|
||||
|
||||
|
||||
const SAUCE_SIZE = 128;
|
||||
const SAUCE_ID = new Buffer([0x53, 0x41, 0x55, 0x43, 0x45]); // 'SAUCE'
|
||||
const COMNT_ID = new Buffer([0x43, 0x4f, 0x4d, 0x4e, 0x54]); // 'COMNT'
|
||||
|
||||
exports.SAUCE_SIZE = SAUCE_SIZE;
|
||||
// :TODO: SAUCE should be a class
|
||||
// - with getFontName()
|
||||
// - ...other methods
|
||||
|
@ -19,6 +19,8 @@ const COMNT_ID = new Buffer([0x43, 0x4f, 0x4d, 0x4e, 0x54]); // 'COMNT'
|
|||
// See
|
||||
// http://www.acid.org/info/sauce/sauce.htm
|
||||
//
|
||||
const SAUCE_VALID_DATA_TYPES = [0, 1, 2, 3, 4, 5, 6, 7, 8 ];
|
||||
|
||||
function readSAUCE(data, cb) {
|
||||
if(data.length < SAUCE_SIZE) {
|
||||
cb(new Error('No SAUCE record present'));
|
||||
|
@ -59,6 +61,11 @@ function readSAUCE(data, cb) {
|
|||
return;
|
||||
}
|
||||
|
||||
if(-1 === SAUCE_VALID_DATA_TYPES.indexOf(vars.dataType)) {
|
||||
cb(new Error('Unsupported SAUCE DataType: ' + vars.dataType));
|
||||
return;
|
||||
}
|
||||
|
||||
var sauce = {
|
||||
id : iconv.decode(vars.id, 'cp437'),
|
||||
version : iconv.decode(vars.version, 'cp437').trim(),
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
/* jslint node: true */
|
||||
'use strict';
|
||||
|
||||
// ENiGMA½
|
||||
var MessageScanTossModule = require('../scan_toss_module.js').MessageScanTossModule;
|
||||
var Config = require('../config.js').config;
|
||||
|
||||
exports.moduleInfo = {
|
||||
name : 'FTN',
|
||||
desc : 'FidoNet Style Message Scanner/Tosser',
|
||||
author : 'NuSkooler',
|
||||
};
|
||||
|
||||
exports.getModule = FTNMessageScanTossModule;
|
||||
|
||||
function FTNMessageScanTossModule() {
|
||||
MessageScanTossModule.call(this);
|
||||
|
||||
this.config = Config.scannerTossers.ftn_bso;
|
||||
|
||||
|
||||
}
|
||||
|
||||
require('util').inherits(FTNMessageScanTossModule, MessageScanTossModule);
|
||||
|
||||
FTNMessageScanTossModule.prototype.startup = function(cb) {
|
||||
cb(null);
|
||||
};
|
||||
|
||||
FTNMessageScanTossModule.prototype.shutdown = function(cb) {
|
||||
cb(null);
|
||||
};
|
||||
|
||||
FTNMessageScanTossModule.prototype.record = function(message, cb) {
|
||||
|
||||
|
||||
cb(null);
|
||||
|
||||
// :TODO: should perhaps record in batches - e.g. start an event, record
|
||||
// to temp location until time is hit or N achieved such that if multiple
|
||||
// messages are being created a .FTN file is not made for each one
|
||||
};
|
|
@ -523,7 +523,8 @@ function displayThemedPause(options, cb) {
|
|||
if(options.clearPrompt) {
|
||||
if(artInfo.startRow && artInfo.height) {
|
||||
options.client.term.rawWrite(ansi.goto(artInfo.startRow, 1));
|
||||
// :TODO: This will not work with NetRunner:
|
||||
|
||||
// Note: Does not work properly in NetRunner < 2.0b17:
|
||||
options.client.term.rawWrite(ansi.deleteLine(artInfo.height));
|
||||
} else {
|
||||
options.client.term.rawWrite(ansi.eraseLine(1))
|
||||
|
|
Loading…
Reference in New Issue