Major commit for new message network WIP
This commit is contained in:
parent
6750c05f07
commit
317af8419a
|
@ -65,7 +65,7 @@ Please see the [Quickstart](docs/index.md#quickstart)
|
||||||
## License
|
## License
|
||||||
Released under the [BSD 2-clause](https://opensource.org/licenses/BSD-2-Clause) license:
|
Released under the [BSD 2-clause](https://opensource.org/licenses/BSD-2-Clause) license:
|
||||||
|
|
||||||
Copyright (c) 2015, Bryan D. Ashby
|
Copyright (c) 2015-2016, Bryan D. Ashby
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
|
|
@ -804,16 +804,13 @@ module.exports = (function() {
|
||||||
return !isNaN(value) && user.getAge() >= value;
|
return !isNaN(value) && user.getAge() >= value;
|
||||||
},
|
},
|
||||||
AS : function accountStatus() {
|
AS : function accountStatus() {
|
||||||
|
if(!_.isArray(value)) {
|
||||||
if(_.isNumber(value)) {
|
|
||||||
value = [ value ];
|
value = [ value ];
|
||||||
}
|
}
|
||||||
|
|
||||||
assert(_.isArray(value));
|
const userAccountStatus = parseInt(user.properties.account_status, 10);
|
||||||
|
value = value.map(n => parseInt(n, 10)); // ensure we have integers
|
||||||
return _.findIndex(value, function cmp(accStatus) {
|
return value.indexOf(userAccountStatus) > -1;
|
||||||
return parseInt(accStatus, 10) === parseInt(user.properties.account_status, 10);
|
|
||||||
}) > -1;
|
|
||||||
},
|
},
|
||||||
EC : function isEncoding() {
|
EC : function isEncoding() {
|
||||||
switch(value) {
|
switch(value) {
|
||||||
|
@ -842,7 +839,7 @@ module.exports = (function() {
|
||||||
// :TODO: implement me!!
|
// :TODO: implement me!!
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
SC : function isSecerConnection() {
|
SC : function isSecureConnection() {
|
||||||
return client.session.isSecure;
|
return client.session.isSecure;
|
||||||
},
|
},
|
||||||
ML : function minutesLeft() {
|
ML : function minutesLeft() {
|
||||||
|
@ -870,16 +867,20 @@ module.exports = (function() {
|
||||||
return !isNaN(value) && client.term.termWidth >= value;
|
return !isNaN(value) && client.term.termWidth >= value;
|
||||||
},
|
},
|
||||||
ID : function isUserId(value) {
|
ID : function isUserId(value) {
|
||||||
return user.userId === value;
|
if(!_.isArray(value)) {
|
||||||
|
value = [ value ];
|
||||||
|
}
|
||||||
|
|
||||||
|
value = value.map(n => parseInt(n, 10)); // ensure we have integers
|
||||||
|
return value.indexOf(user.userId) > -1;
|
||||||
},
|
},
|
||||||
WD : function isOneOfDayOfWeek() {
|
WD : function isOneOfDayOfWeek() {
|
||||||
// :TODO: return true if DoW
|
if(!_.isArray(value)) {
|
||||||
if(_.isNumber(value)) {
|
value = [ value ];
|
||||||
|
|
||||||
} else if(_.isArray(value)) {
|
|
||||||
|
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
|
value = value.map(n => parseInt(n, 10)); // ensure we have integers
|
||||||
|
return value.indexOf(new Date().getDay()) > -1;
|
||||||
},
|
},
|
||||||
MM : function isMinutesPastMidnight() {
|
MM : function isMinutesPastMidnight() {
|
||||||
// :TODO: return true if value is >= minutes past midnight sys time
|
// :TODO: return true if value is >= minutes past midnight sys time
|
||||||
|
|
|
@ -7,8 +7,13 @@ var acsParser = require('./acs_parser.js');
|
||||||
var _ = require('lodash');
|
var _ = require('lodash');
|
||||||
var assert = require('assert');
|
var assert = require('assert');
|
||||||
|
|
||||||
|
exports.checkAcs = checkAcs;
|
||||||
exports.getConditionalValue = getConditionalValue;
|
exports.getConditionalValue = getConditionalValue;
|
||||||
|
|
||||||
|
function checkAcs(client, acsString) {
|
||||||
|
return acsParser.parse(acsString, { client : client } );
|
||||||
|
}
|
||||||
|
|
||||||
function getConditionalValue(client, condArray, memberName) {
|
function getConditionalValue(client, condArray, memberName) {
|
||||||
assert(_.isObject(client));
|
assert(_.isObject(client));
|
||||||
assert(_.isArray(condArray));
|
assert(_.isArray(condArray));
|
||||||
|
|
157
core/art.js
157
core/art.js
|
@ -12,6 +12,7 @@ var events = require('events');
|
||||||
var util = require('util');
|
var util = require('util');
|
||||||
var ansi = require('./ansi_term.js');
|
var ansi = require('./ansi_term.js');
|
||||||
var aep = require('./ansi_escape_parser.js');
|
var aep = require('./ansi_escape_parser.js');
|
||||||
|
var sauce = require('./sauce.js');
|
||||||
|
|
||||||
var _ = require('lodash');
|
var _ = require('lodash');
|
||||||
|
|
||||||
|
@ -20,10 +21,6 @@ exports.getArtFromPath = getArtFromPath;
|
||||||
exports.display = display;
|
exports.display = display;
|
||||||
exports.defaultEncodingFromExtension = defaultEncodingFromExtension;
|
exports.defaultEncodingFromExtension = defaultEncodingFromExtension;
|
||||||
|
|
||||||
var SAUCE_SIZE = 128;
|
|
||||||
var SAUCE_ID = new Buffer([0x53, 0x41, 0x55, 0x43, 0x45]); // 'SAUCE'
|
|
||||||
var COMNT_ID = new Buffer([0x43, 0x4f, 0x4d, 0x4e, 0x54]); // 'COMNT'
|
|
||||||
|
|
||||||
// :TODO: Return MCI code information
|
// :TODO: Return MCI code information
|
||||||
// :TODO: process SAUCE comments
|
// :TODO: process SAUCE comments
|
||||||
// :TODO: return font + font mapped information from SAUCE
|
// :TODO: return font + font mapped information from SAUCE
|
||||||
|
@ -43,156 +40,6 @@ var SUPPORTED_ART_TYPES = {
|
||||||
// :TODO: extension for topaz ansi/ascii.
|
// :TODO: extension for topaz ansi/ascii.
|
||||||
};
|
};
|
||||||
|
|
||||||
//
|
|
||||||
// See
|
|
||||||
// http://www.acid.org/info/sauce/sauce.htm
|
|
||||||
//
|
|
||||||
// :TODO: Move all SAUCE stuff to sauce.js
|
|
||||||
function readSAUCE(data, cb) {
|
|
||||||
if(data.length < SAUCE_SIZE) {
|
|
||||||
cb(new Error('No SAUCE record present'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var offset = data.length - SAUCE_SIZE;
|
|
||||||
var sauceRec = data.slice(offset);
|
|
||||||
|
|
||||||
binary.parse(sauceRec)
|
|
||||||
.buffer('id', 5)
|
|
||||||
.buffer('version', 2)
|
|
||||||
.buffer('title', 35)
|
|
||||||
.buffer('author', 20)
|
|
||||||
.buffer('group', 20)
|
|
||||||
.buffer('date', 8)
|
|
||||||
.word32lu('fileSize')
|
|
||||||
.word8('dataType')
|
|
||||||
.word8('fileType')
|
|
||||||
.word16lu('tinfo1')
|
|
||||||
.word16lu('tinfo2')
|
|
||||||
.word16lu('tinfo3')
|
|
||||||
.word16lu('tinfo4')
|
|
||||||
.word8('numComments')
|
|
||||||
.word8('flags')
|
|
||||||
.buffer('tinfos', 22) // SAUCE 00.5
|
|
||||||
.tap(function onVars(vars) {
|
|
||||||
|
|
||||||
if(!SAUCE_ID.equals(vars.id)) {
|
|
||||||
cb(new Error('No SAUCE record present'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var ver = iconv.decode(vars.version, 'cp437');
|
|
||||||
|
|
||||||
if('00' !== ver) {
|
|
||||||
cb(new Error('Unsupported SAUCE version: ' + ver));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var sauce = {
|
|
||||||
id : iconv.decode(vars.id, 'cp437'),
|
|
||||||
version : iconv.decode(vars.version, 'cp437').trim(),
|
|
||||||
title : iconv.decode(vars.title, 'cp437').trim(),
|
|
||||||
author : iconv.decode(vars.author, 'cp437').trim(),
|
|
||||||
group : iconv.decode(vars.group, 'cp437').trim(),
|
|
||||||
date : iconv.decode(vars.date, 'cp437').trim(),
|
|
||||||
fileSize : vars.fileSize,
|
|
||||||
dataType : vars.dataType,
|
|
||||||
fileType : vars.fileType,
|
|
||||||
tinfo1 : vars.tinfo1,
|
|
||||||
tinfo2 : vars.tinfo2,
|
|
||||||
tinfo3 : vars.tinfo3,
|
|
||||||
tinfo4 : vars.tinfo4,
|
|
||||||
numComments : vars.numComments,
|
|
||||||
flags : vars.flags,
|
|
||||||
tinfos : vars.tinfos,
|
|
||||||
};
|
|
||||||
|
|
||||||
var dt = SAUCE_DATA_TYPES[sauce.dataType];
|
|
||||||
if(dt && dt.parser) {
|
|
||||||
sauce[dt.name] = dt.parser(sauce);
|
|
||||||
}
|
|
||||||
|
|
||||||
cb(null, sauce);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// :TODO: These need completed:
|
|
||||||
var SAUCE_DATA_TYPES = {};
|
|
||||||
SAUCE_DATA_TYPES[0] = { name : 'None' };
|
|
||||||
SAUCE_DATA_TYPES[1] = { name : 'Character', parser : parseCharacterSAUCE };
|
|
||||||
SAUCE_DATA_TYPES[2] = 'Bitmap';
|
|
||||||
SAUCE_DATA_TYPES[3] = 'Vector';
|
|
||||||
SAUCE_DATA_TYPES[4] = 'Audio';
|
|
||||||
SAUCE_DATA_TYPES[5] = 'BinaryText';
|
|
||||||
SAUCE_DATA_TYPES[6] = 'XBin';
|
|
||||||
SAUCE_DATA_TYPES[7] = 'Archive';
|
|
||||||
SAUCE_DATA_TYPES[8] = 'Executable';
|
|
||||||
|
|
||||||
var SAUCE_CHARACTER_FILE_TYPES = {};
|
|
||||||
SAUCE_CHARACTER_FILE_TYPES[0] = 'ASCII';
|
|
||||||
SAUCE_CHARACTER_FILE_TYPES[1] = 'ANSi';
|
|
||||||
SAUCE_CHARACTER_FILE_TYPES[2] = 'ANSiMation';
|
|
||||||
SAUCE_CHARACTER_FILE_TYPES[3] = 'RIP script';
|
|
||||||
SAUCE_CHARACTER_FILE_TYPES[4] = 'PCBoard';
|
|
||||||
SAUCE_CHARACTER_FILE_TYPES[5] = 'Avatar';
|
|
||||||
SAUCE_CHARACTER_FILE_TYPES[6] = 'HTML';
|
|
||||||
SAUCE_CHARACTER_FILE_TYPES[7] = 'Source';
|
|
||||||
SAUCE_CHARACTER_FILE_TYPES[8] = 'TundraDraw';
|
|
||||||
|
|
||||||
//
|
|
||||||
// Map of SAUCE font -> encoding hint
|
|
||||||
//
|
|
||||||
// Note that this is the same mapping that x84 uses. Be compatible!
|
|
||||||
//
|
|
||||||
var SAUCE_FONT_TO_ENCODING_HINT = {
|
|
||||||
'Amiga MicroKnight' : 'amiga',
|
|
||||||
'Amiga MicroKnight+' : 'amiga',
|
|
||||||
'Amiga mOsOul' : 'amiga',
|
|
||||||
'Amiga P0T-NOoDLE' : 'amiga',
|
|
||||||
'Amiga Topaz 1' : 'amiga',
|
|
||||||
'Amiga Topaz 1+' : 'amiga',
|
|
||||||
'Amiga Topaz 2' : 'amiga',
|
|
||||||
'Amiga Topaz 2+' : 'amiga',
|
|
||||||
'Atari ATASCII' : 'atari',
|
|
||||||
'IBM EGA43' : 'cp437',
|
|
||||||
'IBM EGA' : 'cp437',
|
|
||||||
'IBM VGA25G' : 'cp437',
|
|
||||||
'IBM VGA50' : 'cp437',
|
|
||||||
'IBM VGA' : 'cp437',
|
|
||||||
};
|
|
||||||
|
|
||||||
['437', '720', '737', '775', '819', '850', '852', '855', '857', '858',
|
|
||||||
'860', '861', '862', '863', '864', '865', '866', '869', '872'].forEach(function onPage(page) {
|
|
||||||
var codec = 'cp' + page;
|
|
||||||
SAUCE_FONT_TO_ENCODING_HINT['IBM EGA43 ' + page] = codec;
|
|
||||||
SAUCE_FONT_TO_ENCODING_HINT['IBM EGA ' + page] = codec;
|
|
||||||
SAUCE_FONT_TO_ENCODING_HINT['IBM VGA25g ' + page] = codec;
|
|
||||||
SAUCE_FONT_TO_ENCODING_HINT['IBM VGA50 ' + page] = codec;
|
|
||||||
SAUCE_FONT_TO_ENCODING_HINT['IBM VGA ' + page] = codec;
|
|
||||||
});
|
|
||||||
|
|
||||||
function parseCharacterSAUCE(sauce) {
|
|
||||||
var result = {};
|
|
||||||
|
|
||||||
result.fileType = SAUCE_CHARACTER_FILE_TYPES[sauce.fileType] || 'Unknown';
|
|
||||||
|
|
||||||
if(sauce.fileType === 0 || sauce.fileType === 1 || sauce.fileType === 2) {
|
|
||||||
// convience: create ansiFlags
|
|
||||||
sauce.ansiFlags = sauce.flags;
|
|
||||||
|
|
||||||
var i = 0;
|
|
||||||
while(i < sauce.tinfos.length && sauce.tinfos[i] !== 0x00) {
|
|
||||||
++i;
|
|
||||||
}
|
|
||||||
var fontName = iconv.decode(sauce.tinfos.slice(0, i), 'cp437');
|
|
||||||
if(fontName.length > 0) {
|
|
||||||
result.fontName = fontName;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getFontNameFromSAUCE(sauce) {
|
function getFontNameFromSAUCE(sauce) {
|
||||||
if(sauce.Character) {
|
if(sauce.Character) {
|
||||||
return sauce.Character.fontName;
|
return sauce.Character.fontName;
|
||||||
|
@ -249,7 +96,7 @@ function getArtFromPath(path, options, cb) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if(options.readSauce === true) {
|
if(options.readSauce === true) {
|
||||||
readSAUCE(data, function onSauce(err, sauce) {
|
sauce.readSAUCE(data, function onSauce(err, sauce) {
|
||||||
if(err) {
|
if(err) {
|
||||||
cb(null, getResult());
|
cb(null, getResult());
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var Config = require('./config.js').config;
|
var Config = require('./config.js').config;
|
||||||
var theme = require('./theme.js');
|
|
||||||
|
|
||||||
var _ = require('lodash');
|
var _ = require('lodash');
|
||||||
var assert = require('assert');
|
var assert = require('assert');
|
||||||
|
|
10
core/bbs.js
10
core/bbs.js
|
@ -1,6 +1,9 @@
|
||||||
/* jslint node: true */
|
/* jslint node: true */
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
//var SegfaultHandler = require('segfault-handler');
|
||||||
|
//SegfaultHandler.registerHandler('enigma-bbs-segfault.log');
|
||||||
|
|
||||||
// ENiGMA½
|
// ENiGMA½
|
||||||
var conf = require('./config.js');
|
var conf = require('./config.js');
|
||||||
var logger = require('./logger.js');
|
var logger = require('./logger.js');
|
||||||
|
@ -21,7 +24,7 @@ function bbsMain() {
|
||||||
async.waterfall(
|
async.waterfall(
|
||||||
[
|
[
|
||||||
function processArgs(callback) {
|
function processArgs(callback) {
|
||||||
var args = parseArgs();
|
const args = parseArgs();
|
||||||
|
|
||||||
var configPath;
|
var configPath;
|
||||||
|
|
||||||
|
@ -37,8 +40,7 @@ function bbsMain() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var configPathSupplied = _.isString(configPath);
|
callback(null, configPath || conf.getDefaultPath(), _.isString(configPath));
|
||||||
callback(null, configPath || conf.getDefaultPath(), configPathSupplied);
|
|
||||||
},
|
},
|
||||||
function initConfig(configPath, configPathSupplied, callback) {
|
function initConfig(configPath, configPathSupplied, callback) {
|
||||||
conf.init(configPath, function configInit(err) {
|
conf.init(configPath, function configInit(err) {
|
||||||
|
@ -117,7 +119,7 @@ function initialize(cb) {
|
||||||
|
|
||||||
process.exit();
|
process.exit();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Init some extensions
|
// Init some extensions
|
||||||
require('string-format').extend(String.prototype, require('./string_util.js').stringFormatExtensions);
|
require('string-format').extend(String.prototype, require('./string_util.js').stringFormatExtensions);
|
||||||
|
|
||||||
|
|
|
@ -8,10 +8,37 @@ var paths = require('path');
|
||||||
var async = require('async');
|
var async = require('async');
|
||||||
var _ = require('lodash');
|
var _ = require('lodash');
|
||||||
var hjson = require('hjson');
|
var hjson = require('hjson');
|
||||||
|
var assert = require('assert');
|
||||||
|
|
||||||
exports.init = init;
|
exports.init = init;
|
||||||
exports.getDefaultPath = getDefaultPath;
|
exports.getDefaultPath = getDefaultPath;
|
||||||
|
|
||||||
|
function hasMessageConferenceAndArea(config) {
|
||||||
|
assert(_.isObject(config.messageConferences)); // we create one ourself!
|
||||||
|
|
||||||
|
const nonInternalConfs = Object.keys(config.messageConferences).filter(confTag => {
|
||||||
|
return 'system_internal' !== confTag;
|
||||||
|
});
|
||||||
|
|
||||||
|
if(0 === nonInternalConfs.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// :TODO: there is likely a better/cleaner way of doing this
|
||||||
|
|
||||||
|
var result = false;
|
||||||
|
_.forEach(nonInternalConfs, confTag => {
|
||||||
|
if(_.has(config.messageConferences[confTag], 'areas') &&
|
||||||
|
Object.keys(config.messageConferences[confTag].areas) > 0)
|
||||||
|
{
|
||||||
|
result = true;
|
||||||
|
return false; // stop iteration
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
function init(configPath, cb) {
|
function init(configPath, cb) {
|
||||||
async.waterfall(
|
async.waterfall(
|
||||||
[
|
[
|
||||||
|
@ -48,18 +75,13 @@ function init(configPath, cb) {
|
||||||
//
|
//
|
||||||
// Various sections must now exist in config
|
// Various sections must now exist in config
|
||||||
//
|
//
|
||||||
if(!_.has(mergedConfig, 'messages.areas.') ||
|
if(hasMessageConferenceAndArea(mergedConfig)) {
|
||||||
!_.isArray(mergedConfig.messages.areas) ||
|
var msgAreasErr = new Error('Please create at least one message conference and area!');
|
||||||
0 === mergedConfig.messages.areas.length ||
|
|
||||||
!_.isString(mergedConfig.messages.areas[0].name))
|
|
||||||
{
|
|
||||||
var msgAreasErr = new Error('Please create at least one message area');
|
|
||||||
msgAreasErr.code = 'EBADCONFIG';
|
msgAreasErr.code = 'EBADCONFIG';
|
||||||
callback(msgAreasErr);
|
callback(msgAreasErr);
|
||||||
return;
|
} else {
|
||||||
}
|
callback(null, mergedConfig);
|
||||||
|
}
|
||||||
callback(null, mergedConfig);
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
function complete(err, mergedConfig) {
|
function complete(err, mergedConfig) {
|
||||||
|
@ -150,6 +172,7 @@ function getDefaultConfig() {
|
||||||
paths : {
|
paths : {
|
||||||
mods : paths.join(__dirname, './../mods/'),
|
mods : paths.join(__dirname, './../mods/'),
|
||||||
servers : paths.join(__dirname, './servers/'),
|
servers : paths.join(__dirname, './servers/'),
|
||||||
|
msgNetworks : paths.join(__dirname, './msg_networks/'),
|
||||||
art : paths.join(__dirname, './../mods/art/'),
|
art : paths.join(__dirname, './../mods/art/'),
|
||||||
themes : paths.join(__dirname, './../mods/themes/'),
|
themes : paths.join(__dirname, './../mods/themes/'),
|
||||||
logs : paths.join(__dirname, './../logs/'), // :TODO: set up based on system, e.g. /var/logs/enigmabbs or such
|
logs : paths.join(__dirname, './../logs/'), // :TODO: set up based on system, e.g. /var/logs/enigmabbs or such
|
||||||
|
@ -183,6 +206,25 @@ function getDefaultConfig() {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
messageConferences : {
|
||||||
|
system_internal : {
|
||||||
|
name : 'System Internal',
|
||||||
|
desc : 'Built in conference for private messages, bulletins, etc.',
|
||||||
|
|
||||||
|
areas : {
|
||||||
|
private_mail : {
|
||||||
|
name : 'Private Mail',
|
||||||
|
desc : 'Private user to user mail/email',
|
||||||
|
},
|
||||||
|
|
||||||
|
local_bulletin : {
|
||||||
|
name : 'System Bulletins',
|
||||||
|
desc : 'Bulletin messages for all users',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
messages : {
|
messages : {
|
||||||
areas : [
|
areas : [
|
||||||
{ name : 'private_mail', desc : 'Private Email', groups : [ 'users' ] }
|
{ name : 'private_mail', desc : 'Private Email', groups : [ 'users' ] }
|
||||||
|
|
|
@ -132,7 +132,7 @@ function createMessageBaseTables() {
|
||||||
dbs.message.run(
|
dbs.message.run(
|
||||||
'CREATE TABLE IF NOT EXISTS message (' +
|
'CREATE TABLE IF NOT EXISTS message (' +
|
||||||
' message_id INTEGER PRIMARY KEY,' +
|
' message_id INTEGER PRIMARY KEY,' +
|
||||||
' area_name VARCHAR NOT NULL,' +
|
' area_tag VARCHAR NOT NULL,' +
|
||||||
' message_uuid VARCHAR(36) NOT NULL,' +
|
' message_uuid VARCHAR(36) NOT NULL,' +
|
||||||
' reply_to_message_id INTEGER,' +
|
' reply_to_message_id INTEGER,' +
|
||||||
' to_user_name VARCHAR NOT NULL,' +
|
' to_user_name VARCHAR NOT NULL,' +
|
||||||
|
@ -198,9 +198,9 @@ function createMessageBaseTables() {
|
||||||
dbs.message.run(
|
dbs.message.run(
|
||||||
'CREATE TABLE IF NOT EXISTS user_message_area_last_read (' +
|
'CREATE TABLE IF NOT EXISTS user_message_area_last_read (' +
|
||||||
' user_id INTEGER NOT NULL,' +
|
' user_id INTEGER NOT NULL,' +
|
||||||
' area_name VARCHAR NOT NULL,' +
|
' area_tag VARCHAR NOT NULL,' +
|
||||||
' message_id INTEGER NOT NULL,' +
|
' message_id INTEGER NOT NULL,' +
|
||||||
' UNIQUE(user_id, area_name)' +
|
' UNIQUE(user_id, area_tag)' +
|
||||||
');'
|
');'
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
32
core/fse.js
32
core/fse.js
|
@ -7,7 +7,7 @@ var ansi = require('../core/ansi_term.js');
|
||||||
var theme = require('../core/theme.js');
|
var theme = require('../core/theme.js');
|
||||||
var MultiLineEditTextView = require('../core/multi_line_edit_text_view.js').MultiLineEditTextView;
|
var MultiLineEditTextView = require('../core/multi_line_edit_text_view.js').MultiLineEditTextView;
|
||||||
var Message = require('../core/message.js');
|
var Message = require('../core/message.js');
|
||||||
var getMessageAreaByName = require('../core/message_area.js').getMessageAreaByName;
|
var getMessageAreaByTag = require('../core/message_area.js').getMessageAreaByTag;
|
||||||
var updateMessageAreaLastReadId = require('../core/message_area.js').updateMessageAreaLastReadId;
|
var updateMessageAreaLastReadId = require('../core/message_area.js').updateMessageAreaLastReadId;
|
||||||
var getUserIdAndName = require('../core/user.js').getUserIdAndName;
|
var getUserIdAndName = require('../core/user.js').getUserIdAndName;
|
||||||
|
|
||||||
|
@ -75,6 +75,8 @@ var MCICodeIds = {
|
||||||
HashTags : 9,
|
HashTags : 9,
|
||||||
MessageID : 10,
|
MessageID : 10,
|
||||||
ReplyToMsgID : 11,
|
ReplyToMsgID : 11,
|
||||||
|
|
||||||
|
// :TODO: ConfName
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -104,15 +106,15 @@ function FullScreenEditorModule(options) {
|
||||||
// editorMode : view | edit | quote
|
// editorMode : view | edit | quote
|
||||||
//
|
//
|
||||||
// menuConfig.config or extraArgs
|
// menuConfig.config or extraArgs
|
||||||
// messageAreaName
|
// messageAreaTag
|
||||||
// messageIndex / messageTotal
|
// messageIndex / messageTotal
|
||||||
// toUserId
|
// toUserId
|
||||||
//
|
//
|
||||||
this.editorType = config.editorType;
|
this.editorType = config.editorType;
|
||||||
this.editorMode = config.editorMode;
|
this.editorMode = config.editorMode;
|
||||||
|
|
||||||
if(config.messageAreaName) {
|
if(config.messageAreaTag) {
|
||||||
this.messageAreaName = config.messageAreaName;
|
this.messageAreaTag = config.messageAreaTag;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.messageIndex = config.messageIndex || 0;
|
this.messageIndex = config.messageIndex || 0;
|
||||||
|
@ -121,8 +123,8 @@ function FullScreenEditorModule(options) {
|
||||||
|
|
||||||
// extraArgs can override some config
|
// extraArgs can override some config
|
||||||
if(_.isObject(options.extraArgs)) {
|
if(_.isObject(options.extraArgs)) {
|
||||||
if(options.extraArgs.messageAreaName) {
|
if(options.extraArgs.messageAreaTag) {
|
||||||
this.messageAreaName = options.extraArgs.messageAreaName;
|
this.messageAreaTag = options.extraArgs.messageAreaTag;
|
||||||
}
|
}
|
||||||
if(options.extraArgs.messageIndex) {
|
if(options.extraArgs.messageIndex) {
|
||||||
this.messageIndex = options.extraArgs.messageIndex;
|
this.messageIndex = options.extraArgs.messageIndex;
|
||||||
|
@ -134,9 +136,6 @@ function FullScreenEditorModule(options) {
|
||||||
this.toUserId = options.extraArgs.toUserId;
|
this.toUserId = options.extraArgs.toUserId;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(this.toUserId)
|
|
||||||
console.log(this.messageAreaName)
|
|
||||||
|
|
||||||
this.isReady = false;
|
this.isReady = false;
|
||||||
|
|
||||||
|
@ -149,7 +148,7 @@ function FullScreenEditorModule(options) {
|
||||||
};
|
};
|
||||||
|
|
||||||
this.isLocalEmail = function() {
|
this.isLocalEmail = function() {
|
||||||
return Message.WellKnownAreaNames.Private === self.messageAreaName;
|
return Message.WellKnownAreaTags.Private === self.messageAreaTag;
|
||||||
};
|
};
|
||||||
|
|
||||||
this.isReply = function() {
|
this.isReply = function() {
|
||||||
|
@ -217,7 +216,7 @@ function FullScreenEditorModule(options) {
|
||||||
var headerValues = self.viewControllers.header.getFormData().value;
|
var headerValues = self.viewControllers.header.getFormData().value;
|
||||||
|
|
||||||
var msgOpts = {
|
var msgOpts = {
|
||||||
areaName : self.messageAreaName,
|
areaTag : self.messageAreaTag,
|
||||||
toUserName : headerValues.to,
|
toUserName : headerValues.to,
|
||||||
fromUserName : headerValues.from,
|
fromUserName : headerValues.from,
|
||||||
subject : headerValues.subject,
|
subject : headerValues.subject,
|
||||||
|
@ -235,7 +234,7 @@ function FullScreenEditorModule(options) {
|
||||||
self.message = message;
|
self.message = message;
|
||||||
|
|
||||||
updateMessageAreaLastReadId(
|
updateMessageAreaLastReadId(
|
||||||
self.client.user.userId, self.messageAreaName, self.message.messageId,
|
self.client.user.userId, self.messageAreaTag, self.message.messageId,
|
||||||
function lastReadUpdated() {
|
function lastReadUpdated() {
|
||||||
|
|
||||||
if(self.isReady) {
|
if(self.isReady) {
|
||||||
|
@ -631,7 +630,7 @@ function FullScreenEditorModule(options) {
|
||||||
};
|
};
|
||||||
|
|
||||||
this.initHeaderGeneric = function() {
|
this.initHeaderGeneric = function() {
|
||||||
self.setHeaderText(MCICodeIds.ViewModeHeader.AreaName, getMessageAreaByName(self.messageAreaName).desc);
|
self.setHeaderText(MCICodeIds.ViewModeHeader.AreaName, getMessageAreaByTag(self.messageAreaTag).name);
|
||||||
};
|
};
|
||||||
|
|
||||||
this.initHeaderViewMode = function() {
|
this.initHeaderViewMode = function() {
|
||||||
|
@ -965,13 +964,10 @@ function FullScreenEditorModule(options) {
|
||||||
|
|
||||||
require('util').inherits(FullScreenEditorModule, MenuModule);
|
require('util').inherits(FullScreenEditorModule, MenuModule);
|
||||||
|
|
||||||
FullScreenEditorModule.prototype.enter = function(client) {
|
FullScreenEditorModule.prototype.enter = function() {
|
||||||
FullScreenEditorModule.super_.prototype.enter.call(this, client);
|
FullScreenEditorModule.super_.prototype.enter.call(this);
|
||||||
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
FullScreenEditorModule.prototype.mciReady = function(mciData, cb) {
|
FullScreenEditorModule.prototype.mciReady = function(mciData, cb) {
|
||||||
this.mciReadyHandler(mciData, cb);
|
this.mciReadyHandler(mciData, cb);
|
||||||
//this['mciReadyHandler' + _.capitalize(this.editorType)](mciData);
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
/* jslint node: true */
|
/* jslint node: true */
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var MailPacket = require('./mail_packet.js');
|
//var MailPacket = require('./mail_packet.js');
|
||||||
var ftn = require('./ftn_util.js');
|
var ftn = require('./ftn_util.js');
|
||||||
var Message = require('./message.js');
|
var Message = require('./message.js');
|
||||||
|
var sauce = require('./sauce.js');
|
||||||
|
|
||||||
var _ = require('lodash');
|
var _ = require('lodash');
|
||||||
var assert = require('assert');
|
var assert = require('assert');
|
||||||
|
@ -12,6 +13,8 @@ var fs = require('fs');
|
||||||
var util = require('util');
|
var util = require('util');
|
||||||
var async = require('async');
|
var async = require('async');
|
||||||
var iconv = require('iconv-lite');
|
var iconv = require('iconv-lite');
|
||||||
|
var buffers = require('buffers');
|
||||||
|
var moment = require('moment');
|
||||||
|
|
||||||
/*
|
/*
|
||||||
:TODO: should probably be broken up
|
:TODO: should probably be broken up
|
||||||
|
@ -20,6 +23,493 @@ var iconv = require('iconv-lite');
|
||||||
FTNPacketExport: message(s) -> packet
|
FTNPacketExport: message(s) -> packet
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
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;
|
||||||
|
|
||||||
|
// EOF + SAUCE.id + SAUCE.version ('00')
|
||||||
|
const FTN_MESSAGE_SAUCE_HEADER =
|
||||||
|
new Buffer( [ 0x1a, 'S', 'A', 'U', 'C', 'E', '0', '0' ] );
|
||||||
|
|
||||||
|
const FTN_MESSAGE_KLUDGE_PREFIX = '\x01';
|
||||||
|
|
||||||
|
function FTNPacket() {
|
||||||
|
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 padded 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(packetHeader => {
|
||||||
|
// Convert password from NULL padded array to string
|
||||||
|
packetHeader.password = ftn.stringFromFTN(packetHeader.password);
|
||||||
|
|
||||||
|
if(FTN_PACKET_HEADER_TYPE !== packetHeader.packetType) {
|
||||||
|
cb(new Error('Unsupported header type: ' + packetHeader.packetType));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Date/time components into something more reasonable
|
||||||
|
// Note: The names above match up with object members moment() allows
|
||||||
|
//
|
||||||
|
packetHeader.created = moment(packetHeader);
|
||||||
|
|
||||||
|
cb(null, packetHeader);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
this.writePacketHeader = function(headerInfo, ws) {
|
||||||
|
let buffer = new Buffer(FTN_PACKET_HEADER_SIZE);
|
||||||
|
|
||||||
|
buffer.writeUInt16LE(headerInfo.origNode, 0);
|
||||||
|
buffer.writeUInt16LE(headerInfo.destNode, 2);
|
||||||
|
buffer.writeUInt16LE(headerInfo.created.year(), 4);
|
||||||
|
buffer.writeUInt16LE(headerInfo.created.month(), 6);
|
||||||
|
buffer.writeUInt16LE(headerInfo.created.date(), 8);
|
||||||
|
buffer.writeUInt16LE(headerInfo.created.hour(), 10);
|
||||||
|
buffer.writeUInt16LE(headerInfo.created.minute(), 12);
|
||||||
|
buffer.writeUInt16LE(headerInfo.created.second(), 14);
|
||||||
|
buffer.writeUInt16LE(headerInfo.baud, 16);
|
||||||
|
buffer.writeUInt16LE(FTN_PACKET_HEADER_TYPE, 18);
|
||||||
|
buffer.writeUInt16LE(headerInfo.origNet, 20);
|
||||||
|
buffer.writeUInt16LE(headerInfo.destNet, 22);
|
||||||
|
buffer.writeUInt8(headerInfo.prodCodeLo, 24);
|
||||||
|
buffer.writeUInt8(headerInfo.revisionMajor, 25);
|
||||||
|
|
||||||
|
const pass = ftn.stringToNullPaddedBuffer(headerInfo.password, 8);
|
||||||
|
pass.copy(buffer, 26);
|
||||||
|
|
||||||
|
buffer.writeUInt16LE(headerInfo.origZone, 34);
|
||||||
|
buffer.writeUInt16LE(headerInfo.destZone, 36);
|
||||||
|
|
||||||
|
// FSC-0048.002 additions...
|
||||||
|
buffer.writeUInt16LE(headerInfo.auxNet, 38);
|
||||||
|
buffer.writeUInt16LE(headerInfo.capWordA, 40);
|
||||||
|
buffer.writeUInt8(headerInfo.prodCodeHi, 42);
|
||||||
|
buffer.writeUInt8(headerInfo.revisionMinor, 43);
|
||||||
|
buffer.writeUInt16LE(headerInfo.capWordB, 44);
|
||||||
|
buffer.writeUInt16LE(headerInfo.origZone2, 46);
|
||||||
|
buffer.writeUInt16LE(headerInfo.destZone2, 48);
|
||||||
|
buffer.writeUInt16LE(headerInfo.origPoint, 50);
|
||||||
|
buffer.writeUInt16LE(headerInfo.destPoint, 52);
|
||||||
|
buffer.writeUInt32LE(headerInfo.prodData, 54);
|
||||||
|
|
||||||
|
ws.write(buffer);
|
||||||
|
};
|
||||||
|
|
||||||
|
this.processMessageBody = function(messageBodyBuffer, 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 can be a bit tricky:
|
||||||
|
// * Decoding as CP437 converts 0x8d -> 0xec, so we'll need to correct for that
|
||||||
|
// * Many kludge lines specify an encoding. If we find one of such lines, we'll
|
||||||
|
// likely need to re-decode as the specified encoding
|
||||||
|
// * SAUCE is binary-ish data, so we need to inspect for it before any
|
||||||
|
// decoding occurs
|
||||||
|
//
|
||||||
|
let messageBodyData = {
|
||||||
|
message : [],
|
||||||
|
kludgeLines : {}, // KLUDGE:[value1, value2, ...] map
|
||||||
|
seenBy : [],
|
||||||
|
};
|
||||||
|
|
||||||
|
function addKludgeLine(line) {
|
||||||
|
const sepIndex = line.indexOf(':');
|
||||||
|
const key = line.substr(0, sepIndex).toUpperCase();
|
||||||
|
const value = line.substr(sepIndex + 1).trim();
|
||||||
|
|
||||||
|
//
|
||||||
|
// Allow mapped value to be either a key:value if there is only
|
||||||
|
// one entry, or key:[value1, value2,...] if there are more
|
||||||
|
//
|
||||||
|
if(messageBodyData.kludgeLines[key]) {
|
||||||
|
if(!_.isArray(messageBodyData.kludgeLines[key])) {
|
||||||
|
messageBodyData.kludgeLines[key] = [ messageBodyData.kludgeLines[key] ];
|
||||||
|
}
|
||||||
|
messageBodyData.kludgeLines[key].push(value);
|
||||||
|
} else {
|
||||||
|
messageBodyData.kludgeLines[key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async.series(
|
||||||
|
[
|
||||||
|
function extractSauce(callback) {
|
||||||
|
// :TODO: This is wrong: SAUCE may not have EOF marker for one, also if it's
|
||||||
|
// present, we need to extract it but keep the rest of hte message intact as it likely
|
||||||
|
// 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) => {
|
||||||
|
if(!err) {
|
||||||
|
// we read some SAUCE - don't re-process that portion into the body
|
||||||
|
messageBodyBuffer = messageBodyBuffer.slice(0, sauceHeaderPosition);
|
||||||
|
messageBodyData.sauce = theSauce;
|
||||||
|
}
|
||||||
|
callback(null); // failure to read SAUCE is OK
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
callback(null);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
function extractMessageData(callback) {
|
||||||
|
const messageLines =
|
||||||
|
iconv.decode(messageBodyBuffer, 'CP437').replace(/[\xec\n]/g, '').split(/\r/g);
|
||||||
|
|
||||||
|
let preOrigin = true;
|
||||||
|
|
||||||
|
messageLines.forEach(line => {
|
||||||
|
if(0 === line.length) {
|
||||||
|
messageBodyData.message.push('');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(preOrigin) {
|
||||||
|
if(line.startsWith('AREA:')) {
|
||||||
|
messageBodyData.area = line.substring(line.indexOf(':') + 1).trim();
|
||||||
|
} else if(line.startsWith('--- ')) {
|
||||||
|
// Tear Lines are tracked allowing for specialized display/etc.
|
||||||
|
messageBodyData.tearLine = line;
|
||||||
|
} else if(/[ ]{1,2}(\* )?Origin\: /.test(line)) { // To spec is " * Origin: ..."
|
||||||
|
messageBodyData.originLine = line;
|
||||||
|
preOrigin = false;
|
||||||
|
} else if(FTN_MESSAGE_KLUDGE_PREFIX === line.charAt(0)) {
|
||||||
|
addKludgeLine(line.slice(1));
|
||||||
|
} else {
|
||||||
|
// regular ol' message line
|
||||||
|
messageBodyData.message.push(line);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if(line.startsWith('SEEN-BY:')) {
|
||||||
|
messageBodyData.seenBy.push(line.substring(line.indexOf(':') + 1).trim());
|
||||||
|
} else if(FTN_MESSAGE_KLUDGE_PREFIX === line.charAt(0)) {
|
||||||
|
addKludgeLine(line.slice(1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
callback(null);
|
||||||
|
}
|
||||||
|
],
|
||||||
|
function complete(err) {
|
||||||
|
messageBodyData.message = messageBodyData.message.join('\n');
|
||||||
|
cb(messageBodyData);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
this.parsePacketMessages = function(messagesBuffer, iterator, cb) {
|
||||||
|
const NULL_TERM_BUFFER = new Buffer( [ 0 ] );
|
||||||
|
|
||||||
|
binary.stream(messagesBuffer).loop(function looper(end, vars) {
|
||||||
|
//
|
||||||
|
// Some variable names used here match up directly with well known
|
||||||
|
// meta data names used with FTN messages.
|
||||||
|
//
|
||||||
|
this
|
||||||
|
.word16lu('messageType')
|
||||||
|
.word16lu('ftn_orig_node')
|
||||||
|
.word16lu('ftn_dest_node')
|
||||||
|
.word16lu('ftn_orig_network')
|
||||||
|
.word16lu('ftn_dest_network')
|
||||||
|
.word8('ftn_attr_flags1')
|
||||||
|
.word8('ftn_attr_flags2')
|
||||||
|
.word16lu('ftn_cost')
|
||||||
|
.scan('modDateTime', NULL_TERM_BUFFER) // :TODO: 20 bytes max
|
||||||
|
.scan('toUserName', NULL_TERM_BUFFER) // :TODO: 36 bytes max
|
||||||
|
.scan('fromUserName', NULL_TERM_BUFFER) // :TODO: 36 bytes max
|
||||||
|
.scan('subject', NULL_TERM_BUFFER) // :TODO: 72 bytes max
|
||||||
|
.scan('message', NULL_TERM_BUFFER)
|
||||||
|
.tap(function tapped(msgData) {
|
||||||
|
if(!msgData.ftn_orig_node) {
|
||||||
|
// end marker -- no more messages
|
||||||
|
end();
|
||||||
|
cb(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(FTN_PACKET_MESSAGE_TYPE != msgData.messageType) {
|
||||||
|
end();
|
||||||
|
cb(new Error('Unsupported message type: ' + msgData.messageType));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Convert null terminated arrays to strings
|
||||||
|
//
|
||||||
|
let convMsgData = {};
|
||||||
|
[ 'modDateTime', 'toUserName', 'fromUserName', 'subject' ].forEach(k => {
|
||||||
|
convMsgData[k] = iconv.decode(msgData[k], 'CP437');
|
||||||
|
});
|
||||||
|
|
||||||
|
//
|
||||||
|
// The message body itself is a special beast as it may
|
||||||
|
// contain special origin lines, kludges, SAUCE in the case
|
||||||
|
// of ANSI files, etc.
|
||||||
|
//
|
||||||
|
let msg = new Message( {
|
||||||
|
toUserName : convMsgData.toUserName,
|
||||||
|
fromUserName : convMsgData.fromUserName,
|
||||||
|
subject : convMsgData.subject,
|
||||||
|
modTimestamp : ftn.getDateFromFtnDateTime(convMsgData.modDateTime),
|
||||||
|
});
|
||||||
|
|
||||||
|
msg.meta.FtnProperty = {};
|
||||||
|
msg.meta.FtnProperty.ftn_orig_node = msgData.ftn_orig_node;
|
||||||
|
msg.meta.FtnProperty.ftn_dest_node = msgData.ftn_dest_node;
|
||||||
|
msg.meta.FtnProperty.ftn_orig_network = msgData.ftn_orig_network;
|
||||||
|
msg.meta.FtnProperty.ftn_dest_network = msgData.ftn_dest_network;
|
||||||
|
msg.meta.FtnProperty.ftn_attr_flags1 = msgData.ftn_attr_flags1;
|
||||||
|
msg.meta.FtnProperty.ftn_attr_flags2 = msgData.ftn_attr_flags2;
|
||||||
|
msg.meta.FtnProperty.ftn_cost = msgData.ftn_cost;
|
||||||
|
|
||||||
|
self.processMessageBody(msgData.message, function processed(messageBodyData) {
|
||||||
|
msg.message = messageBodyData.message;
|
||||||
|
msg.meta.FtnKludge = messageBodyData.kludgeLines;
|
||||||
|
|
||||||
|
if(messageBodyData.tearLine) {
|
||||||
|
msg.meta.FtnProperty.ftn_tear_line = messageBodyData.tearLine;
|
||||||
|
}
|
||||||
|
if(messageBodyData.seenBy.length > 0) {
|
||||||
|
msg.meta.FtnProperty.ftn_seen_by = messageBodyData.seenBy;
|
||||||
|
}
|
||||||
|
if(messageBodyData.area) {
|
||||||
|
msg.meta.FtnProperty.ftn_area = messageBodyData.area;
|
||||||
|
}
|
||||||
|
if(messageBodyData.originLine) {
|
||||||
|
msg.meta.FtnProperty.ftn_origin = messageBodyData.originLine;
|
||||||
|
}
|
||||||
|
|
||||||
|
iterator('message', msg);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
this.writeMessage = function(message, ws) {
|
||||||
|
let basicHeader = new Buffer(34);
|
||||||
|
|
||||||
|
basicHeader.writeUInt16LE(FTN_PACKET_MESSAGE_TYPE, 0);
|
||||||
|
basicHeader.writeUInt16LE(message.meta.FtnProperty.ftn_orig_node, 2);
|
||||||
|
basicHeader.writeUInt16LE(message.meta.FtnProperty.ftn_dest_node, 4);
|
||||||
|
basicHeader.writeUInt16LE(message.meta.FtnProperty.ftn_orig_network, 6);
|
||||||
|
basicHeader.writeUInt16LE(message.meta.FtnProperty.ftn_dest_network, 8);
|
||||||
|
basicHeader.writeUInt8(message.meta.FtnProperty.ftn_attr_flags1, 10);
|
||||||
|
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);
|
||||||
|
|
||||||
|
ws.write(basicHeader);
|
||||||
|
|
||||||
|
// toUserName & fromUserName: up to 36 bytes in length, NULL term'd
|
||||||
|
// :TODO: DRY...
|
||||||
|
let encBuf = iconv.encode(message.toUserName + '\0', 'CP437').slice(0, 36);
|
||||||
|
encBuf[encBuf.length - 1] = '\0'; // ensure it's null term'd
|
||||||
|
ws.write(encBuf);
|
||||||
|
|
||||||
|
encBuf = iconv.encode(message.fromUserName + '\0', 'CP437').slice(0, 36);
|
||||||
|
encBuf[encBuf.length - 1] = '\0'; // ensure it's null term'd
|
||||||
|
ws.write(encBuf);
|
||||||
|
|
||||||
|
// subject: up to 72 bytes in length, NULL term'd
|
||||||
|
encBuf = iconv.encode(message.subject + '\0', 'CP437').slice(0, 72);
|
||||||
|
encBuf[encBuf.length - 1] = '\0'; // ensure it's null term'd
|
||||||
|
ws.write(encBuf);
|
||||||
|
|
||||||
|
//
|
||||||
|
// message: unbound length, NULL term'd
|
||||||
|
//
|
||||||
|
// We need to build in various special lines - kludges, area,
|
||||||
|
// seen-by, etc.
|
||||||
|
//
|
||||||
|
// :TODO: Put this in it's own method
|
||||||
|
let msgBody = '';
|
||||||
|
|
||||||
|
function appendMeta(k, m) {
|
||||||
|
if(m) {
|
||||||
|
let a = m;
|
||||||
|
if(!_.isArray(a)) {
|
||||||
|
a = [ a ];
|
||||||
|
}
|
||||||
|
a.forEach(v => {
|
||||||
|
msgBody += `${k}: ${v}\n`;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// :TODO: is Area really any differnt (e.g. no space between AREA:the_area)
|
||||||
|
if(message.meta.FtnProperty.ftn_area) {
|
||||||
|
msgBody += `AREA:${message.meta.FtnProperty.ftn_area}\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.keys(message.meta.FtnKludge).forEach(k => {
|
||||||
|
if('PATH' !== k) {
|
||||||
|
appendMeta(k, message.meta.FtnKludge[k]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
msgBody += message.message;
|
||||||
|
|
||||||
|
appendMeta('', message.meta.FtnProperty.ftn_tear_line);
|
||||||
|
appendMeta('', message.meta.FtnProperty.ftn_origin);
|
||||||
|
|
||||||
|
appendMeta('SEEN-BY', message.meta.FtnProperty.ftn_seen_by);
|
||||||
|
appendMeta('PATH', message.meta.FtnKludge['PATH']);
|
||||||
|
|
||||||
|
ws.write(iconv.encode(msgBody + '\0', 'CP437'));
|
||||||
|
};
|
||||||
|
|
||||||
|
this.parsePacketBuffer = function(packetBuffer, iterator, cb) {
|
||||||
|
async.series(
|
||||||
|
[
|
||||||
|
function processHeader(callback) {
|
||||||
|
self.parsePacketHeader(packetBuffer, (err, header) => {
|
||||||
|
if(!err) {
|
||||||
|
iterator('header', header);
|
||||||
|
}
|
||||||
|
callback(err);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
function processMessages(callback) {
|
||||||
|
self.parsePacketMessages(
|
||||||
|
packetBuffer.slice(FTN_PACKET_HEADER_SIZE),
|
||||||
|
iterator,
|
||||||
|
callback);
|
||||||
|
}
|
||||||
|
],
|
||||||
|
cb
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
FTNPacket.prototype.read = function(pathOrBuffer, iterator, cb) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
async.series(
|
||||||
|
[
|
||||||
|
function getBufferIfPath(callback) {
|
||||||
|
if(_.isString(pathOrBuffer)) {
|
||||||
|
fs.readFile(pathOrBuffer, (err, data) => {
|
||||||
|
pathOrBuffer = data;
|
||||||
|
callback(err);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
callback(null);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
function parseBuffer(callback) {
|
||||||
|
self.parsePacketBuffer(pathOrBuffer, iterator, callback);
|
||||||
|
}
|
||||||
|
],
|
||||||
|
cb // completion callback
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
FTNPacket.prototype.write = function(path, headerInfo, messages, cb) {
|
||||||
|
headerInfo.created = headerInfo.created || moment();
|
||||||
|
headerInfo.baud = headerInfo.baud || 0;
|
||||||
|
// :TODO: Other defaults?
|
||||||
|
|
||||||
|
if(!_.isArray(messages)) {
|
||||||
|
messages = [ messages ] ;
|
||||||
|
}
|
||||||
|
|
||||||
|
let ws = fs.createWriteStream(path);
|
||||||
|
this.writePacketHeader(headerInfo, ws);
|
||||||
|
|
||||||
|
messages.forEach(msg => {
|
||||||
|
this.writeMessage(msg, ws);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// References
|
// References
|
||||||
// * http://ftsc.org/docs/fts-0001.016
|
// * http://ftsc.org/docs/fts-0001.016
|
||||||
|
@ -30,7 +520,7 @@ var iconv = require('iconv-lite');
|
||||||
//
|
//
|
||||||
function FTNMailPacket(options) {
|
function FTNMailPacket(options) {
|
||||||
|
|
||||||
MailPacket.call(this, options);
|
//MailPacket.call(this, options);
|
||||||
|
|
||||||
var self = this;
|
var self = this;
|
||||||
self.KLUDGE_PREFIX = '\x01';
|
self.KLUDGE_PREFIX = '\x01';
|
||||||
|
@ -77,7 +567,7 @@ function FTNMailPacket(options) {
|
||||||
.word16lu('second')
|
.word16lu('second')
|
||||||
.word16lu('baud')
|
.word16lu('baud')
|
||||||
.word16lu('packetType')
|
.word16lu('packetType')
|
||||||
.word16lu('originNet')
|
.word16lu('origNet')
|
||||||
.word16lu('destNet')
|
.word16lu('destNet')
|
||||||
.word8('prodCodeLo')
|
.word8('prodCodeLo')
|
||||||
.word8('revisionMajor') // aka serialNo
|
.word8('revisionMajor') // aka serialNo
|
||||||
|
@ -100,35 +590,110 @@ function FTNMailPacket(options) {
|
||||||
|
|
||||||
// :TODO: Don't hard code magic # here
|
// :TODO: Don't hard code magic # here
|
||||||
if(2 !== packetHeader.packetType) {
|
if(2 !== packetHeader.packetType) {
|
||||||
|
console.log(packetHeader.packetType)
|
||||||
cb(new Error('Packet is not Type-2'));
|
cb(new Error('Packet is not Type-2'));
|
||||||
return;
|
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
|
// :TODO: validate & pass error if failure
|
||||||
cb(null, packetHeader);
|
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) {
|
self.getMessageMeta = function(msgBody, msgData) {
|
||||||
var meta = {
|
var meta = {
|
||||||
FtnKludge : msgBody.kludgeLines,
|
FtnKludge : msgBody.kludgeLines,
|
||||||
FtnProperty : {},
|
FtnProperty : {},
|
||||||
};
|
};
|
||||||
|
|
||||||
if(msgBody.tearLine) {
|
if(msgBody.tearLine) {
|
||||||
meta.FtnProperty.ftn_tear_line = [ msgBody.tearLine ];
|
meta.FtnProperty.ftn_tear_line = msgBody.tearLine;
|
||||||
}
|
}
|
||||||
if(msgBody.seenBy.length > 0) {
|
if(msgBody.seenBy.length > 0) {
|
||||||
meta.FtnProperty.ftn_seen_by = msgBody.seenBy;
|
meta.FtnProperty.ftn_seen_by = msgBody.seenBy;
|
||||||
}
|
}
|
||||||
if(msgBody.area) {
|
if(msgBody.area) {
|
||||||
meta.FtnProperty.ftn_area = [ msgBody.area ];
|
meta.FtnProperty.ftn_area = msgBody.area;
|
||||||
}
|
}
|
||||||
if(msgBody.originLine) {
|
if(msgBody.originLine) {
|
||||||
meta.FtnProperty.ftn_origin = [ 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;
|
return meta;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -172,13 +737,15 @@ function FTNMailPacket(options) {
|
||||||
var preOrigin = true;
|
var preOrigin = true;
|
||||||
|
|
||||||
function addKludgeLine(kl) {
|
function addKludgeLine(kl) {
|
||||||
var kludgeParts = kl.split(':');
|
const kludgeParts = kl.split(':');
|
||||||
kludgeParts[0] = kludgeParts[0].toUpperCase();
|
kludgeParts[0] = kludgeParts[0].toUpperCase();
|
||||||
kludgeParts[1] = kludgeParts[1].trim();
|
kludgeParts[1] = kludgeParts[1].trim();
|
||||||
|
|
||||||
(msgBody.kludgeLines[kludgeParts[0]] = msgBody.kludgeLines[kludgeParts[0]] || []).push(kludgeParts[1]);
|
self.setOrAppend(kludgeParts[1], msgBody.kludgeLines[kludgeParts[0]]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var sauceBuffers;
|
||||||
|
|
||||||
msgLines.forEach(function nextLine(line) {
|
msgLines.forEach(function nextLine(line) {
|
||||||
if(0 === line.length) {
|
if(0 === line.length) {
|
||||||
msgBody.message.push('');
|
msgBody.message.push('');
|
||||||
|
@ -196,10 +763,12 @@ function FTNMailPacket(options) {
|
||||||
preOrigin = false;
|
preOrigin = false;
|
||||||
} else if(self.KLUDGE_PREFIX === line.charAt(0)) {
|
} else if(self.KLUDGE_PREFIX === line.charAt(0)) {
|
||||||
addKludgeLine(line.slice(1));
|
addKludgeLine(line.slice(1));
|
||||||
|
} else if(!sauceBuffers || _.startsWith(line, '\x1aSAUCE00')) {
|
||||||
|
sauceBuffers = sauceBuffers || buffers();
|
||||||
|
sauceBuffers.push(new Buffer(line));
|
||||||
} else {
|
} else {
|
||||||
msgBody.message.push(line);
|
msgBody.message.push(line);
|
||||||
}
|
}
|
||||||
// :TODO: SAUCE/etc. can be present?
|
|
||||||
} else {
|
} else {
|
||||||
if(_.startsWith(line, 'SEEN-BY:')) {
|
if(_.startsWith(line, 'SEEN-BY:')) {
|
||||||
msgBody.seenBy.push(line.substring(line.indexOf(':') + 1).trim());
|
msgBody.seenBy.push(line.substring(line.indexOf(':') + 1).trim());
|
||||||
|
@ -209,29 +778,36 @@ function FTNMailPacket(options) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if(sauceBuffers) {
|
||||||
|
// :TODO: parse sauce -> sauce buffer. This needs changes to this method to return message & optional sauce
|
||||||
|
}
|
||||||
|
|
||||||
cb(null, msgBody);
|
cb(null, msgBody);
|
||||||
};
|
};
|
||||||
|
|
||||||
this.extractMessages = function(buffer, cb) {
|
this.extractMessages = function(buffer, iterator, cb) {
|
||||||
var nullTermBuf = new Buffer( [ 0 ] );
|
assert(Buffer.isBuffer(buffer));
|
||||||
|
assert(_.isFunction(iterator));
|
||||||
|
|
||||||
|
const NULL_TERM_BUFFER = new Buffer( [ 0 ] );
|
||||||
|
|
||||||
binary.stream(buffer).loop(function looper(end, vars) {
|
binary.stream(buffer).loop(function looper(end, vars) {
|
||||||
this
|
this
|
||||||
.word16lu('messageType')
|
.word16lu('messageType')
|
||||||
.word16lu('originNode')
|
.word16lu('origNode')
|
||||||
.word16lu('destNode')
|
.word16lu('destNode')
|
||||||
.word16lu('originNet')
|
.word16lu('origNet')
|
||||||
.word16lu('destNet')
|
.word16lu('destNet')
|
||||||
.word8('attrFlags1')
|
.word8('attrFlags1')
|
||||||
.word8('attrFlags2')
|
.word8('attrFlags2')
|
||||||
.word16lu('cost')
|
.word16lu('cost')
|
||||||
.scan('modDateTime', nullTermBuf)
|
.scan('modDateTime', NULL_TERM_BUFFER)
|
||||||
.scan('toUserName', nullTermBuf)
|
.scan('toUserName', NULL_TERM_BUFFER)
|
||||||
.scan('fromUserName', nullTermBuf)
|
.scan('fromUserName', NULL_TERM_BUFFER)
|
||||||
.scan('subject', nullTermBuf)
|
.scan('subject', NULL_TERM_BUFFER)
|
||||||
.scan('message', nullTermBuf)
|
.scan('message', NULL_TERM_BUFFER)
|
||||||
.tap(function tapped(msgData) {
|
.tap(function tapped(msgData) {
|
||||||
if(!msgData.originNode) {
|
if(!msgData.origNode) {
|
||||||
end();
|
end();
|
||||||
cb(null);
|
cb(null);
|
||||||
return;
|
return;
|
||||||
|
@ -247,20 +823,25 @@ function FTNMailPacket(options) {
|
||||||
// Now, create a Message object
|
// Now, create a Message object
|
||||||
//
|
//
|
||||||
var msg = new Message( {
|
var msg = new Message( {
|
||||||
// :TODO: areaId needs to be looked up via AREA line - may need a 1:n alias -> area ID lookup
|
// AREA FTN -> local conf/area occurs elsewhere
|
||||||
toUserName : msgData.toUserName,
|
toUserName : msgData.toUserName,
|
||||||
fromUserName : msgData.fromUserName,
|
fromUserName : msgData.fromUserName,
|
||||||
subject : msgData.subject,
|
subject : msgData.subject,
|
||||||
message : msgBody.message.join('\n'), // :TODO: \r\n is better?
|
message : msgBody.message.join('\n'), // :TODO: \r\n is better?
|
||||||
modTimestamp : ftn.getDateFromFtnDateTime(msgData.modDateTime),
|
modTimestamp : ftn.getDateFromFtnDateTime(msgData.modDateTime),
|
||||||
meta : self.getMessageMeta(msgBody),
|
meta : self.getMessageMeta(msgBody, msgData),
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
self.emit('message', msg); // :TODO: Placeholder
|
iterator(msg);
|
||||||
|
//self.emit('message', msg); // :TODO: Placeholder
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
//this.getMessageHeaderBuffer = function(headerInfo)
|
||||||
|
|
||||||
this.parseFtnMessages = function(buffer, cb) {
|
this.parseFtnMessages = function(buffer, cb) {
|
||||||
var nullTermBuf = new Buffer( [ 0 ] );
|
var nullTermBuf = new Buffer( [ 0 ] );
|
||||||
|
@ -269,9 +850,9 @@ function FTNMailPacket(options) {
|
||||||
binary.stream(buffer).loop(function looper(end, vars) {
|
binary.stream(buffer).loop(function looper(end, vars) {
|
||||||
this
|
this
|
||||||
.word16lu('messageType')
|
.word16lu('messageType')
|
||||||
.word16lu('originNode')
|
.word16lu('origNode')
|
||||||
.word16lu('destNode')
|
.word16lu('destNode')
|
||||||
.word16lu('originNet')
|
.word16lu('origNet')
|
||||||
.word16lu('destNet')
|
.word16lu('destNet')
|
||||||
.word8('attrFlags1')
|
.word8('attrFlags1')
|
||||||
.word8('attrFlags2')
|
.word8('attrFlags2')
|
||||||
|
@ -282,7 +863,7 @@ function FTNMailPacket(options) {
|
||||||
.scan('subject', nullTermBuf)
|
.scan('subject', nullTermBuf)
|
||||||
.scan('message', nullTermBuf)
|
.scan('message', nullTermBuf)
|
||||||
.tap(function tapped(msgData) {
|
.tap(function tapped(msgData) {
|
||||||
if(!msgData.originNode) {
|
if(!msgData.origNode) {
|
||||||
end();
|
end();
|
||||||
cb(null, fidoMessages);
|
cb(null, fidoMessages);
|
||||||
return;
|
return;
|
||||||
|
@ -302,7 +883,10 @@ function FTNMailPacket(options) {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
this.extractMesssagesFromPacketBuffer = function(packetBuffer, cb) {
|
this.extractMesssagesFromPacketBuffer = function(packetBuffer, iterator, cb) {
|
||||||
|
assert(Buffer.isBuffer(packetBuffer));
|
||||||
|
assert(_.isFunction(iterator));
|
||||||
|
|
||||||
async.waterfall(
|
async.waterfall(
|
||||||
[
|
[
|
||||||
function parseHeader(callback) {
|
function parseHeader(callback) {
|
||||||
|
@ -318,7 +902,8 @@ function FTNMailPacket(options) {
|
||||||
},
|
},
|
||||||
function extractEmbeddedMessages(callback) {
|
function extractEmbeddedMessages(callback) {
|
||||||
// note: packet header is 58 bytes in length
|
// note: packet header is 58 bytes in length
|
||||||
self.extractMessages(packetBuffer.slice(58), function extracted(err) {
|
self.extractMessages(
|
||||||
|
packetBuffer.slice(58), iterator, function extracted(err) {
|
||||||
callback(err);
|
callback(err);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -361,7 +946,7 @@ function FTNMailPacket(options) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
require('util').inherits(FTNMailPacket, MailPacket);
|
//require('util').inherits(FTNMailPacket, MailPacket);
|
||||||
|
|
||||||
FTNMailPacket.prototype.parse = function(path, cb) {
|
FTNMailPacket.prototype.parse = function(path, cb) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
@ -385,41 +970,67 @@ FTNMailPacket.prototype.parse = function(path, cb) {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
FTNMailPacket.prototype.read = function(options) {
|
FTNMailPacket.prototype.read = function(pathOrBuffer, iterator, cb) {
|
||||||
FTNMailPacket.super_.prototype.read.call(this, options);
|
|
||||||
|
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
if(_.isString(options.packetPath)) {
|
if(_.isString(pathOrBuffer)) {
|
||||||
async.waterfall(
|
async.waterfall(
|
||||||
[
|
[
|
||||||
function readPacketFile(callback) {
|
function readPacketFile(callback) {
|
||||||
fs.readFile(options.packetPath, function packetData(err, data) {
|
fs.readFile(pathOrBuffer, function packetData(err, data) {
|
||||||
callback(err, data);
|
callback(err, data);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
function extractMessages(data, callback) {
|
function extractMessages(data, callback) {
|
||||||
self.extractMesssagesFromPacketBuffer(data, function extracted(err) {
|
self.extractMesssagesFromPacketBuffer(data, iterator, callback);
|
||||||
callback(err);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
function complete(err) {
|
cb
|
||||||
if(err) {
|
|
||||||
self.emit('error', err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
} else if(Buffer.isBuffer(options.packetBuffer)) {
|
} else if(Buffer.isBuffer(pathOrBuffer)) {
|
||||||
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
FTNMailPacket.prototype.write = function(options) {
|
FTNMailPacket.prototype.write = function(messages, fileName, options) {
|
||||||
FTNMailPacket.super_.prototype.write.call(this, options);
|
if(!_.isArray(messages)) {
|
||||||
|
messages = [ messages ];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var ftnPacket = new FTNPacket();
|
||||||
|
var theHeader;
|
||||||
|
var written = false;
|
||||||
|
ftnPacket.read(
|
||||||
|
process.argv[2],
|
||||||
|
function iterator(dataType, data) {
|
||||||
|
if('header' === dataType) {
|
||||||
|
theHeader = data;
|
||||||
|
console.log(theHeader);
|
||||||
|
} else if('message' === dataType) {
|
||||||
|
const msg = data;
|
||||||
|
console.log(msg);
|
||||||
|
|
||||||
|
if(!written) {
|
||||||
|
written = true;
|
||||||
|
|
||||||
|
let messages = [ msg ];
|
||||||
|
ftnPacket.write('/home/nuskooler/Downloads/ftnout/test1.pkt', theHeader, messages, err => {
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
function completion(err) {
|
||||||
|
console.log(err);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
/*
|
||||||
var mailPacket = new FTNMailPacket(
|
var mailPacket = new FTNMailPacket(
|
||||||
{
|
{
|
||||||
nodeAddresses : {
|
nodeAddresses : {
|
||||||
|
@ -434,11 +1045,42 @@ var mailPacket = new FTNMailPacket(
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
mailPacket.on('message', function msgParsed(msg) {
|
|
||||||
console.log(msg);
|
|
||||||
});
|
|
||||||
|
|
||||||
mailPacket.read( { packetPath : '/home/nuskooler/ownCloud/Projects/ENiGMA½ BBS/FTNPackets/BAD_BNDL.007' } );
|
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) {
|
mailPacket.parse('/home/nuskooler/ownCloud/Projects/ENiGMA½ BBS/FTNPackets/BAD_BNDL.007', function parsed(err, messages) {
|
||||||
|
|
|
@ -10,11 +10,14 @@ var binary = require('binary');
|
||||||
var fs = require('fs');
|
var fs = require('fs');
|
||||||
var util = require('util');
|
var util = require('util');
|
||||||
var iconv = require('iconv-lite');
|
var iconv = require('iconv-lite');
|
||||||
|
var moment = require('moment');
|
||||||
|
|
||||||
// :TODO: Remove "Ftn" from most of these -- it's implied in the module
|
// :TODO: Remove "Ftn" from most of these -- it's implied in the module
|
||||||
exports.stringFromFTN = stringFromFTN;
|
exports.stringFromFTN = stringFromFTN;
|
||||||
|
exports.stringToNullPaddedBuffer = stringToNullPaddedBuffer;
|
||||||
exports.getFormattedFTNAddress = getFormattedFTNAddress;
|
exports.getFormattedFTNAddress = getFormattedFTNAddress;
|
||||||
exports.getDateFromFtnDateTime = getDateFromFtnDateTime;
|
exports.getDateFromFtnDateTime = getDateFromFtnDateTime;
|
||||||
|
exports.getDateTimeString = getDateTimeString;
|
||||||
|
|
||||||
exports.getQuotePrefix = getQuotePrefix;
|
exports.getQuotePrefix = getQuotePrefix;
|
||||||
|
|
||||||
|
@ -33,6 +36,14 @@ function stringFromFTN(buf, encoding) {
|
||||||
return iconv.decode(buf.slice(0, nullPos), encoding || 'utf-8');
|
return iconv.decode(buf.slice(0, nullPos), encoding || 'utf-8');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function stringToNullPaddedBuffer(s, bufLen) {
|
||||||
|
let buffer = new Buffer(bufLen).fill(0x00);
|
||||||
|
let enc = iconv.encode(s, 'CP437').slice(0, bufLen);
|
||||||
|
for(let i = 0; i < enc.length; ++i) {
|
||||||
|
buffer[i] = enc[i];
|
||||||
|
}
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Convert a FTN style DateTime string to a Date object
|
// Convert a FTN style DateTime string to a Date object
|
||||||
|
@ -44,9 +55,34 @@ function getDateFromFtnDateTime(dateTime) {
|
||||||
// "Tue 01 Jan 80 00:00"
|
// "Tue 01 Jan 80 00:00"
|
||||||
// "27 Feb 15 00:00:03"
|
// "27 Feb 15 00:00:03"
|
||||||
//
|
//
|
||||||
|
// :TODO: Use moment.js here
|
||||||
return (new Date(Date.parse(dateTime))).toISOString();
|
return (new Date(Date.parse(dateTime))).toISOString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getDateTimeString(m) {
|
||||||
|
//
|
||||||
|
// 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"
|
||||||
|
//
|
||||||
|
if(!moment.isMoment(m)) {
|
||||||
|
m = moment(m);
|
||||||
|
}
|
||||||
|
|
||||||
|
return m.format('DD MMM YY HH:mm:ss');
|
||||||
|
}
|
||||||
|
|
||||||
function getFormattedFTNAddress(address, dimensions) {
|
function getFormattedFTNAddress(address, dimensions) {
|
||||||
//var addr = util.format('%d:%d', address.zone, address.net);
|
//var addr = util.format('%d:%d', address.zone, address.net);
|
||||||
var addr = '{0}:{1}'.format(address.zone, address.net);
|
var addr = '{0}:{1}'.format(address.zone, address.net);
|
||||||
|
|
|
@ -25,6 +25,10 @@ function MenuModule(options) {
|
||||||
var self = this;
|
var self = this;
|
||||||
this.menuName = options.menuName;
|
this.menuName = options.menuName;
|
||||||
this.menuConfig = options.menuConfig;
|
this.menuConfig = options.menuConfig;
|
||||||
|
this.client = options.client;
|
||||||
|
|
||||||
|
// :TODO: this and the line below with .config creates empty ({}) objects in the theme --
|
||||||
|
// ...which we really should not do. If they aren't there already, don't use 'em.
|
||||||
this.menuConfig.options = options.menuConfig.options || {};
|
this.menuConfig.options = options.menuConfig.options || {};
|
||||||
this.menuMethods = {}; // methods called from @method's
|
this.menuMethods = {}; // methods called from @method's
|
||||||
|
|
||||||
|
@ -190,10 +194,7 @@ require('util').inherits(MenuModule, PluginModule);
|
||||||
require('./mod_mixins.js').ViewControllerManagement.call(MenuModule.prototype);
|
require('./mod_mixins.js').ViewControllerManagement.call(MenuModule.prototype);
|
||||||
|
|
||||||
|
|
||||||
MenuModule.prototype.enter = function(client) {
|
MenuModule.prototype.enter = function() {
|
||||||
this.client = client;
|
|
||||||
assert(_.isObject(client));
|
|
||||||
|
|
||||||
if(_.isString(this.menuConfig.status)) {
|
if(_.isString(this.menuConfig.status)) {
|
||||||
this.client.currentStatus = this.menuConfig.status;
|
this.client.currentStatus = this.menuConfig.status;
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -131,7 +131,7 @@ MenuStack.prototype.goto = function(name, options, cb) {
|
||||||
modInst.restoreSavedState(options.savedState);
|
modInst.restoreSavedState(options.savedState);
|
||||||
}
|
}
|
||||||
|
|
||||||
modInst.enter(self.client);
|
modInst.enter();
|
||||||
|
|
||||||
self.client.log.trace(
|
self.client.log.trace(
|
||||||
{ stack : _.map(self.stack, function(si) { return si.name; } ) },
|
{ stack : _.map(self.stack, function(si) { return si.name; } ) },
|
||||||
|
|
|
@ -4,10 +4,8 @@
|
||||||
// ENiGMA½
|
// ENiGMA½
|
||||||
var moduleUtil = require('./module_util.js');
|
var moduleUtil = require('./module_util.js');
|
||||||
var Log = require('./logger.js').log;
|
var Log = require('./logger.js').log;
|
||||||
var conf = require('./config.js'); // :TODO: remove me!
|
|
||||||
var Config = require('./config.js').config;
|
var Config = require('./config.js').config;
|
||||||
var asset = require('./asset.js');
|
var asset = require('./asset.js');
|
||||||
var theme = require('./theme.js');
|
|
||||||
var getFullConfig = require('./config_util.js').getFullConfig;
|
var getFullConfig = require('./config_util.js').getFullConfig;
|
||||||
var MCIViewFactory = require('./mci_view_factory.js').MCIViewFactory;
|
var MCIViewFactory = require('./mci_view_factory.js').MCIViewFactory;
|
||||||
var acsUtil = require('./acs_util.js');
|
var acsUtil = require('./acs_util.js');
|
||||||
|
@ -68,17 +66,18 @@ function loadMenu(options, cb) {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
function loadMenuModule(menuConfig, callback) {
|
function loadMenuModule(menuConfig, callback) {
|
||||||
var modAsset = asset.getModuleAsset(menuConfig.module);
|
|
||||||
var modSupplied = null !== modAsset;
|
|
||||||
|
|
||||||
var modLoadOpts = {
|
const modAsset = asset.getModuleAsset(menuConfig.module);
|
||||||
|
const modSupplied = null !== modAsset;
|
||||||
|
|
||||||
|
const modLoadOpts = {
|
||||||
name : modSupplied ? modAsset.asset : 'standard_menu',
|
name : modSupplied ? modAsset.asset : 'standard_menu',
|
||||||
path : (!modSupplied || 'systemModule' === modAsset.type) ? __dirname : Config.paths.mods,
|
path : (!modSupplied || 'systemModule' === modAsset.type) ? __dirname : Config.paths.mods,
|
||||||
category : (!modSupplied || 'systemModule' === modAsset.type) ? null : 'mods',
|
category : (!modSupplied || 'systemModule' === modAsset.type) ? null : 'mods',
|
||||||
};
|
};
|
||||||
|
|
||||||
moduleUtil.loadModuleEx(modLoadOpts, function moduleLoaded(err, mod) {
|
moduleUtil.loadModuleEx(modLoadOpts, function moduleLoaded(err, mod) {
|
||||||
var modData = {
|
const modData = {
|
||||||
name : modLoadOpts.name,
|
name : modLoadOpts.name,
|
||||||
config : menuConfig,
|
config : menuConfig,
|
||||||
mod : mod,
|
mod : mod,
|
||||||
|
@ -97,7 +96,8 @@ function loadMenu(options, cb) {
|
||||||
{
|
{
|
||||||
menuName : options.name,
|
menuName : options.name,
|
||||||
menuConfig : modData.config,
|
menuConfig : modData.config,
|
||||||
extraArgs : options.extraArgs
|
extraArgs : options.extraArgs,
|
||||||
|
client : options.client,
|
||||||
});
|
});
|
||||||
callback(null, moduleInstance);
|
callback(null, moduleInstance);
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
|
@ -174,7 +174,7 @@ function handleAction(client, formData, conf) {
|
||||||
assert(_.isObject(conf));
|
assert(_.isObject(conf));
|
||||||
assert(_.isString(conf.action));
|
assert(_.isString(conf.action));
|
||||||
|
|
||||||
var actionAsset = asset.parseAsset(conf.action);
|
const actionAsset = asset.parseAsset(conf.action);
|
||||||
assert(_.isObject(actionAsset));
|
assert(_.isObject(actionAsset));
|
||||||
|
|
||||||
switch(actionAsset.type) {
|
switch(actionAsset.type) {
|
||||||
|
@ -245,84 +245,3 @@ function handleNext(client, nextSpec, conf) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// :TODO: Seems better in theme.js, but that includes ViewController...which would then include theme.js
|
|
||||||
// ...theme.js only brings in VC to create themed pause prompt. Perhaps that should live elsewhere
|
|
||||||
/*
|
|
||||||
function applyGeneralThemeCustomization(options) {
|
|
||||||
//
|
|
||||||
// options.name
|
|
||||||
// options.client
|
|
||||||
// options.type
|
|
||||||
// options.config
|
|
||||||
//
|
|
||||||
assert(_.isString(options.name));
|
|
||||||
assert(_.isObject(options.client));
|
|
||||||
assert("menus" === options.type || "prompts" === options.type);
|
|
||||||
|
|
||||||
if(_.has(options.client.currentTheme, [ 'customization', options.type, options.name ])) {
|
|
||||||
var themeConfig = options.client.currentTheme.customization[options.type][options.name];
|
|
||||||
|
|
||||||
if(themeConfig.config) {
|
|
||||||
Object.keys(themeConfig.config).forEach(function confEntry(conf) {
|
|
||||||
if(options.config[conf]) {
|
|
||||||
_.defaultsDeep(options.config[conf], themeConfig.config[conf]);
|
|
||||||
} else {
|
|
||||||
options.config[conf] = themeConfig.config[conf];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
function applyMciThemeCustomization(options) {
|
|
||||||
//
|
|
||||||
// options.name : menu/prompt name
|
|
||||||
// options.mci : menu/prompt .mci section
|
|
||||||
// options.client : client
|
|
||||||
// options.type : menu|prompt
|
|
||||||
// options.formId : (optional) form ID in cases where multiple forms may exist wanting their own customization
|
|
||||||
//
|
|
||||||
// In the case of formId, the theme must include the ID as well, e.g.:
|
|
||||||
// {
|
|
||||||
// ...
|
|
||||||
// "2" : {
|
|
||||||
// "TL1" : { ... }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
assert(_.isString(options.name));
|
|
||||||
assert("menus" === options.type || "prompts" === options.type);
|
|
||||||
assert(_.isObject(options.client));
|
|
||||||
|
|
||||||
if(_.isUndefined(options.mci)) {
|
|
||||||
options.mci = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
if(_.has(options.client.currentTheme, [ 'customization', options.type, options.name ])) {
|
|
||||||
var themeConfig = options.client.currentTheme.customization[options.type][options.name];
|
|
||||||
|
|
||||||
if(options.formId && _.has(themeConfig, options.formId.toString())) {
|
|
||||||
// form ID found - use exact match
|
|
||||||
themeConfig = themeConfig[options.formId];
|
|
||||||
}
|
|
||||||
|
|
||||||
if(themeConfig.mci) {
|
|
||||||
Object.keys(themeConfig.mci).forEach(function mciEntry(mci) {
|
|
||||||
// :TODO: a better way to do this?
|
|
||||||
if(options.mci[mci]) {
|
|
||||||
_.defaults(options.mci[mci], themeConfig.mci[mci]);
|
|
||||||
} else {
|
|
||||||
options.mci[mci] = themeConfig.mci[mci];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// :TODO: apply generic stuff, e.g. "VM" (vs "VM1")
|
|
||||||
}
|
|
||||||
*/
|
|
|
@ -16,7 +16,7 @@ function Message(options) {
|
||||||
options = options || {};
|
options = options || {};
|
||||||
|
|
||||||
this.messageId = options.messageId || 0; // always generated @ persist
|
this.messageId = options.messageId || 0; // always generated @ persist
|
||||||
this.areaName = options.areaName || Message.WellKnownAreaNames.Invalid;
|
this.areaTag = options.areaTag || Message.WellKnownAreaTags.Invalid;
|
||||||
this.uuid = uuid.v1();
|
this.uuid = uuid.v1();
|
||||||
this.replyToMsgId = options.replyToMsgId || 0;
|
this.replyToMsgId = options.replyToMsgId || 0;
|
||||||
this.toUserName = options.toUserName || '';
|
this.toUserName = options.toUserName || '';
|
||||||
|
@ -55,7 +55,7 @@ function Message(options) {
|
||||||
};
|
};
|
||||||
|
|
||||||
this.isPrivate = function() {
|
this.isPrivate = function() {
|
||||||
return this.areaName === Message.WellKnownAreaNames.Private ? true : false;
|
return this.areaTag === Message.WellKnownAreaTags.Private ? true : false;
|
||||||
};
|
};
|
||||||
|
|
||||||
this.getMessageTimestampString = function(ts) {
|
this.getMessageTimestampString = function(ts) {
|
||||||
|
@ -80,7 +80,7 @@ function Message(options) {
|
||||||
*/
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
Message.WellKnownAreaNames = {
|
Message.WellKnownAreaTags = {
|
||||||
Invalid : '',
|
Invalid : '',
|
||||||
Private : 'private_mail',
|
Private : 'private_mail',
|
||||||
Bulletin : 'local_bulletin',
|
Bulletin : 'local_bulletin',
|
||||||
|
@ -104,16 +104,21 @@ Message.SystemMetaNames = {
|
||||||
LocalFromUserID : 'local_from_user_id',
|
LocalFromUserID : 'local_from_user_id',
|
||||||
};
|
};
|
||||||
|
|
||||||
Message.FtnPropertyNames = {
|
Message.FtnPropertyNames = {
|
||||||
FtnCost : 'ftn_cost',
|
|
||||||
FtnOrigNode : 'ftn_orig_node',
|
FtnOrigNode : 'ftn_orig_node',
|
||||||
FtnDestNode : 'ftn_dest_node',
|
FtnDestNode : 'ftn_dest_node',
|
||||||
FtnOrigNetwork : 'ftn_orig_network',
|
FtnOrigNetwork : 'ftn_orig_network',
|
||||||
FtnDestNetwork : 'ftn_dest_network',
|
FtnDestNetwork : 'ftn_dest_network',
|
||||||
|
FtnAttrFlags1 : 'ftn_attr_flags1',
|
||||||
|
FtnAttrFlags2 : 'ftn_attr_flags2',
|
||||||
|
FtnCost : 'ftn_cost',
|
||||||
FtnOrigZone : 'ftn_orig_zone',
|
FtnOrigZone : 'ftn_orig_zone',
|
||||||
FtnDestZone : 'ftn_dest_zone',
|
FtnDestZone : 'ftn_dest_zone',
|
||||||
FtnOrigPoint : 'ftn_orig_point',
|
FtnOrigPoint : 'ftn_orig_point',
|
||||||
FtnDestPoint : 'ftn_dest_point',
|
FtnDestPoint : 'ftn_dest_point',
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
FtnAttribute : 'ftn_attribute',
|
FtnAttribute : 'ftn_attribute',
|
||||||
|
|
||||||
FtnTearLine : 'ftn_tear_line', // http://ftsc.org/docs/fts-0004.001
|
FtnTearLine : 'ftn_tear_line', // http://ftsc.org/docs/fts-0004.001
|
||||||
|
@ -141,7 +146,7 @@ Message.prototype.load = function(options, cb) {
|
||||||
[
|
[
|
||||||
function loadMessage(callback) {
|
function loadMessage(callback) {
|
||||||
msgDb.get(
|
msgDb.get(
|
||||||
'SELECT message_id, area_name, message_uuid, reply_to_message_id, to_user_name, from_user_name, subject, ' +
|
'SELECT message_id, area_tag, message_uuid, reply_to_message_id, to_user_name, from_user_name, subject, ' +
|
||||||
'message, modified_timestamp, view_count ' +
|
'message, modified_timestamp, view_count ' +
|
||||||
'FROM message ' +
|
'FROM message ' +
|
||||||
'WHERE message_uuid=? ' +
|
'WHERE message_uuid=? ' +
|
||||||
|
@ -149,7 +154,7 @@ Message.prototype.load = function(options, cb) {
|
||||||
[ options.uuid ],
|
[ options.uuid ],
|
||||||
function row(err, msgRow) {
|
function row(err, msgRow) {
|
||||||
self.messageId = msgRow.message_id;
|
self.messageId = msgRow.message_id;
|
||||||
self.areaName = msgRow.area_name;
|
self.areaTag = msgRow.area_tag;
|
||||||
self.messageUuid = msgRow.message_uuid;
|
self.messageUuid = msgRow.message_uuid;
|
||||||
self.replyToMsgId = msgRow.reply_to_message_id;
|
self.replyToMsgId = msgRow.reply_to_message_id;
|
||||||
self.toUserName = msgRow.to_user_name;
|
self.toUserName = msgRow.to_user_name;
|
||||||
|
@ -202,8 +207,8 @@ Message.prototype.persist = function(cb) {
|
||||||
},
|
},
|
||||||
function storeMessage(callback) {
|
function storeMessage(callback) {
|
||||||
msgDb.run(
|
msgDb.run(
|
||||||
'INSERT INTO message (area_name, message_uuid, reply_to_message_id, to_user_name, from_user_name, subject, message, modified_timestamp) ' +
|
'INSERT INTO message (area_tag, message_uuid, reply_to_message_id, to_user_name, from_user_name, subject, message, modified_timestamp) ' +
|
||||||
'VALUES (?, ?, ?, ?, ?, ?, ?, ?);', [ self.areaName, self.uuid, self.replyToMsgId, self.toUserName, self.fromUserName, self.subject, self.message, self.getMessageTimestampString(self.modTimestamp) ],
|
'VALUES (?, ?, ?, ?, ?, ?, ?, ?);', [ self.areaTag, self.uuid, self.replyToMsgId, self.toUserName, self.fromUserName, self.subject, self.message, self.getMessageTimestampString(self.modTimestamp) ],
|
||||||
function msgInsert(err) {
|
function msgInsert(err) {
|
||||||
if(!err) {
|
if(!err) {
|
||||||
self.messageId = this.lastID;
|
self.messageId = this.lastID;
|
||||||
|
|
|
@ -5,100 +5,275 @@ var msgDb = require('./database.js').dbs.message;
|
||||||
var Config = require('./config.js').config;
|
var Config = require('./config.js').config;
|
||||||
var Message = require('./message.js');
|
var Message = require('./message.js');
|
||||||
var Log = require('./logger.js').log;
|
var Log = require('./logger.js').log;
|
||||||
|
var checkAcs = require('./acs_util.js').checkAcs;
|
||||||
|
|
||||||
var async = require('async');
|
var async = require('async');
|
||||||
var _ = require('lodash');
|
var _ = require('lodash');
|
||||||
var assert = require('assert');
|
var assert = require('assert');
|
||||||
|
|
||||||
exports.getAvailableMessageAreas = getAvailableMessageAreas;
|
exports.getAvailableMessageConferences = getAvailableMessageConferences;
|
||||||
exports.getDefaultMessageArea = getDefaultMessageArea;
|
exports.getSortedAvailMessageConferences = getSortedAvailMessageConferences;
|
||||||
exports.getMessageAreaByName = getMessageAreaByName;
|
exports.getAvailableMessageAreasByConfTag = getAvailableMessageAreasByConfTag;
|
||||||
|
exports.getSortedAvailMessageAreasByConfTag = getSortedAvailMessageAreasByConfTag;
|
||||||
|
exports.getDefaultMessageConferenceTag = getDefaultMessageConferenceTag;
|
||||||
|
exports.getDefaultMessageAreaTagByConfTag = getDefaultMessageAreaTagByConfTag;
|
||||||
|
exports.getMessageConferenceByTag = getMessageConferenceByTag;
|
||||||
|
exports.getMessageAreaByTag = getMessageAreaByTag;
|
||||||
|
exports.changeMessageConference = changeMessageConference;
|
||||||
exports.changeMessageArea = changeMessageArea;
|
exports.changeMessageArea = changeMessageArea;
|
||||||
exports.getMessageListForArea = getMessageListForArea;
|
exports.getMessageListForArea = getMessageListForArea;
|
||||||
exports.getNewMessagesInAreaForUser = getNewMessagesInAreaForUser;
|
exports.getNewMessagesInAreaForUser = getNewMessagesInAreaForUser;
|
||||||
exports.getMessageAreaLastReadId = getMessageAreaLastReadId;
|
exports.getMessageAreaLastReadId = getMessageAreaLastReadId;
|
||||||
exports.updateMessageAreaLastReadId = updateMessageAreaLastReadId;
|
exports.updateMessageAreaLastReadId = updateMessageAreaLastReadId;
|
||||||
|
|
||||||
function getAvailableMessageAreas(options) {
|
const CONF_AREA_RW_ACS_DEFAULT = 'GM[users]';
|
||||||
// example: [ { "name" : "local_music", "desc" : "Music Discussion", "groups" : ["somegroup"] }, ... ]
|
const AREA_MANAGE_ACS_DEFAULT = 'GM[sysops]';
|
||||||
options = options || {};
|
|
||||||
|
|
||||||
var areas = Config.messages.areas;
|
const AREA_ACS_DEFAULT = {
|
||||||
var avail = [];
|
read : CONF_AREA_RW_ACS_DEFAULT,
|
||||||
for(var i = 0; i < areas.length; ++i) {
|
write : CONF_AREA_RW_ACS_DEFAULT,
|
||||||
if(true !== options.includePrivate &&
|
manage : AREA_MANAGE_ACS_DEFAULT,
|
||||||
Message.WellKnownAreaNames.Private === areas[i].name)
|
};
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
avail.push(areas[i]);
|
function getAvailableMessageConferences(client, options) {
|
||||||
}
|
options = options || { includeSystemInternal : false };
|
||||||
|
|
||||||
return avail;
|
// perform ACS check per conf & omit system_internal if desired
|
||||||
|
return _.omit(Config.messageConferences, (v, k) => {
|
||||||
|
if(!options.includeSystemInternal && 'system_internal' === k) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const readAcs = v.acs || CONF_AREA_RW_ACS_DEFAULT;
|
||||||
|
return !checkAcs(client, readAcs);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDefaultMessageArea() {
|
function getSortedAvailMessageConferences(client, options) {
|
||||||
//
|
var sorted = _.map(getAvailableMessageConferences(client, options), (v, k) => {
|
||||||
// Return first non-private/etc. area name. This will be from config.hjson
|
return {
|
||||||
//
|
confTag : k,
|
||||||
return getAvailableMessageAreas()[0];
|
conf : v,
|
||||||
/*
|
};
|
||||||
var avail = getAvailableMessageAreas();
|
});
|
||||||
for(var i = 0; i < avail.length; ++i) {
|
|
||||||
if(Message.WellKnownAreaNames.Private !== avail[i].name) {
|
sorted.sort((a, b) => {
|
||||||
return avail[i];
|
return a.conf.name.localeCompare(b.conf.name);
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
|
|
||||||
function getMessageAreaByName(areaName) {
|
|
||||||
areaName = areaName.toLowerCase();
|
|
||||||
|
|
||||||
var availAreas = getAvailableMessageAreas( { includePrivate : true } );
|
|
||||||
var index = _.findIndex(availAreas, function pred(an) {
|
|
||||||
return an.name == areaName;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if(index > -1) {
|
return sorted;
|
||||||
return availAreas[index];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function changeMessageArea(client, areaName, cb) {
|
// Return an *object* of available areas within |confTag|
|
||||||
|
function getAvailableMessageAreasByConfTag(confTag, options) {
|
||||||
|
options = options || {};
|
||||||
|
|
||||||
|
if(_.has(Config.messageConferences, [ confTag, 'areas' ])) {
|
||||||
|
const areas = Config.messageConferences[confTag].areas;
|
||||||
|
|
||||||
|
if(!options.client || true === options.noAcsCheck) {
|
||||||
|
// everything - no ACS checks
|
||||||
|
return areas;
|
||||||
|
} else {
|
||||||
|
// perform ACS check per area
|
||||||
|
return _.omit(areas, (v, k) => {
|
||||||
|
const readAcs = _.has(v, 'acs.read') ? v.acs.read : CONF_AREA_RW_ACS_DEFAULT;
|
||||||
|
return !checkAcs(options.client, readAcs);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSortedAvailMessageAreasByConfTag(confTag, options) {
|
||||||
|
const areas = getAvailableMessageAreasByConfTag(confTag, options);
|
||||||
|
|
||||||
|
// :TODO: should probably be using localeCompare / sort
|
||||||
|
return _.sortBy(_.map(areas, (v, k) => {
|
||||||
|
return {
|
||||||
|
areaTag : k,
|
||||||
|
area : v,
|
||||||
|
};
|
||||||
|
}), o => o.area.name); // sort by name
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDefaultMessageConferenceTag(client, disableAcsCheck) {
|
||||||
|
//
|
||||||
|
// Find the first conference marked 'default'. If found,
|
||||||
|
// inspect |client| against *read* ACS using defaults if not
|
||||||
|
// specified.
|
||||||
|
//
|
||||||
|
// If the above fails, just go down the list until we get one
|
||||||
|
// that passes.
|
||||||
|
//
|
||||||
|
// It's possible that we end up with nothing here!
|
||||||
|
//
|
||||||
|
// Note that built in 'system_internal' is always ommited here
|
||||||
|
//
|
||||||
|
let defaultConf = _.findKey(Config.messageConferences, o => o.default);
|
||||||
|
if(defaultConf) {
|
||||||
|
const acs = Config.messageConferences[defaultConf].acs || CONF_AREA_RW_ACS_DEFAULT;
|
||||||
|
if(true === disableAcsCheck || checkAcs(client, acs)) {
|
||||||
|
return defaultConf;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// just use anything we can
|
||||||
|
defaultConf = _.findKey(Config.messageConferences, (o, k) => {
|
||||||
|
const acs = o.acs || CONF_AREA_RW_ACS_DEFAULT;
|
||||||
|
return 'system_internal' !== k && (true === disableAcsCheck || checkAcs(client, acs));
|
||||||
|
});
|
||||||
|
|
||||||
|
return defaultConf;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDefaultMessageAreaTagByConfTag(client, confTag, disableAcsCheck) {
|
||||||
|
//
|
||||||
|
// Similar to finding the default conference:
|
||||||
|
// Find the first entry marked 'default', if any. If found, check | client| against
|
||||||
|
// *read* ACS. If this fails, just find the first one we can that passes checks.
|
||||||
|
//
|
||||||
|
// It's possible that we end up with nothing!
|
||||||
|
//
|
||||||
|
confTag = confTag || getDefaultMessageConferenceTag(client);
|
||||||
|
|
||||||
|
if(confTag && _.has(Config.messageConferences, [ confTag, 'areas' ])) {
|
||||||
|
const areaPool = Config.messageConferences[confTag].areas;
|
||||||
|
let defaultArea = _.findKey(areaPool, o => o.default);
|
||||||
|
if(defaultArea) {
|
||||||
|
const readAcs = _.has(areaPool, [ defaultArea, 'acs', 'read' ]) ? areaPool[defaultArea].acs.read : AREA_ACS_DEFAULT.read;
|
||||||
|
if(true === disableAcsCheck || checkAcs(client, readAcs)) {
|
||||||
|
return defaultArea;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultArea = _.findKey(areaPool, (o, k) => {
|
||||||
|
const readAcs = _.has(areaPool, [ defaultArea, 'acs', 'read' ]) ? areaPool[defaultArea].acs.read : AREA_ACS_DEFAULT.read;
|
||||||
|
return (true === disableAcsCheck || checkAcs(client, readAcs));
|
||||||
|
});
|
||||||
|
|
||||||
|
return defaultArea;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMessageConferenceByTag(confTag) {
|
||||||
|
return Config.messageConferences[confTag];
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMessageAreaByTag(areaTag, optionalConfTag) {
|
||||||
|
const confs = Config.messageConferences;
|
||||||
|
|
||||||
|
if(_.isString(optionalConfTag)) {
|
||||||
|
if(_.has(confs, [ optionalConfTag, 'areas', areaTag ])) {
|
||||||
|
return confs[optionalConfTag].areas[areaTag];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
//
|
||||||
|
// No confTag to work with - we'll have to search through them all
|
||||||
|
//
|
||||||
|
var area;
|
||||||
|
_.forEach(confs, (v, k) => {
|
||||||
|
if(_.has(v, [ 'areas', areaTag ])) {
|
||||||
|
area = v.areas[areaTag];
|
||||||
|
return false; // stop iteration
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return area;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function changeMessageConference(client, confTag, cb) {
|
||||||
|
async.waterfall(
|
||||||
|
[
|
||||||
|
function getConf(callback) {
|
||||||
|
const conf = getMessageConferenceByTag(confTag);
|
||||||
|
|
||||||
|
if(conf) {
|
||||||
|
callback(null, conf);
|
||||||
|
} else {
|
||||||
|
callback(new Error('Invalid message conference tag'));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
function getDefaultAreaInConf(conf, callback) {
|
||||||
|
const areaTag = getDefaultMessageAreaTagByConfTag(client, confTag);
|
||||||
|
const area = getMessageAreaByTag(areaTag, confTag);
|
||||||
|
|
||||||
|
if(area) {
|
||||||
|
callback(null, conf, { areaTag : areaTag, area : area } );
|
||||||
|
} else {
|
||||||
|
callback(new Error('No available areas for this user in conference'));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
function validateAccess(conf, areaInfo, callback) {
|
||||||
|
const confAcs = conf.acs || CONF_AREA_RW_ACS_DEFAULT;
|
||||||
|
|
||||||
|
if(!checkAcs(client, confAcs)) {
|
||||||
|
callback(new Error('User does not have access to this conference'));
|
||||||
|
} else {
|
||||||
|
const areaAcs = _.has(areaInfo, 'area.acs.read') ? areaInfo.area.acs.read : CONF_AREA_RW_ACS_DEFAULT;
|
||||||
|
if(!checkAcs(client, areaAcs)) {
|
||||||
|
callback(new Error('User does not have access to default area in this conference'));
|
||||||
|
} else {
|
||||||
|
callback(null, conf, areaInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
function changeConferenceAndArea(conf, areaInfo, callback) {
|
||||||
|
const newProps = {
|
||||||
|
message_conf_tag : confTag,
|
||||||
|
message_area_tag : areaInfo.areaTag,
|
||||||
|
};
|
||||||
|
client.user.persistProperties(newProps, err => {
|
||||||
|
callback(err, conf, areaInfo);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
],
|
||||||
|
function complete(err, conf, areaInfo) {
|
||||||
|
if(!err) {
|
||||||
|
client.log.info( { confTag : confTag, confName : conf.name, areaTag : areaInfo.areaTag }, 'Current message conference changed');
|
||||||
|
} else {
|
||||||
|
client.log.warn( { confTag : confTag, error : err.message }, 'Could not change message conference');
|
||||||
|
}
|
||||||
|
cb(err);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function changeMessageArea(client, areaTag, cb) {
|
||||||
|
|
||||||
async.waterfall(
|
async.waterfall(
|
||||||
[
|
[
|
||||||
function getArea(callback) {
|
function getArea(callback) {
|
||||||
var area = getMessageAreaByName(areaName);
|
const area = getMessageAreaByTag(areaTag);
|
||||||
|
|
||||||
if(area) {
|
if(area) {
|
||||||
callback(null, area);
|
callback(null, area);
|
||||||
} else {
|
} else {
|
||||||
callback(new Error('Invalid message area'));
|
callback(new Error('Invalid message area tag'));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
function validateAccess(area, callback) {
|
function validateAccess(area, callback) {
|
||||||
if(_.isArray(area.groups) && !
|
//
|
||||||
client.user.isGroupMember(area.groups))
|
// Need at least *read* to access the area
|
||||||
{
|
//
|
||||||
|
const readAcs = _.has(area, 'acs.read') ? area.acs.read : CONF_AREA_RW_ACS_DEFAULT;
|
||||||
|
if(!checkAcs(client, readAcs)) {
|
||||||
callback(new Error('User does not have access to this area'));
|
callback(new Error('User does not have access to this area'));
|
||||||
} else {
|
} else {
|
||||||
callback(null, area);
|
callback(null, area);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
function changeArea(area, callback) {
|
function changeArea(area, callback) {
|
||||||
client.user.persistProperty('message_area_name', area.name, function persisted(err) {
|
client.user.persistProperty('message_area_tag', areaTag, function persisted(err) {
|
||||||
callback(err, area);
|
callback(err, area);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
function complete(err, area) {
|
function complete(err, area) {
|
||||||
if(!err) {
|
if(!err) {
|
||||||
client.log.info( area, 'Current message area changed');
|
client.log.info( { areaTag : areaTag, area : area }, 'Current message area changed');
|
||||||
} else {
|
} else {
|
||||||
client.log.warn( { area : area, error : err.message }, 'Could not change message area');
|
client.log.warn( { areaTag : areaTag, area : area, error : err.message }, 'Could not change message area');
|
||||||
}
|
}
|
||||||
|
|
||||||
cb(err);
|
cb(err);
|
||||||
|
@ -119,9 +294,9 @@ function getMessageFromRow(row) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function getNewMessagesInAreaForUser(userId, areaName, cb) {
|
function getNewMessagesInAreaForUser(userId, areaTag, cb) {
|
||||||
//
|
//
|
||||||
// If |areaName| is Message.WellKnownAreaNames.Private,
|
// If |areaTag| is Message.WellKnownAreaTags.Private,
|
||||||
// only messages addressed to |userId| should be returned.
|
// only messages addressed to |userId| should be returned.
|
||||||
//
|
//
|
||||||
// Only messages > lastMessageId should be returned
|
// Only messages > lastMessageId should be returned
|
||||||
|
@ -131,7 +306,7 @@ function getNewMessagesInAreaForUser(userId, areaName, cb) {
|
||||||
async.waterfall(
|
async.waterfall(
|
||||||
[
|
[
|
||||||
function getLastMessageId(callback) {
|
function getLastMessageId(callback) {
|
||||||
getMessageAreaLastReadId(userId, areaName, function fetched(err, lastMessageId) {
|
getMessageAreaLastReadId(userId, areaTag, function fetched(err, lastMessageId) {
|
||||||
callback(null, lastMessageId || 0); // note: willingly ignoring any errors here!
|
callback(null, lastMessageId || 0); // note: willingly ignoring any errors here!
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -139,9 +314,9 @@ function getNewMessagesInAreaForUser(userId, areaName, cb) {
|
||||||
var sql =
|
var sql =
|
||||||
'SELECT message_id, message_uuid, reply_to_message_id, to_user_name, from_user_name, subject, modified_timestamp, view_count ' +
|
'SELECT message_id, message_uuid, reply_to_message_id, to_user_name, from_user_name, subject, modified_timestamp, view_count ' +
|
||||||
'FROM message ' +
|
'FROM message ' +
|
||||||
'WHERE area_name="' + areaName + '" AND message_id > ' + lastMessageId;
|
'WHERE area_tag ="' + areaTag + '" AND message_id > ' + lastMessageId;
|
||||||
|
|
||||||
if(Message.WellKnownAreaNames.Private === areaName) {
|
if(Message.WellKnownAreaTags.Private === areaTag) {
|
||||||
sql +=
|
sql +=
|
||||||
' AND message_id in (' +
|
' AND message_id in (' +
|
||||||
'SELECT message_id from message_meta where meta_category=' + Message.MetaCategories.System +
|
'SELECT message_id from message_meta where meta_category=' + Message.MetaCategories.System +
|
||||||
|
@ -150,8 +325,6 @@ function getNewMessagesInAreaForUser(userId, areaName, cb) {
|
||||||
|
|
||||||
sql += ' ORDER BY message_id;';
|
sql += ' ORDER BY message_id;';
|
||||||
|
|
||||||
console.log(sql)
|
|
||||||
|
|
||||||
msgDb.each(sql, function msgRow(err, row) {
|
msgDb.each(sql, function msgRow(err, row) {
|
||||||
if(!err) {
|
if(!err) {
|
||||||
msgList.push(getMessageFromRow(row));
|
msgList.push(getMessageFromRow(row));
|
||||||
|
@ -160,18 +333,17 @@ function getNewMessagesInAreaForUser(userId, areaName, cb) {
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
function complete(err) {
|
function complete(err) {
|
||||||
console.log(msgList)
|
|
||||||
cb(err, msgList);
|
cb(err, msgList);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getMessageListForArea(options, areaName, cb) {
|
function getMessageListForArea(options, areaTag, cb) {
|
||||||
//
|
//
|
||||||
// options.client (required)
|
// options.client (required)
|
||||||
//
|
//
|
||||||
|
|
||||||
options.client.log.debug( { areaName : areaName }, 'Fetching available messages');
|
options.client.log.debug( { areaTag : areaTag }, 'Fetching available messages');
|
||||||
|
|
||||||
assert(_.isObject(options.client));
|
assert(_.isObject(options.client));
|
||||||
|
|
||||||
|
@ -193,9 +365,9 @@ function getMessageListForArea(options, areaName, cb) {
|
||||||
msgDb.each(
|
msgDb.each(
|
||||||
'SELECT message_id, message_uuid, reply_to_message_id, to_user_name, from_user_name, subject, modified_timestamp, view_count ' +
|
'SELECT message_id, message_uuid, reply_to_message_id, to_user_name, from_user_name, subject, modified_timestamp, view_count ' +
|
||||||
'FROM message ' +
|
'FROM message ' +
|
||||||
'WHERE area_name=? ' +
|
'WHERE area_tag = ? ' +
|
||||||
'ORDER BY message_id;',
|
'ORDER BY message_id;',
|
||||||
[ areaName.toLowerCase() ],
|
[ areaTag.toLowerCase() ],
|
||||||
function msgRow(err, row) {
|
function msgRow(err, row) {
|
||||||
if(!err) {
|
if(!err) {
|
||||||
msgList.push(getMessageFromRow(row));
|
msgList.push(getMessageFromRow(row));
|
||||||
|
@ -214,24 +386,24 @@ function getMessageListForArea(options, areaName, cb) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getMessageAreaLastReadId(userId, areaName, cb) {
|
function getMessageAreaLastReadId(userId, areaTag, cb) {
|
||||||
msgDb.get(
|
msgDb.get(
|
||||||
'SELECT message_id ' +
|
'SELECT message_id ' +
|
||||||
'FROM user_message_area_last_read ' +
|
'FROM user_message_area_last_read ' +
|
||||||
'WHERE user_id = ? AND area_name = ?;',
|
'WHERE user_id = ? AND area_tag = ?;',
|
||||||
[ userId, areaName ],
|
[ userId, areaTag ],
|
||||||
function complete(err, row) {
|
function complete(err, row) {
|
||||||
cb(err, row ? row.message_id : 0);
|
cb(err, row ? row.message_id : 0);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateMessageAreaLastReadId(userId, areaName, messageId, cb) {
|
function updateMessageAreaLastReadId(userId, areaTag, messageId, cb) {
|
||||||
// :TODO: likely a better way to do this...
|
// :TODO: likely a better way to do this...
|
||||||
async.waterfall(
|
async.waterfall(
|
||||||
[
|
[
|
||||||
function getCurrent(callback) {
|
function getCurrent(callback) {
|
||||||
getMessageAreaLastReadId(userId, areaName, function result(err, lastId) {
|
getMessageAreaLastReadId(userId, areaTag, function result(err, lastId) {
|
||||||
lastId = lastId || 0;
|
lastId = lastId || 0;
|
||||||
callback(null, lastId); // ignore errors as we default to 0
|
callback(null, lastId); // ignore errors as we default to 0
|
||||||
});
|
});
|
||||||
|
@ -239,25 +411,29 @@ function updateMessageAreaLastReadId(userId, areaName, messageId, cb) {
|
||||||
function update(lastId, callback) {
|
function update(lastId, callback) {
|
||||||
if(messageId > lastId) {
|
if(messageId > lastId) {
|
||||||
msgDb.run(
|
msgDb.run(
|
||||||
'REPLACE INTO user_message_area_last_read (user_id, area_name, message_id) ' +
|
'REPLACE INTO user_message_area_last_read (user_id, area_tag, message_id) ' +
|
||||||
'VALUES (?, ?, ?);',
|
'VALUES (?, ?, ?);',
|
||||||
[ userId, areaName, messageId ],
|
[ userId, areaTag, messageId ],
|
||||||
callback
|
function written(err) {
|
||||||
|
callback(err, true); // true=didUpdate
|
||||||
|
}
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
callback(null);
|
callback(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
function complete(err) {
|
function complete(err, didUpdate) {
|
||||||
if(err) {
|
if(err) {
|
||||||
Log.debug(
|
Log.debug(
|
||||||
{ error : err.toString(), userId : userId, areaName : areaName, messageId : messageId },
|
{ error : err.toString(), userId : userId, areaTag : areaTag, messageId : messageId },
|
||||||
'Failed updating area last read ID');
|
'Failed updating area last read ID');
|
||||||
} else {
|
} else {
|
||||||
Log.trace(
|
if(true === didUpdate) {
|
||||||
{ userId : userId, areaName : areaName, messageId : messageId },
|
Log.trace(
|
||||||
'Area last read ID updated');
|
{ userId : userId, areaTag : areaTag, messageId : messageId },
|
||||||
|
'Area last read ID updated');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
cb(err);
|
cb(err);
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,38 +14,40 @@ exports.loadModuleEx = loadModuleEx;
|
||||||
exports.loadModule = loadModule;
|
exports.loadModule = loadModule;
|
||||||
exports.loadModulesForCategory = loadModulesForCategory;
|
exports.loadModulesForCategory = loadModulesForCategory;
|
||||||
|
|
||||||
|
|
||||||
function loadModuleEx(options, cb) {
|
function loadModuleEx(options, cb) {
|
||||||
assert(_.isObject(options));
|
assert(_.isObject(options));
|
||||||
assert(_.isString(options.name));
|
assert(_.isString(options.name));
|
||||||
assert(_.isString(options.path));
|
assert(_.isString(options.path));
|
||||||
|
|
||||||
var modConfig = _.isObject(Config[options.category]) ? Config[options.category][options.name] : null;
|
const modConfig = _.isObject(Config[options.category]) ? Config[options.category][options.name] : null;
|
||||||
|
|
||||||
if(_.isObject(modConfig) && false === modConfig.enabled) {
|
if(_.isObject(modConfig) && false === modConfig.enabled) {
|
||||||
cb(new Error('Module "' + options.name + '" is disabled'));
|
cb(new Error('Module "' + options.name + '" is disabled'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var mod;
|
||||||
try {
|
try {
|
||||||
var mod = require(paths.join(options.path, options.name + '.js'));
|
mod = require(paths.join(options.path, options.name + '.js'));
|
||||||
|
|
||||||
if(!_.isObject(mod.moduleInfo)) {
|
|
||||||
cb(new Error('Module is missing "moduleInfo" section'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!_.isFunction(mod.getModule)) {
|
|
||||||
cb(new Error('Invalid or missing "getModule" method for module!'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Safe configuration, if any, for convience to the module
|
|
||||||
mod.runtime = { config : modConfig };
|
|
||||||
|
|
||||||
cb(null, mod);
|
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
cb(e);
|
cb(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(!_.isObject(mod.moduleInfo)) {
|
||||||
|
cb(new Error('Module is missing "moduleInfo" section'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!_.isFunction(mod.getModule)) {
|
||||||
|
cb(new Error('Invalid or missing "getModule" method for module!'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ref configuration, if any, for convience to the module
|
||||||
|
mod.runtime = { config : modConfig };
|
||||||
|
|
||||||
|
cb(null, mod);
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadModule(name, category, cb) {
|
function loadModule(name, category, cb) {
|
||||||
|
@ -61,7 +63,7 @@ function loadModule(name, category, cb) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadModulesForCategory(category, cb) {
|
function loadModulesForCategory(category, iterator) {
|
||||||
var path = Config.paths[category];
|
var path = Config.paths[category];
|
||||||
|
|
||||||
fs.readdir(path, function onFiles(err, files) {
|
fs.readdir(path, function onFiles(err, files) {
|
||||||
|
@ -72,8 +74,7 @@ function loadModulesForCategory(category, cb) {
|
||||||
|
|
||||||
var filtered = files.filter(function onFilter(file) { return '.js' === paths.extname(file); });
|
var filtered = files.filter(function onFilter(file) { return '.js' === paths.extname(file); });
|
||||||
filtered.forEach(function onFile(file) {
|
filtered.forEach(function onFile(file) {
|
||||||
var modName = paths.basename(file, '.js');
|
loadModule(paths.basename(file, '.js'), category, iterator);
|
||||||
loadModule(paths.basename(file, '.js'), category, cb);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
/* 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);
|
||||||
|
};
|
|
@ -0,0 +1,27 @@
|
||||||
|
/* 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
|
||||||
|
};
|
109
core/new_scan.js
109
core/new_scan.js
|
@ -7,6 +7,7 @@ var Message = require('./message.js');
|
||||||
var MenuModule = require('./menu_module.js').MenuModule;
|
var MenuModule = require('./menu_module.js').MenuModule;
|
||||||
var ViewController = require('../core/view_controller.js').ViewController;
|
var ViewController = require('../core/view_controller.js').ViewController;
|
||||||
|
|
||||||
|
var _ = require('lodash');
|
||||||
var async = require('async');
|
var async = require('async');
|
||||||
|
|
||||||
exports.moduleInfo = {
|
exports.moduleInfo = {
|
||||||
|
@ -36,10 +37,11 @@ function NewScanModule(options) {
|
||||||
var self = this;
|
var self = this;
|
||||||
var config = this.menuConfig.config;
|
var config = this.menuConfig.config;
|
||||||
|
|
||||||
this.currentStep = 'messageAreas';
|
this.currentStep = 'messageConferences';
|
||||||
this.currentScanAux = 0; // e.g. Message.WellKnownAreaNames.Private when currentSteps = messageAreas
|
this.currentScanAux = {};
|
||||||
|
|
||||||
this.scanStartFmt = config.scanStartFmt || 'Scanning {desc}...';
|
// :TODO: Make this conf/area specific:
|
||||||
|
this.scanStartFmt = config.scanStartFmt || 'Scanning {confName} - {areaName}...';
|
||||||
this.scanFinishNoneFmt = config.scanFinishNoneFmt || 'Nothing new';
|
this.scanFinishNoneFmt = config.scanFinishNoneFmt || 'Nothing new';
|
||||||
this.scanFinishNewFmt = config.scanFinishNewFmt || '{count} entries found';
|
this.scanFinishNewFmt = config.scanFinishNewFmt || '{count} entries found';
|
||||||
this.scanCompleteMsg = config.scanCompleteMsg || 'Finished newscan';
|
this.scanCompleteMsg = config.scanCompleteMsg || 'Finished newscan';
|
||||||
|
@ -57,10 +59,65 @@ function NewScanModule(options) {
|
||||||
if(view) {
|
if(view) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.newScanMessageConference = function(cb) {
|
||||||
|
// lazy init
|
||||||
|
if(!self.sortedMessageConfs) {
|
||||||
|
const getAvailOpts = { includeSystemInternal : true }; // find new private messages, bulletins, etc.
|
||||||
|
|
||||||
this.newScanMessageArea = function(cb) {
|
self.sortedMessageConfs = _.map(msgArea.getAvailableMessageConferences(self.client, getAvailOpts), (v, k) => {
|
||||||
var availMsgAreas = msgArea.getAvailableMessageAreas( { includePrivate : true } );
|
return {
|
||||||
var currentArea = availMsgAreas[self.currentScanAux];
|
confTag : k,
|
||||||
|
conf : v,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
//
|
||||||
|
// Sort conferences by name, other than 'system_internal' which should
|
||||||
|
// always come first such that we display private mails/etc. before
|
||||||
|
// other conferences & areas
|
||||||
|
//
|
||||||
|
self.sortedMessageConfs.sort((a, b) => {
|
||||||
|
if('system_internal' === a.confTag) {
|
||||||
|
return -1;
|
||||||
|
} else {
|
||||||
|
return a.conf.name.localeCompare(b.conf.name);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
self.currentScanAux.conf = self.currentScanAux.conf || 0;
|
||||||
|
self.currentScanAux.area = self.currentScanAux.area || 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentConf = self.sortedMessageConfs[self.currentScanAux.conf];
|
||||||
|
|
||||||
|
async.series(
|
||||||
|
[
|
||||||
|
function scanArea(callback) {
|
||||||
|
//self.currentScanAux.area = self.currentScanAux.area || 0;
|
||||||
|
|
||||||
|
self.newScanMessageArea(currentConf, function areaScanComplete(err) {
|
||||||
|
if(self.sortedMessageConfs.length > self.currentScanAux.conf + 1) {
|
||||||
|
self.currentScanAux.conf += 1;
|
||||||
|
self.currentScanAux.area = 0;
|
||||||
|
|
||||||
|
self.newScanMessageConference(cb); // recursive to next conf
|
||||||
|
//callback(null);
|
||||||
|
} else {
|
||||||
|
self.updateScanStatus(self.scanCompleteMsg);
|
||||||
|
callback(new Error('No more conferences'));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
],
|
||||||
|
cb
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
this.newScanMessageArea = function(conf, cb) {
|
||||||
|
// :TODO: it would be nice to cache this - must be done by conf!
|
||||||
|
const sortedAreas = msgArea.getSortedAvailMessageAreasByConfTag(conf.confTag, { client : self.client } );
|
||||||
|
const currentArea = sortedAreas[self.currentScanAux.area];
|
||||||
|
|
||||||
//
|
//
|
||||||
// Scan and update index until we find something. If results are found,
|
// Scan and update index until we find something. If results are found,
|
||||||
|
@ -70,8 +127,8 @@ function NewScanModule(options) {
|
||||||
[
|
[
|
||||||
function checkAndUpdateIndex(callback) {
|
function checkAndUpdateIndex(callback) {
|
||||||
// Advance to next area if possible
|
// Advance to next area if possible
|
||||||
if(availMsgAreas.length >= self.currentScanAux + 1) {
|
if(sortedAreas.length >= self.currentScanAux.area + 1) {
|
||||||
self.currentScanAux += 1;
|
self.currentScanAux.area += 1;
|
||||||
callback(null);
|
callback(null);
|
||||||
} else {
|
} else {
|
||||||
self.updateScanStatus(self.scanCompleteMsg);
|
self.updateScanStatus(self.scanCompleteMsg);
|
||||||
|
@ -80,22 +137,30 @@ function NewScanModule(options) {
|
||||||
},
|
},
|
||||||
function updateStatusScanStarted(callback) {
|
function updateStatusScanStarted(callback) {
|
||||||
self.updateScanStatus(self.scanStartFmt.format({
|
self.updateScanStatus(self.scanStartFmt.format({
|
||||||
desc : currentArea.desc,
|
confName : conf.conf.name,
|
||||||
|
confDesc : conf.conf.desc,
|
||||||
|
areaName : currentArea.area.name,
|
||||||
|
areaDesc : currentArea.area.desc,
|
||||||
}));
|
}));
|
||||||
callback(null);
|
callback(null);
|
||||||
},
|
},
|
||||||
function newScanAreaAndGetMessages(callback) {
|
function newScanAreaAndGetMessages(callback) {
|
||||||
msgArea.getNewMessagesInAreaForUser(
|
msgArea.getNewMessagesInAreaForUser(
|
||||||
self.client.user.userId, currentArea.name, function msgs(err, msgList) {
|
self.client.user.userId, currentArea.areaTag, function msgs(err, msgList) {
|
||||||
if(!err) {
|
if(!err) {
|
||||||
if(0 === msgList.length) {
|
if(0 === msgList.length) {
|
||||||
self.updateScanStatus(self.scanFinishNoneFmt.format({
|
self.updateScanStatus(self.scanFinishNoneFmt.format({
|
||||||
desc : currentArea.desc,
|
confName : conf.conf.name,
|
||||||
|
confDesc : conf.conf.desc,
|
||||||
|
areaName : currentArea.area.name,
|
||||||
|
areaDesc : currentArea.area.desc,
|
||||||
}));
|
}));
|
||||||
} else {
|
} else {
|
||||||
self.updateScanStatus(self.scanFinishNewFmt.format({
|
self.updateScanStatus(self.scanFinishNewFmt.format({
|
||||||
desc : currentArea.desc,
|
confName : conf.conf.name,
|
||||||
count : msgList.length,
|
confDesc : conf.conf.desc,
|
||||||
|
areaName : currentArea.area.name,
|
||||||
|
count : msgList.length,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -107,14 +172,14 @@ function NewScanModule(options) {
|
||||||
if(msgList && msgList.length > 0) {
|
if(msgList && msgList.length > 0) {
|
||||||
var nextModuleOpts = {
|
var nextModuleOpts = {
|
||||||
extraArgs: {
|
extraArgs: {
|
||||||
messageAreaName : currentArea.name,
|
messageAreaTag : currentArea.areaTag,
|
||||||
messageList : msgList,
|
messageList : msgList,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
self.gotoMenu(config.newScanMessageList || 'newScanMessageList', nextModuleOpts);
|
self.gotoMenu(config.newScanMessageList || 'newScanMessageList', nextModuleOpts);
|
||||||
} else {
|
} else {
|
||||||
self.newScanMessageArea(cb);
|
self.newScanMessageArea(conf, cb);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
@ -161,10 +226,10 @@ NewScanModule.prototype.mciReady = function(mciData, cb) {
|
||||||
},
|
},
|
||||||
function performCurrentStepScan(callback) {
|
function performCurrentStepScan(callback) {
|
||||||
switch(self.currentStep) {
|
switch(self.currentStep) {
|
||||||
case 'messageAreas' :
|
case 'messageConferences' :
|
||||||
self.newScanMessageArea(function scanComplete(err) {
|
self.newScanMessageConference(function scanComplete(err) {
|
||||||
callback(null); // finished
|
callback(null); // finished
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default :
|
default :
|
||||||
|
@ -180,9 +245,3 @@ NewScanModule.prototype.mciReady = function(mciData, cb) {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
|
||||||
NewScanModule.prototype.finishedLoading = function() {
|
|
||||||
NewScanModule.super_.prototype.finishedLoading.call(this);
|
|
||||||
};
|
|
||||||
*/
|
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
var Config = require('./config.js').config;
|
var Config = require('./config.js').config;
|
||||||
var Log = require('./logger.js').log;
|
var Log = require('./logger.js').log;
|
||||||
var getMessageAreaByName = require('./message_area.js').getMessageAreaByName;
|
var getMessageAreaByTag = require('./message_area.js').getMessageAreaByTag;
|
||||||
var clientConnections = require('./client_connections.js');
|
var clientConnections = require('./client_connections.js');
|
||||||
var sysProp = require('./system_property.js');
|
var sysProp = require('./system_property.js');
|
||||||
|
|
||||||
|
@ -63,10 +63,15 @@ function getPredefinedMCIValue(client, code) {
|
||||||
return _.has(client, 'currentMenuModule.menuConfig.desc') ? client.currentMenuModule.menuConfig.desc : '';
|
return _.has(client, 'currentMenuModule.menuConfig.desc') ? client.currentMenuModule.menuConfig.desc : '';
|
||||||
},
|
},
|
||||||
|
|
||||||
MA : function messageAreaDescription() {
|
MA : function messageAreaName() {
|
||||||
var area = getMessageAreaByName(client.user.properties.message_area_name);
|
const area = getMessageAreaByTag(client.user.properties.message_area_tag);
|
||||||
return area ? area.desc : '';
|
return area ? area.name : '';
|
||||||
},
|
},
|
||||||
|
|
||||||
|
ML : function messageAreaDescription() {
|
||||||
|
const area = getMessageAreaByTag(client.user.properties.message_area_tag);
|
||||||
|
return area ? area.desc : '';
|
||||||
|
},
|
||||||
|
|
||||||
SH : function termHeight() { return client.term.termHeight.toString(); },
|
SH : function termHeight() { return client.term.termHeight.toString(); },
|
||||||
SW : function termWidth() { return client.term.termWidth.toString(); },
|
SW : function termWidth() { return client.term.termWidth.toString(); },
|
||||||
|
|
|
@ -0,0 +1,165 @@
|
||||||
|
/* jslint node: true */
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var binary = require('binary');
|
||||||
|
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'
|
||||||
|
|
||||||
|
// :TODO: SAUCE should be a class
|
||||||
|
// - with getFontName()
|
||||||
|
// - ...other methods
|
||||||
|
|
||||||
|
//
|
||||||
|
// See
|
||||||
|
// http://www.acid.org/info/sauce/sauce.htm
|
||||||
|
//
|
||||||
|
function readSAUCE(data, cb) {
|
||||||
|
if(data.length < SAUCE_SIZE) {
|
||||||
|
cb(new Error('No SAUCE record present'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var offset = data.length - SAUCE_SIZE;
|
||||||
|
var sauceRec = data.slice(offset);
|
||||||
|
|
||||||
|
binary.parse(sauceRec)
|
||||||
|
.buffer('id', 5)
|
||||||
|
.buffer('version', 2)
|
||||||
|
.buffer('title', 35)
|
||||||
|
.buffer('author', 20)
|
||||||
|
.buffer('group', 20)
|
||||||
|
.buffer('date', 8)
|
||||||
|
.word32lu('fileSize')
|
||||||
|
.word8('dataType')
|
||||||
|
.word8('fileType')
|
||||||
|
.word16lu('tinfo1')
|
||||||
|
.word16lu('tinfo2')
|
||||||
|
.word16lu('tinfo3')
|
||||||
|
.word16lu('tinfo4')
|
||||||
|
.word8('numComments')
|
||||||
|
.word8('flags')
|
||||||
|
.buffer('tinfos', 22) // SAUCE 00.5
|
||||||
|
.tap(function onVars(vars) {
|
||||||
|
|
||||||
|
if(!SAUCE_ID.equals(vars.id)) {
|
||||||
|
cb(new Error('No SAUCE record present'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var ver = iconv.decode(vars.version, 'cp437');
|
||||||
|
|
||||||
|
if('00' !== ver) {
|
||||||
|
cb(new Error('Unsupported SAUCE version: ' + ver));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var sauce = {
|
||||||
|
id : iconv.decode(vars.id, 'cp437'),
|
||||||
|
version : iconv.decode(vars.version, 'cp437').trim(),
|
||||||
|
title : iconv.decode(vars.title, 'cp437').trim(),
|
||||||
|
author : iconv.decode(vars.author, 'cp437').trim(),
|
||||||
|
group : iconv.decode(vars.group, 'cp437').trim(),
|
||||||
|
date : iconv.decode(vars.date, 'cp437').trim(),
|
||||||
|
fileSize : vars.fileSize,
|
||||||
|
dataType : vars.dataType,
|
||||||
|
fileType : vars.fileType,
|
||||||
|
tinfo1 : vars.tinfo1,
|
||||||
|
tinfo2 : vars.tinfo2,
|
||||||
|
tinfo3 : vars.tinfo3,
|
||||||
|
tinfo4 : vars.tinfo4,
|
||||||
|
numComments : vars.numComments,
|
||||||
|
flags : vars.flags,
|
||||||
|
tinfos : vars.tinfos,
|
||||||
|
};
|
||||||
|
|
||||||
|
var dt = SAUCE_DATA_TYPES[sauce.dataType];
|
||||||
|
if(dt && dt.parser) {
|
||||||
|
sauce[dt.name] = dt.parser(sauce);
|
||||||
|
}
|
||||||
|
|
||||||
|
cb(null, sauce);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// :TODO: These need completed:
|
||||||
|
var SAUCE_DATA_TYPES = {};
|
||||||
|
SAUCE_DATA_TYPES[0] = { name : 'None' };
|
||||||
|
SAUCE_DATA_TYPES[1] = { name : 'Character', parser : parseCharacterSAUCE };
|
||||||
|
SAUCE_DATA_TYPES[2] = 'Bitmap';
|
||||||
|
SAUCE_DATA_TYPES[3] = 'Vector';
|
||||||
|
SAUCE_DATA_TYPES[4] = 'Audio';
|
||||||
|
SAUCE_DATA_TYPES[5] = 'BinaryText';
|
||||||
|
SAUCE_DATA_TYPES[6] = 'XBin';
|
||||||
|
SAUCE_DATA_TYPES[7] = 'Archive';
|
||||||
|
SAUCE_DATA_TYPES[8] = 'Executable';
|
||||||
|
|
||||||
|
var SAUCE_CHARACTER_FILE_TYPES = {};
|
||||||
|
SAUCE_CHARACTER_FILE_TYPES[0] = 'ASCII';
|
||||||
|
SAUCE_CHARACTER_FILE_TYPES[1] = 'ANSi';
|
||||||
|
SAUCE_CHARACTER_FILE_TYPES[2] = 'ANSiMation';
|
||||||
|
SAUCE_CHARACTER_FILE_TYPES[3] = 'RIP script';
|
||||||
|
SAUCE_CHARACTER_FILE_TYPES[4] = 'PCBoard';
|
||||||
|
SAUCE_CHARACTER_FILE_TYPES[5] = 'Avatar';
|
||||||
|
SAUCE_CHARACTER_FILE_TYPES[6] = 'HTML';
|
||||||
|
SAUCE_CHARACTER_FILE_TYPES[7] = 'Source';
|
||||||
|
SAUCE_CHARACTER_FILE_TYPES[8] = 'TundraDraw';
|
||||||
|
|
||||||
|
//
|
||||||
|
// Map of SAUCE font -> encoding hint
|
||||||
|
//
|
||||||
|
// Note that this is the same mapping that x84 uses. Be compatible!
|
||||||
|
//
|
||||||
|
var SAUCE_FONT_TO_ENCODING_HINT = {
|
||||||
|
'Amiga MicroKnight' : 'amiga',
|
||||||
|
'Amiga MicroKnight+' : 'amiga',
|
||||||
|
'Amiga mOsOul' : 'amiga',
|
||||||
|
'Amiga P0T-NOoDLE' : 'amiga',
|
||||||
|
'Amiga Topaz 1' : 'amiga',
|
||||||
|
'Amiga Topaz 1+' : 'amiga',
|
||||||
|
'Amiga Topaz 2' : 'amiga',
|
||||||
|
'Amiga Topaz 2+' : 'amiga',
|
||||||
|
'Atari ATASCII' : 'atari',
|
||||||
|
'IBM EGA43' : 'cp437',
|
||||||
|
'IBM EGA' : 'cp437',
|
||||||
|
'IBM VGA25G' : 'cp437',
|
||||||
|
'IBM VGA50' : 'cp437',
|
||||||
|
'IBM VGA' : 'cp437',
|
||||||
|
};
|
||||||
|
|
||||||
|
['437', '720', '737', '775', '819', '850', '852', '855', '857', '858',
|
||||||
|
'860', '861', '862', '863', '864', '865', '866', '869', '872'].forEach(function onPage(page) {
|
||||||
|
var codec = 'cp' + page;
|
||||||
|
SAUCE_FONT_TO_ENCODING_HINT['IBM EGA43 ' + page] = codec;
|
||||||
|
SAUCE_FONT_TO_ENCODING_HINT['IBM EGA ' + page] = codec;
|
||||||
|
SAUCE_FONT_TO_ENCODING_HINT['IBM VGA25g ' + page] = codec;
|
||||||
|
SAUCE_FONT_TO_ENCODING_HINT['IBM VGA50 ' + page] = codec;
|
||||||
|
SAUCE_FONT_TO_ENCODING_HINT['IBM VGA ' + page] = codec;
|
||||||
|
});
|
||||||
|
|
||||||
|
function parseCharacterSAUCE(sauce) {
|
||||||
|
var result = {};
|
||||||
|
|
||||||
|
result.fileType = SAUCE_CHARACTER_FILE_TYPES[sauce.fileType] || 'Unknown';
|
||||||
|
|
||||||
|
if(sauce.fileType === 0 || sauce.fileType === 1 || sauce.fileType === 2) {
|
||||||
|
// convience: create ansiFlags
|
||||||
|
sauce.ansiFlags = sauce.flags;
|
||||||
|
|
||||||
|
var i = 0;
|
||||||
|
while(i < sauce.tinfos.length && sauce.tinfos[i] !== 0x00) {
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
var fontName = iconv.decode(sauce.tinfos.slice(0, i), 'cp437');
|
||||||
|
if(fontName.length > 0) {
|
||||||
|
result.fontName = fontName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
|
@ -18,8 +18,8 @@ function StandardMenuModule(menuConfig) {
|
||||||
require('util').inherits(StandardMenuModule, MenuModule);
|
require('util').inherits(StandardMenuModule, MenuModule);
|
||||||
|
|
||||||
|
|
||||||
StandardMenuModule.prototype.enter = function(client) {
|
StandardMenuModule.prototype.enter = function() {
|
||||||
StandardMenuModule.super_.prototype.enter.call(this, client);
|
StandardMenuModule.super_.prototype.enter.call(this);
|
||||||
};
|
};
|
||||||
|
|
||||||
StandardMenuModule.prototype.beforeArt = function() {
|
StandardMenuModule.prototype.beforeArt = function() {
|
||||||
|
|
|
@ -203,13 +203,13 @@ function getMergedTheme(menuConfig, promptConfig, theme) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[ 'menus', 'prompts' ].forEach(function areaEntry(areaName) {
|
[ 'menus', 'prompts' ].forEach(function areaEntry(sectionName) {
|
||||||
_.keys(mergedTheme[areaName]).forEach(function menuEntry(menuName) {
|
_.keys(mergedTheme[sectionName]).forEach(function menuEntry(menuName) {
|
||||||
var createdFormSection = false;
|
var createdFormSection = false;
|
||||||
var mergedThemeMenu = mergedTheme[areaName][menuName];
|
var mergedThemeMenu = mergedTheme[sectionName][menuName];
|
||||||
|
|
||||||
if(_.has(theme, [ 'customization', areaName, menuName ])) {
|
if(_.has(theme, [ 'customization', sectionName, menuName ])) {
|
||||||
var menuTheme = theme.customization[areaName][menuName];
|
var menuTheme = theme.customization[sectionName][menuName];
|
||||||
|
|
||||||
// config block is direct assign/overwrite
|
// config block is direct assign/overwrite
|
||||||
// :TODO: should probably be _.merge()
|
// :TODO: should probably be _.merge()
|
||||||
|
@ -217,7 +217,7 @@ function getMergedTheme(menuConfig, promptConfig, theme) {
|
||||||
mergedThemeMenu.config = _.assign(mergedThemeMenu.config || {}, menuTheme.config);
|
mergedThemeMenu.config = _.assign(mergedThemeMenu.config || {}, menuTheme.config);
|
||||||
}
|
}
|
||||||
|
|
||||||
if('menus' === areaName) {
|
if('menus' === sectionName) {
|
||||||
if(_.isObject(mergedThemeMenu.form)) {
|
if(_.isObject(mergedThemeMenu.form)) {
|
||||||
getFormKeys(mergedThemeMenu.form).forEach(function formKeyEntry(formKey) {
|
getFormKeys(mergedThemeMenu.form).forEach(function formKeyEntry(formKey) {
|
||||||
applyToForm(mergedThemeMenu.form[formKey], menuTheme, formKey);
|
applyToForm(mergedThemeMenu.form[formKey], menuTheme, formKey);
|
||||||
|
@ -233,7 +233,7 @@ function getMergedTheme(menuConfig, promptConfig, theme) {
|
||||||
createdFormSection = true;
|
createdFormSection = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if('prompts' === areaName) {
|
} else if('prompts' === sectionName) {
|
||||||
// no 'form' or form keys for prompts -- direct to mci
|
// no 'form' or form keys for prompts -- direct to mci
|
||||||
applyToForm(mergedThemeMenu, menuTheme);
|
applyToForm(mergedThemeMenu, menuTheme);
|
||||||
}
|
}
|
||||||
|
@ -247,7 +247,7 @@ function getMergedTheme(menuConfig, promptConfig, theme) {
|
||||||
// * There is/was no explicit 'form' section
|
// * There is/was no explicit 'form' section
|
||||||
// * There is no 'prompt' specified
|
// * There is no 'prompt' specified
|
||||||
//
|
//
|
||||||
if('menus' === areaName && !_.isString(mergedThemeMenu.prompt) &&
|
if('menus' === sectionName && !_.isString(mergedThemeMenu.prompt) &&
|
||||||
(createdFormSection || !_.isObject(mergedThemeMenu.form)))
|
(createdFormSection || !_.isObject(mergedThemeMenu.form)))
|
||||||
{
|
{
|
||||||
mergedThemeMenu.runtime = _.merge(mergedThemeMenu.runtime || {}, { autoNext : true } );
|
mergedThemeMenu.runtime = _.merge(mergedThemeMenu.runtime || {}, { autoNext : true } );
|
||||||
|
|
|
@ -56,7 +56,7 @@ function userLogin(client, username, password, cb) {
|
||||||
|
|
||||||
// update client logger with addition of username
|
// update client logger with addition of username
|
||||||
client.log = logger.log.child( { clientId : client.log.fields.clientId, username : user.username });
|
client.log = logger.log.child( { clientId : client.log.fields.clientId, username : user.username });
|
||||||
client.log.info('Successful login');
|
client.log.info('Successful login');
|
||||||
|
|
||||||
async.parallel(
|
async.parallel(
|
||||||
[
|
[
|
||||||
|
|
|
@ -27,7 +27,7 @@ function VerticalMenuView(options) {
|
||||||
this.dimens.height = Math.min(self.dimens.height, self.client.term.termHeight - self.position.row);
|
this.dimens.height = Math.min(self.dimens.height, self.client.term.termHeight - self.position.row);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(this.autoScale.width) {
|
if(self.autoScale.width) {
|
||||||
var l = 0;
|
var l = 0;
|
||||||
self.items.forEach(function item(i) {
|
self.items.forEach(function item(i) {
|
||||||
if(i.text.length > l) {
|
if(i.text.length > l) {
|
||||||
|
@ -148,6 +148,17 @@ VerticalMenuView.prototype.setFocus = function(focused) {
|
||||||
|
|
||||||
VerticalMenuView.prototype.setFocusItemIndex = function(index) {
|
VerticalMenuView.prototype.setFocusItemIndex = function(index) {
|
||||||
VerticalMenuView.super_.prototype.setFocusItemIndex.call(this, index); // sets this.focusedItemIndex
|
VerticalMenuView.super_.prototype.setFocusItemIndex.call(this, index); // sets this.focusedItemIndex
|
||||||
|
|
||||||
|
//this.updateViewVisibleItems();
|
||||||
|
|
||||||
|
// :TODO: |viewWindow| must be updated to reflect position change --
|
||||||
|
// if > visibile then += by diff, if < visible
|
||||||
|
|
||||||
|
if(this.focusedItemIndex > this.viewWindow.bottom) {
|
||||||
|
} else if (this.focusedItemIndex < this.viewWindow.top) {
|
||||||
|
// this.viewWindow.top--;
|
||||||
|
// this.viewWindow.bottom--;
|
||||||
|
}
|
||||||
|
|
||||||
this.redraw();
|
this.redraw();
|
||||||
};
|
};
|
||||||
|
|
|
@ -488,7 +488,6 @@ ViewController.prototype.loadFromPromptConfig = function(options, cb) {
|
||||||
assert(_.isObject(options.mciMap));
|
assert(_.isObject(options.mciMap));
|
||||||
|
|
||||||
var self = this;
|
var self = this;
|
||||||
var promptName = _.isString(options.promptName) ? options.promptName : self.client.currentMenuModule.menuConfig.prompt;
|
|
||||||
var promptConfig = _.isObject(options.config) ? options.config : self.client.currentMenuModule.menuConfig.promptConfig;
|
var promptConfig = _.isObject(options.config) ? options.config : self.client.currentMenuModule.menuConfig.promptConfig;
|
||||||
var initialFocusId = 1; // default to first
|
var initialFocusId = 1; // default to first
|
||||||
|
|
||||||
|
|
|
@ -16,15 +16,13 @@
|
||||||
return !isNaN(value) && user.getAge() >= value;
|
return !isNaN(value) && user.getAge() >= value;
|
||||||
},
|
},
|
||||||
AS : function accountStatus() {
|
AS : function accountStatus() {
|
||||||
if(_.isNumber(value)) {
|
if(!_.isArray(value)) {
|
||||||
value = [ value ];
|
value = [ value ];
|
||||||
}
|
}
|
||||||
|
|
||||||
assert(_.isArray(value));
|
const userAccountStatus = parseInt(user.properties.account_status, 10);
|
||||||
|
value = value.map(n => parseInt(n, 10)); // ensure we have integers
|
||||||
return _.findIndex(value, function cmp(accStatus) {
|
return value.indexOf(userAccountStatus) > -1;
|
||||||
return parseInt(accStatus, 10) === parseInt(user.properties.account_status, 10);
|
|
||||||
}) > -1;
|
|
||||||
},
|
},
|
||||||
EC : function isEncoding() {
|
EC : function isEncoding() {
|
||||||
switch(value) {
|
switch(value) {
|
||||||
|
@ -53,7 +51,7 @@
|
||||||
// :TODO: implement me!!
|
// :TODO: implement me!!
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
SC : function isSecerConnection() {
|
SC : function isSecureConnection() {
|
||||||
return client.session.isSecure;
|
return client.session.isSecure;
|
||||||
},
|
},
|
||||||
ML : function minutesLeft() {
|
ML : function minutesLeft() {
|
||||||
|
@ -81,28 +79,20 @@
|
||||||
return !isNaN(value) && client.term.termWidth >= value;
|
return !isNaN(value) && client.term.termWidth >= value;
|
||||||
},
|
},
|
||||||
ID : function isUserId(value) {
|
ID : function isUserId(value) {
|
||||||
if(_.isNumber(value)) {
|
if(!_.isArray(value)) {
|
||||||
value = [ value ];
|
value = [ value ];
|
||||||
}
|
}
|
||||||
|
|
||||||
assert(_.isArray(value));
|
value = value.map(n => parseInt(n, 10)); // ensure we have integers
|
||||||
|
return value.indexOf(user.userId) > -1;
|
||||||
return _.findIndex(value, function cmp(uid) {
|
|
||||||
return user.userId === parseInt(uid, 10);
|
|
||||||
}) > -1;
|
|
||||||
},
|
},
|
||||||
WD : function isOneOfDayOfWeek() {
|
WD : function isOneOfDayOfWeek() {
|
||||||
if(_.isNumber(value)) {
|
if(!_.isArray(value)) {
|
||||||
value = [ value ];
|
value = [ value ];
|
||||||
}
|
}
|
||||||
|
|
||||||
assert(_.isArray(value));
|
value = value.map(n => parseInt(n, 10)); // ensure we have integers
|
||||||
|
return value.indexOf(new Date().getDay()) > -1;
|
||||||
var nowDayOfWeek = new Date().getDay();
|
|
||||||
|
|
||||||
return _.findIndex(value, function cmp(dow) {
|
|
||||||
return nowDayOfWeek === parseInt(dow, 10);
|
|
||||||
}) > -1;
|
|
||||||
},
|
},
|
||||||
MM : function isMinutesPastMidnight() {
|
MM : function isMinutesPastMidnight() {
|
||||||
// :TODO: return true if value is >= minutes past midnight sys time
|
// :TODO: return true if value is >= minutes past midnight sys time
|
||||||
|
|
|
@ -177,12 +177,6 @@ function AbracadabraModule(options) {
|
||||||
|
|
||||||
require('util').inherits(AbracadabraModule, MenuModule);
|
require('util').inherits(AbracadabraModule, MenuModule);
|
||||||
|
|
||||||
/*
|
|
||||||
AbracadabraModule.prototype.enter = function(client) {
|
|
||||||
AbracadabraModule.super_.prototype.enter.call(this, client);
|
|
||||||
};
|
|
||||||
*/
|
|
||||||
|
|
||||||
AbracadabraModule.prototype.leave = function() {
|
AbracadabraModule.prototype.leave = function() {
|
||||||
AbracadabraModule.super_.prototype.leave.call(this);
|
AbracadabraModule.super_.prototype.leave.call(this);
|
||||||
|
|
||||||
|
|
|
@ -393,7 +393,7 @@
|
||||||
},
|
},
|
||||||
editorMode: edit
|
editorMode: edit
|
||||||
editorType: email
|
editorType: email
|
||||||
messageAreaName: private_mail
|
messageAreaTag: private_mail
|
||||||
toUserId: 1 /* always to +op */
|
toUserId: 1 /* always to +op */
|
||||||
}
|
}
|
||||||
form: {
|
form: {
|
||||||
|
@ -806,7 +806,7 @@
|
||||||
},
|
},
|
||||||
editorMode: edit
|
editorMode: edit
|
||||||
editorType: email
|
editorType: email
|
||||||
messageAreaName: private_mail
|
messageAreaTag: private_mail
|
||||||
toUserId: 1 /* always to +op */
|
toUserId: 1 /* always to +op */
|
||||||
}
|
}
|
||||||
form: {
|
form: {
|
||||||
|
@ -1019,6 +1019,10 @@
|
||||||
value: { command: "P" }
|
value: { command: "P" }
|
||||||
action: @menu:messageAreaNewPost
|
action: @menu:messageAreaNewPost
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
value: { command: "J" }
|
||||||
|
action: @menu:messageAreaChangeCurrentConference
|
||||||
|
}
|
||||||
{
|
{
|
||||||
value: { command: "C" }
|
value: { command: "C" }
|
||||||
action: @menu:messageAreaChangeCurrentArea
|
action: @menu:messageAreaChangeCurrentArea
|
||||||
|
@ -1041,7 +1045,39 @@
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
messageAreaChangeCurrentConference: {
|
||||||
|
art: CCHANGE
|
||||||
|
module: msg_conf_list
|
||||||
|
form: {
|
||||||
|
0: {
|
||||||
|
mci: {
|
||||||
|
VM1: {
|
||||||
|
focus: true
|
||||||
|
submit: true
|
||||||
|
argName: conf
|
||||||
|
}
|
||||||
|
}
|
||||||
|
submit: {
|
||||||
|
*: [
|
||||||
|
{
|
||||||
|
value: { conf: null }
|
||||||
|
action: @method:changeConference
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
actionKeys: [
|
||||||
|
{
|
||||||
|
keys: [ "escape", "q", "shift + q" ]
|
||||||
|
action: @systemMethod:prevMenu
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
messageAreaChangeCurrentArea: {
|
messageAreaChangeCurrentArea: {
|
||||||
|
// :TODO: rename this art to ACHANGE
|
||||||
art: CHANGE
|
art: CHANGE
|
||||||
module: msg_area_list
|
module: msg_area_list
|
||||||
form: {
|
form: {
|
||||||
|
@ -1070,6 +1106,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
messageAreaMessageList: {
|
messageAreaMessageList: {
|
||||||
module: msg_list
|
module: msg_list
|
||||||
art: MSGLIST
|
art: MSGLIST
|
||||||
|
|
|
@ -5,7 +5,6 @@ var MenuModule = require('../core/menu_module.js').MenuModule;
|
||||||
var ViewController = require('../core/view_controller.js').ViewController;
|
var ViewController = require('../core/view_controller.js').ViewController;
|
||||||
var messageArea = require('../core/message_area.js');
|
var messageArea = require('../core/message_area.js');
|
||||||
var strUtil = require('../core/string_util.js');
|
var strUtil = require('../core/string_util.js');
|
||||||
//var msgDb = require('./database.js').dbs.message;
|
|
||||||
|
|
||||||
var async = require('async');
|
var async = require('async');
|
||||||
var assert = require('assert');
|
var assert = require('assert');
|
||||||
|
@ -43,30 +42,33 @@ function MessageAreaListModule(options) {
|
||||||
|
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
this.messageAreas = messageArea.getAvailableMessageAreas();
|
this.messageAreas = messageArea.getSortedAvailMessageAreasByConfTag(
|
||||||
|
self.client.user.properties.message_conf_tag,
|
||||||
|
{ client : self.client }
|
||||||
|
);
|
||||||
|
|
||||||
this.menuMethods = {
|
this.menuMethods = {
|
||||||
changeArea : function(formData, extraArgs) {
|
changeArea : function(formData, extraArgs) {
|
||||||
if(1 === formData.submitId) {
|
if(1 === formData.submitId) {
|
||||||
var areaName = self.messageAreas[formData.value.area].name;
|
const areaTag = self.messageAreas[formData.value.area].areaTag;
|
||||||
|
|
||||||
messageArea.changeMessageArea(self.client, areaName, function areaChanged(err) {
|
messageArea.changeMessageArea(self.client, areaTag, function areaChanged(err) {
|
||||||
if(err) {
|
if(err) {
|
||||||
self.client.term.pipeWrite('\n|00Cannot change area: ' + err.message + '\n');
|
self.client.term.pipeWrite('\n|00Cannot change area: ' + err.message + '\n');
|
||||||
|
|
||||||
setTimeout(function timeout() {
|
setTimeout(function timeout() {
|
||||||
self.prevMenu();
|
self.prevMenu();
|
||||||
}, 1000);
|
}, 1000);
|
||||||
} else {
|
} else {
|
||||||
self.prevMenu();
|
self.prevMenu();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
this.setViewText = function(id, text) {
|
this.setViewText = function(id, text) {
|
||||||
var v = self.viewControllers.areaList.getView(id);
|
const v = self.viewControllers.areaList.getView(id);
|
||||||
if(v) {
|
if(v) {
|
||||||
v.setText(text);
|
v.setText(text);
|
||||||
}
|
}
|
||||||
|
@ -78,7 +80,7 @@ require('util').inherits(MessageAreaListModule, MenuModule);
|
||||||
|
|
||||||
MessageAreaListModule.prototype.mciReady = function(mciData, cb) {
|
MessageAreaListModule.prototype.mciReady = function(mciData, cb) {
|
||||||
var self = this;
|
var self = this;
|
||||||
var vc = self.viewControllers.areaList = new ViewController( { client : self.client } );
|
const vc = self.viewControllers.areaList = new ViewController( { client : self.client } );
|
||||||
|
|
||||||
async.series(
|
async.series(
|
||||||
[
|
[
|
||||||
|
@ -99,26 +101,29 @@ MessageAreaListModule.prototype.mciReady = function(mciData, cb) {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
function populateAreaListView(callback) {
|
function populateAreaListView(callback) {
|
||||||
var listFormat = self.menuConfig.config.listFormat || '{index} ) - {desc}';
|
const listFormat = self.menuConfig.config.listFormat || '{index} ) - {name}';
|
||||||
var focusListFormat = self.menuConfig.config.focusListFormat || listFormat;
|
const focusListFormat = self.menuConfig.config.focusListFormat || listFormat;
|
||||||
|
|
||||||
var areaListItems = [];
|
const areaListView = vc.getView(1);
|
||||||
var focusListItems = [];
|
let i = 1;
|
||||||
|
areaListView.setItems(_.map(self.messageAreas, v => {
|
||||||
// :TODO: use _.map() here
|
return listFormat.format({
|
||||||
for(var i = 0; i < self.messageAreas.length; ++i) {
|
index : i++,
|
||||||
areaListItems.push(listFormat.format(
|
areaTag : v.area.areaTag,
|
||||||
{ index : i, name : self.messageAreas[i].name, desc : self.messageAreas[i].desc } )
|
name : v.area.name,
|
||||||
);
|
desc : v.area.desc,
|
||||||
focusListItems.push(focusListFormat.format(
|
});
|
||||||
{ index : i, name : self.messageAreas[i].name, desc : self.messageAreas[i].desc } )
|
}));
|
||||||
);
|
|
||||||
}
|
i = 1;
|
||||||
|
areaListView.setFocusItems(_.map(self.messageAreas, v => {
|
||||||
var areaListView = vc.getView(1);
|
return focusListFormat.format({
|
||||||
|
index : i++,
|
||||||
areaListView.setItems(areaListItems);
|
areaTag : v.area.areaTag,
|
||||||
areaListView.setFocusItems(focusListItems);
|
name : v.area.name,
|
||||||
|
desc : v.area.desc,
|
||||||
|
})
|
||||||
|
}));
|
||||||
|
|
||||||
areaListView.redraw();
|
areaListView.redraw();
|
||||||
|
|
||||||
|
|
|
@ -56,11 +56,11 @@ function AreaPostFSEModule(options) {
|
||||||
|
|
||||||
require('util').inherits(AreaPostFSEModule, FullScreenEditorModule);
|
require('util').inherits(AreaPostFSEModule, FullScreenEditorModule);
|
||||||
|
|
||||||
AreaPostFSEModule.prototype.enter = function(client) {
|
AreaPostFSEModule.prototype.enter = function() {
|
||||||
|
|
||||||
if(_.isString(client.user.properties.message_area_name) && !_.isString(this.messageAreaName)) {
|
if(_.isString(this.client.user.properties.message_area_tag) && !_.isString(this.messageAreaTag)) {
|
||||||
this.messageAreaName = client.user.properties.message_area_name;
|
this.messageAreaTag = this.client.user.properties.message_area_tag;
|
||||||
}
|
}
|
||||||
|
|
||||||
AreaPostFSEModule.super_.prototype.enter.call(this, client);
|
AreaPostFSEModule.super_.prototype.enter.call(this);
|
||||||
};
|
};
|
||||||
|
|
|
@ -72,7 +72,7 @@ function AreaViewFSEModule(options) {
|
||||||
if(_.isString(extraArgs.menu)) {
|
if(_.isString(extraArgs.menu)) {
|
||||||
var modOpts = {
|
var modOpts = {
|
||||||
extraArgs : {
|
extraArgs : {
|
||||||
messageAreaName : self.messageAreaName,
|
messageAreaTag : self.messageAreaTag,
|
||||||
replyToMessage : self.message,
|
replyToMessage : self.message,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,122 @@
|
||||||
|
/* jslint node: true */
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var MenuModule = require('../core/menu_module.js').MenuModule;
|
||||||
|
var ViewController = require('../core/view_controller.js').ViewController;
|
||||||
|
var messageArea = require('../core/message_area.js');
|
||||||
|
|
||||||
|
var async = require('async');
|
||||||
|
var assert = require('assert');
|
||||||
|
var _ = require('lodash');
|
||||||
|
|
||||||
|
exports.getModule = MessageConfListModule;
|
||||||
|
|
||||||
|
exports.moduleInfo = {
|
||||||
|
name : 'Message Conference List',
|
||||||
|
desc : 'Module for listing / choosing message conferences',
|
||||||
|
author : 'NuSkooler',
|
||||||
|
};
|
||||||
|
|
||||||
|
var MciCodesIds = {
|
||||||
|
ConfList : 1,
|
||||||
|
CurrentConf : 2,
|
||||||
|
|
||||||
|
// :TODO:
|
||||||
|
// # areas in con
|
||||||
|
//
|
||||||
|
};
|
||||||
|
|
||||||
|
function MessageConfListModule(options) {
|
||||||
|
MenuModule.call(this, options);
|
||||||
|
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
this.messageConfs = messageArea.getSortedAvailMessageConferences(self.client);
|
||||||
|
|
||||||
|
this.menuMethods = {
|
||||||
|
changeConference : function(formData, extraArgs) {
|
||||||
|
if(1 === formData.submitId) {
|
||||||
|
const confTag = self.messageConfs[formData.value.conf].confTag;
|
||||||
|
|
||||||
|
messageArea.changeMessageConference(self.client, confTag, err => {
|
||||||
|
if(err) {
|
||||||
|
self.client.term.pipeWrite(`\n|00Cannot change conference: ${err.message}\n`);
|
||||||
|
|
||||||
|
setTimeout(function timeout() {
|
||||||
|
self.prevMenu();
|
||||||
|
}, 1000);
|
||||||
|
} else {
|
||||||
|
self.prevMenu();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.setViewText = function(id, text) {
|
||||||
|
const v = self.viewControllers.areaList.getView(id);
|
||||||
|
if(v) {
|
||||||
|
v.setText(text);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
require('util').inherits(MessageConfListModule, MenuModule);
|
||||||
|
|
||||||
|
MessageConfListModule.prototype.mciReady = function(mciData, cb) {
|
||||||
|
var self = this;
|
||||||
|
const vc = self.viewControllers.areaList = new ViewController( { client : self.client } );
|
||||||
|
|
||||||
|
async.series(
|
||||||
|
[
|
||||||
|
function callParentMciReady(callback) {
|
||||||
|
MessageConfListModule.super_.prototype.mciReady.call(this, mciData, callback);
|
||||||
|
},
|
||||||
|
function loadFromConfig(callback) {
|
||||||
|
let loadOpts = {
|
||||||
|
callingMenu : self,
|
||||||
|
mciMap : mciData.menu,
|
||||||
|
formId : 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
vc.loadFromMenuConfig(loadOpts, callback);
|
||||||
|
},
|
||||||
|
function populateConfListView(callback) {
|
||||||
|
const listFormat = self.menuConfig.config.listFormat || '{index} ) - {name}';
|
||||||
|
const focusListFormat = self.menuConfig.config.focusListFormat || listFormat;
|
||||||
|
|
||||||
|
const confListView = vc.getView(1);
|
||||||
|
let i = 1;
|
||||||
|
confListView.setItems(_.map(self.messageConfs, v => {
|
||||||
|
return listFormat.format({
|
||||||
|
index : i++,
|
||||||
|
confTag : v.conf.confTag,
|
||||||
|
name : v.conf.name,
|
||||||
|
desc : v.conf.desc,
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
i = 1;
|
||||||
|
confListView.setFocusItems(_.map(self.messageConfs, v => {
|
||||||
|
return focusListFormat.format({
|
||||||
|
index : i++,
|
||||||
|
confTag : v.conf.confTag,
|
||||||
|
name : v.conf.name,
|
||||||
|
desc : v.conf.desc,
|
||||||
|
})
|
||||||
|
}));
|
||||||
|
|
||||||
|
confListView.redraw();
|
||||||
|
|
||||||
|
callback(null);
|
||||||
|
},
|
||||||
|
function populateTextViews(callback) {
|
||||||
|
// :TODO: populate other avail MCI, e.g. current conf name
|
||||||
|
callback(null);
|
||||||
|
}
|
||||||
|
],
|
||||||
|
function complete(err) {
|
||||||
|
cb(err);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
|
@ -52,15 +52,15 @@ function MessageListModule(options) {
|
||||||
var self = this;
|
var self = this;
|
||||||
var config = this.menuConfig.config;
|
var config = this.menuConfig.config;
|
||||||
|
|
||||||
this.messageAreaName = config.messageAreaName;
|
this.messageAreaTag = config.messageAreaTag;
|
||||||
|
|
||||||
if(options.extraArgs) {
|
if(options.extraArgs) {
|
||||||
//
|
//
|
||||||
// |extraArgs| can override |messageAreaName| provided by config
|
// |extraArgs| can override |messageAreaTag| provided by config
|
||||||
// as well as supply a pre-defined message list
|
// as well as supply a pre-defined message list
|
||||||
//
|
//
|
||||||
if(options.extraArgs.messageAreaName) {
|
if(options.extraArgs.messageAreaTag) {
|
||||||
this.messageAreaName = options.extraArgs.messageAreaName;
|
this.messageAreaTag = options.extraArgs.messageAreaTag;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(options.extraArgs.messageList) {
|
if(options.extraArgs.messageList) {
|
||||||
|
@ -73,7 +73,7 @@ function MessageListModule(options) {
|
||||||
if(1 === formData.submitId) {
|
if(1 === formData.submitId) {
|
||||||
var modOpts = {
|
var modOpts = {
|
||||||
extraArgs : {
|
extraArgs : {
|
||||||
messageAreaName : self.messageAreaName,
|
messageAreaTag : self.messageAreaTag,
|
||||||
messageList : self.messageList,
|
messageList : self.messageList,
|
||||||
messageIndex : formData.value.message,
|
messageIndex : formData.value.message,
|
||||||
}
|
}
|
||||||
|
@ -94,15 +94,15 @@ function MessageListModule(options) {
|
||||||
|
|
||||||
require('util').inherits(MessageListModule, MenuModule);
|
require('util').inherits(MessageListModule, MenuModule);
|
||||||
|
|
||||||
MessageListModule.prototype.enter = function(client) {
|
MessageListModule.prototype.enter = function() {
|
||||||
MessageListModule.super_.prototype.enter.call(this, client);
|
MessageListModule.super_.prototype.enter.call(this);
|
||||||
|
|
||||||
//
|
//
|
||||||
// Config can specify |messageAreaName| else it comes from
|
// Config can specify |messageAreaTag| else it comes from
|
||||||
// the user's current area
|
// the user's current area
|
||||||
//
|
//
|
||||||
if(!this.messageAreaName) {
|
if(!this.messageAreaTag) {
|
||||||
this.messageAreaName = client.user.properties.message_area_name;
|
this.messageAreaTag = this.client.user.properties.message_area_tag;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -110,6 +110,8 @@ MessageListModule.prototype.mciReady = function(mciData, cb) {
|
||||||
var self = this;
|
var self = this;
|
||||||
var vc = self.viewControllers.allViews = new ViewController( { client : self.client } );
|
var vc = self.viewControllers.allViews = new ViewController( { client : self.client } );
|
||||||
|
|
||||||
|
var firstNewEntryIndex;
|
||||||
|
|
||||||
async.series(
|
async.series(
|
||||||
[
|
[
|
||||||
function callParentMciReady(callback) {
|
function callParentMciReady(callback) {
|
||||||
|
@ -130,7 +132,7 @@ MessageListModule.prototype.mciReady = function(mciData, cb) {
|
||||||
if(_.isArray(self.messageList)) {
|
if(_.isArray(self.messageList)) {
|
||||||
callback(0 === self.messageList.length ? new Error('No messages in area') : null);
|
callback(0 === self.messageList.length ? new Error('No messages in area') : null);
|
||||||
} else {
|
} else {
|
||||||
messageArea.getMessageListForArea( { client : self.client }, self.messageAreaName, function msgs(err, msgList) {
|
messageArea.getMessageListForArea( { client : self.client }, self.messageAreaTag, function msgs(err, msgList) {
|
||||||
if(msgList && 0 === msgList.length) {
|
if(msgList && 0 === msgList.length) {
|
||||||
callback(new Error('No messages in area'));
|
callback(new Error('No messages in area'));
|
||||||
} else {
|
} else {
|
||||||
|
@ -141,7 +143,7 @@ MessageListModule.prototype.mciReady = function(mciData, cb) {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
function getLastReadMesageId(callback) {
|
function getLastReadMesageId(callback) {
|
||||||
messageArea.getMessageAreaLastReadId(self.client.user.userId, self.messageAreaName, function lastRead(err, lastReadId) {
|
messageArea.getMessageAreaLastReadId(self.client.user.userId, self.messageAreaTag, function lastRead(err, lastReadId) {
|
||||||
self.lastReadId = lastReadId || 0;
|
self.lastReadId = lastReadId || 0;
|
||||||
callback(null); // ignore any errors, e.g. missing value
|
callback(null); // ignore any errors, e.g. missing value
|
||||||
});
|
});
|
||||||
|
@ -158,6 +160,13 @@ MessageListModule.prototype.mciReady = function(mciData, cb) {
|
||||||
var msgNum = 1;
|
var msgNum = 1;
|
||||||
|
|
||||||
function getMsgFmtObj(mle) {
|
function getMsgFmtObj(mle) {
|
||||||
|
|
||||||
|
if(_.isUndefined(firstNewEntryIndex) &&
|
||||||
|
mle.messageId > self.lastReadId)
|
||||||
|
{
|
||||||
|
firstNewEntryIndex = msgNum - 1;
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
msgNum : msgNum++,
|
msgNum : msgNum++,
|
||||||
subj : mle.subject,
|
subj : mle.subject,
|
||||||
|
@ -180,14 +189,18 @@ MessageListModule.prototype.mciReady = function(mciData, cb) {
|
||||||
msgListView.on('index update', function indexUpdated(idx) {
|
msgListView.on('index update', function indexUpdated(idx) {
|
||||||
self.setViewText(MciCodesIds.MsgSelNum, (idx + 1).toString());
|
self.setViewText(MciCodesIds.MsgSelNum, (idx + 1).toString());
|
||||||
});
|
});
|
||||||
|
|
||||||
msgListView.redraw();
|
msgListView.redraw();
|
||||||
|
|
||||||
|
if(firstNewEntryIndex > 0) {
|
||||||
|
msgListView.setFocusItemIndex(firstNewEntryIndex);
|
||||||
|
}
|
||||||
|
|
||||||
callback(null);
|
callback(null);
|
||||||
},
|
},
|
||||||
function populateOtherMciViews(callback) {
|
function populateOtherMciViews(callback) {
|
||||||
|
|
||||||
self.setViewText(MciCodesIds.MsgAreaDesc, messageArea.getMessageAreaByName(self.messageAreaName).desc);
|
self.setViewText(MciCodesIds.MsgAreaDesc, messageArea.getMessageAreaByTag(self.messageAreaTag).name);
|
||||||
self.setViewText(MciCodesIds.MsgSelNum, (vc.getView(MciCodesIds.MsgList).getData() + 1).toString());
|
self.setViewText(MciCodesIds.MsgSelNum, (vc.getView(MciCodesIds.MsgList).getData() + 1).toString());
|
||||||
self.setViewText(MciCodesIds.MsgTotal, self.messageList.length.toString());
|
self.setViewText(MciCodesIds.MsgTotal, self.messageList.length.toString());
|
||||||
|
|
||||||
|
|
26
mods/nua.js
26
mods/nua.js
|
@ -5,7 +5,7 @@ var user = require('../core/user.js');
|
||||||
var theme = require('../core/theme.js');
|
var theme = require('../core/theme.js');
|
||||||
var login = require('../core/system_menu_method.js').login;
|
var login = require('../core/system_menu_method.js').login;
|
||||||
var Config = require('../core/config.js').config;
|
var Config = require('../core/config.js').config;
|
||||||
var getDefaultMessageArea = require('../core/message_area.js').getDefaultMessageArea;
|
var messageArea = require('../core/message_area.js');
|
||||||
|
|
||||||
var async = require('async');
|
var async = require('async');
|
||||||
|
|
||||||
|
@ -65,6 +65,16 @@ function NewUserAppModule(options) {
|
||||||
|
|
||||||
newUser.username = formData.value.username;
|
newUser.username = formData.value.username;
|
||||||
|
|
||||||
|
//
|
||||||
|
// We have to disable ACS checks for initial default areas as the user is not yet ready
|
||||||
|
//
|
||||||
|
var confTag = messageArea.getDefaultMessageConferenceTag(self.client, true); // true=disableAcsCheck
|
||||||
|
var areaTag = messageArea.getDefaultMessageAreaTagByConfTag(self.client, confTag, true); // true=disableAcsCheck
|
||||||
|
|
||||||
|
// can't store undefined!
|
||||||
|
confTag = confTag || '';
|
||||||
|
areaTag = areaTag || '';
|
||||||
|
|
||||||
newUser.properties = {
|
newUser.properties = {
|
||||||
real_name : formData.value.realName,
|
real_name : formData.value.realName,
|
||||||
birthdate : new Date(Date.parse(formData.value.birthdate)).toISOString(),
|
birthdate : new Date(Date.parse(formData.value.birthdate)).toISOString(),
|
||||||
|
@ -74,14 +84,12 @@ function NewUserAppModule(options) {
|
||||||
email_address : formData.value.email,
|
email_address : formData.value.email,
|
||||||
web_address : formData.value.web,
|
web_address : formData.value.web,
|
||||||
account_created : new Date().toISOString(),
|
account_created : new Date().toISOString(),
|
||||||
|
|
||||||
message_area_name : getDefaultMessageArea().name,
|
message_conf_tag : confTag,
|
||||||
|
message_area_tag : areaTag,
|
||||||
|
|
||||||
term_height : self.client.term.termHeight,
|
term_height : self.client.term.termHeight,
|
||||||
term_width : self.client.term.termWidth,
|
term_width : self.client.term.termWidth,
|
||||||
|
|
||||||
// :TODO: This is set in User.create() -- proabbly don't need it here:
|
|
||||||
//account_status : Config.users.requireActivation ? user.User.AccountStatus.inactive : user.User.AccountStatus.active,
|
|
||||||
|
|
||||||
// :TODO: Other defaults
|
// :TODO: Other defaults
|
||||||
// :TODO: should probably have a place to create defaults/etc.
|
// :TODO: should probably have a place to create defaults/etc.
|
||||||
|
@ -92,8 +100,8 @@ function NewUserAppModule(options) {
|
||||||
} else {
|
} else {
|
||||||
newUser.properties.theme_id = Config.defaults.theme;
|
newUser.properties.theme_id = Config.defaults.theme;
|
||||||
}
|
}
|
||||||
|
|
||||||
// :TODO: .create() should also validate email uniqueness!
|
// :TODO: User.create() should validate email uniqueness!
|
||||||
newUser.create( { password : formData.value.password }, function created(err) {
|
newUser.create( { password : formData.value.password }, function created(err) {
|
||||||
if(err) {
|
if(err) {
|
||||||
self.client.log.info( { error : err, username : formData.value.username }, 'New user creation failed');
|
self.client.log.info( { error : err, username : formData.value.username }, 'New user creation failed');
|
||||||
|
|
|
@ -172,8 +172,8 @@
|
||||||
|
|
||||||
messageAreaChangeCurrentArea: {
|
messageAreaChangeCurrentArea: {
|
||||||
config: {
|
config: {
|
||||||
listFormat: "|00|15{index} |07- |03{desc}"
|
listFormat: "|00|15{index} |07- |03{name}"
|
||||||
focusListFormat: "|00|19|15{index} - {desc}"
|
focusListFormat: "|00|19|15{index} - {name}"
|
||||||
}
|
}
|
||||||
mci: {
|
mci: {
|
||||||
VM1: {
|
VM1: {
|
||||||
|
@ -310,6 +310,19 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
newScanMessageList: {
|
||||||
|
config: {
|
||||||
|
listFormat: "|00|15 {msgNum:<5.5}|03{subj:<29.29} |15{from:<20.20} {ts}"
|
||||||
|
focusListFormat: "|00|19> |15{msgNum:<5.5}{subj:<29.29} {from:<20.20} {ts}"
|
||||||
|
dateTimeFormat: ddd MMM Do
|
||||||
|
}
|
||||||
|
mci: {
|
||||||
|
VM1: {
|
||||||
|
height: 14
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -2,7 +2,7 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var MenuModule = require('../core/menu_module.js').MenuModule;
|
var MenuModule = require('../core/menu_module.js').MenuModule;
|
||||||
var userDb = require('../core/database.js').dbs.user;
|
//var userDb = require('../core/database.js').dbs.user;
|
||||||
var getUserList = require('../core/user.js').getUserList;
|
var getUserList = require('../core/user.js').getUserList;
|
||||||
var ViewController = require('../core/view_controller.js').ViewController;
|
var ViewController = require('../core/view_controller.js').ViewController;
|
||||||
|
|
||||||
|
|
|
@ -84,7 +84,6 @@ WhosOnlineModule.prototype.mciReady = function(mciData, cb) {
|
||||||
return listFormat.format(oe);
|
return listFormat.format(oe);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// :TODO: This is a hack until pipe codes are better implemented
|
|
||||||
onlineListView.focusItems = onlineListView.items;
|
onlineListView.focusItems = onlineListView.items;
|
||||||
|
|
||||||
onlineListView.redraw();
|
onlineListView.redraw();
|
||||||
|
|
14
oputil.js
14
oputil.js
|
@ -13,7 +13,7 @@ var assert = require('assert');
|
||||||
|
|
||||||
var argv = require('minimist')(process.argv.slice(2));
|
var argv = require('minimist')(process.argv.slice(2));
|
||||||
|
|
||||||
var ExitCodes = {
|
const ExitCodes = {
|
||||||
SUCCESS : 0,
|
SUCCESS : 0,
|
||||||
ERROR : -1,
|
ERROR : -1,
|
||||||
BAD_COMMAND : -2,
|
BAD_COMMAND : -2,
|
||||||
|
@ -28,9 +28,13 @@ function printUsage(command) {
|
||||||
usage =
|
usage =
|
||||||
'usage: oputil.js [--version] [--help]\n' +
|
'usage: oputil.js [--version] [--help]\n' +
|
||||||
' <command> [<args>]' +
|
' <command> [<args>]' +
|
||||||
'\n' +
|
'\n\n' +
|
||||||
'global args:\n' +
|
'global args:\n' +
|
||||||
' --config PATH : specify config path';
|
' --config PATH : specify config path' +
|
||||||
|
'\n\n' +
|
||||||
|
'commands:\n' +
|
||||||
|
' user : User utilities' +
|
||||||
|
'\n';
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'user' :
|
case 'user' :
|
||||||
|
@ -47,7 +51,7 @@ function printUsage(command) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function initConfig(cb) {
|
function initConfig(cb) {
|
||||||
var configPath = argv.config ? argv.config : config.getDefaultPath();
|
const configPath = argv.config ? argv.config : config.getDefaultPath();
|
||||||
|
|
||||||
config.init(configPath, cb);
|
config.init(configPath, cb);
|
||||||
}
|
}
|
||||||
|
@ -88,7 +92,7 @@ function handleUserCommand() {
|
||||||
assert(_.isNumber(userId));
|
assert(_.isNumber(userId));
|
||||||
assert(userId > 0);
|
assert(userId > 0);
|
||||||
|
|
||||||
var u = new user.User();
|
let u = new user.User();
|
||||||
u.userId = userId;
|
u.userId = userId;
|
||||||
|
|
||||||
u.setNewAuthCredentials(argv.password, function credsSet(err) {
|
u.setNewAuthCredentials(argv.password, function credsSet(err) {
|
||||||
|
|
Loading…
Reference in New Issue