153 lines
4.9 KiB
JavaScript
153 lines
4.9 KiB
JavaScript
/* jslint node: true */
|
|
'use strict';
|
|
|
|
// ENiGMA½
|
|
const Config = require('./config.js').get;
|
|
const StatLog = require('./stat_log.js');
|
|
const UserProps = require('./user_property.js');
|
|
const SysProps = require('./system_property.js');
|
|
const paths = require('path');
|
|
const Log = require('./logger.js').log;
|
|
const getPredefinedMCIFormatObject =
|
|
require('./predefined_mci').getPredefinedMCIFormatObject;
|
|
const stringFormat = require('./string_format');
|
|
|
|
// deps
|
|
const fs = require('graceful-fs');
|
|
const _ = require('lodash');
|
|
const moment = require('moment');
|
|
const iconv = require('iconv-lite');
|
|
const { mkdirs } = require('fs-extra');
|
|
const { stripMciColorCodes } = require('./color_codes.js');
|
|
|
|
//
|
|
// Resources
|
|
// * https://github.com/NuSkooler/ansi-bbs/tree/master/docs/dropfile_formats
|
|
// * http://goldfndr.home.mindspring.com/dropfile/
|
|
// * https://en.wikipedia.org/wiki/Talk%3ADropfile
|
|
// * http://thoughtproject.com/libraries/bbs/Sysop/Doors/DropFiles/index.htm
|
|
// * http://thebbs.org/bbsfaq/ch06.02.htm
|
|
// * http://lord.lordlegacy.com/dosemu/
|
|
//
|
|
module.exports = class DropFile {
|
|
constructor(
|
|
client,
|
|
{ fileType = 'DORINFO', baseDir = Config().paths.dropFiles } = {}
|
|
) {
|
|
this.client = client;
|
|
this.fileType = fileType.toUpperCase();
|
|
this.baseDir = baseDir;
|
|
|
|
|
|
this.dropFileFormatDirectory = paths.join(
|
|
__dirname,
|
|
'dropfile_formats'
|
|
);
|
|
}
|
|
|
|
static dropFileDirectory(baseDir, client) {
|
|
return paths.join(baseDir, 'node' + client.node);
|
|
}
|
|
|
|
get fullPath() {
|
|
return paths.join(
|
|
DropFile.dropFileDirectory(this.baseDir, this.client),
|
|
this.fileName
|
|
);
|
|
}
|
|
|
|
get fileName() {
|
|
return {
|
|
DOOR: 'DOOR.SYS', // GAP BBS, many others
|
|
DOOR32: 'door32.sys', // Mystic, EleBBS, Syncronet, Maximus, Telegard, AdeptXBBS (lowercase name as per spec)
|
|
CALLINFO: 'CALLINFO.BBS', // Citadel?
|
|
DORINFO: this.getDoorInfoFileName(), // RBBS, RemoteAccess, QBBS, ...
|
|
CHAIN: 'CHAIN.TXT', // WWIV
|
|
CURRUSER: 'CURRUSER.BBS', // RyBBS
|
|
SFDOORS: 'SFDOORS.DAT', // Spitfire
|
|
PCBOARD: 'PCBOARD.SYS', // PCBoard
|
|
TRIBBS: 'TRIBBS.SYS', // TriBBS
|
|
USERINFO: 'USERINFO.DAT', // Wildcat! 3.0+
|
|
JUMPER: 'JUMPER.DAT', // 2AM BBS
|
|
SXDOOR: 'SXDOOR.' + _.pad(this.client.node.toString(), 3, '0'), // System/X, dESiRE
|
|
INFO: 'INFO.BBS', // Phoenix BBS
|
|
SOLARREALMS: 'DOORFILE.SR',
|
|
XTRN: 'XTRN.DAT',
|
|
}[this.fileType];
|
|
}
|
|
|
|
isSupported() {
|
|
return this.getHandler() ? true : false;
|
|
}
|
|
|
|
getHandler() {
|
|
// TODO: Replace with a switch statement once we have binary handlers as well
|
|
|
|
// Read the directory containing the dropfile formats, and return undefined if we don't have the format
|
|
const fileName = this.fileName();
|
|
if (!fileName) {
|
|
Log.info({fileType: this.fileType}, 'Dropfile format not supported.');
|
|
return undefined;
|
|
}
|
|
const filePath = paths.join(this.dropFileFormatDirectory, fileName);
|
|
fs.access(filePath, fs.constants.R_OK, err => {
|
|
if (err) {
|
|
Log.info({filename: fileName}, 'Dropfile format not found.');
|
|
return undefined;
|
|
}
|
|
});
|
|
|
|
// Return the handler to get the dropfile, because in the future we may have additional handlers
|
|
return this.getDropfile;
|
|
}
|
|
|
|
getContents() {
|
|
const handler = this.getHandler().bind(this);
|
|
return handler();
|
|
}
|
|
|
|
getDropfile() {
|
|
// Get the filename to read
|
|
const fileName = paths.join(this.dropFileFormatDirectory, this.fileName());
|
|
|
|
// Read file, or return empty string if it doesn't exist
|
|
fs.readFile(fileName, (err, data) => {
|
|
if (err) {
|
|
Log.warn({filename: fileName}, 'Error reading dropfile format file.');
|
|
return '';
|
|
}
|
|
let text = data;
|
|
// Format the data with string_format and predefined_mci
|
|
const formatObj = getPredefinedMCIFormatObject(this.client, data);
|
|
if (formatObj) {
|
|
// Expand the text
|
|
text = stringFormat(text, formatObj, true);
|
|
}
|
|
return text;
|
|
});
|
|
}
|
|
|
|
getDoorInfoFileName() {
|
|
let x;
|
|
const node = this.client.node;
|
|
if (10 === node) {
|
|
x = 0;
|
|
} else if (node < 10) {
|
|
x = node;
|
|
} else {
|
|
x = String.fromCharCode('a'.charCodeAt(0) + (node - 11));
|
|
}
|
|
return 'DORINFO' + x + '.DEF';
|
|
}
|
|
|
|
|
|
createFile(cb) {
|
|
mkdirs(paths.dirname(this.fullPath), err => {
|
|
if (err) {
|
|
return cb(err);
|
|
}
|
|
return fs.writeFile(this.fullPath, this.getContents(), cb);
|
|
});
|
|
}
|
|
};
|