Compare commits
7 Commits
master
...
feature/dr
Author | SHA1 | Date |
---|---|---|
Nathan Byrd | 04392e387d | |
Nathan Byrd | 9bb8277583 | |
Nathan Byrd | e2a9d35bb1 | |
Nathan Byrd | e6cc890338 | |
Nathan Byrd | 8a27bb6758 | |
Nathan Byrd | d3a0fb40f9 | |
Nathan Byrd | 19d92b34e4 |
|
@ -16,3 +16,7 @@ optutil.js text eol=lf
|
||||||
# The devcontainer is also unix
|
# The devcontainer is also unix
|
||||||
.devcontainer/Dockerfile text eol=lf
|
.devcontainer/Dockerfile text eol=lf
|
||||||
.devcontainer/devcontainer.json text eol=lf
|
.devcontainer/devcontainer.json text eol=lf
|
||||||
|
|
||||||
|
|
||||||
|
# All dropfiles are DOS
|
||||||
|
dropfile_formats/* eol=crlf
|
|
@ -11,14 +11,14 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v3
|
uses: docker/setup-qemu-action@v2
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
id: buildx
|
id: buildx
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v2
|
||||||
|
|
||||||
- name: Login to DockerHub
|
- name: Login to DockerHub
|
||||||
uses: docker/login-action@v2
|
uses: docker/login-action@v2
|
||||||
|
@ -31,5 +31,5 @@ jobs:
|
||||||
with:
|
with:
|
||||||
tags: enigmabbs/enigma-bbs:latest
|
tags: enigmabbs/enigma-bbs:latest
|
||||||
file: docker/Dockerfile
|
file: docker/Dockerfile
|
||||||
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
platforms: linux/amd64,linux/arm64
|
||||||
push: true
|
push: true
|
|
@ -158,6 +158,11 @@ exports.getModule = class AbracadabraModule extends MenuModule {
|
||||||
fileType: self.config.dropFileType,
|
fileType: self.config.dropFileType,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if(!(self.dropFile.isSupported())) {
|
||||||
|
// Return error so complete will log and return
|
||||||
|
return callback(Errors.AccessDenied('Dropfile format not supported'));
|
||||||
|
}
|
||||||
|
|
||||||
return self.dropFile.createFile(callback);
|
return self.dropFile.createFile(callback);
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
286
core/dropfile.js
286
core/dropfile.js
|
@ -6,15 +6,20 @@ const Config = require('./config.js').get;
|
||||||
const StatLog = require('./stat_log.js');
|
const StatLog = require('./stat_log.js');
|
||||||
const UserProps = require('./user_property.js');
|
const UserProps = require('./user_property.js');
|
||||||
const SysProps = require('./system_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
|
// deps
|
||||||
const fs = require('graceful-fs');
|
const fs = require('graceful-fs');
|
||||||
const paths = require('path');
|
|
||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
const moment = require('moment');
|
const moment = require('moment');
|
||||||
const iconv = require('iconv-lite');
|
|
||||||
const { mkdirs } = require('fs-extra');
|
const { mkdirs } = require('fs-extra');
|
||||||
|
|
||||||
|
const parseFullName = require('parse-full-name').parseFullName;
|
||||||
|
|
||||||
//
|
//
|
||||||
// Resources
|
// Resources
|
||||||
// * https://github.com/NuSkooler/ansi-bbs/tree/master/docs/dropfile_formats
|
// * https://github.com/NuSkooler/ansi-bbs/tree/master/docs/dropfile_formats
|
||||||
|
@ -32,6 +37,13 @@ module.exports = class DropFile {
|
||||||
this.client = client;
|
this.client = client;
|
||||||
this.fileType = fileType.toUpperCase();
|
this.fileType = fileType.toUpperCase();
|
||||||
this.baseDir = baseDir;
|
this.baseDir = baseDir;
|
||||||
|
|
||||||
|
|
||||||
|
this.dropFileFormatDirectory = paths.join(
|
||||||
|
__dirname,
|
||||||
|
'..',
|
||||||
|
'dropfile_formats'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static dropFileDirectory(baseDir, client) {
|
static dropFileDirectory(baseDir, client) {
|
||||||
|
@ -60,6 +72,8 @@ module.exports = class DropFile {
|
||||||
JUMPER: 'JUMPER.DAT', // 2AM BBS
|
JUMPER: 'JUMPER.DAT', // 2AM BBS
|
||||||
SXDOOR: 'SXDOOR.' + _.pad(this.client.node.toString(), 3, '0'), // System/X, dESiRE
|
SXDOOR: 'SXDOOR.' + _.pad(this.client.node.toString(), 3, '0'), // System/X, dESiRE
|
||||||
INFO: 'INFO.BBS', // Phoenix BBS
|
INFO: 'INFO.BBS', // Phoenix BBS
|
||||||
|
SOLARREALMS: 'DOORFILE.SR',
|
||||||
|
XTRN: 'XTRN.DAT',
|
||||||
}[this.fileType];
|
}[this.fileType];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,16 +82,118 @@ module.exports = class DropFile {
|
||||||
}
|
}
|
||||||
|
|
||||||
getHandler() {
|
getHandler() {
|
||||||
return {
|
// TODO: Replace with a switch statement once we have binary handlers as well
|
||||||
DOOR: this.getDoorSysBuffer,
|
|
||||||
DOOR32: this.getDoor32Buffer,
|
// Read the directory containing the dropfile formats, and return undefined if we don't have the format
|
||||||
DORINFO: this.getDoorInfoDefBuffer,
|
const fileName = this.fileName;
|
||||||
}[this.fileType];
|
if (!fileName) {
|
||||||
|
Log.info({fileType: this.fileType}, 'Dropfile format not supported.');
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
const filePath = paths.join(this.dropFileFormatDirectory, fileName);
|
||||||
|
if(!fs.existsSync(filePath)) {
|
||||||
|
Log.info({filename: fileName}, 'Dropfile format not found or readable.');
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the handler to get the dropfile, because in the future we may have additional handlers
|
||||||
|
return this.getDropfile;
|
||||||
}
|
}
|
||||||
|
|
||||||
getContents() {
|
getContents() {
|
||||||
const handler = this.getHandler().bind(this);
|
const handlerRef = this.getHandler();
|
||||||
return handler();
|
if(!handlerRef) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
const handler = handlerRef.bind(this);
|
||||||
|
const contents = handler();
|
||||||
|
return contents;
|
||||||
|
}
|
||||||
|
|
||||||
|
getDropfile() {
|
||||||
|
// Get the filename to read
|
||||||
|
const fileName = paths.join(this.dropFileFormatDirectory, this.fileName);
|
||||||
|
|
||||||
|
let text = fs.readFileSync(fileName);
|
||||||
|
|
||||||
|
// Format the data with string_format and predefined_mci
|
||||||
|
let formatObj = getPredefinedMCIFormatObject(this.client, text);
|
||||||
|
|
||||||
|
const additionalFormatObj = {
|
||||||
|
'getSysopFirstName': this.getSysopFirstName(),
|
||||||
|
'getSysopLastName': this.getSysopLastName(),
|
||||||
|
'getUserFirstName': this.getUserFirstName(),
|
||||||
|
'getUserLastName': this.getUserLastName(),
|
||||||
|
'getUserTotalDownloadK': this.getUserTotalDownloadK(),
|
||||||
|
'getUserTotalUploadK': this.getUserTotalUploadK(),
|
||||||
|
'getCurrentDateMMDDYY': this.getCurrentDateMMDDYY(),
|
||||||
|
'getSystemDailyDownloadK': this.getSystemDailyDownloadK(),
|
||||||
|
'getUserBirthDateMMDDYY': this.getUserBirthDateMMDDYY(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add additional format objects to the format object
|
||||||
|
formatObj = _.merge(formatObj, additionalFormatObj);
|
||||||
|
|
||||||
|
if (formatObj) {
|
||||||
|
// Expand the text
|
||||||
|
text = stringFormat(text, formatObj, true);
|
||||||
|
}
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
_getFirstName(fullname) {
|
||||||
|
return parseFullName(fullname).first;
|
||||||
|
}
|
||||||
|
|
||||||
|
_getLastName(fullname) {
|
||||||
|
return parseFullName(fullname).last;
|
||||||
|
}
|
||||||
|
|
||||||
|
getSysopFirstName() {
|
||||||
|
return this._getFirstName(StatLog.getSystemStat(SysProps.SysOpRealName));
|
||||||
|
}
|
||||||
|
|
||||||
|
getSysopLastName() {
|
||||||
|
return this._getLastName(StatLog.getSystemStat(SysProps.SysOpRealName));
|
||||||
|
}
|
||||||
|
|
||||||
|
_userStatAsString(statName, defaultValue) {
|
||||||
|
return (StatLog.getUserStat(this.client.user, statName) || defaultValue).toLocaleString();
|
||||||
|
}
|
||||||
|
|
||||||
|
_getUserRealName() {
|
||||||
|
return this._userStatAsString(UserProps.RealName, 'Unknown Unknown');
|
||||||
|
}
|
||||||
|
|
||||||
|
getUserFirstName() {
|
||||||
|
return this._getFirstName(this._getUserRealName);
|
||||||
|
}
|
||||||
|
|
||||||
|
getUserLastName() {
|
||||||
|
return this._getLastName(this._getUserRealName);
|
||||||
|
}
|
||||||
|
|
||||||
|
getUserTotalDownloadK() {
|
||||||
|
return StatLog.getUserStatNum(this.client.user, UserProps.FileDlTotalBytes) / 1024;
|
||||||
|
}
|
||||||
|
|
||||||
|
getSystemDailyDownloadK() {
|
||||||
|
return StatLog.getSystemStatNum(SysProps.getSystemDailyDownloadK) / 1024;
|
||||||
|
}
|
||||||
|
|
||||||
|
getUserTotalUploadK() {
|
||||||
|
return StatLog.getUserStatNum(this.client.user, UserProps.FileUlTotalBytes) / 1024;
|
||||||
|
}
|
||||||
|
|
||||||
|
getCurrentDateMMDDYY() {
|
||||||
|
// Return current date in MM/DD/YY format
|
||||||
|
return moment().format('MM/DD/YY');
|
||||||
|
}
|
||||||
|
|
||||||
|
getUserBirthDateMMDDYY() {
|
||||||
|
// Return user's birthdate in MM/DD/YY format
|
||||||
|
return moment(this.client.user.properties[UserProps.Birthdate]).format('MM/DD/YY');
|
||||||
}
|
}
|
||||||
|
|
||||||
getDoorInfoFileName() {
|
getDoorInfoFileName() {
|
||||||
|
@ -93,160 +209,14 @@ module.exports = class DropFile {
|
||||||
return 'DORINFO' + x + '.DEF';
|
return 'DORINFO' + x + '.DEF';
|
||||||
}
|
}
|
||||||
|
|
||||||
getDoorSysBuffer() {
|
|
||||||
const prop = this.client.user.properties;
|
|
||||||
const now = moment();
|
|
||||||
const secLevel = this.client.user.getLegacySecurityLevel().toString();
|
|
||||||
const fullName = this.client.user.getSanitizedName('real');
|
|
||||||
const bd = moment(prop[UserProps.Birthdate]).format('MM/DD/YY');
|
|
||||||
|
|
||||||
const upK = Math.floor((parseInt(prop[UserProps.FileUlTotalBytes]) || 0) / 1024);
|
|
||||||
const downK = Math.floor(
|
|
||||||
(parseInt(prop[UserProps.FileDlTotalBytes]) || 0) / 1024
|
|
||||||
);
|
|
||||||
|
|
||||||
const timeOfCall = moment(prop[UserProps.LastLoginTs] || moment()).format(
|
|
||||||
'hh:mm'
|
|
||||||
);
|
|
||||||
|
|
||||||
// :TODO: fix time remaining
|
|
||||||
// :TODO: fix default protocol -- user prop: transfer_protocol
|
|
||||||
return iconv.encode(
|
|
||||||
[
|
|
||||||
'COM1:', // "Comm Port - COM0: = LOCAL MODE"
|
|
||||||
'57600', // "Baud Rate - 300 to 38400" (Note: set as 57600 instead!)
|
|
||||||
'8', // "Parity - 7 or 8"
|
|
||||||
this.client.node.toString(), // "Node Number - 1 to 99"
|
|
||||||
'57600', // "DTE Rate. Actual BPS rate to use. (kg)"
|
|
||||||
'Y', // "Screen Display - Y=On N=Off (Default to Y)"
|
|
||||||
'Y', // "Printer Toggle - Y=On N=Off (Default to Y)"
|
|
||||||
'Y', // "Page Bell - Y=On N=Off (Default to Y)"
|
|
||||||
'Y', // "Caller Alarm - Y=On N=Off (Default to Y)"
|
|
||||||
fullName, // "User Full Name"
|
|
||||||
prop[UserProps.Location] || 'Anywhere', // "Calling From"
|
|
||||||
'123-456-7890', // "Home Phone"
|
|
||||||
'123-456-7890', // "Work/Data Phone"
|
|
||||||
'NOPE', // "Password" (Note: this is never given out or even stored plaintext)
|
|
||||||
secLevel, // "Security Level"
|
|
||||||
prop[UserProps.LoginCount].toString(), // "Total Times On"
|
|
||||||
now.format('MM/DD/YY'), // "Last Date Called"
|
|
||||||
'15360', // "Seconds Remaining THIS call (for those that particular)"
|
|
||||||
'256', // "Minutes Remaining THIS call"
|
|
||||||
'GR', // "Graphics Mode - GR=Graph, NG=Non-Graph, 7E=7,E Caller"
|
|
||||||
this.client.term.termHeight.toString(), // "Page Length"
|
|
||||||
'N', // "User Mode - Y = Expert, N = Novice"
|
|
||||||
'1,2,3,4,5,6,7', // "Conferences/Forums Registered In (ABCDEFG)"
|
|
||||||
'1', // "Conference Exited To DOOR From (G)"
|
|
||||||
'01/01/99', // "User Expiration Date (mm/dd/yy)"
|
|
||||||
this.client.user.userId.toString(), // "User File's Record Number"
|
|
||||||
'Z', // "Default Protocol - X, C, Y, G, I, N, Etc."
|
|
||||||
// :TODO: fix up, down, etc. form user properties
|
|
||||||
'0', // "Total Uploads"
|
|
||||||
'0', // "Total Downloads"
|
|
||||||
'0', // "Daily Download "K" Total"
|
|
||||||
'999999', // "Daily Download Max. "K" Limit"
|
|
||||||
bd, // "Caller's Birthdate"
|
|
||||||
'X:\\MAIN\\', // "Path to the MAIN directory (where User File is)"
|
|
||||||
'X:\\GEN\\', // "Path to the GEN directory"
|
|
||||||
StatLog.getSystemStat(SysProps.SysOpUsername), // "Sysop's Name (name BBS refers to Sysop as)"
|
|
||||||
this.client.user.getSanitizedName(), // "Alias name"
|
|
||||||
'00:05', // "Event time (hh:mm)" (note: wat?)
|
|
||||||
'Y', // "If its an error correcting connection (Y/N)"
|
|
||||||
'Y', // "ANSI supported & caller using NG mode (Y/N)"
|
|
||||||
'Y', // "Use Record Locking (Y/N)"
|
|
||||||
'7', // "BBS Default Color (Standard IBM color code, ie, 1-15)"
|
|
||||||
// :TODO: fix minutes here also:
|
|
||||||
'256', // "Time Credits In Minutes (positive/negative)"
|
|
||||||
'07/07/90', // "Last New Files Scan Date (mm/dd/yy)"
|
|
||||||
timeOfCall, // "Time of This Call"
|
|
||||||
timeOfCall, // "Time of Last Call (hh:mm)"
|
|
||||||
'9999', // "Maximum daily files available"
|
|
||||||
'0', // "Files d/led so far today"
|
|
||||||
upK.toString(), // "Total "K" Bytes Uploaded"
|
|
||||||
downK.toString(), // "Total "K" Bytes Downloaded"
|
|
||||||
prop[UserProps.UserComment] || 'None', // "User Comment"
|
|
||||||
'0', // "Total Doors Opened"
|
|
||||||
'0', // "Total Messages Left"
|
|
||||||
].join('\r\n') + '\r\n',
|
|
||||||
'cp437'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
getDoor32Buffer() {
|
|
||||||
//
|
|
||||||
// Resources:
|
|
||||||
// * http://wiki.bbses.info/index.php/DOOR32.SYS
|
|
||||||
// * https://github.com/NuSkooler/ansi-bbs/blob/master/docs/dropfile_formats/door32_sys.txt
|
|
||||||
//
|
|
||||||
// :TODO: local/serial/telnet need to be configurable -- which also changes socket handle!
|
|
||||||
const Door32CommTypes = {
|
|
||||||
Local: 0,
|
|
||||||
Serial: 1,
|
|
||||||
Telnet: 2,
|
|
||||||
};
|
|
||||||
|
|
||||||
const commType = Door32CommTypes.Telnet;
|
|
||||||
|
|
||||||
return iconv.encode(
|
|
||||||
[
|
|
||||||
commType.toString(),
|
|
||||||
'-1',
|
|
||||||
'115200',
|
|
||||||
Config().general.boardName,
|
|
||||||
this.client.user.userId.toString(),
|
|
||||||
this.client.user.getSanitizedName('real'),
|
|
||||||
this.client.user.getSanitizedName(),
|
|
||||||
this.client.user.getLegacySecurityLevel().toString(),
|
|
||||||
'546', // :TODO: Minutes left!
|
|
||||||
'1', // ANSI
|
|
||||||
this.client.node.toString(),
|
|
||||||
].join('\r\n') + '\r\n',
|
|
||||||
'cp437'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
getDoorInfoDefBuffer() {
|
|
||||||
// :TODO: fix time remaining
|
|
||||||
|
|
||||||
//
|
|
||||||
// Resources:
|
|
||||||
// * http://goldfndr.home.mindspring.com/dropfile/dorinfo.htm
|
|
||||||
//
|
|
||||||
// Note that usernames are just used for first/last names here
|
|
||||||
//
|
|
||||||
const opUserName = /[^\s]*/.exec(
|
|
||||||
StatLog.getSystemStat(SysProps.SysOpUsername)
|
|
||||||
)[0];
|
|
||||||
const userName = /[^\s]*/.exec(this.client.user.getSanitizedName())[0];
|
|
||||||
const secLevel = this.client.user.getLegacySecurityLevel().toString();
|
|
||||||
const location = this.client.user.properties[UserProps.Location];
|
|
||||||
|
|
||||||
return iconv.encode(
|
|
||||||
[
|
|
||||||
Config().general.boardName, // "The name of the system."
|
|
||||||
opUserName, // "The sysop's name up to the first space."
|
|
||||||
opUserName, // "The sysop's name following the first space."
|
|
||||||
'COM1', // "The serial port the modem is connected to, or 0 if logged in on console."
|
|
||||||
'57600', // "The current port (DTE) rate."
|
|
||||||
'0', // "The number "0""
|
|
||||||
userName, // "The current user's name, up to the first space."
|
|
||||||
userName, // "The current user's name, following the first space."
|
|
||||||
location || '', // "Where the user lives, or a blank line if unknown."
|
|
||||||
'1', // "The number "0" if TTY, or "1" if ANSI."
|
|
||||||
secLevel, // "The number 5 for problem users, 30 for regular users, 80 for Aides, and 100 for Sysops."
|
|
||||||
'546', // "The number of minutes left in the current user's account, limited to 546 to keep from overflowing other software."
|
|
||||||
'-1', // "The number "-1" if using an external serial driver or "0" if using internal serial routines."
|
|
||||||
].join('\r\n') + '\r\n',
|
|
||||||
'cp437'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
createFile(cb) {
|
createFile(cb) {
|
||||||
mkdirs(paths.dirname(this.fullPath), err => {
|
mkdirs(paths.dirname(this.fullPath), err => {
|
||||||
if (err) {
|
if (err) {
|
||||||
return cb(err);
|
return cb(err);
|
||||||
}
|
}
|
||||||
return fs.writeFile(this.fullPath, this.getContents(), cb);
|
const fullPath = this.fullPath;
|
||||||
|
const contents = this.getContents();
|
||||||
|
return fs.writeFile(fullPath, contents, cb);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -124,6 +124,12 @@ const PREDEFINED_MCI_GENERATORS = {
|
||||||
UN: function userName(client) {
|
UN: function userName(client) {
|
||||||
return client.user.username;
|
return client.user.username;
|
||||||
},
|
},
|
||||||
|
UZ: function sanitizedUserName(client) {
|
||||||
|
return client.user.getSanitizedName();
|
||||||
|
},
|
||||||
|
LL: function legacyUserLevel(client) {
|
||||||
|
return client.user.getLegacySecurityLevel().toString();
|
||||||
|
},
|
||||||
UI: function userId(client) {
|
UI: function userId(client) {
|
||||||
return client.user.userId.toString();
|
return client.user.userId.toString();
|
||||||
},
|
},
|
||||||
|
|
|
@ -14,6 +14,8 @@ const {
|
||||||
formatCountAbbr,
|
formatCountAbbr,
|
||||||
} = require('./string_util.js');
|
} = require('./string_util.js');
|
||||||
|
|
||||||
|
const colorCodes = require('./color_codes.js');
|
||||||
|
|
||||||
// deps
|
// deps
|
||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
const moment = require('moment');
|
const moment = require('moment');
|
||||||
|
@ -210,7 +212,7 @@ function formatNumberHelper(n, precision, type) {
|
||||||
function formatNumber(value, tokens) {
|
function formatNumber(value, tokens) {
|
||||||
const fill = tokens.fill || (tokens['0'] ? '0' : ' ');
|
const fill = tokens.fill || (tokens['0'] ? '0' : ' ');
|
||||||
const align = tokens.align || (tokens['0'] ? '=' : '>');
|
const align = tokens.align || (tokens['0'] ? '=' : '>');
|
||||||
const width = Number(tokens.width);
|
const width = Number(tokens.width);value.replace(/\x1b\[[0-9;]*m/g, '');
|
||||||
const type = tokens.type || (tokens.precision ? 'g' : '');
|
const type = tokens.type || (tokens.precision ? 'g' : '');
|
||||||
|
|
||||||
if (['c', 'd', 'b', 'o', 'x', 'X'].indexOf(type) > -1) {
|
if (['c', 'd', 'b', 'o', 'x', 'X'].indexOf(type) > -1) {
|
||||||
|
@ -299,6 +301,7 @@ const transformers = {
|
||||||
styleSmallI: s => stylizeString(s, 'small i'),
|
styleSmallI: s => stylizeString(s, 'small i'),
|
||||||
styleMixed: s => stylizeString(s, 'mixed'),
|
styleMixed: s => stylizeString(s, 'mixed'),
|
||||||
styleL33t: s => stylizeString(s, 'l33t'),
|
styleL33t: s => stylizeString(s, 'l33t'),
|
||||||
|
sanitized: s => stylizeString(s, 'sanitized'),
|
||||||
|
|
||||||
// :TODO:
|
// :TODO:
|
||||||
// toMegs(), toKilobytes(), ...
|
// toMegs(), toKilobytes(), ...
|
||||||
|
@ -337,7 +340,7 @@ function getValue(obj, path) {
|
||||||
throw new KeyError(quote(path));
|
throw new KeyError(quote(path));
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = function format(fmt, obj) {
|
module.exports = function format(fmt, obj, stripMciColorCodes = false) {
|
||||||
const re = REGEXP_BASIC_FORMAT;
|
const re = REGEXP_BASIC_FORMAT;
|
||||||
re.lastIndex = 0; // reset from prev
|
re.lastIndex = 0; // reset from prev
|
||||||
|
|
||||||
|
@ -369,6 +372,11 @@ module.exports = function format(fmt, obj) {
|
||||||
value = transformValue(transformer, value);
|
value = transformValue(transformer, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This is used in cases where the output shouldn't allow color codes
|
||||||
|
if (stripMciColorCodes) {
|
||||||
|
value = colorCodes.stripMciColorCodes(value);
|
||||||
|
}
|
||||||
|
|
||||||
tokens = tokenizeFormatSpec(formatSpec || '');
|
tokens = tokenizeFormatSpec(formatSpec || '');
|
||||||
|
|
||||||
if (_.isNumber(value)) {
|
if (_.isNumber(value)) {
|
||||||
|
|
|
@ -1,25 +1,19 @@
|
||||||
FROM --platform=${BUILDPLATFORM:-linux/amd64} node:20-bookworm-slim
|
FROM node:18-buster-slim
|
||||||
|
|
||||||
ARG TARGETPLATFORM
|
|
||||||
ARG BUILDPLATFORM
|
|
||||||
ARG TARGETOS
|
|
||||||
ARG TARGETBRANCH
|
|
||||||
|
|
||||||
LABEL maintainer="dave@force9.org"
|
LABEL maintainer="dave@force9.org"
|
||||||
|
|
||||||
ENV NVM_DIR /root/.nvm
|
ENV NVM_DIR /root/.nvm
|
||||||
ENV DEBIAN_FRONTEND noninteractive
|
ENV DEBIAN_FRONTEND noninteractive
|
||||||
|
COPY . /enigma-bbs
|
||||||
|
|
||||||
|
# Do some installing! (and alot of cleaning up) keeping it in one step for less docker layers
|
||||||
# Just copy the package.json so it only needs to build once
|
# - if you need to debug i recommend to break the steps with individual RUNs)
|
||||||
COPY package.json /enigma-bbs/
|
|
||||||
|
|
||||||
# Install APT and NPM packages
|
|
||||||
RUN apt-get update \
|
RUN apt-get update \
|
||||||
&& apt-get install -y \
|
&& apt-get install -y \
|
||||||
git \
|
git \
|
||||||
curl \
|
curl \
|
||||||
build-essential \
|
build-essential \
|
||||||
|
python \
|
||||||
python3 \
|
python3 \
|
||||||
libssl-dev \
|
libssl-dev \
|
||||||
lrzsz \
|
lrzsz \
|
||||||
|
@ -28,21 +22,9 @@ RUN apt-get update \
|
||||||
unrar-free \
|
unrar-free \
|
||||||
p7zip-full \
|
p7zip-full \
|
||||||
dos2unix \
|
dos2unix \
|
||||||
&& npm set progress=false && npm config set depth 0 \
|
|
||||||
&& npm install -g npm@latest \
|
&& npm install -g npm@latest \
|
||||||
&& npm install -g pm2 \
|
&& npm install -g pm2 \
|
||||||
&& cd /enigma-bbs && npm install
|
&& cd /enigma-bbs && npm install \
|
||||||
|
|
||||||
|
|
||||||
# Do this after npm install to avoid cache-miss on every code change
|
|
||||||
COPY . /enigma-bbs
|
|
||||||
|
|
||||||
# Then run post source copy steps that have to happen every time
|
|
||||||
RUN dos2unix /enigma-bbs/docker/bin/docker-entrypoint.sh \
|
|
||||||
&& apt-get remove dos2unix -y \
|
|
||||||
&& chmod +x /enigma-bbs/docker/bin/docker-entrypoint.sh \
|
|
||||||
&& cp -f /enigma-bbs/docker/bin/sexyz /usr/local/bin \
|
|
||||||
&& cd /enigma-bbs \
|
|
||||||
&& pm2 start main.js \
|
&& pm2 start main.js \
|
||||||
&& mkdir -p /enigma-bbs-pre/art \
|
&& mkdir -p /enigma-bbs-pre/art \
|
||||||
&& mkdir /enigma-bbs-pre/mods \
|
&& mkdir /enigma-bbs-pre/mods \
|
||||||
|
@ -50,11 +32,16 @@ RUN dos2unix /enigma-bbs/docker/bin/docker-entrypoint.sh \
|
||||||
&& cp -rp art/* ../enigma-bbs-pre/art/ \
|
&& cp -rp art/* ../enigma-bbs-pre/art/ \
|
||||||
&& cp -rp mods/* ../enigma-bbs-pre/mods/ \
|
&& cp -rp mods/* ../enigma-bbs-pre/mods/ \
|
||||||
&& cp -rp config/* ../enigma-bbs-pre/config/ \
|
&& cp -rp config/* ../enigma-bbs-pre/config/ \
|
||||||
&& apt-get remove build-essential python3 libssl-dev git curl -y \
|
&& apt-get remove build-essential python python3 libssl-dev git curl -y \
|
||||||
&& apt-get autoremove -y \
|
&& apt-get autoremove -y \
|
||||||
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* \
|
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* \
|
||||||
&& apt-get clean
|
&& apt-get clean
|
||||||
|
|
||||||
|
# sexyz
|
||||||
|
COPY docker/bin/sexyz /usr/local/bin
|
||||||
|
RUN dos2unix /enigma-bbs/docker/bin/docker-entrypoint.sh && apt-get remove dos2unix -y
|
||||||
|
RUN chmod +x /enigma-bbs/docker/bin/docker-entrypoint.sh
|
||||||
|
|
||||||
# enigma storage mounts
|
# enigma storage mounts
|
||||||
VOLUME /enigma-bbs/art
|
VOLUME /enigma-bbs/art
|
||||||
VOLUME /enigma-bbs/config
|
VOLUME /enigma-bbs/config
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
{UR}
|
||||||
|
4
|
||||||
|
{SL}
|
||||||
|
{LL}
|
||||||
|
999
|
||||||
|
COLOR
|
||||||
|
NOTPROVIDED
|
||||||
|
{UI}
|
||||||
|
0
|
||||||
|
{CT}
|
||||||
|
{CT} {getCurrentDateMMDDYY}
|
||||||
|
{MC}
|
||||||
|
{DN}
|
||||||
|
999
|
||||||
|
0
|
||||||
|
999999
|
||||||
|
555-555-5555
|
||||||
|
{getCurrentDateMMDDYY} {CT}
|
||||||
|
NOVICE
|
||||||
|
All
|
||||||
|
{getCurrentDateMMDDYY}
|
||||||
|
{UC}
|
||||||
|
{SH}
|
||||||
|
0
|
||||||
|
{UP}
|
||||||
|
{DN}
|
||||||
|
8
|
||||||
|
REMOTE
|
||||||
|
1
|
||||||
|
{BD}
|
||||||
|
38400
|
||||||
|
FALSE
|
||||||
|
Normal Connection
|
||||||
|
{getCurrentDateMMDDYY} {CT}
|
||||||
|
{ND}
|
||||||
|
0
|
|
@ -0,0 +1,31 @@
|
||||||
|
{UI}
|
||||||
|
{UN}
|
||||||
|
{UR}
|
||||||
|
?
|
||||||
|
{UA}
|
||||||
|
M
|
||||||
|
{AP}
|
||||||
|
{getCurrentDateMMDDYY}
|
||||||
|
{SW}
|
||||||
|
{SH}
|
||||||
|
{LL}
|
||||||
|
0
|
||||||
|
0
|
||||||
|
1
|
||||||
|
0
|
||||||
|
99999
|
||||||
|
C:\DATA
|
||||||
|
C:\WWIV\DATA
|
||||||
|
C:\DATA\logfile.txt
|
||||||
|
C:\DATA\BBS.LOG
|
||||||
|
19200
|
||||||
|
1
|
||||||
|
{BN}
|
||||||
|
{SR}
|
||||||
|
1
|
||||||
|
0
|
||||||
|
{getUserTotalUploadK}
|
||||||
|
{UP}
|
||||||
|
{getUserTotalDownloadK}
|
||||||
|
{DN}
|
||||||
|
8N1
|
|
@ -0,0 +1,52 @@
|
||||||
|
COM1:
|
||||||
|
38400
|
||||||
|
8
|
||||||
|
{ND}
|
||||||
|
19200
|
||||||
|
N
|
||||||
|
N
|
||||||
|
N
|
||||||
|
N
|
||||||
|
{UR}
|
||||||
|
{LO}
|
||||||
|
555 555-5555
|
||||||
|
555 555-5555
|
||||||
|
NOT PROVIDED
|
||||||
|
{LL}
|
||||||
|
{UC}
|
||||||
|
{getCurrentDateMMDDYY}
|
||||||
|
9999
|
||||||
|
999
|
||||||
|
GR
|
||||||
|
{SH}
|
||||||
|
N
|
||||||
|
{MC}
|
||||||
|
{MC}
|
||||||
|
12/31/9999
|
||||||
|
{UI}
|
||||||
|
Y
|
||||||
|
{UP}
|
||||||
|
{DN}
|
||||||
|
{getSystemDailyDownloadK}
|
||||||
|
9999999
|
||||||
|
{getUserBirthDateMMDDYY}
|
||||||
|
C:\DATA
|
||||||
|
C:\DATA
|
||||||
|
{SR}
|
||||||
|
{UZ}
|
||||||
|
{CT}
|
||||||
|
Y
|
||||||
|
Y
|
||||||
|
Y
|
||||||
|
7
|
||||||
|
32767
|
||||||
|
{getCurrentDateMMDDYY}
|
||||||
|
{CT}
|
||||||
|
{CT}
|
||||||
|
9999
|
||||||
|
{DD}
|
||||||
|
{getUserTotalUploadK}
|
||||||
|
{getUserTotalDownloadK}
|
||||||
|
|
||||||
|
{DR}
|
||||||
|
32767
|
|
@ -0,0 +1,8 @@
|
||||||
|
{UN}
|
||||||
|
1
|
||||||
|
1
|
||||||
|
{SH}
|
||||||
|
19200
|
||||||
|
1
|
||||||
|
-1
|
||||||
|
{UR}
|
|
@ -0,0 +1,13 @@
|
||||||
|
{BN}
|
||||||
|
{getSysopFirstName}
|
||||||
|
{getSysopLastName}
|
||||||
|
COM1
|
||||||
|
19200 BAUD,N,8,1
|
||||||
|
0
|
||||||
|
{getUserFirstName}
|
||||||
|
{getUserLastName}
|
||||||
|
{LO}
|
||||||
|
1
|
||||||
|
{LL}
|
||||||
|
999
|
||||||
|
-1
|
|
@ -0,0 +1,18 @@
|
||||||
|
{UI}
|
||||||
|
{UN}
|
||||||
|
NOTPROVIDED
|
||||||
|
{LL}
|
||||||
|
N
|
||||||
|
Y
|
||||||
|
999
|
||||||
|
555-555-1212
|
||||||
|
{LO}
|
||||||
|
{getUserBirthDateMMDDYY}
|
||||||
|
{ND}
|
||||||
|
1
|
||||||
|
19200
|
||||||
|
0
|
||||||
|
N
|
||||||
|
Y
|
||||||
|
{BN}
|
||||||
|
{SR}
|
|
@ -0,0 +1,66 @@
|
||||||
|
{UR}
|
||||||
|
{BN}
|
||||||
|
{SN}
|
||||||
|
The Guru
|
||||||
|
C:\CONTROL
|
||||||
|
C:\DATA
|
||||||
|
99
|
||||||
|
{ND}
|
||||||
|
99999
|
||||||
|
Yes
|
||||||
|
{SH}
|
||||||
|
209087929
|
||||||
|
{LL}
|
||||||
|
1
|
||||||
|
{BD}
|
||||||
|
M
|
||||||
|
{UI}
|
||||||
|
555-555-5555
|
||||||
|
1
|
||||||
|
4
|
||||||
|
3f8
|
||||||
|
38400
|
||||||
|
No
|
||||||
|
No
|
||||||
|
AT&FS0=0S2=128E0V0X4&C1&D2
|
||||||
|
ATC0\V1
|
||||||
|
ATE1V1\V2
|
||||||
|
ATDT
|
||||||
|
ATM0H1
|
||||||
|
ATA
|
||||||
|
533922058
|
||||||
|
99
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
ABCDEFGHIJKLMNOPQRSTUVWXYZ
|
||||||
|
ABCDEFGHIJKLMNOPQRSTUVWXYZ
|
||||||
|
ABCDEFGHIJKLMNOPQRSTUVWXYZ
|
||||||
|
|
||||||
|
0
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
0
|
||||||
|
{UR}
|
||||||
|
38400
|
|
@ -0,0 +1,11 @@
|
||||||
|
1
|
||||||
|
1
|
||||||
|
19200
|
||||||
|
{BN}
|
||||||
|
{UI}
|
||||||
|
{UR}
|
||||||
|
{UN}
|
||||||
|
1
|
||||||
|
999
|
||||||
|
1
|
||||||
|
{ND}
|
|
@ -53,6 +53,7 @@
|
||||||
"node-pty": "1.0.0",
|
"node-pty": "1.0.0",
|
||||||
"nodemailer": "6.7.7",
|
"nodemailer": "6.7.7",
|
||||||
"otplib": "11.0.1",
|
"otplib": "11.0.1",
|
||||||
|
"parse-full-name": "^1.2.6",
|
||||||
"qrcode-generator": "^1.4.4",
|
"qrcode-generator": "^1.4.4",
|
||||||
"rlogin": "^1.0.0",
|
"rlogin": "^1.0.0",
|
||||||
"sane": "5.0.1",
|
"sane": "5.0.1",
|
||||||
|
|
Loading…
Reference in New Issue