Compare commits
No commits in common. "master" and "449-nntp-write-access" have entirely different histories.
master
...
449-nntp-w
|
@ -1,9 +0,0 @@
|
||||||
FROM library/node:lts-bookworm
|
|
||||||
|
|
||||||
ARG DEBIAN_FRONTEND=noninteractive
|
|
||||||
RUN apt update \
|
|
||||||
&& apt install -y --no-install-recommends sudo telnet \
|
|
||||||
&& apt autoremove -y \
|
|
||||||
&& rm -rf /var/lib/apt/lists/* \
|
|
||||||
&& echo "node ALL=(ALL) NOPASSWD: ALL" >/etc/sudoers.d/node \
|
|
||||||
&& chmod 0440 /etc/sudoers.d/node
|
|
|
@ -1,24 +0,0 @@
|
||||||
{
|
|
||||||
"name": "Basic Node.js",
|
|
||||||
"build": { "dockerfile": "Dockerfile" },
|
|
||||||
"remoteUser": "root",
|
|
||||||
"forwardPorts": [8888, 4000],
|
|
||||||
"postCreateCommand": "gem install jekyll bundler && /bin/rm -rf node_modules && npm install && cd docs && bundle install && cd ..",
|
|
||||||
"features": {
|
|
||||||
"ghcr.io/devcontainers/features/python:1": {
|
|
||||||
"installTools": true,
|
|
||||||
"version": "3.11"
|
|
||||||
},
|
|
||||||
"ghcr.io/devcontainers-contrib/features/curl-apt-get:1": {},
|
|
||||||
"ghcr.io/jungaretti/features/ripgrep:1": {},
|
|
||||||
"ghcr.io/warrenbuckley/codespace-features/sqlite:1": {},
|
|
||||||
"ghcr.io/devcontainers/features/ruby:1": {
|
|
||||||
"version": "3.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"customizations": {
|
|
||||||
"vscode": {
|
|
||||||
"extensions": ["ms-azuretools.vscode-docker","alexcvzz.vscode-sqlite","yzhang.markdown-all-in-one", "DavidAnson.vscode-markdownlint", "christian-kohler.npm-intellisense", "dbaeumer.vscode-eslint", "bierner.markdown-yaml-preamble"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
node_modules
|
|
||||||
package-lock.json
|
|
||||||
yarn.lock
|
|
||||||
|
|
||||||
filebase
|
|
||||||
db
|
|
||||||
drop
|
|
||||||
file_base
|
|
||||||
logs
|
|
||||||
mail
|
|
||||||
docs/_site
|
|
||||||
docs/.sass-cache
|
|
|
@ -6,13 +6,3 @@
|
||||||
*.TXT eol=crlf
|
*.TXT eol=crlf
|
||||||
*.diz eol=crlf
|
*.diz eol=crlf
|
||||||
*.DIZ eol=crlf
|
*.DIZ eol=crlf
|
||||||
|
|
||||||
# Don't mess with shell script line endings
|
|
||||||
*.sh text eol=lf
|
|
||||||
|
|
||||||
# Same thing for optutil.js which functions as a shell script
|
|
||||||
optutil.js text eol=lf
|
|
||||||
|
|
||||||
# The devcontainer is also unix
|
|
||||||
.devcontainer/Dockerfile text eol=lf
|
|
||||||
.devcontainer/devcontainer.json text eol=lf
|
|
|
@ -1,74 +0,0 @@
|
||||||
# For most projects, this workflow file will not need changing; you simply need
|
|
||||||
# to commit it to your repository.
|
|
||||||
#
|
|
||||||
# You may wish to alter this file to override the set of languages analyzed,
|
|
||||||
# or to provide custom queries or build logic.
|
|
||||||
#
|
|
||||||
# ******** NOTE ********
|
|
||||||
# We have attempted to detect the languages in your repository. Please check
|
|
||||||
# the `language` matrix defined below to confirm you have the correct set of
|
|
||||||
# supported CodeQL languages.
|
|
||||||
#
|
|
||||||
name: "CodeQL"
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [ "master" ]
|
|
||||||
pull_request:
|
|
||||||
# The branches below must be a subset of the branches above
|
|
||||||
branches: [ "master" ]
|
|
||||||
schedule:
|
|
||||||
- cron: '41 0 * * 5'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
analyze:
|
|
||||||
name: Analyze
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
actions: read
|
|
||||||
contents: read
|
|
||||||
security-events: write
|
|
||||||
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
language: [ 'javascript' ]
|
|
||||||
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
|
|
||||||
# Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
|
|
||||||
# Initializes the CodeQL tools for scanning.
|
|
||||||
- name: Initialize CodeQL
|
|
||||||
uses: github/codeql-action/init@v2
|
|
||||||
with:
|
|
||||||
languages: ${{ matrix.language }}
|
|
||||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
|
||||||
# By default, queries listed here will override any specified in a config file.
|
|
||||||
# Prefix the list here with "+" to use these queries and those in the config file.
|
|
||||||
|
|
||||||
# Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
|
|
||||||
# queries: security-extended,security-and-quality
|
|
||||||
|
|
||||||
|
|
||||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
|
||||||
# If this step fails, then you should remove it and run the build manually (see below)
|
|
||||||
- name: Autobuild
|
|
||||||
uses: github/codeql-action/autobuild@v2
|
|
||||||
|
|
||||||
# ℹ️ Command-line programs to run using the OS shell.
|
|
||||||
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
|
||||||
|
|
||||||
# If the Autobuild fails above, remove it and uncomment the following three lines.
|
|
||||||
# modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
|
|
||||||
|
|
||||||
# - run: |
|
|
||||||
# echo "Run, Build Application using script"
|
|
||||||
# ./location_of_script_within_repo/buildscript.sh
|
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
|
||||||
uses: github/codeql-action/analyze@v2
|
|
||||||
with:
|
|
||||||
category: "/language:${{matrix.language}}"
|
|
|
@ -11,25 +11,25 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v3
|
uses: docker/setup-qemu-action@v1
|
||||||
|
|
||||||
- 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@v1
|
||||||
|
|
||||||
- name: Login to DockerHub
|
- name: Login to DockerHub
|
||||||
uses: docker/login-action@v2
|
uses: docker/login-action@v1
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Build and push
|
- name: Build and push
|
||||||
uses: docker/build-push-action@v3
|
uses: docker/build-push-action@v2
|
||||||
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
|
|
@ -11,5 +11,4 @@ mail/
|
||||||
node_modules/
|
node_modules/
|
||||||
docs/_site/
|
docs/_site/
|
||||||
docs/.sass-cache/
|
docs/.sass-cache/
|
||||||
|
.vscode/
|
||||||
docs/.jekyll-cache/
|
|
||||||
|
|
|
@ -1,6 +0,0 @@
|
||||||
{
|
|
||||||
"recommendations": [
|
|
||||||
"ms-vscode-remote.remote-containers",
|
|
||||||
"laktak.hjson"
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1,26 +0,0 @@
|
||||||
{
|
|
||||||
"version": "2.0.0",
|
|
||||||
"tasks": [
|
|
||||||
{
|
|
||||||
"label": "Start Jekyll (ENiGMA½ documentation server)",
|
|
||||||
"command": "cd docs && bundle exec jekyll serve",
|
|
||||||
"isBackground": true,
|
|
||||||
"type": "shell"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "(re)build Jekyll bundles",
|
|
||||||
"command": "cd docs && bundle install",
|
|
||||||
"type": "shell"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "(re)build node modules",
|
|
||||||
"command": "/bin/rm -rf node_modules && npm install",
|
|
||||||
"type": "shell"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "ENiGMA½ new configuration",
|
|
||||||
"command": "./oputil.js config new",
|
|
||||||
"type": "shell"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
20
README.md
20
README.md
|
@ -8,29 +8,29 @@ ENiGMA½ is a modern BBS software with a nostalgic flair!
|
||||||
Below are just some of the features ENiGMA½ supports out of the box:
|
Below are just some of the features ENiGMA½ supports out of the box:
|
||||||
* **Multi platform** — Anywhere [Node.js](https://nodejs.org/) runs likely works (known to work under Linux, FreeBSD, OpenBSD, OS X and Windows)
|
* **Multi platform** — Anywhere [Node.js](https://nodejs.org/) runs likely works (known to work under Linux, FreeBSD, OpenBSD, OS X and Windows)
|
||||||
* Unlimited multi node support (for all those BBS "callers"!)
|
* Unlimited multi node support (for all those BBS "callers"!)
|
||||||
* **Highly customizable** via [HJSON](http://hjson.org/) based configuration, menus, and themes in addition to JavaScript based [mods](./docs/_docs/modding/existing-mods.md)
|
* **Highly customizable** via [HJSON](http://hjson.org/) based configuration, menus, and themes in addition to JavaScript based [mods](docs/modding/existing-mods.md)
|
||||||
* [MCI support](./docs/_docs/art/mci.md) for lightbars, toggles, input areas, and so on plus many other other bells and whistles
|
* [MCI support](docs/art/mci.md) for lightbars, toggles, input areas, and so on plus many other other bells and whistles
|
||||||
* Telnet, **SSH**, and both secure and non-secure [WebSocket](https://en.wikipedia.org/wiki/WebSocket) access built in! Additional servers are easy to implement
|
* Telnet, **SSH**, and both secure and non-secure [WebSocket](https://en.wikipedia.org/wiki/WebSocket) access built in! Additional servers are easy to implement
|
||||||
* [CP437](http://www.ascii-codes.com/) and UTF-8 output
|
* [CP437](http://www.ascii-codes.com/) and UTF-8 output
|
||||||
* [SyncTERM](http://syncterm.bbsdev.net/) style font and baud emulation support. Display PC/DOS and Amiga style artwork as it's intended! In general, ANSI-BBS / [cterm.txt](http://cvs.synchro.net/cgi-bin/viewcvs.cgi/*checkout*/src/conio/cterm.txt?content-type=text%2Fplain&revision=HEAD) / [bansi.txt](http://www.bbsdocumentary.com/library/PROGRAMS/GRAPHICS/ANSI/bansi.txt) are followed for expected BBS behavior.
|
* [SyncTERM](http://syncterm.bbsdev.net/) style font and baud emulation support. Display PC/DOS and Amiga style artwork as it's intended! In general, ANSI-BBS / [cterm.txt](http://cvs.synchro.net/cgi-bin/viewcvs.cgi/*checkout*/src/conio/cterm.txt?content-type=text%2Fplain&revision=HEAD) / [bansi.txt](http://www.bbsdocumentary.com/library/PROGRAMS/GRAPHICS/ANSI/bansi.txt) are followed for expected BBS behavior.
|
||||||
* Full [SAUCE](http://www.acid.org/info/sauce/sauce.htm) support.
|
* Full [SAUCE](http://www.acid.org/info/sauce/sauce.htm) support.
|
||||||
* Renegade style [pipe color codes](./docs/_docs/configuration/colour-codes.md).
|
* Renegade style [pipe color codes](./docs/configuration/colour-codes.md).
|
||||||
* [SQLite](http://sqlite.org/) storage of users, message areas, etc.
|
* [SQLite](http://sqlite.org/) storage of users, message areas, etc.
|
||||||
* Strong [PBKDF2](https://en.wikipedia.org/wiki/PBKDF2) backed password encryption.
|
* Strong [PBKDF2](https://en.wikipedia.org/wiki/PBKDF2) backed password encryption.
|
||||||
* Support for **2-Factor Authentication** with One-Time-Passwords
|
* Support for **2-Factor Authentication** with One-Time-Passwords
|
||||||
* [Door support](./docs/_docs/modding/door-servers.md) including common dropfile formats for legacy DOS doors. Built in [BBSLink](http://bbslink.net/), [DoorParty](http://forums.throwbackbbs.com/), and [Exodus](https://oddnetwork.org/exodus/)!
|
* [Door support](docs/modding/door-servers.md) including common dropfile formats for legacy DOS doors. Built in [BBSLink](http://bbslink.net/), [DoorParty](http://forums.throwbackbbs.com/), [Exodus](https://oddnetwork.org/exodus/) and [CombatNet](http://combatnet.us/) support!
|
||||||
* Structured [Bunyan](https://github.com/trentm/node-bunyan) logging!
|
* Structured [Bunyan](https://github.com/trentm/node-bunyan) logging!
|
||||||
* [Message networks](./docs/_docs/messageareas/message-networks.md) with FidoNet Type Network (FTN) + BinkleyTerm Style Outbound (BSO) message import/export. Messages Bases can also be exposed via [Gopher](./docs/_docs/servers/contentservers/gopher.md), or [NNTP](./docs/_docs/servers/contentservers/nntp.md)!
|
* [Message networks](docs/messageareas/message-networks.md) with FidoNet Type Network (FTN) + BinkleyTerm Style Outbound (BSO) message import/export. Messages Bases can also be exposed via [Gopher](docs/servers/gopher.md), or [NNTP](docs/servers/nntp.md)!
|
||||||
* [Gazelle](https://github.com/WhatCD/Gazelle) inspired File Bases including fast fully indexed full text search (FTS), #tags, and HTTP(S) temporary download URLs using a built in [web server](./docs/_docs/servers/contentservers/web-server.md). Legacy X/Y/Z modem also supported!
|
* [Gazelle](https://github.com/WhatCD/Gazelle) inspired File Bases including fast fully indexed full text search (FTS), #tags, and HTTP(S) temporary download URLs using a built in [web server](docs/servers/web-server.md). Legacy X/Y/Z modem also supported!
|
||||||
* Upload processor supporting [FILE_ID.DIZ](https://en.wikipedia.org/wiki/FILE_ID.DIZ) and [NFO](https://en.wikipedia.org/wiki/.nfo) extraction, year estimation, and more!
|
* Upload processor supporting [FILE_ID.DIZ](https://en.wikipedia.org/wiki/FILE_ID.DIZ) and [NFO](https://en.wikipedia.org/wiki/.nfo) extraction, year estimation, and more!
|
||||||
* ANSI support in the Full Screen Editor (FSE), file descriptions, etc.
|
* ANSI support in the Full Screen Editor (FSE), file descriptions, etc.
|
||||||
* Expandable **achievement system** — BBSing gamified!
|
* Expandable **achievement system** — BBSing gamified!
|
||||||
* A remote accessible [Waiting For Caller (WFC)](./docs/_docs/modding/wfc.md)!
|
* A remote accessible Waiting For Caller (WFC)!
|
||||||
|
|
||||||
...and much much more. Please check out [the issue tracker](https://github.com/NuSkooler/enigma-bbs/issues) and feel free to request features (or contribute!) features!
|
...and much much more. Please check out [the issue tracker](https://github.com/NuSkooler/enigma-bbs/issues) and feel free to request features (or contribute!) features!
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
[Browse the docs online](https://nuskooler.github.io/enigma-bbs/). Be sure to checkout the [/docs/](./docs/_docs/) folder as well for the latest and greatest documentation.
|
[Browse the docs online](https://nuskooler.github.io/enigma-bbs/). Be sure to checkout the [/docs/](./docs/) folder as well for the latest and greatest documentation.
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
On most *nix systems simply run the following from your terminal:
|
On most *nix systems simply run the following from your terminal:
|
||||||
|
@ -49,7 +49,7 @@ If you feel the urge to donate, [you can do so here](https://liberapay.com/NuSko
|
||||||
* See [Discussions](https://github.com/NuSkooler/enigma-bbs/discussions) and [the issue tracker](https://github.com/NuSkooler/enigma-bbs/issues)
|
* See [Discussions](https://github.com/NuSkooler/enigma-bbs/discussions) and [the issue tracker](https://github.com/NuSkooler/enigma-bbs/issues)
|
||||||
* **Discussion on a ENiGMA BBS!** (see Boards below)
|
* **Discussion on a ENiGMA BBS!** (see Boards below)
|
||||||
* IRC: **#enigma-bbs** on **irc.libera.chat:6697(TLS)** ([webchat](https://web.libera.chat/gamja/?channels=#enigma-bbs))
|
* IRC: **#enigma-bbs** on **irc.libera.chat:6697(TLS)** ([webchat](https://web.libera.chat/gamja/?channels=#enigma-bbs))
|
||||||
* `FSX_ENG` on [fsxNet](https://fsxnet.nz) or `ARK_ENIG` on [ArakNet](https://www.araknet.xyz/) available on many fine boards
|
* `FSX_ENG` on [fsxNet](http://bbs.geek.nz/#fsxNet) or `ARK_ENIG` on [ArakNet](https://www.araknet.xyz/) available on many fine boards
|
||||||
* Email: bryan -at- l33t.codes
|
* Email: bryan -at- l33t.codes
|
||||||
* [Facebook ENiGMA½ group](https://www.facebook.com/groups/enigmabbs/)
|
* [Facebook ENiGMA½ group](https://www.facebook.com/groups/enigmabbs/)
|
||||||
|
|
||||||
|
@ -77,7 +77,7 @@ ENiGMA has been tested with many terminals. However, the following are suggested
|
||||||
* [Luciano Ayres](http://www.lucianoayres.com.br/) of [Blocktronics](http://blocktronics.org/), creator of the "Mystery Skulls" default ENiGMA½ theme!
|
* [Luciano Ayres](http://www.lucianoayres.com.br/) of [Blocktronics](http://blocktronics.org/), creator of the "Mystery Skulls" default ENiGMA½ theme!
|
||||||
* Sudndeath for Xibalba ANSI work!
|
* Sudndeath for Xibalba ANSI work!
|
||||||
* Jack Phlash for kick ass ENiGMA½ and Xibalba ASCII (Check out [IMPURE60](http://pc.textmod.es/pack/impure60/)!!)
|
* Jack Phlash for kick ass ENiGMA½ and Xibalba ASCII (Check out [IMPURE60](http://pc.textmod.es/pack/impure60/)!!)
|
||||||
* Avon of [Agency BBS](http://bbs.nz/) and [fsxNet](https://fsxnet.nz) for putting up with my experiments to his system and for FSX_ENG!
|
* Avon of [Agency BBS](http://bbs.geek.nz/) and [fsxNet](http://bbs.geek.nz/#fsxNet) for putting up with my experiments to his system and for FSX_ENG!
|
||||||
* Maskreet of [Throwback BBS](http://www.throwbackbbs.com/) hosting [DoorParty](http://forums.throwbackbbs.com/)!
|
* Maskreet of [Throwback BBS](http://www.throwbackbbs.com/) hosting [DoorParty](http://forums.throwbackbbs.com/)!
|
||||||
* [Apam](https://github.com/apamment) of [Magicka](https://magickabbs.com/)
|
* [Apam](https://github.com/apamment) of [Magicka](https://magickabbs.com/)
|
||||||
* [nail/blocktronics](http://blocktronics.org/tag/nail/) for the [sickmade Xibalba logo](http://pc.textmod.es/pack/blocktronics-420/n-xbalba.ans)!
|
* [nail/blocktronics](http://blocktronics.org/tag/nail/) for the [sickmade Xibalba logo](http://pc.textmod.es/pack/blocktronics-420/n-xbalba.ans)!
|
||||||
|
|
37
UPGRADE.md
37
UPGRADE.md
|
@ -30,16 +30,7 @@ npm install # or simply 'yarn'
|
||||||
1. Check [TROUBLESHOOTING](TROUBLESHOOTING.md) first.
|
1. Check [TROUBLESHOOTING](TROUBLESHOOTING.md) first.
|
||||||
2. Report your issue on Xibalba BBS, hop in #`enigma-bbs` on FreeNode and chat, or [file a issue on GitHub](https://github.com/NuSkooler/enigma-bbs/issues) if you believe you've found a bug or missing feature.
|
2. Report your issue on Xibalba BBS, hop in #`enigma-bbs` on FreeNode and chat, or [file a issue on GitHub](https://github.com/NuSkooler/enigma-bbs/issues) if you believe you've found a bug or missing feature.
|
||||||
|
|
||||||
# Version to Version Notes
|
# 0.0.12-beta to 0.0.13-beta
|
||||||
> :warning: Be sure to inspect these notes during any upgrades!
|
|
||||||
|
|
||||||
## 0.0.13-beta to 0.0.14-beta
|
|
||||||
|
|
||||||
* Due to changes to supported algorithms in newer versions of openssl, the default list of supported algorithms for the ssh login server has changed. There are both removed ciphers as well as optional new kex algorithms available now. ***NOTE:*** Changes to supported algorithms are only needed to support keys generated with new versions of openssl, if you already have a ssl key in use you should not have to make any changes to your config.
|
|
||||||
* Removed ciphers: 'blowfish-cbc', 'arcfour256', 'arcfour128', and 'cast128-cbc'
|
|
||||||
* Added kex: 'curve25519-sha256', 'curve25519-sha256@libssh.org', 'curve25519-sha256', 'curve25519-sha256@libssh.org', 'ecdh-sha2-nistp256', 'ecdh-sha2-nistp384', 'ecdh-sha2-nistp521'
|
|
||||||
|
|
||||||
## 0.0.12-beta to 0.0.13-beta
|
|
||||||
* To enable the new Waiting for Caller (WFC) support, please see [WFC](docs/modding/wfc.md).
|
* To enable the new Waiting for Caller (WFC) support, please see [WFC](docs/modding/wfc.md).
|
||||||
* :exclamation: The SSH server's `ssh2` module has gone through a major upgrade. Existing users will need to comment out two SSH KEX algorithms from their `config.hjson` if present else clients such as NetRunner will not be able to connect over SSH. Comment out `diffie-hellman-group-exchange-sha256` and `diffie-hellman-group-exchange-sha1`
|
* :exclamation: The SSH server's `ssh2` module has gone through a major upgrade. Existing users will need to comment out two SSH KEX algorithms from their `config.hjson` if present else clients such as NetRunner will not be able to connect over SSH. Comment out `diffie-hellman-group-exchange-sha256` and `diffie-hellman-group-exchange-sha1`
|
||||||
* Gopher configuration change. See [WHATSNEW](WHATSNEW.md)
|
* Gopher configuration change. See [WHATSNEW](WHATSNEW.md)
|
||||||
|
@ -69,7 +60,7 @@ npm install # or simply 'yarn'
|
||||||
|
|
||||||
In addition to these, there are also new options for `term.cp437TermList` and `term.utf8TermList`. Under most circumstances these should not need to be changed. If you want to customize these lists, more information is available in `config_default.js`
|
In addition to these, there are also new options for `term.cp437TermList` and `term.utf8TermList`. Under most circumstances these should not need to be changed. If you want to customize these lists, more information is available in `config_default.js`
|
||||||
|
|
||||||
## 0.0.11-beta to 0.0.12-beta
|
# 0.0.11-beta to 0.0.12-beta
|
||||||
* Be aware that `master` is now mainline! This means all `git pull`'s will yield the latest version. See [WHATSNEW](WHATSNEW.md) for more information.
|
* Be aware that `master` is now mainline! This means all `git pull`'s will yield the latest version. See [WHATSNEW](WHATSNEW.md) for more information.
|
||||||
* **BREAKING CHANGE** There is no longer a `prompt.hjson` file. Prompts are now simply part of the menu set in the `prompts` section. If you have an existing system you will need to add your `prompt.hjson` to your `menu.hjson`'s `includes` section at a minimum. Example:
|
* **BREAKING CHANGE** There is no longer a `prompt.hjson` file. Prompts are now simply part of the menu set in the `prompts` section. If you have an existing system you will need to add your `prompt.hjson` to your `menu.hjson`'s `includes` section at a minimum. Example:
|
||||||
```hjson
|
```hjson
|
||||||
|
@ -88,14 +79,14 @@ In addition to these, there are also new options for `term.cp437TermList` and `t
|
||||||
sqlite3 db/file.sqlite3 < ./misc/update/tables_update_2020-11-29.sql
|
sqlite3 db/file.sqlite3 < ./misc/update/tables_update_2020-11-29.sql
|
||||||
```
|
```
|
||||||
|
|
||||||
## 0.0.10-alpha to 0.0.11-beta
|
# 0.0.10-alpha to 0.0.11-beta
|
||||||
* Node.js 12.x LTS is now in use. Follow standard Node.js upgrade procedures (e.g.: `nvm install 12 && nvm use 12`).
|
* Node.js 12.x LTS is now in use. Follow standard Node.js upgrade procedures (e.g.: `nvm install 12 && nvm use 12`).
|
||||||
|
|
||||||
## 0.0.9-alpha to 0.0.10-alpha
|
# 0.0.9-alpha to 0.0.10-alpha
|
||||||
* Security related files such as private keys and certs are now looked for in `config/security` by default.
|
* Security related files such as private keys and certs are now looked for in `config/security` by default.
|
||||||
* Default archive handler for zip files has switched to InfoZip due to a bug in the latest p7Zip packages causing "volume not found" errors. Ensure you have the InfoZip `zip` and `unzip` commands in ENiGMA's path. You can switch back to 7Zip by overriding `archiveHandler` for `application/zip` in your `config.hjson` under `fileTypes` to `7Zip`.
|
* Default archive handler for zip files has switched to InfoZip due to a bug in the latest p7Zip packages causing "volume not found" errors. Ensure you have the InfoZip `zip` and `unzip` commands in ENiGMA's path. You can switch back to 7Zip by overriding `archiveHandler` for `application/zip` in your `config.hjson` under `fileTypes` to `7Zip`.
|
||||||
|
|
||||||
## 0.0.8-alpha to 0.0.9-alpha
|
# 0.0.8-alpha to 0.0.9-alpha
|
||||||
* Development is now against Node.js 10.x LTS. Follow your standard upgrade path to update to Node 10.x before using 0.0.9-alpha!
|
* Development is now against Node.js 10.x LTS. Follow your standard upgrade path to update to Node 10.x before using 0.0.9-alpha!
|
||||||
* The property `justify` found on various views previously had `left` and `right` values swapped (oops!); you will need to adjust any custom `theme.hjson` that use one or the other and swap them as well.
|
* The property `justify` found on various views previously had `left` and `right` values swapped (oops!); you will need to adjust any custom `theme.hjson` that use one or the other and swap them as well.
|
||||||
* Possible breaking changes in FSE: The MCI code `%TL13` for error indicator is now `%TL4`. This is part of a cleanup and standardization on "custom ranges". You may need to update your `theme.hjson` and related artwork.
|
* Possible breaking changes in FSE: The MCI code `%TL13` for error indicator is now `%TL4`. This is part of a cleanup and standardization on "custom ranges". You may need to update your `theme.hjson` and related artwork.
|
||||||
|
@ -122,7 +113,7 @@ webSocket: {
|
||||||
* Similar to the last item, `defaults.general.passwordChar` in `theme.hjson` is now just `defaults.passwordChar`.
|
* Similar to the last item, `defaults.general.passwordChar` in `theme.hjson` is now just `defaults.passwordChar`.
|
||||||
|
|
||||||
|
|
||||||
## 0.0.7-alpha to 0.0.8-alpha
|
# 0.0.7-alpha to 0.0.8-alpha
|
||||||
ENiGMA 0.0.8-alpha comes with some structure changes:
|
ENiGMA 0.0.8-alpha comes with some structure changes:
|
||||||
* Configuration files are defaulted to `./config`. Related, the `--config` option now points to a configuration **directory**
|
* Configuration files are defaulted to `./config`. Related, the `--config` option now points to a configuration **directory**
|
||||||
* `./mods/art` has been moved to `./art/general`
|
* `./mods/art` has been moved to `./art/general`
|
||||||
|
@ -139,17 +130,17 @@ With the above changes, you'll need to to at least:
|
||||||
* Move any certificates, pub/private keys, etc. from `./misc` to `./config`
|
* Move any certificates, pub/private keys, etc. from `./misc` to `./config`
|
||||||
* Specify user modules as `@userModule:my_module_name`
|
* Specify user modules as `@userModule:my_module_name`
|
||||||
|
|
||||||
## 0.0.6-alpha to 0.0.7-alpha
|
# 0.0.6-alpha to 0.0.7-alpha
|
||||||
No issues
|
No issues
|
||||||
|
|
||||||
## 0.0.5-alpha to 0.0.6-alpha
|
# 0.0.5-alpha to 0.0.6-alpha
|
||||||
No issues
|
No issues
|
||||||
|
|
||||||
## 0.0.4-alpha to 0.0.5-alpha
|
# 0.0.4-alpha to 0.0.5-alpha
|
||||||
No issues
|
No issues
|
||||||
|
|
||||||
## 0.0.1-alpha to 0.0.4-alpha
|
# 0.0.1-alpha to 0.0.4-alpha
|
||||||
### Node.js 6.x+ LTS is now **required**
|
## Node.js 6.x+ LTS is now **required**
|
||||||
You will need to upgrade Node.js to [6.x+](https://github.com/nodejs/node/blob/master/doc/changelogs/CHANGELOG_V6.md). If using [nvm](https://github.com/creationix/nvm) (you should be!) the process will go something like this:
|
You will need to upgrade Node.js to [6.x+](https://github.com/nodejs/node/blob/master/doc/changelogs/CHANGELOG_V6.md). If using [nvm](https://github.com/creationix/nvm) (you should be!) the process will go something like this:
|
||||||
```bash
|
```bash
|
||||||
nvm install 6
|
nvm install 6
|
||||||
|
@ -159,7 +150,7 @@ nvm alias default 6
|
||||||
### ES6
|
### ES6
|
||||||
Newly written code will use ES6 and a lot of code has started the migration process. Of note is the `MenuModule` class. If you have created a mod that inherits from `MenuModule`, you will need to upgrade your class to ES6.
|
Newly written code will use ES6 and a lot of code has started the migration process. Of note is the `MenuModule` class. If you have created a mod that inherits from `MenuModule`, you will need to upgrade your class to ES6.
|
||||||
|
|
||||||
### Manual Database Upgrade
|
## Manual Database Upgrade
|
||||||
A few upgrades need to be made to your SQLite databases:
|
A few upgrades need to be made to your SQLite databases:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
@ -168,8 +159,8 @@ sqlite3 db/message.sqlite
|
||||||
sqlite> INSERT INTO message_fts(message_fts) VALUES('rebuild');
|
sqlite> INSERT INTO message_fts(message_fts) VALUES('rebuild');
|
||||||
```
|
```
|
||||||
|
|
||||||
### Archiver Changes
|
## Archiver Changes
|
||||||
If you have overridden or made additions to archivers in your `config.hjson` you will need to update them. See [Archive Configuration](docs/archive.md) and `core/config.js`
|
If you have overridden or made additions to archivers in your `config.hjson` you will need to update them. See [Archive Configuration](docs/archive.md) and `core/config.js`
|
||||||
|
|
||||||
### File Base Configuration
|
## File Base Configuration
|
||||||
As 0.0.4-alpha contains file bases, you'll want to create a suitable configuration if you wish to use the feature. See [File Base Configuration](docs/file_base.md).
|
As 0.0.4-alpha contains file bases, you'll want to create a suitable configuration if you wish to use the feature. See [File Base Configuration](docs/file_base.md).
|
||||||
|
|
16
WHATSNEW.md
16
WHATSNEW.md
|
@ -1,32 +1,18 @@
|
||||||
# Whats New
|
# Whats New
|
||||||
This document attempts to track **major** changes and additions in ENiGMA½. For details, see GitHub.
|
This document attempts to track **major** changes and additions in ENiGMA½. For details, see GitHub.
|
||||||
|
|
||||||
## 0.0.14-beta
|
|
||||||
* The [Web Server](/docs/_docs/servers/contentservers/web-server.md) has made some possibly breaking changes:
|
|
||||||
* `/static/` prefixes are no longer required. This was a ugly hack.
|
|
||||||
* Some internal routes such as those used for password resets live within `/_internal/`.
|
|
||||||
* Routes for the file base now default to `/_f/` prefixed instead of just `/f/`. If `/f/` is in your `config.hjson` you are encouraged to update it!
|
|
||||||
* Finally, the system will search for `index.html` and `index.htm` in that order, if another suitable route cannot be established.
|
|
||||||
* CombatNet has shut down, so the module (`combatnet.js`) has been removed.
|
|
||||||
* The Menu Flag `popParent` has been removed and `noHistory` has been updated to work as expected. In general things should "Just Work", but check your `menu.hjson` entries if you see menu stack issues.
|
|
||||||
* Various New User Application (NUA) properties are now optional. If you would like to reduce the information users are required, remove optional fields from NUA artwork and collect less. These properties will be stored as "" (empty). Optional properties are as follows: Real name, Birth date, Sex, Location, Affiliations (Affils), Email, and Web address.
|
|
||||||
* Art handling has been changed to respect the art width contained in SAUCE when present in the case where the terminal width is greater than the art width. This fixes art files that assume wrapping at 80 columns on wide (mostly new utf8) terminals.
|
|
||||||
|
|
||||||
## 0.0.13-beta
|
## 0.0.13-beta
|
||||||
* **Note for contributors**: ENiGMA has switched to [Prettier](https://prettier.io) for formatting/style. Please see [CONTRIBUTING](CONTRIBUTING.md) and the Prettier website for more information.
|
* **Note for contributors**: ENiGMA has switched to [Prettier](https://prettier.io) for formatting/style. Please see [CONTRIBUTING](CONTRIBUTING.md) and the Prettier website for more information.
|
||||||
* Removed terminal `cursor position reports` from most locations in the code. This should greatly increase the number of terminal programs that work with Enigma 1/2. For more information, see [Issue #222](https://github.com/NuSkooler/enigma-bbs/issues/222). This may also resolve other issues, such as [Issue #365](https://github.com/NuSkooler/enigma-bbs/issues/365), and [Issue #320](https://github.com/NuSkooler/enigma-bbs/issues/320). Anyone that previously had terminal incompatibilities please re-check and let us know!
|
* Removed terminal `cursor position reports` from most locations in the code. This should greatly increase the number of terminal programs that work with Enigma 1/2. For more information, see [Issue #222](https://github.com/NuSkooler/enigma-bbs/issues/222). This may also resolve other issues, such as [Issue #365](https://github.com/NuSkooler/enigma-bbs/issues/365), and [Issue #320](https://github.com/NuSkooler/enigma-bbs/issues/320). Anyone that previously had terminal incompatibilities please re-check and let us know!
|
||||||
* Bumped up the minimum [Node.js](https://nodejs.org/en/) version to v14. This will allow more expressive Javascript programming syntax with ECMAScript 2020 to improve the development experience.
|
* Bumped up the minimum [Node.js](https://nodejs.org/en/) version to v14. This will allow more expressive Javascript programming syntax with ECMAScript 2020 to improve the development experience.
|
||||||
* **New Waiting For Caller (WFC)** support via the `wfc.js` module.
|
* **New Waiting For Caller (WFC)** support via the `wfc.js` module.
|
||||||
* Added new configuration options for `term.checkUtf8Encoding`, `term.checkAnsiHomePosition`, `term.cp437TermList`, and `term.utf8TermList`. More information on these options is available in [UPGRADE](UPGRADE.md).
|
* Added new configuration options for `term.checkUtf8Encoding`, `term.checkAnsiHomePostion`, `term.cp437TermList`, and `term.utf8TermList`. More information on these options is available in [UPGRADE](UPGRADE.md).
|
||||||
* Many new system statistics available via the StatLog such as current and average load, memory, etc.
|
* Many new system statistics available via the StatLog such as current and average load, memory, etc.
|
||||||
* Many new MCI codes: `MB`, `MF`, `LA`, `CL`, `UU`, `FT`, `DD`, `FB`, `DB`, `LC`, `LT`, `LD`, and more. See [MCI](./docs/art/mci.md).
|
* Many new MCI codes: `MB`, `MF`, `LA`, `CL`, `UU`, `FT`, `DD`, `FB`, `DB`, `LC`, `LT`, `LD`, and more. See [MCI](./docs/art/mci.md).
|
||||||
* SyncTERM style font support detection.
|
* SyncTERM style font support detection.
|
||||||
* Added a system method to support setting the client encoding from menus, `@systemMethod:setClientEncoding`.
|
* Added a system method to support setting the client encoding from menus, `@systemMethod:setClientEncoding`.
|
||||||
* Many additional backward-compatible bug fixes since the first release of 0.0.12-beta. See the [project repository](https://github.com/NuSkooler/enigma-bbs) for more information.
|
* Many additional backward-compatible bug fixes since the first release of 0.0.12-beta. See the [project repository](https://github.com/NuSkooler/enigma-bbs) for more information.
|
||||||
* Deprecated Gopher's `messageConferences` configuration key in favor of a easier to deal with `exposedConfAreas` allowing wildcards and exclusions. See [Gopher](./docs/servers/contentservers/gopher.md).
|
* Deprecated Gopher's `messageConferences` configuration key in favor of a easier to deal with `exposedConfAreas` allowing wildcards and exclusions. See [Gopher](./docs/servers/contentservers/gopher.md).
|
||||||
* NNTP write (aka POST) access support for authenticated users over TLS.
|
|
||||||
* [Advanced MCI formatting](./docs/art/mci.md#mci-formatting)!
|
|
||||||
* Additional options in the `abracadabra` module for launching doors. See [Local Doors](./docs/modding/local-doors.md)
|
|
||||||
|
|
||||||
## 0.0.12-beta
|
## 0.0.12-beta
|
||||||
* The `master` branch has become mainline. What this means to users is `git pull` will always give you the latest and greatest. Make sure to read [Updating](./docs/admin/updating.md) and keep an eye on `WHATSNEW.md` (this file) and [UPGRADE](UPGRADE.md)! See also [ticket #276](https://github.com/NuSkooler/enigma-bbs/issues/276).
|
* The `master` branch has become mainline. What this means to users is `git pull` will always give you the latest and greatest. Make sure to read [Updating](./docs/admin/updating.md) and keep an eye on `WHATSNEW.md` (this file) and [UPGRADE](UPGRADE.md)! See also [ticket #276](https://github.com/NuSkooler/enigma-bbs/issues/276).
|
||||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -187,7 +187,7 @@
|
||||||
}
|
}
|
||||||
mci: {
|
mci: {
|
||||||
VM1: {
|
VM1: {
|
||||||
height: 14
|
height: 15,
|
||||||
width: 50
|
width: 50
|
||||||
itemFormat: "|00|11{userName:<17.17}|03{affils:<21.21}|11{location:<19.19}|03{lastLoginTs}"
|
itemFormat: "|00|11{userName:<17.17}|03{affils:<21.21}|11{location:<19.19}|03{lastLoginTs}"
|
||||||
focusItemFormat: "|00|19|15{userName:<17.17}{affils:<21.21}{location:<19.19}{lastLoginTs}"
|
focusItemFormat: "|00|19|15{userName:<17.17}{affils:<21.21}{location:<19.19}{lastLoginTs}"
|
||||||
|
@ -343,7 +343,7 @@
|
||||||
}
|
}
|
||||||
mci: {
|
mci: {
|
||||||
VM1: {
|
VM1: {
|
||||||
height: 13
|
height: 14
|
||||||
width: 70
|
width: 70
|
||||||
itemFormat: "|00|15{msgNum:>4} |03{subject:<28.27} |11{fromUserName:<20.20} |03{ts:<15.16} |15{newIndicator}"
|
itemFormat: "|00|15{msgNum:>4} |03{subject:<28.27} |11{fromUserName:<20.20} |03{ts:<15.16} |15{newIndicator}"
|
||||||
focusItemFormat: "|00|19|15{msgNum:>4} {subject:<28.27} {fromUserName:<20.20} {ts:<15.16} {newIndicator}"
|
focusItemFormat: "|00|19|15{msgNum:>4} {subject:<28.27} {fromUserName:<20.20} {ts:<15.16} {newIndicator}"
|
||||||
|
@ -416,7 +416,7 @@
|
||||||
}
|
}
|
||||||
mci: {
|
mci: {
|
||||||
VM1: {
|
VM1: {
|
||||||
height: 12
|
height: 14
|
||||||
width: 70
|
width: 70
|
||||||
itemFormat: "|00|15{msgNum:>4} |03{subject:<28.27} |11{fromUserName:<20.20} |03{ts} |15{newIndicator}"
|
itemFormat: "|00|15{msgNum:>4} |03{subject:<28.27} |11{fromUserName:<20.20} |03{ts} |15{newIndicator}"
|
||||||
focusItemFormat: "|00|19|15{msgNum:>4} {subject:<28.27} {fromUserName:<20.20} {ts} {newIndicator}"
|
focusItemFormat: "|00|19|15{msgNum:>4} {subject:<28.27} {fromUserName:<20.20} {ts} {newIndicator}"
|
||||||
|
@ -595,7 +595,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
messageBaseSearchResultsMessageList: {
|
messageBaseSearchMessageList: {
|
||||||
config: {
|
config: {
|
||||||
allViewsInfoFormat10: "|00|15{msgNumSelected:>4.4} |08/ |15{msgNumTotal:<4.4}"
|
allViewsInfoFormat10: "|00|15{msgNumSelected:>4.4} |08/ |15{msgNumTotal:<4.4}"
|
||||||
// Fri Sep 25th
|
// Fri Sep 25th
|
||||||
|
@ -618,7 +618,7 @@
|
||||||
}
|
}
|
||||||
mci: {
|
mci: {
|
||||||
VM1: {
|
VM1: {
|
||||||
height: 12
|
height: 16
|
||||||
width: 71
|
width: 71
|
||||||
itemFormat: "|00|15 {msgNum:<4.4} |03{subject:<34.33} {fromUserName:<19.18} |03{ts:<12.12}"
|
itemFormat: "|00|15 {msgNum:<4.4} |03{subject:<34.33} {fromUserName:<19.18} |03{ts:<12.12}"
|
||||||
focusItemFormat: "|00|19> |15{msgNum:<4.4} {subject:<34.33} {fromUserName:<19.18} {ts:<12.12}"
|
focusItemFormat: "|00|19> |15{msgNum:<4.4} {subject:<34.33} {fromUserName:<19.18} {ts:<12.12}"
|
||||||
|
@ -783,7 +783,7 @@
|
||||||
}
|
}
|
||||||
mci: {
|
mci: {
|
||||||
VM1: {
|
VM1: {
|
||||||
height: 12
|
height: 14
|
||||||
width: 70
|
width: 70
|
||||||
itemFormat: "|00|15 {msgNum:<5.5}|03{subject:<28.27} |15{fromUserName:<20.20} {ts}"
|
itemFormat: "|00|15 {msgNum:<5.5}|03{subject:<28.27} |15{fromUserName:<20.20} {ts}"
|
||||||
focusItemFormat: "|00|19> |15{msgNum:<5.5}{subject:<28.27} {fromUserName:<20.20} {ts}"
|
focusItemFormat: "|00|19> |15{msgNum:<5.5}{subject:<28.27} {fromUserName:<20.20} {ts}"
|
||||||
|
@ -1214,7 +1214,7 @@
|
||||||
2: {
|
2: {
|
||||||
mci: {
|
mci: {
|
||||||
MT1: {
|
MT1: {
|
||||||
height: 13
|
height: 14
|
||||||
width: 45
|
width: 45
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1364,4 +1364,4 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -9,7 +9,6 @@ const ansi = require('./ansi_term.js');
|
||||||
const { Errors } = require('./enig_error.js');
|
const { Errors } = require('./enig_error.js');
|
||||||
const { trackDoorRunBegin, trackDoorRunEnd } = require('./door_util.js');
|
const { trackDoorRunBegin, trackDoorRunEnd } = require('./door_util.js');
|
||||||
const Log = require('./logger').log;
|
const Log = require('./logger').log;
|
||||||
const Config = require('./config.js').get;
|
|
||||||
|
|
||||||
// deps
|
// deps
|
||||||
const async = require('async');
|
const async = require('async');
|
||||||
|
@ -182,8 +181,6 @@ exports.getModule = class AbracadabraModule extends MenuModule {
|
||||||
const exeInfo = {
|
const exeInfo = {
|
||||||
name: this.config.name,
|
name: this.config.name,
|
||||||
cmd: this.config.cmd,
|
cmd: this.config.cmd,
|
||||||
preCmd: this.config.preCmd,
|
|
||||||
preCmdArgs: this.config.preCmdArgs,
|
|
||||||
cwd: this.config.cwd || paths.dirname(this.config.cmd),
|
cwd: this.config.cwd || paths.dirname(this.config.cmd),
|
||||||
args: this.config.args,
|
args: this.config.args,
|
||||||
io: this.config.io || 'stdio',
|
io: this.config.io || 'stdio',
|
||||||
|
@ -192,86 +189,55 @@ exports.getModule = class AbracadabraModule extends MenuModule {
|
||||||
env: this.config.env,
|
env: this.config.env,
|
||||||
};
|
};
|
||||||
|
|
||||||
exeInfo.dropFileDir = DropFile.dropFileDirectory(
|
|
||||||
Config().paths.dropFiles,
|
|
||||||
this.client
|
|
||||||
);
|
|
||||||
exeInfo.userAreaDir = paths.join(
|
|
||||||
exeInfo.dropFileDir,
|
|
||||||
this.client.user.getSanitizedName(),
|
|
||||||
this.config.name.toLowerCase()
|
|
||||||
);
|
|
||||||
|
|
||||||
if (this.dropFile) {
|
if (this.dropFile) {
|
||||||
exeInfo.dropFile = this.dropFile.fileName;
|
exeInfo.dropFile = this.dropFile.fileName;
|
||||||
exeInfo.dropFilePath = this.dropFile.fullPath;
|
exeInfo.dropFilePath = this.dropFile.fullPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
this._makeDropDirs([exeInfo.dropFileDir, exeInfo.userAreaDir], err => {
|
const doorTracking = trackDoorRunBegin(this.client, this.config.name);
|
||||||
|
|
||||||
|
this.doorInstance.run(exeInfo, err => {
|
||||||
if (err) {
|
if (err) {
|
||||||
Log.warn(
|
Log.error(`Error running "${this.config.name}": ${err.message}`);
|
||||||
`Failed creating directory ${exeInfo.dropFilePath}: ${err.message}`
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const doorTracking = trackDoorRunBegin(this.client, this.config.name);
|
trackDoorRunEnd(doorTracking);
|
||||||
|
this.decrementActiveDoorNodeInstances();
|
||||||
|
|
||||||
this.doorInstance.run(exeInfo, err => {
|
// Clean up dropfile, if any
|
||||||
if (err) {
|
if (exeInfo.dropFilePath) {
|
||||||
Log.error(`Error running "${this.config.name}": ${err.message}`);
|
fs.unlink(exeInfo.dropFilePath, err => {
|
||||||
}
|
if (err) {
|
||||||
|
Log.warn(
|
||||||
|
{ error: err, path: exeInfo.dropFilePath },
|
||||||
|
'Failed to remove drop file.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
trackDoorRunEnd(doorTracking);
|
// client may have disconnected while process was active -
|
||||||
this.decrementActiveDoorNodeInstances();
|
// we're done here if so.
|
||||||
|
if (!this.client.term.output) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Clean up dropfile, if any
|
//
|
||||||
if (exeInfo.dropFilePath) {
|
// Try to clean up various settings such as scroll regions that may
|
||||||
fs.unlink(exeInfo.dropFilePath, err => {
|
// have been set within the door
|
||||||
if (err) {
|
//
|
||||||
Log.warn(
|
this.client.term.rawWrite(
|
||||||
{ error: err, path: exeInfo.dropFilePath },
|
ansi.normal() +
|
||||||
'Failed to remove drop file.'
|
ansi.goto(this.client.term.termHeight, this.client.term.termWidth) +
|
||||||
);
|
ansi.setScrollRegion() +
|
||||||
}
|
ansi.goto(this.client.term.termHeight, 0) +
|
||||||
});
|
'\r\n\r\n'
|
||||||
}
|
);
|
||||||
|
|
||||||
// client may have disconnected while process was active -
|
this.autoNextMenu();
|
||||||
// we're done here if so.
|
|
||||||
if (!this.client.term.output) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Try to clean up various settings such as scroll regions that may
|
|
||||||
// have been set within the door
|
|
||||||
//
|
|
||||||
this.client.term.rawWrite(
|
|
||||||
ansi.normal() +
|
|
||||||
ansi.goto(
|
|
||||||
this.client.term.termHeight,
|
|
||||||
this.client.term.termWidth
|
|
||||||
) +
|
|
||||||
ansi.setScrollRegion() +
|
|
||||||
ansi.goto(this.client.term.termHeight, 0) +
|
|
||||||
'\r\n\r\n'
|
|
||||||
);
|
|
||||||
|
|
||||||
this.autoNextMenu();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
_makeDropDirs(dirs, cb) {
|
|
||||||
async.forEach(
|
|
||||||
dirs,
|
|
||||||
(dir, nextDir) => {
|
|
||||||
fs.mkdir(dir, { recursive: true }, nextDir);
|
|
||||||
},
|
|
||||||
cb
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
leave() {
|
leave() {
|
||||||
super.leave();
|
super.leave();
|
||||||
this.decrementActiveDoorNodeInstances();
|
this.decrementActiveDoorNodeInstances();
|
||||||
|
|
|
@ -505,9 +505,9 @@ class Achievements {
|
||||||
getFormatObject(info) {
|
getFormatObject(info) {
|
||||||
return {
|
return {
|
||||||
userName: info.user.username,
|
userName: info.user.username,
|
||||||
userRealName: info.user.realName(false) || 'N/A',
|
userRealName: info.user.properties[UserProps.RealName],
|
||||||
userLocation: info.user.properties[UserProps.Location] || 'N/A',
|
userLocation: info.user.properties[UserProps.Location],
|
||||||
userAffils: info.user.properties[UserProps.Affiliations] || 'N/A',
|
userAffils: info.user.properties[UserProps.Affiliations],
|
||||||
nodeId: info.client.node,
|
nodeId: info.client.node,
|
||||||
title: info.details.title,
|
title: info.details.title,
|
||||||
//text : info.global ? info.details.globalText : info.details.text,
|
//text : info.global ? info.details.globalText : info.details.text,
|
||||||
|
|
|
@ -24,7 +24,7 @@ function ANSIEscapeParser(options) {
|
||||||
this.graphicRendition = {};
|
this.graphicRendition = {};
|
||||||
|
|
||||||
this.parseState = {
|
this.parseState = {
|
||||||
re: /(?:\x1b)(?:(?:\x5b([?=;0-9]*?)([ABCDEFGfHJKLmMsSTuUYZt@PXhlnpt]))|([78DEHM]))/g, // eslint-disable-line no-control-regex
|
re: /(?:\x1b\x5b)([?=;0-9]*?)([ABCDHJKfhlmnpsu])/g, // eslint-disable-line no-control-regex
|
||||||
};
|
};
|
||||||
|
|
||||||
options = miscUtil.valueWithDefault(options, {
|
options = miscUtil.valueWithDefault(options, {
|
||||||
|
@ -37,12 +37,6 @@ function ANSIEscapeParser(options) {
|
||||||
this.mciReplaceChar = miscUtil.valueWithDefault(options.mciReplaceChar, '');
|
this.mciReplaceChar = miscUtil.valueWithDefault(options.mciReplaceChar, '');
|
||||||
this.termHeight = miscUtil.valueWithDefault(options.termHeight, 25);
|
this.termHeight = miscUtil.valueWithDefault(options.termHeight, 25);
|
||||||
this.termWidth = miscUtil.valueWithDefault(options.termWidth, 80);
|
this.termWidth = miscUtil.valueWithDefault(options.termWidth, 80);
|
||||||
this.breakWidth = this.termWidth;
|
|
||||||
// toNumber takes care of null, undefined etc as well.
|
|
||||||
let artWidth = _.toNumber(options.artWidth);
|
|
||||||
if(!(_.isNaN(artWidth)) && artWidth > 0 && artWidth < this.breakWidth) {
|
|
||||||
this.breakWidth = options.artWidth;
|
|
||||||
}
|
|
||||||
this.trailingLF = miscUtil.valueWithDefault(options.trailingLF, 'default');
|
this.trailingLF = miscUtil.valueWithDefault(options.trailingLF, 'default');
|
||||||
|
|
||||||
this.row = Math.min(options?.startRow ?? 1, this.termHeight);
|
this.row = Math.min(options?.startRow ?? 1, this.termHeight);
|
||||||
|
@ -77,25 +71,10 @@ function ANSIEscapeParser(options) {
|
||||||
self.clearScreen = function () {
|
self.clearScreen = function () {
|
||||||
self.column = 1;
|
self.column = 1;
|
||||||
self.row = 1;
|
self.row = 1;
|
||||||
self.positionUpdated();
|
|
||||||
self.emit('clear screen');
|
self.emit('clear screen');
|
||||||
};
|
};
|
||||||
|
|
||||||
self.positionUpdated = function () {
|
self.positionUpdated = function () {
|
||||||
if(self.row > self.termHeight) {
|
|
||||||
if(this.savedPosition) {
|
|
||||||
this.savedPosition.row -= self.row - self.termHeight;
|
|
||||||
}
|
|
||||||
self.emit('scroll', self.row - self.termHeight);
|
|
||||||
self.row = self.termHeight;
|
|
||||||
}
|
|
||||||
else if(self.row < 1) {
|
|
||||||
if(this.savedPosition) {
|
|
||||||
this.savedPosition.row -= self.row - 1;
|
|
||||||
}
|
|
||||||
self.emit('scroll', -(self.row - 1));
|
|
||||||
self.row = 1;
|
|
||||||
}
|
|
||||||
self.emit('position update', self.row, self.column);
|
self.emit('position update', self.row, self.column);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -111,8 +90,8 @@ function ANSIEscapeParser(options) {
|
||||||
|
|
||||||
switch (charCode) {
|
switch (charCode) {
|
||||||
case CR:
|
case CR:
|
||||||
self.emit('literal', text.slice(start, pos + 1));
|
self.emit('literal', text.slice(start, pos));
|
||||||
start = pos + 1;
|
start = pos;
|
||||||
|
|
||||||
self.column = 1;
|
self.column = 1;
|
||||||
|
|
||||||
|
@ -126,8 +105,8 @@ function ANSIEscapeParser(options) {
|
||||||
self.column = 1;
|
self.column = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.emit('literal', text.slice(start, pos + 1));
|
self.emit('literal', text.slice(start, pos));
|
||||||
start = pos + 1;
|
start = pos;
|
||||||
|
|
||||||
self.row += 1;
|
self.row += 1;
|
||||||
|
|
||||||
|
@ -135,16 +114,13 @@ function ANSIEscapeParser(options) {
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
if (self.column === self.breakWidth) {
|
if (self.column === self.termWidth) {
|
||||||
self.emit('literal', text.slice(start, pos + 1));
|
self.emit('literal', text.slice(start, pos + 1));
|
||||||
start = pos + 1;
|
start = pos + 1;
|
||||||
|
|
||||||
// If we hit breakWidth before termWidth then we need to force the terminal to go to the next line.
|
|
||||||
if(self.column < self.termWidth) {
|
|
||||||
self.emit('literal', '\r\n');
|
|
||||||
}
|
|
||||||
self.column = 1;
|
self.column = 1;
|
||||||
self.row += 1;
|
self.row += 1;
|
||||||
|
|
||||||
self.positionUpdated();
|
self.positionUpdated();
|
||||||
} else {
|
} else {
|
||||||
self.column += 1;
|
self.column += 1;
|
||||||
|
@ -159,7 +135,7 @@ function ANSIEscapeParser(options) {
|
||||||
//
|
//
|
||||||
// Finalize this chunk
|
// Finalize this chunk
|
||||||
//
|
//
|
||||||
if (self.column > self.breakWidth) {
|
if (self.column > self.termWidth) {
|
||||||
self.column = 1;
|
self.column = 1;
|
||||||
self.row += 1;
|
self.row += 1;
|
||||||
|
|
||||||
|
@ -246,7 +222,7 @@ function ANSIEscapeParser(options) {
|
||||||
self.parseState = {
|
self.parseState = {
|
||||||
// ignore anything past EOF marker, if any
|
// ignore anything past EOF marker, if any
|
||||||
buffer: input.split(String.fromCharCode(0x1a), 1)[0],
|
buffer: input.split(String.fromCharCode(0x1a), 1)[0],
|
||||||
re: /(?:\x1b)(?:(?:\x5b([?=;0-9]*?)([ABCDEFGfHJKLmMsSTuUYZt@PXhlnpt]))|([78DEHM]))/g, // eslint-disable-line no-control-regex
|
re: /(?:\x1b\x5b)([?=;0-9]*?)([ABCDHJKfhlmnpsu])/g, // eslint-disable-line no-control-regex
|
||||||
stop: false,
|
stop: false,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -286,47 +262,9 @@ function ANSIEscapeParser(options) {
|
||||||
opCode = match[2];
|
opCode = match[2];
|
||||||
args = match[1].split(';').map(v => parseInt(v, 10)); // convert to array of ints
|
args = match[1].split(';').map(v => parseInt(v, 10)); // convert to array of ints
|
||||||
|
|
||||||
// Handle the case where there is no bracket
|
escape(opCode, args);
|
||||||
if(!(_.isNil(match[3]))) {
|
|
||||||
opCode = match[3];
|
|
||||||
args = [];
|
|
||||||
// no bracket
|
|
||||||
switch(opCode) {
|
|
||||||
// save cursor position
|
|
||||||
case '7':
|
|
||||||
escape('s', args);
|
|
||||||
break;
|
|
||||||
// restore cursor position
|
|
||||||
case '8':
|
|
||||||
escape('u', args);
|
|
||||||
break;
|
|
||||||
|
|
||||||
// scroll up
|
|
||||||
case 'D':
|
|
||||||
escape('S', args);
|
|
||||||
break;
|
|
||||||
|
|
||||||
// move to next line
|
|
||||||
case 'E':
|
|
||||||
// functonality is the same as ESC [ E
|
|
||||||
escape(opCode, args);
|
|
||||||
break;
|
|
||||||
|
|
||||||
// create a tab at current cursor position
|
|
||||||
case 'H':
|
|
||||||
literal('\t');
|
|
||||||
break;
|
|
||||||
|
|
||||||
// scroll down
|
|
||||||
case 'M':
|
|
||||||
escape('T', args);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
escape(opCode, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
//self.emit('chunk', match[0]);
|
||||||
self.emit('control', match[0], opCode, args);
|
self.emit('control', match[0], opCode, args);
|
||||||
}
|
}
|
||||||
} while (0 !== re.lastIndex);
|
} while (0 !== re.lastIndex);
|
||||||
|
@ -334,8 +272,8 @@ function ANSIEscapeParser(options) {
|
||||||
if (pos < buffer.length) {
|
if (pos < buffer.length) {
|
||||||
var lastBit = buffer.slice(pos);
|
var lastBit = buffer.slice(pos);
|
||||||
|
|
||||||
// handles either \r\n or \n
|
// :TODO: check for various ending LF's, not just DOS \r\n
|
||||||
if ('\n' === lastBit.slice(-1).toString()) {
|
if ('\r\n' === lastBit.slice(-2).toString()) {
|
||||||
switch (self.trailingLF) {
|
switch (self.trailingLF) {
|
||||||
case 'default':
|
case 'default':
|
||||||
//
|
//
|
||||||
|
@ -343,14 +281,14 @@ function ANSIEscapeParser(options) {
|
||||||
// if we're going to end on termHeight
|
// if we're going to end on termHeight
|
||||||
//
|
//
|
||||||
if (this.termHeight === self.row) {
|
if (this.termHeight === self.row) {
|
||||||
lastBit = lastBit.slice(0, -1);
|
lastBit = lastBit.slice(0, -2);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'omit':
|
case 'omit':
|
||||||
case 'no':
|
case 'no':
|
||||||
case false:
|
case false:
|
||||||
lastBit = lastBit.slice(0, -1);
|
lastBit = lastBit.slice(0, -2);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -361,6 +299,48 @@ function ANSIEscapeParser(options) {
|
||||||
self.emit('complete');
|
self.emit('complete');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
self.parse = function(buffer, savedRe) {
|
||||||
|
// :TODO: ensure this conforms to ANSI-BBS / CTerm / bansi.txt for movement/etc.
|
||||||
|
// :TODO: move this to "constants" section @ top
|
||||||
|
var re = /(?:\x1b\x5b)([\?=;0-9]*?)([ABCDHJKfhlmnpsu])/g;
|
||||||
|
var pos = 0;
|
||||||
|
var match;
|
||||||
|
var opCode;
|
||||||
|
var args;
|
||||||
|
|
||||||
|
// ignore anything past EOF marker, if any
|
||||||
|
buffer = buffer.split(String.fromCharCode(0x1a), 1)[0];
|
||||||
|
|
||||||
|
do {
|
||||||
|
pos = re.lastIndex;
|
||||||
|
match = re.exec(buffer);
|
||||||
|
|
||||||
|
if(null !== match) {
|
||||||
|
if(match.index > pos) {
|
||||||
|
parseMCI(buffer.slice(pos, match.index));
|
||||||
|
}
|
||||||
|
|
||||||
|
opCode = match[2];
|
||||||
|
args = getArgArray(match[1].split(';'));
|
||||||
|
|
||||||
|
escape(opCode, args);
|
||||||
|
|
||||||
|
self.emit('chunk', match[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
} while(0 !== re.lastIndex);
|
||||||
|
|
||||||
|
if(pos < buffer.length) {
|
||||||
|
parseMCI(buffer.slice(pos));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.emit('complete');
|
||||||
|
};
|
||||||
|
*/
|
||||||
|
|
||||||
function escape(opCode, args) {
|
function escape(opCode, args) {
|
||||||
let arg;
|
let arg;
|
||||||
|
|
||||||
|
@ -393,37 +373,6 @@ function ANSIEscapeParser(options) {
|
||||||
self.moveCursor(-arg, 0);
|
self.moveCursor(-arg, 0);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// line feed
|
|
||||||
case 'E':
|
|
||||||
arg = isNaN(args[0]) ? 1 : args[0];
|
|
||||||
if(this.row + arg > this.termHeight) {
|
|
||||||
this.emit('scroll', arg - (this.termHeight - this.row));
|
|
||||||
self.moveCursor(0, this.termHeight);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
self.moveCursor(0, arg);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
// reverse line feed
|
|
||||||
case 'F':
|
|
||||||
arg = isNaN(args[0]) ? 1 : args[0];
|
|
||||||
if(this.row - arg < 1) {
|
|
||||||
this.emit('scroll', -(arg - this.row));
|
|
||||||
self.moveCursor(0, 1 - this.row);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
self.moveCursor(0, -arg);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
// absolute horizontal cursor position
|
|
||||||
case 'G':
|
|
||||||
arg = isNaN(args[0]) ? 1 : args[0];
|
|
||||||
self.column = Math.max(1, arg);
|
|
||||||
self.positionUpdated();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'f': // horiz & vertical
|
case 'f': // horiz & vertical
|
||||||
case 'H': // cursor position
|
case 'H': // cursor position
|
||||||
//self.row = args[0] || 1;
|
//self.row = args[0] || 1;
|
||||||
|
@ -434,37 +383,14 @@ function ANSIEscapeParser(options) {
|
||||||
self.positionUpdated();
|
self.positionUpdated();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
// save position
|
||||||
// erase display/screen
|
case 's':
|
||||||
case 'J':
|
self.saveCursorPosition();
|
||||||
if(isNaN(args[0]) || 0 === args[0]) {
|
|
||||||
self.emit('erase rows', self.row, self.termHeight);
|
|
||||||
}
|
|
||||||
else if (1 === args[0]) {
|
|
||||||
self.emit('erase rows', 1, self.row);
|
|
||||||
}
|
|
||||||
else if (2 === args[0]) {
|
|
||||||
self.clearScreen();
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// erase text in line
|
// restore position
|
||||||
case 'K':
|
case 'u':
|
||||||
if(isNaN(args[0]) || 0 === args[0]) {
|
self.restoreCursorPosition();
|
||||||
self.emit('erase columns', self.row, self.column, self.termWidth);
|
|
||||||
}
|
|
||||||
else if (1 === args[0]) {
|
|
||||||
self.emit('erase columns', self.row, 1, self.column);
|
|
||||||
}
|
|
||||||
else if (2 === args[0]) {
|
|
||||||
self.emit('erase columns', self.row, 1, self.termWidth);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
// insert line
|
|
||||||
case 'L':
|
|
||||||
arg = isNaN(args[0]) ? 1 : args[0];
|
|
||||||
self.emit('insert line', self.row, arg);
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// set graphic rendition
|
// set graphic rendition
|
||||||
|
@ -536,52 +462,15 @@ function ANSIEscapeParser(options) {
|
||||||
self.emit('sgr update', self.graphicRendition);
|
self.emit('sgr update', self.graphicRendition);
|
||||||
break; // m
|
break; // m
|
||||||
|
|
||||||
// save position
|
// :TODO: s, u, K
|
||||||
case 's':
|
|
||||||
self.saveCursorPosition();
|
|
||||||
break;
|
|
||||||
|
|
||||||
// Scroll up
|
// erase display/screen
|
||||||
case 'S':
|
case 'J':
|
||||||
arg = isNaN(args[0]) ? 1 : args[0];
|
// :TODO: Handle other 'J' types!
|
||||||
self.emit('scroll', arg);
|
if (2 === args[0]) {
|
||||||
|
self.clearScreen();
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// Scroll down
|
|
||||||
case 'T':
|
|
||||||
arg = isNaN(args[0]) ? 1 : args[0];
|
|
||||||
self.emit('scroll', -arg);
|
|
||||||
break;
|
|
||||||
|
|
||||||
// restore position
|
|
||||||
case 'u':
|
|
||||||
self.restoreCursorPosition();
|
|
||||||
break;
|
|
||||||
|
|
||||||
// clear
|
|
||||||
case 'U':
|
|
||||||
self.clearScreen();
|
|
||||||
break;
|
|
||||||
|
|
||||||
// delete line
|
|
||||||
// TODO: how should we handle 'M'?
|
|
||||||
case 'Y':
|
|
||||||
arg = isNaN(args[0]) ? 1 : args[0];
|
|
||||||
self.emit('delete line', self.row, arg);
|
|
||||||
break;
|
|
||||||
|
|
||||||
// back tab
|
|
||||||
case 'Z':
|
|
||||||
// calculate previous tabstop
|
|
||||||
self.column = Math.max( 1, self.column - (self.column % 8 || 8) );
|
|
||||||
self.positionUpdated();
|
|
||||||
break;
|
|
||||||
case '@':
|
|
||||||
// insert column(s)
|
|
||||||
arg = isNaN(args[0]) ? 1 : args[0];
|
|
||||||
self.emit('insert columns', self.row, self.column, arg);
|
|
||||||
break;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -208,17 +208,17 @@ module.exports = class ArchiveUtil {
|
||||||
// pty.js doesn't currently give us a error when things fail,
|
// pty.js doesn't currently give us a error when things fail,
|
||||||
// so we have this horrible, horrible hack:
|
// so we have this horrible, horrible hack:
|
||||||
let err;
|
let err;
|
||||||
proc.onData(d => {
|
proc.once('data', d => {
|
||||||
if (_.isString(d) && d.startsWith('execvp(3) failed.')) {
|
if (_.isString(d) && d.startsWith('execvp(3) failed.')) {
|
||||||
err = Errors.ExternalProcess(`${action} failed: ${d.trim()}`);
|
err = Errors.ExternalProcess(`${action} failed: ${d.trim()}`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
proc.onExit(exitEvent => {
|
proc.once('exit', exitCode => {
|
||||||
return cb(
|
return cb(
|
||||||
exitEvent.exitCode
|
exitCode
|
||||||
? Errors.ExternalProcess(
|
? Errors.ExternalProcess(
|
||||||
`${action} failed with exit code: ${exitEvent.exitCode}`
|
`${action} failed with exit code: ${exitCode}`
|
||||||
)
|
)
|
||||||
: err
|
: err
|
||||||
);
|
);
|
||||||
|
@ -358,10 +358,10 @@ module.exports = class ArchiveUtil {
|
||||||
output += data;
|
output += data;
|
||||||
});
|
});
|
||||||
|
|
||||||
proc.onExit(exitEvent => {
|
proc.once('exit', exitCode => {
|
||||||
if (exitEvent.exitCode) {
|
if (exitCode) {
|
||||||
return cb(
|
return cb(
|
||||||
Errors.ExternalProcess(`List failed with exit code: ${exitEvent.exitCode}`)
|
Errors.ExternalProcess(`List failed with exit code: ${exitCode}`)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
82
core/art.js
82
core/art.js
|
@ -41,21 +41,11 @@ const SUPPORTED_ART_TYPES = {
|
||||||
};
|
};
|
||||||
|
|
||||||
function getFontNameFromSAUCE(sauce) {
|
function getFontNameFromSAUCE(sauce) {
|
||||||
if (sauce && sauce.Character) {
|
if (sauce.Character) {
|
||||||
return sauce.Character.fontName;
|
return sauce.Character.fontName;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getWidthFromSAUCE(sauce) {
|
|
||||||
if (sauce && sauce.Character) {
|
|
||||||
let sauceWidth = _.toNumber(sauce.Character.characterWidth);
|
|
||||||
if(!(_.isNaN(sauceWidth)) && sauceWidth > 0) {
|
|
||||||
return sauceWidth;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function sliceAtEOF(data, eofMarker) {
|
function sliceAtEOF(data, eofMarker) {
|
||||||
let eof = data.length;
|
let eof = data.length;
|
||||||
const stopPos = Math.max(data.length - 256, 0); // 256 = 2 * sizeof(SAUCE)
|
const stopPos = Math.max(data.length - 256, 0); // 256 = 2 * sizeof(SAUCE)
|
||||||
|
@ -284,7 +274,6 @@ function display(client, art, options, cb) {
|
||||||
mciReplaceChar: options.mciReplaceChar,
|
mciReplaceChar: options.mciReplaceChar,
|
||||||
termHeight: client.term.termHeight,
|
termHeight: client.term.termHeight,
|
||||||
termWidth: client.term.termWidth,
|
termWidth: client.term.termWidth,
|
||||||
artWidth: getWidthFromSAUCE(options.sauce),
|
|
||||||
trailingLF: options.trailingLF,
|
trailingLF: options.trailingLF,
|
||||||
startRow: options.startRow,
|
startRow: options.startRow,
|
||||||
});
|
});
|
||||||
|
@ -316,75 +305,6 @@ function display(client, art, options, cb) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Remove any MCI's that are in erased rows
|
|
||||||
ansiParser.on('erase row', (startRow, endRow) => {
|
|
||||||
_.forEach(mciMap, (mciInfo, mapKey) => {
|
|
||||||
if (mciInfo.position[0] >= startRow && mciInfo.position[0] <= endRow) {
|
|
||||||
delete mciMap[mapKey];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Remove any MCI's that are in erased columns
|
|
||||||
ansiParser.on('erase columns', (row, startCol, endCol) => {
|
|
||||||
_.forEach(mciMap, (mciInfo, mapKey) => {
|
|
||||||
if (
|
|
||||||
mciInfo.position[0] === row &&
|
|
||||||
mciInfo.position[1] >= startCol &&
|
|
||||||
mciInfo.position[1] <= endCol
|
|
||||||
) {
|
|
||||||
delete mciMap[mapKey];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
ansiParser.on('insert columns', (row, startCol, numCols) => {
|
|
||||||
_.forEach(mciMap, (mciInfo, mapKey) => {
|
|
||||||
if (mciInfo.position[0] === row && mciInfo.position[1] >= startCol) {
|
|
||||||
mciInfo.position[1] += numCols;
|
|
||||||
if(mciInfo.position[1] > client.term.termWidth) {
|
|
||||||
delete mciMap[mapKey];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Clear the screen, removing any MCI's
|
|
||||||
ansiParser.on('clear screen', () => {
|
|
||||||
_.forEach(mciMap, (mciInfo, mapKey) => {
|
|
||||||
delete mciMap[mapKey];
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
ansiParser.on('scroll', (scrollY) => {
|
|
||||||
_.forEach(mciMap, (mciInfo) => {
|
|
||||||
mciInfo.position[0] -= scrollY;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
ansiParser.on('insert line', (row, numLines) => {
|
|
||||||
_.forEach(mciMap, (mciInfo) => {
|
|
||||||
if (mciInfo.position[0] >= row) {
|
|
||||||
mciInfo.position[0] += numLines;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
ansiParser.on('delete line', (row, numLines) => {
|
|
||||||
_.forEach(mciMap, (mciInfo, mapKey) => {
|
|
||||||
if (mciInfo.position[0] >= row) {
|
|
||||||
if(mciInfo.position[0] < row + numLines) {
|
|
||||||
// unlike scrolling, the rows are actually gone,
|
|
||||||
// so we need to delete any MCI's that are in them
|
|
||||||
delete mciMap[mapKey];
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
mciInfo.position[0] -= numLines;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
ansiParser.on('literal', literal => client.term.write(literal, false));
|
ansiParser.on('literal', literal => client.term.write(literal, false));
|
||||||
ansiParser.on('control', control => client.term.rawWrite(control));
|
ansiParser.on('control', control => client.term.rawWrite(control));
|
||||||
|
|
||||||
|
|
|
@ -87,7 +87,7 @@ function getActiveConnectionList(
|
||||||
//
|
//
|
||||||
entry.text = ac.user?.username || 'N/A';
|
entry.text = ac.user?.username || 'N/A';
|
||||||
entry.userName = ac.user?.username || 'N/A';
|
entry.userName = ac.user?.username || 'N/A';
|
||||||
entry.realName = ac.user?.realName(false) || 'N/A';
|
entry.realName = ac.user?.getProperty(UserProps.RealName) || 'N/A';
|
||||||
entry.location = ac.user?.getProperty(UserProps.Location) || 'N/A';
|
entry.location = ac.user?.getProperty(UserProps.Location) || 'N/A';
|
||||||
entry.affils = entry.affiliation =
|
entry.affils = entry.affiliation =
|
||||||
ac.user?.getProperty(UserProps.Affiliations) || 'N/A';
|
ac.user?.getProperty(UserProps.Affiliations) || 'N/A';
|
||||||
|
|
|
@ -0,0 +1,136 @@
|
||||||
|
/* jslint node: true */
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
// enigma-bbs
|
||||||
|
const { MenuModule } = require('../core/menu_module.js');
|
||||||
|
const { resetScreen } = require('../core/ansi_term.js');
|
||||||
|
const { Errors } = require('./enig_error.js');
|
||||||
|
const { trackDoorRunBegin, trackDoorRunEnd } = require('./door_util.js');
|
||||||
|
|
||||||
|
// deps
|
||||||
|
const async = require('async');
|
||||||
|
const RLogin = require('rlogin');
|
||||||
|
|
||||||
|
exports.moduleInfo = {
|
||||||
|
name: 'CombatNet',
|
||||||
|
desc: 'CombatNet Access Module',
|
||||||
|
author: 'Dave Stephens',
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.getModule = class CombatNetModule extends MenuModule {
|
||||||
|
constructor(options) {
|
||||||
|
super(options);
|
||||||
|
|
||||||
|
// establish defaults
|
||||||
|
this.config = options.menuConfig.config;
|
||||||
|
this.config.host = this.config.host || 'bbs.combatnet.us';
|
||||||
|
this.config.rloginPort = this.config.rloginPort || 4513;
|
||||||
|
}
|
||||||
|
|
||||||
|
initSequence() {
|
||||||
|
const self = this;
|
||||||
|
|
||||||
|
async.series(
|
||||||
|
[
|
||||||
|
function validateConfig(callback) {
|
||||||
|
return self.validateConfigFields(
|
||||||
|
{
|
||||||
|
host: 'string',
|
||||||
|
password: 'string',
|
||||||
|
bbsTag: 'string',
|
||||||
|
rloginPort: 'number',
|
||||||
|
},
|
||||||
|
callback
|
||||||
|
);
|
||||||
|
},
|
||||||
|
function establishRloginConnection(callback) {
|
||||||
|
self.client.term.write(resetScreen());
|
||||||
|
self.client.term.write('Connecting to CombatNet, please wait...\n');
|
||||||
|
|
||||||
|
let doorTracking;
|
||||||
|
|
||||||
|
const restorePipeToNormal = function () {
|
||||||
|
if (self.client.term.output) {
|
||||||
|
self.client.term.output.removeListener(
|
||||||
|
'data',
|
||||||
|
sendToRloginBuffer
|
||||||
|
);
|
||||||
|
|
||||||
|
if (doorTracking) {
|
||||||
|
trackDoorRunEnd(doorTracking);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const rlogin = new RLogin({
|
||||||
|
clientUsername: self.config.password,
|
||||||
|
serverUsername: `${self.config.bbsTag}${self.client.user.username}`,
|
||||||
|
host: self.config.host,
|
||||||
|
port: self.config.rloginPort,
|
||||||
|
terminalType: self.client.term.termClient,
|
||||||
|
terminalSpeed: 57600,
|
||||||
|
});
|
||||||
|
|
||||||
|
// If there was an error ...
|
||||||
|
rlogin.on('error', err => {
|
||||||
|
self.client.log.info(
|
||||||
|
`CombatNet rlogin client error: ${err.message}`
|
||||||
|
);
|
||||||
|
restorePipeToNormal();
|
||||||
|
return callback(err);
|
||||||
|
});
|
||||||
|
|
||||||
|
// If we've been disconnected ...
|
||||||
|
rlogin.on('disconnect', () => {
|
||||||
|
self.client.log.info('Disconnected from CombatNet');
|
||||||
|
restorePipeToNormal();
|
||||||
|
return callback(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
function sendToRloginBuffer(buffer) {
|
||||||
|
rlogin.send(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
rlogin.on(
|
||||||
|
'connect',
|
||||||
|
/* The 'connect' event handler will be supplied with one argument,
|
||||||
|
a boolean indicating whether or not the connection was established. */
|
||||||
|
|
||||||
|
function (state) {
|
||||||
|
if (state) {
|
||||||
|
self.client.log.info('Connected to CombatNet');
|
||||||
|
self.client.term.output.on('data', sendToRloginBuffer);
|
||||||
|
|
||||||
|
doorTracking = trackDoorRunBegin(self.client);
|
||||||
|
} else {
|
||||||
|
return callback(
|
||||||
|
Errors.General(
|
||||||
|
'Failed to establish establish CombatNet connection'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// If data (a Buffer) has been received from the server ...
|
||||||
|
rlogin.on('data', data => {
|
||||||
|
self.client.term.rawWrite(data);
|
||||||
|
});
|
||||||
|
|
||||||
|
// connect...
|
||||||
|
rlogin.connect();
|
||||||
|
|
||||||
|
// note: no explicit callback() until we're finished!
|
||||||
|
},
|
||||||
|
],
|
||||||
|
err => {
|
||||||
|
if (err) {
|
||||||
|
self.client.log.warn({ error: err.message }, 'CombatNet error');
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the client is still here, go to previous
|
||||||
|
self.prevMenu();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
|
@ -188,15 +188,22 @@ module.exports = () => {
|
||||||
//
|
//
|
||||||
// 1 - Generate a Private Key (PK):
|
// 1 - Generate a Private Key (PK):
|
||||||
// Currently ENiGMA 1/2 requires a PKCS#1 PEM formatted PK.
|
// Currently ENiGMA 1/2 requires a PKCS#1 PEM formatted PK.
|
||||||
// For information on generating a key, see:
|
// To generate a secure PK, issue the following command:
|
||||||
// https://nuskooler.github.io/enigma-bbs/servers/loginservers/ssh.html#generate-a-ssh-private-key
|
//
|
||||||
|
// > openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 \
|
||||||
|
// -pkeyopt rsa_keygen_pubexp:65537 | openssl rsa \
|
||||||
|
// -out ./config/security/ssh_private_key.pem -aes128
|
||||||
|
//
|
||||||
|
// (The above is a more modern equivalent of the following):
|
||||||
|
// > openssl genrsa -aes128 -out ./config/security/ssh_private_key.pem 2048
|
||||||
//
|
//
|
||||||
// 2 - Set 'privateKeyPass' to the password you used in step #1
|
// 2 - Set 'privateKeyPass' to the password you used in step #1
|
||||||
//
|
//
|
||||||
// 3 - Finally, set 'enabled' to 'true'
|
// 3 - Finally, set 'enabled' to 'true'
|
||||||
//
|
//
|
||||||
// Additional reading:
|
// Additional reading:
|
||||||
// - https://nuskooler.github.io/enigma-bbs/servers/loginservers/ssh.html
|
// - https://blog.sleeplessbeastie.eu/2017/12/28/how-to-generate-private-key/
|
||||||
|
// - https://gist.github.com/briansmith/2ee42439923d8e65a266994d0f70180b
|
||||||
//
|
//
|
||||||
privateKeyPem: paths.join(
|
privateKeyPem: paths.join(
|
||||||
__dirname,
|
__dirname,
|
||||||
|
@ -215,18 +222,14 @@ module.exports = () => {
|
||||||
//
|
//
|
||||||
algorithms: {
|
algorithms: {
|
||||||
kex: [
|
kex: [
|
||||||
'curve25519-sha256',
|
|
||||||
'curve25519-sha256@libssh.org',
|
|
||||||
'ecdh-sha2-nistp256',
|
'ecdh-sha2-nistp256',
|
||||||
'ecdh-sha2-nistp384',
|
'ecdh-sha2-nistp384',
|
||||||
'ecdh-sha2-nistp521',
|
'ecdh-sha2-nistp521',
|
||||||
'diffie-hellman-group14-sha1',
|
'diffie-hellman-group14-sha1',
|
||||||
'diffie-hellman-group1-sha1',
|
'diffie-hellman-group1-sha1',
|
||||||
'curve25519-sha256',
|
// Group exchange not currnetly supported
|
||||||
'curve25519-sha256@libssh.org',
|
// 'diffie-hellman-group-exchange-sha256',
|
||||||
'ecdh-sha2-nistp256',
|
// 'diffie-hellman-group-exchange-sha1',
|
||||||
'ecdh-sha2-nistp384',
|
|
||||||
'ecdh-sha2-nistp521',
|
|
||||||
],
|
],
|
||||||
cipher: [
|
cipher: [
|
||||||
'aes128-ctr',
|
'aes128-ctr',
|
||||||
|
@ -239,7 +242,12 @@ module.exports = () => {
|
||||||
'aes256-cbc',
|
'aes256-cbc',
|
||||||
'aes192-cbc',
|
'aes192-cbc',
|
||||||
'aes128-cbc',
|
'aes128-cbc',
|
||||||
|
'blowfish-cbc',
|
||||||
'3des-cbc',
|
'3des-cbc',
|
||||||
|
'arcfour256',
|
||||||
|
'arcfour128',
|
||||||
|
'cast128-cbc',
|
||||||
|
'arcfour',
|
||||||
],
|
],
|
||||||
hmac: [
|
hmac: [
|
||||||
'hmac-sha2-256',
|
'hmac-sha2-256',
|
||||||
|
@ -937,10 +945,8 @@ module.exports = () => {
|
||||||
],
|
],
|
||||||
|
|
||||||
web: {
|
web: {
|
||||||
// if you change the /_f/ prefix here, ensure something
|
path: '/f/',
|
||||||
// non-colliding with other routes is utilized
|
routePath: '/f/[a-zA-Z0-9]+$',
|
||||||
path: '/_f/',
|
|
||||||
routePath: '^/_f/[a-zA-Z0-9]+$',
|
|
||||||
expireMinutes: 1440, // 1 day
|
expireMinutes: 1440, // 1 day
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
205
core/door.js
205
core/door.js
|
@ -11,7 +11,6 @@ const decode = require('iconv-lite').decode;
|
||||||
const createServer = require('net').createServer;
|
const createServer = require('net').createServer;
|
||||||
const paths = require('path');
|
const paths = require('path');
|
||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
const async = require('async');
|
|
||||||
|
|
||||||
module.exports = class Door {
|
module.exports = class Door {
|
||||||
constructor(client) {
|
constructor(client) {
|
||||||
|
@ -57,10 +56,8 @@ module.exports = class Door {
|
||||||
run(exeInfo, cb) {
|
run(exeInfo, cb) {
|
||||||
this.encoding = (exeInfo.encoding || 'cp437').toLowerCase();
|
this.encoding = (exeInfo.encoding || 'cp437').toLowerCase();
|
||||||
|
|
||||||
if ('socket' === this.io) {
|
if ('socket' === this.io && !this.sockServer) {
|
||||||
if(!this.sockServer) {
|
return cb(Errors.UnexpectedState('Socket server is not running'));
|
||||||
return cb(Errors.UnexpectedState('Socket server is not running'));
|
|
||||||
}
|
|
||||||
} else if ('stdio' !== this.io) {
|
} else if ('stdio' !== this.io) {
|
||||||
return cb(Errors.Invalid(`"${this.io}" is not a valid io type!`));
|
return cb(Errors.Invalid(`"${this.io}" is not a valid io type!`));
|
||||||
}
|
}
|
||||||
|
@ -70,141 +67,91 @@ module.exports = class Door {
|
||||||
const formatObj = {
|
const formatObj = {
|
||||||
dropFile: exeInfo.dropFile,
|
dropFile: exeInfo.dropFile,
|
||||||
dropFilePath: exeInfo.dropFilePath,
|
dropFilePath: exeInfo.dropFilePath,
|
||||||
dropFileDir: exeInfo.dropFileDir,
|
|
||||||
userAreaDir: exeInfo.userAreaDir,
|
|
||||||
node: exeInfo.node.toString(),
|
node: exeInfo.node.toString(),
|
||||||
srvPort: this.sockServer ? this.sockServer.address().port.toString() : '-1',
|
srvPort: this.sockServer ? this.sockServer.address().port.toString() : '-1',
|
||||||
userId: this.client.user.userId.toString(),
|
userId: this.client.user.userId.toString(),
|
||||||
userName: this.client.user.getSanitizedName(),
|
userName: this.client.user.getSanitizedName(),
|
||||||
userNameRaw: this.client.user.username,
|
userNameRaw: this.client.user.username,
|
||||||
termWidth: this.client.term.termWidth,
|
|
||||||
termHeight: this.client.term.termHeight,
|
|
||||||
cwd: cwd,
|
cwd: cwd,
|
||||||
};
|
};
|
||||||
|
|
||||||
const args = exeInfo.args.map(arg => stringFormat(arg, formatObj));
|
const args = exeInfo.args.map(arg => stringFormat(arg, formatObj));
|
||||||
|
|
||||||
const spawnOptions = {
|
this.client.log.info(
|
||||||
cols: this.client.term.termWidth,
|
{ cmd: exeInfo.cmd, args, io: this.io },
|
||||||
rows: this.client.term.termHeight,
|
`Executing external door (${exeInfo.name})`
|
||||||
cwd: cwd,
|
|
||||||
env: exeInfo.env,
|
|
||||||
encoding: null, // we want to handle all encoding ourself
|
|
||||||
};
|
|
||||||
|
|
||||||
async.series(
|
|
||||||
[
|
|
||||||
callback => {
|
|
||||||
if (!_.isString(exeInfo.preCmd)) {
|
|
||||||
return callback(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
const preCmdArgs = (exeInfo.preCmdArgs || []).map(arg =>
|
|
||||||
stringFormat(arg, formatObj)
|
|
||||||
);
|
|
||||||
|
|
||||||
this.client.log.info(
|
|
||||||
{ cmd: exeInfo.preCmd, args: preCmdArgs },
|
|
||||||
`Executing external door pre-command (${exeInfo.name})`
|
|
||||||
);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const prePty = pty.spawn(
|
|
||||||
exeInfo.preCmd,
|
|
||||||
preCmdArgs,
|
|
||||||
spawnOptions
|
|
||||||
);
|
|
||||||
|
|
||||||
prePty.onExit(exitEvent => {
|
|
||||||
const {exitCode, signal} = exitEvent;
|
|
||||||
this.client.log.info(
|
|
||||||
{ exitCode, signal },
|
|
||||||
'Door pre-command exited'
|
|
||||||
);
|
|
||||||
return callback(null);
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
return callback(e);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
callback => {
|
|
||||||
this.client.log.info(
|
|
||||||
{ cmd: exeInfo.cmd, args, io: this.io },
|
|
||||||
`Executing external door (${exeInfo.name})`
|
|
||||||
);
|
|
||||||
|
|
||||||
try {
|
|
||||||
this.doorPty = pty.spawn(exeInfo.cmd, args, spawnOptions);
|
|
||||||
} catch (e) {
|
|
||||||
return cb(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// PID is launched. Make sure it's killed off if the user disconnects.
|
|
||||||
//
|
|
||||||
Events.once(Events.getSystemEvents().ClientDisconnected, evt => {
|
|
||||||
if (
|
|
||||||
this.doorPty &&
|
|
||||||
this.client.session.uniqueId ===
|
|
||||||
_.get(evt, 'client.session.uniqueId')
|
|
||||||
) {
|
|
||||||
this.client.log.info(
|
|
||||||
{ pid: this.doorPty.pid },
|
|
||||||
'User has disconnected; Killing door process.'
|
|
||||||
);
|
|
||||||
this.doorPty.kill();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.client.log.debug(
|
|
||||||
{ processId: this.doorPty.pid },
|
|
||||||
'External door process spawned'
|
|
||||||
);
|
|
||||||
|
|
||||||
if ('stdio' === this.io) {
|
|
||||||
this.client.log.debug('Using stdio for door I/O');
|
|
||||||
|
|
||||||
this.client.term.output.pipe(this.doorPty);
|
|
||||||
|
|
||||||
this.doorPty.onData(this.doorDataHandler.bind(this));
|
|
||||||
|
|
||||||
this.doorPty.onExit( (/*exitEvent*/) => {
|
|
||||||
return this.restoreIo(this.doorPty);
|
|
||||||
});
|
|
||||||
} else if ('socket' === this.io) {
|
|
||||||
this.client.log.debug(
|
|
||||||
{
|
|
||||||
srvPort: this.sockServer.address().port,
|
|
||||||
srvSocket: this.sockServerSocket,
|
|
||||||
},
|
|
||||||
'Using temporary socket server for door I/O'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.doorPty.onExit(exitEvent => {
|
|
||||||
const {exitCode, signal} = exitEvent;
|
|
||||||
this.client.log.info({ exitCode, signal }, 'Door exited');
|
|
||||||
|
|
||||||
if (this.sockServer) {
|
|
||||||
this.sockServer.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
// we may not get a close
|
|
||||||
if ('stdio' === this.io) {
|
|
||||||
this.restoreIo(this.doorPty);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.doorPty.removeAllListeners();
|
|
||||||
delete this.doorPty;
|
|
||||||
|
|
||||||
return callback(null);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
],
|
|
||||||
() => {
|
|
||||||
return cb(null);
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.doorPty = pty.spawn(exeInfo.cmd, args, {
|
||||||
|
cols: this.client.term.termWidth,
|
||||||
|
rows: this.client.term.termHeight,
|
||||||
|
cwd: cwd,
|
||||||
|
env: exeInfo.env,
|
||||||
|
encoding: null, // we want to handle all encoding ourself
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
return cb(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// PID is launched. Make sure it's killed off if the user disconnects.
|
||||||
|
//
|
||||||
|
Events.once(Events.getSystemEvents().ClientDisconnected, evt => {
|
||||||
|
if (
|
||||||
|
this.doorPty &&
|
||||||
|
this.client.session.uniqueId === _.get(evt, 'client.session.uniqueId')
|
||||||
|
) {
|
||||||
|
this.client.log.info(
|
||||||
|
{ pid: this.doorPty.pid },
|
||||||
|
'User has disconnected; Killing door process.'
|
||||||
|
);
|
||||||
|
this.doorPty.kill();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.client.log.debug(
|
||||||
|
{ processId: this.doorPty.pid },
|
||||||
|
'External door process spawned'
|
||||||
|
);
|
||||||
|
|
||||||
|
if ('stdio' === this.io) {
|
||||||
|
this.client.log.debug('Using stdio for door I/O');
|
||||||
|
|
||||||
|
this.client.term.output.pipe(this.doorPty);
|
||||||
|
|
||||||
|
this.doorPty.onData(this.doorDataHandler.bind(this));
|
||||||
|
|
||||||
|
this.doorPty.once('close', () => {
|
||||||
|
return this.restoreIo(this.doorPty);
|
||||||
|
});
|
||||||
|
} else if ('socket' === this.io) {
|
||||||
|
this.client.log.debug(
|
||||||
|
{
|
||||||
|
srvPort: this.sockServer.address().port,
|
||||||
|
srvSocket: this.sockServerSocket,
|
||||||
|
},
|
||||||
|
'Using temporary socket server for door I/O'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.doorPty.once('exit', exitCode => {
|
||||||
|
this.client.log.info({ exitCode: exitCode }, 'Door exited');
|
||||||
|
|
||||||
|
if (this.sockServer) {
|
||||||
|
this.sockServer.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
// we may not get a close
|
||||||
|
if ('stdio' === this.io) {
|
||||||
|
this.restoreIo(this.doorPty);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.doorPty.removeAllListeners();
|
||||||
|
delete this.doorPty;
|
||||||
|
|
||||||
|
return cb(null);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
doorDataHandler(data) {
|
doorDataHandler(data) {
|
||||||
|
|
|
@ -34,15 +34,8 @@ module.exports = class DropFile {
|
||||||
this.baseDir = baseDir;
|
this.baseDir = baseDir;
|
||||||
}
|
}
|
||||||
|
|
||||||
static dropFileDirectory(baseDir, client) {
|
|
||||||
return paths.join(baseDir, 'node' + client.node);
|
|
||||||
}
|
|
||||||
|
|
||||||
get fullPath() {
|
get fullPath() {
|
||||||
return paths.join(
|
return paths.join(this.baseDir, 'node' + this.client.node, this.fileName);
|
||||||
DropFile.dropFileDirectory(this.baseDir, this.client),
|
|
||||||
this.fileName
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get fileName() {
|
get fileName() {
|
||||||
|
|
|
@ -167,17 +167,17 @@ class ScheduledEvent {
|
||||||
return cb(e);
|
return cb(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
proc.onExit(exitEvent => {
|
proc.once('exit', exitCode => {
|
||||||
if (exitEvent.exitCode) {
|
if (exitCode) {
|
||||||
Log.warn(
|
Log.warn(
|
||||||
{ eventName: this.name, action: this.action, exitCode: exitEvent.exitCode },
|
{ eventName: this.name, action: this.action, exitCode: exitCode },
|
||||||
'Bad exit code while performing scheduled event action'
|
'Bad exit code while performing scheduled event action'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return cb(
|
return cb(
|
||||||
exitEvent.exitCode
|
exitCode
|
||||||
? Errors.ExternalProcess(
|
? Errors.ExternalProcess(
|
||||||
`Bad exit code while performing scheduled event action: ${exitEvent.exitCode}`
|
`Bad exit code while performing scheduled event action: ${exitCode}`
|
||||||
)
|
)
|
||||||
: null
|
: null
|
||||||
);
|
);
|
||||||
|
|
|
@ -2,8 +2,10 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
// ENiGMA½
|
// ENiGMA½
|
||||||
const { MenuModule, MenuFlags } = require('./menu_module.js');
|
const MenuModule = require('./menu_module.js').MenuModule;
|
||||||
|
const ViewController = require('./view_controller.js').ViewController;
|
||||||
const ansi = require('./ansi_term.js');
|
const ansi = require('./ansi_term.js');
|
||||||
|
const theme = require('./theme.js');
|
||||||
const FileEntry = require('./file_entry.js');
|
const FileEntry = require('./file_entry.js');
|
||||||
const stringFormat = require('./string_format.js');
|
const stringFormat = require('./string_format.js');
|
||||||
const FileArea = require('./file_base_area.js');
|
const FileArea = require('./file_base_area.js');
|
||||||
|
@ -75,8 +77,6 @@ exports.getModule = class FileAreaList extends MenuModule {
|
||||||
this.fileList = _.get(options, 'extraArgs.fileList');
|
this.fileList = _.get(options, 'extraArgs.fileList');
|
||||||
this.lastFileNextExit = _.get(options, 'extraArgs.lastFileNextExit', true);
|
this.lastFileNextExit = _.get(options, 'extraArgs.lastFileNextExit', true);
|
||||||
|
|
||||||
this.setMergedFlag(MenuFlags.NoHistory);
|
|
||||||
|
|
||||||
if (this.fileList) {
|
if (this.fileList) {
|
||||||
// we'll need to adjust position as well!
|
// we'll need to adjust position as well!
|
||||||
this.fileListPosition = 0;
|
this.fileListPosition = 0;
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
// enigma-bbs
|
// enigma-bbs
|
||||||
const { MenuModule, MenuFlags } = require('./menu_module.js');
|
const MenuModule = require('./menu_module.js').MenuModule;
|
||||||
const { getSortedAvailableFileAreas } = require('./file_base_area.js');
|
const { getSortedAvailableFileAreas } = require('./file_base_area.js');
|
||||||
const StatLog = require('./stat_log.js');
|
const StatLog = require('./stat_log.js');
|
||||||
const SysProps = require('./system_property.js');
|
const SysProps = require('./system_property.js');
|
||||||
|
@ -24,8 +24,6 @@ exports.getModule = class FileAreaSelectModule extends MenuModule {
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
super(options);
|
super(options);
|
||||||
|
|
||||||
this.setMergedFlag(MenuFlags.NoHistory);
|
|
||||||
|
|
||||||
this.menuMethods = {
|
this.menuMethods = {
|
||||||
selectArea: (formData, extraArgs, cb) => {
|
selectArea: (formData, extraArgs, cb) => {
|
||||||
const filterCriteria = {
|
const filterCriteria = {
|
||||||
|
@ -36,7 +34,7 @@ exports.getModule = class FileAreaSelectModule extends MenuModule {
|
||||||
extraArgs: {
|
extraArgs: {
|
||||||
filterCriteria: filterCriteria,
|
filterCriteria: filterCriteria,
|
||||||
},
|
},
|
||||||
menuFlags: [ MenuFlags.NoHistory ],
|
menuFlags: ['popParent', 'mergeFlags'],
|
||||||
};
|
};
|
||||||
|
|
||||||
return this.gotoMenu(
|
return this.gotoMenu(
|
||||||
|
|
|
@ -2,8 +2,10 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
// ENiGMA½
|
// ENiGMA½
|
||||||
const { MenuModule, MenuFlags } = require('./menu_module.js');
|
const MenuModule = require('./menu_module.js').MenuModule;
|
||||||
|
const ViewController = require('./view_controller.js').ViewController;
|
||||||
const DownloadQueue = require('./download_queue.js');
|
const DownloadQueue = require('./download_queue.js');
|
||||||
|
const theme = require('./theme.js');
|
||||||
const ansi = require('./ansi_term.js');
|
const ansi = require('./ansi_term.js');
|
||||||
const Errors = require('./enig_error.js').Errors;
|
const Errors = require('./enig_error.js').Errors;
|
||||||
const FileAreaWeb = require('./file_area_web.js');
|
const FileAreaWeb = require('./file_area_web.js');
|
||||||
|
@ -36,8 +38,6 @@ exports.getModule = class FileBaseDownloadQueueManager extends MenuModule {
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
super(options);
|
super(options);
|
||||||
|
|
||||||
this.setMergedFlag(MenuFlags.NoHistory);
|
|
||||||
|
|
||||||
this.dlQueue = new DownloadQueue(this.client);
|
this.dlQueue = new DownloadQueue(this.client);
|
||||||
|
|
||||||
if (_.has(options, 'lastMenuResult.sentFileIds')) {
|
if (_.has(options, 'lastMenuResult.sentFileIds')) {
|
||||||
|
|
|
@ -121,6 +121,7 @@ exports.getModule = class FileBaseSearch extends MenuModule {
|
||||||
extraArgs: {
|
extraArgs: {
|
||||||
filterCriteria: filterCriteria,
|
filterCriteria: filterCriteria,
|
||||||
},
|
},
|
||||||
|
menuFlags: ['popParent'],
|
||||||
};
|
};
|
||||||
|
|
||||||
return this.gotoMenu(
|
return this.gotoMenu(
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
// ENiGMA½
|
// ENiGMA½
|
||||||
const { MenuModule, MenuFlags } = require('./menu_module.js');
|
const { MenuModule } = require('./menu_module.js');
|
||||||
const FileEntry = require('./file_entry.js');
|
const FileEntry = require('./file_entry.js');
|
||||||
const FileArea = require('./file_base_area.js');
|
const FileArea = require('./file_base_area.js');
|
||||||
const { renderSubstr } = require('./string_util.js');
|
const { renderSubstr } = require('./string_util.js');
|
||||||
|
@ -65,9 +65,6 @@ const MciViewIds = {
|
||||||
exports.getModule = class FileBaseListExport extends MenuModule {
|
exports.getModule = class FileBaseListExport extends MenuModule {
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
super(options);
|
super(options);
|
||||||
|
|
||||||
this.setMergedFlag(MenuFlags.NoHistory);
|
|
||||||
|
|
||||||
this.config = Object.assign(
|
this.config = Object.assign(
|
||||||
{},
|
{},
|
||||||
_.get(options, 'menuConfig.config'),
|
_.get(options, 'menuConfig.config'),
|
||||||
|
|
|
@ -2,8 +2,10 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
// ENiGMA½
|
// ENiGMA½
|
||||||
const { MenuModule, MenuFlags } = require('./menu_module.js');
|
const MenuModule = require('./menu_module.js').MenuModule;
|
||||||
|
const ViewController = require('./view_controller.js').ViewController;
|
||||||
const DownloadQueue = require('./download_queue.js');
|
const DownloadQueue = require('./download_queue.js');
|
||||||
|
const theme = require('./theme.js');
|
||||||
const ansi = require('./ansi_term.js');
|
const ansi = require('./ansi_term.js');
|
||||||
const Errors = require('./enig_error.js').Errors;
|
const Errors = require('./enig_error.js').Errors;
|
||||||
const FileAreaWeb = require('./file_area_web.js');
|
const FileAreaWeb = require('./file_area_web.js');
|
||||||
|
@ -38,8 +40,6 @@ exports.getModule = class FileBaseWebDownloadQueueManager extends MenuModule {
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
super(options);
|
super(options);
|
||||||
|
|
||||||
this.setMergedFlag(MenuFlags.NoHistory);
|
|
||||||
|
|
||||||
this.dlQueue = new DownloadQueue(this.client);
|
this.dlQueue = new DownloadQueue(this.client);
|
||||||
|
|
||||||
this.menuMethods = {
|
this.menuMethods = {
|
||||||
|
|
|
@ -485,10 +485,13 @@ exports.getModule = class TransferFileModule extends MenuModule {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
externalProc.onExit(exitEvent => {
|
externalProc.once('close', () => {
|
||||||
const {exitCode, signal} = exitEvent;
|
return this.restorePipeAfterExternalProc();
|
||||||
|
});
|
||||||
|
|
||||||
|
externalProc.once('exit', exitCode => {
|
||||||
this.client.log.debug(
|
this.client.log.debug(
|
||||||
{ cmd: cmd, args: args, exitCode, signal },
|
{ cmd: cmd, args: args, exitCode: exitCode },
|
||||||
'Process exited'
|
'Process exited'
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
16
core/fse.js
16
core/fse.js
|
@ -167,7 +167,6 @@ exports.FullScreenEditorModule =
|
||||||
var newFocusViewId;
|
var newFocusViewId;
|
||||||
if (errMsgView) {
|
if (errMsgView) {
|
||||||
if (err) {
|
if (err) {
|
||||||
errMsgView.clearText();
|
|
||||||
errMsgView.setText(err.message);
|
errMsgView.setText(err.message);
|
||||||
|
|
||||||
if (MciViewIds.header.subject === err.view.getId()) {
|
if (MciViewIds.header.subject === err.view.getId()) {
|
||||||
|
@ -184,13 +183,6 @@ exports.FullScreenEditorModule =
|
||||||
return cb(null);
|
return cb(null);
|
||||||
},
|
},
|
||||||
editModeEscPressed: function (formData, extraArgs, cb) {
|
editModeEscPressed: function (formData, extraArgs, cb) {
|
||||||
const errMsgView = self.viewControllers.header.getView(
|
|
||||||
MciViewIds.header.errorMsg
|
|
||||||
);
|
|
||||||
if (errMsgView) {
|
|
||||||
errMsgView.clearText();
|
|
||||||
}
|
|
||||||
|
|
||||||
self.footerMode =
|
self.footerMode =
|
||||||
'editor' === self.footerMode ? 'editorMenu' : 'editor';
|
'editor' === self.footerMode ? 'editorMenu' : 'editor';
|
||||||
|
|
||||||
|
@ -990,7 +982,11 @@ exports.FullScreenEditorModule =
|
||||||
const area = getMessageAreaByTag(self.messageAreaTag);
|
const area = getMessageAreaByTag(self.messageAreaTag);
|
||||||
if (fromView !== undefined) {
|
if (fromView !== undefined) {
|
||||||
if (area && area.realNames) {
|
if (area && area.realNames) {
|
||||||
fromView.setText(self.client.user.realName());
|
fromView.setText(
|
||||||
|
self.client.user.properties[
|
||||||
|
UserProps.RealName
|
||||||
|
] || self.client.user.username
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
fromView.setText(self.client.user.username);
|
fromView.setText(self.client.user.username);
|
||||||
}
|
}
|
||||||
|
@ -1058,7 +1054,7 @@ exports.FullScreenEditorModule =
|
||||||
posView.setText(
|
posView.setText(
|
||||||
_.padStart(String(pos.row + 1), 2, '0') +
|
_.padStart(String(pos.row + 1), 2, '0') +
|
||||||
',' +
|
',' +
|
||||||
_.padStart(String(pos.col + 1), 2, '0')
|
_.padEnd(String(pos.col + 1), 2, '0')
|
||||||
);
|
);
|
||||||
this.client.term.rawWrite(ansi.restorePos());
|
this.client.term.rawWrite(ansi.restorePos());
|
||||||
}
|
}
|
||||||
|
|
|
@ -135,7 +135,7 @@ module.exports = class Address {
|
||||||
static fromString(addrStr) {
|
static fromString(addrStr) {
|
||||||
const m = FTN_ADDRESS_REGEXP.exec(addrStr);
|
const m = FTN_ADDRESS_REGEXP.exec(addrStr);
|
||||||
|
|
||||||
if (m && m[2] && m[3]) {
|
if (m) {
|
||||||
// start with a 2D
|
// start with a 2D
|
||||||
let addr = {
|
let addr = {
|
||||||
net: parseInt(m[2]),
|
net: parseInt(m[2]),
|
||||||
|
|
141
core/goldmine.js
141
core/goldmine.js
|
@ -1,141 +0,0 @@
|
||||||
/* jslint node: true */
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
// enigma-bbs
|
|
||||||
const { MenuModule } = require('../core/menu_module');
|
|
||||||
const { resetScreen } = require('../core/ansi_term');
|
|
||||||
const { Errors } = require('../core/enig_error');
|
|
||||||
const { trackDoorRunBegin, trackDoorRunEnd } = require('./door_util');
|
|
||||||
|
|
||||||
// deps
|
|
||||||
const async = require('async');
|
|
||||||
const _ = require('lodash');
|
|
||||||
const RLogin = require('rlogin');
|
|
||||||
|
|
||||||
exports.moduleInfo = {
|
|
||||||
name: 'gOLD mINE',
|
|
||||||
desc: 'gOLD mINE Community Door Server Module',
|
|
||||||
author: 'NuSkooler',
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.getModule = class GoldmineModule extends MenuModule {
|
|
||||||
constructor(options) {
|
|
||||||
super(options);
|
|
||||||
|
|
||||||
this.setConfigWithExtraArgs(options);
|
|
||||||
|
|
||||||
// http://goldminebbs.com/
|
|
||||||
this.config.host = this.config.host || '165.232.153.209';
|
|
||||||
this.config.rloginPort = this.config.rloginPort || 513;
|
|
||||||
}
|
|
||||||
|
|
||||||
initSequence() {
|
|
||||||
let clientTerminated = false;
|
|
||||||
|
|
||||||
async.series(
|
|
||||||
[
|
|
||||||
callback => {
|
|
||||||
return this.validateConfigFields(
|
|
||||||
{
|
|
||||||
host: 'string',
|
|
||||||
rloginPort: 'number',
|
|
||||||
bbsTag: 'string',
|
|
||||||
},
|
|
||||||
callback
|
|
||||||
);
|
|
||||||
},
|
|
||||||
callback => {
|
|
||||||
this.client.term.write(resetScreen());
|
|
||||||
this.client.term.write('Connecting to gOLD mINE, please wait...\n');
|
|
||||||
|
|
||||||
const username = this.client.user.getSanitizedName();
|
|
||||||
let doorTracking;
|
|
||||||
const rlogin = new RLogin({
|
|
||||||
clientUsername: username,
|
|
||||||
serverUsername: `${this.config.bbsTag}${username}`,
|
|
||||||
host: this.config.host,
|
|
||||||
port: this.config.rloginPort,
|
|
||||||
terminalType: '',
|
|
||||||
terminalSpeed: '',
|
|
||||||
});
|
|
||||||
|
|
||||||
if (
|
|
||||||
_.isString(this.config.directDoorCode) &&
|
|
||||||
this.config.directDoorCode.length > 0
|
|
||||||
) {
|
|
||||||
rlogin.terminalType = `xtrn=${this.config.directDoorCode}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const rloginSend = buffer => {
|
|
||||||
return rlogin.send(buffer);
|
|
||||||
};
|
|
||||||
|
|
||||||
let pipeRestored = false;
|
|
||||||
const restorePipeAndFinish = err => {
|
|
||||||
if (pipeRestored) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
pipeRestored = true;
|
|
||||||
|
|
||||||
if (this.client.term.output) {
|
|
||||||
this.client.term.output.removeListener('data', rloginSend);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (doorTracking) {
|
|
||||||
trackDoorRunEnd(doorTracking);
|
|
||||||
}
|
|
||||||
|
|
||||||
return callback(err);
|
|
||||||
};
|
|
||||||
|
|
||||||
rlogin.on('error', err => {
|
|
||||||
// Eat up RLogin error with terminalSpeed not being a number
|
|
||||||
if (err === 'RLogin: invalid terminalSpeed argument.') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.client.log.info(
|
|
||||||
`gOLD mINE rlogin client error: ${err.message || err}`
|
|
||||||
);
|
|
||||||
return restorePipeAndFinish(err);
|
|
||||||
});
|
|
||||||
|
|
||||||
rlogin.on('disconnect', () => {
|
|
||||||
this.client.log.info('Disconnected from gOLD mINE');
|
|
||||||
return restorePipeAndFinish(null);
|
|
||||||
});
|
|
||||||
|
|
||||||
rlogin.on('connect', connected => {
|
|
||||||
if (!connected) {
|
|
||||||
return callback(
|
|
||||||
Errors.General(
|
|
||||||
'Failed to establish connection to gOLD mINE'
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.client.log.info('Connected to gOLD mINE');
|
|
||||||
this.client.term.output.on('data', rloginSend);
|
|
||||||
|
|
||||||
doorTracking = trackDoorRunBegin(this.client);
|
|
||||||
});
|
|
||||||
|
|
||||||
rlogin.on('data', data => {
|
|
||||||
this.client.term.rawWrite(data);
|
|
||||||
});
|
|
||||||
|
|
||||||
// connect...
|
|
||||||
rlogin.connect();
|
|
||||||
},
|
|
||||||
],
|
|
||||||
err => {
|
|
||||||
if (err) {
|
|
||||||
this.client.log.warn({ error: err.message }, 'gOLD mINE error');
|
|
||||||
}
|
|
||||||
|
|
||||||
this.prevMenu();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
|
@ -33,6 +33,7 @@ MCIViewFactory.UserViewCodes = [
|
||||||
'ET',
|
'ET',
|
||||||
'ME',
|
'ME',
|
||||||
'MT',
|
'MT',
|
||||||
|
'PL',
|
||||||
'BT',
|
'BT',
|
||||||
'VM',
|
'VM',
|
||||||
'HM',
|
'HM',
|
||||||
|
@ -128,6 +129,21 @@ MCIViewFactory.prototype.createFromMCI = function (mci) {
|
||||||
view = new MultiLineEditTextView(options);
|
view = new MultiLineEditTextView(options);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
// Pre-defined Label (Text View)
|
||||||
|
// :TODO: Currently no real point of PL -- @method replaces this pretty much... probably remove
|
||||||
|
case 'PL':
|
||||||
|
if (mci.args.length > 0) {
|
||||||
|
options.text = getPredefinedMCIValue(this.client, mci.args[0]);
|
||||||
|
if (options.text) {
|
||||||
|
setOption(1, 'textStyle');
|
||||||
|
setOption(2, 'justify');
|
||||||
|
setWidth(3);
|
||||||
|
|
||||||
|
view = new TextView(options);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
// Button
|
// Button
|
||||||
case 'BT':
|
case 'BT':
|
||||||
if (mci.args.length > 0) {
|
if (mci.args.length > 0) {
|
||||||
|
|
|
@ -20,23 +20,6 @@ const assert = require('assert');
|
||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
const iconvDecode = require('iconv-lite').decode;
|
const iconvDecode = require('iconv-lite').decode;
|
||||||
|
|
||||||
const MenuFlags = {
|
|
||||||
// When leaving this menu to load/chain to another, remove this
|
|
||||||
// menu from history. In other words, the fallback from
|
|
||||||
// the next menu would *not* be this one, but the previous.
|
|
||||||
NoHistory: 'noHistory',
|
|
||||||
|
|
||||||
// Generally used in code only: Request that any flags from menu.hjson
|
|
||||||
// are merged in to the total set of flags vs overriding the default.
|
|
||||||
MergeFlags: 'mergeFlags',
|
|
||||||
|
|
||||||
// Forward this menu's 'extraArgs' to the next.
|
|
||||||
ForwardArgs: 'forwardArgs',
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.MenuFlags = MenuFlags;
|
|
||||||
|
|
||||||
|
|
||||||
exports.MenuModule = class MenuModule extends PluginModule {
|
exports.MenuModule = class MenuModule extends PluginModule {
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
super(options);
|
super(options);
|
||||||
|
@ -59,17 +42,6 @@ exports.MenuModule = class MenuModule extends PluginModule {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setConfigWithExtraArgs(options) {
|
|
||||||
this.config = Object.assign({}, _.get(options, 'menuConfig.config'), {
|
|
||||||
extraArgs: options.extraArgs,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
setMergedFlag(flag) {
|
|
||||||
this.menuConfig.config.menuFlags.push(flag);
|
|
||||||
this.menuConfig.config.menuFlags = [...new Set([...this.menuConfig.config.menuFlags, MenuFlags.MergeFlags])];
|
|
||||||
}
|
|
||||||
|
|
||||||
static get InterruptTypes() {
|
static get InterruptTypes() {
|
||||||
return {
|
return {
|
||||||
Never: 'never',
|
Never: 'never',
|
||||||
|
|
|
@ -5,12 +5,12 @@
|
||||||
const loadMenu = require('./menu_util.js').loadMenu;
|
const loadMenu = require('./menu_util.js').loadMenu;
|
||||||
const { Errors, ErrorReasons } = require('./enig_error.js');
|
const { Errors, ErrorReasons } = require('./enig_error.js');
|
||||||
const { getResolvedSpec } = require('./menu_util.js');
|
const { getResolvedSpec } = require('./menu_util.js');
|
||||||
const { MenuFlags } = require('./menu_module.js');
|
|
||||||
|
|
||||||
// deps
|
// deps
|
||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
const assert = require('assert');
|
const assert = require('assert');
|
||||||
const bunyan = require('bunyan');
|
|
||||||
|
// :TODO: Stack is backwards.... top should be most recent! :)
|
||||||
|
|
||||||
module.exports = class MenuStack {
|
module.exports = class MenuStack {
|
||||||
constructor(client) {
|
constructor(client) {
|
||||||
|
@ -27,11 +27,19 @@ module.exports = class MenuStack {
|
||||||
}
|
}
|
||||||
|
|
||||||
peekPrev() {
|
peekPrev() {
|
||||||
return this.stack[this.stack.length - 2];
|
if (this.stackSize > 1) {
|
||||||
|
return this.stack[this.stack.length - 2];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
top() {
|
top() {
|
||||||
return this.stack[this.stack.length - 1];
|
if (this.stackSize > 0) {
|
||||||
|
return this.stack[this.stack.length - 1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get stackSize() {
|
||||||
|
return this.stack.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
get currentModule() {
|
get currentModule() {
|
||||||
|
@ -48,13 +56,13 @@ module.exports = class MenuStack {
|
||||||
return cb(
|
return cb(
|
||||||
Array.isArray(menuConfig.next)
|
Array.isArray(menuConfig.next)
|
||||||
? Errors.MenuStack(
|
? Errors.MenuStack(
|
||||||
'No matching condition for "next"',
|
'No matching condition for "next"',
|
||||||
ErrorReasons.NoConditionMatch
|
ErrorReasons.NoConditionMatch
|
||||||
)
|
)
|
||||||
: Errors.MenuStack(
|
: Errors.MenuStack(
|
||||||
'Invalid or missing "next" member in menu config',
|
'Invalid or missing "next" member in menu config',
|
||||||
ErrorReasons.InvalidNextMenu
|
ErrorReasons.InvalidNextMenu
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,6 +81,7 @@ module.exports = class MenuStack {
|
||||||
prev(cb) {
|
prev(cb) {
|
||||||
const menuResult = this.top().instance.getMenuResult();
|
const menuResult = this.top().instance.getMenuResult();
|
||||||
|
|
||||||
|
// :TODO: leave() should really take a cb...
|
||||||
this.pop().instance.leave(); // leave & remove current
|
this.pop().instance.leave(); // leave & remove current
|
||||||
|
|
||||||
const previousModuleInfo = this.pop(); // get previous
|
const previousModuleInfo = this.pop(); // get previous
|
||||||
|
@ -120,7 +129,7 @@ module.exports = class MenuStack {
|
||||||
client: self.client,
|
client: self.client,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (currentModuleInfo && currentModuleInfo.menuFlags.includes(MenuFlags.ForwardArgs)) {
|
if (currentModuleInfo && currentModuleInfo.menuFlags.includes('forwardArgs')) {
|
||||||
loadOpts.extraArgs = currentModuleInfo.extraArgs;
|
loadOpts.extraArgs = currentModuleInfo.extraArgs;
|
||||||
} else {
|
} else {
|
||||||
loadOpts.extraArgs = options.extraArgs || _.get(options, 'formData.value');
|
loadOpts.extraArgs = options.extraArgs || _.get(options, 'formData.value');
|
||||||
|
@ -129,6 +138,7 @@ module.exports = class MenuStack {
|
||||||
|
|
||||||
loadMenu(loadOpts, (err, modInst) => {
|
loadMenu(loadOpts, (err, modInst) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
|
// :TODO: probably should just require a cb...
|
||||||
const errCb = cb || self.client.defaultHandlerMissingMod();
|
const errCb = cb || self.client.defaultHandlerMissingMod();
|
||||||
errCb(err);
|
errCb(err);
|
||||||
} else {
|
} else {
|
||||||
|
@ -141,6 +151,22 @@ module.exports = class MenuStack {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Handle deprecated 'options' block by merging to config and warning user.
|
||||||
|
// :TODO: Remove in 0.0.10+
|
||||||
|
//
|
||||||
|
if (modInst.menuConfig.options) {
|
||||||
|
self.client.log.warn(
|
||||||
|
{ options: modInst.menuConfig.options },
|
||||||
|
'Use of "options" is deprecated. Move relevant members to "config" block! Support will be fully removed in future versions'
|
||||||
|
);
|
||||||
|
Object.assign(
|
||||||
|
modInst.menuConfig.config || {},
|
||||||
|
modInst.menuConfig.options
|
||||||
|
);
|
||||||
|
delete modInst.menuConfig.options;
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// If menuFlags were supplied in menu.hjson, they should win over
|
// If menuFlags were supplied in menu.hjson, they should win over
|
||||||
// anything supplied in code.
|
// anything supplied in code.
|
||||||
|
@ -154,9 +180,9 @@ module.exports = class MenuStack {
|
||||||
// in code we can ask to merge in
|
// in code we can ask to merge in
|
||||||
if (
|
if (
|
||||||
Array.isArray(options.menuFlags) &&
|
Array.isArray(options.menuFlags) &&
|
||||||
options.menuFlags.includes(MenuFlags.MergeFlags)
|
options.menuFlags.includes('mergeFlags')
|
||||||
) {
|
) {
|
||||||
menuFlags = [...new Set(options.menuFlags)]; // make unique
|
menuFlags = _.uniq(menuFlags.concat(options.menuFlags));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -167,8 +193,12 @@ module.exports = class MenuStack {
|
||||||
|
|
||||||
currentModuleInfo.instance.leave();
|
currentModuleInfo.instance.leave();
|
||||||
|
|
||||||
if (currentModuleInfo.menuFlags.includes(MenuFlags.NoHistory)) {
|
if (currentModuleInfo.menuFlags.includes('noHistory')) {
|
||||||
this.pop().instance.leave(); // leave & remove current from stack
|
this.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (menuFlags.includes('popParent')) {
|
||||||
|
this.pop().instance.leave(); // leave & remove current
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -184,19 +214,17 @@ module.exports = class MenuStack {
|
||||||
modInst.restoreSavedState(options.savedState);
|
modInst.restoreSavedState(options.savedState);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (self.client.log.level() <= bunyan.TRACE) {
|
const stackEntries = self.stack.map(stackEntry => {
|
||||||
const stackEntries = self.stack.map(stackEntry => {
|
let name = stackEntry.name;
|
||||||
let name = stackEntry.name;
|
if (stackEntry.instance.menuConfig.config.menuFlags.length > 0) {
|
||||||
if (stackEntry.instance.menuConfig.config.menuFlags.length > 0) {
|
name += ` (${stackEntry.instance.menuConfig.config.menuFlags.join(
|
||||||
name += ` (${stackEntry.instance.menuConfig.config.menuFlags.join(
|
', '
|
||||||
', '
|
)})`;
|
||||||
)})`;
|
}
|
||||||
}
|
return name;
|
||||||
return name;
|
});
|
||||||
});
|
|
||||||
|
|
||||||
self.client.log.trace({ stack: stackEntries }, 'Updated menu stack');
|
self.client.log.trace({ stack: stackEntries }, 'Updated menu stack');
|
||||||
}
|
|
||||||
|
|
||||||
modInst.enter();
|
modInst.enter();
|
||||||
|
|
||||||
|
|
|
@ -763,11 +763,6 @@ module.exports = class Message {
|
||||||
}
|
}
|
||||||
|
|
||||||
persist(cb) {
|
persist(cb) {
|
||||||
const containsNonWhitespaceCharacterRegEx = /\S/;
|
|
||||||
if (!containsNonWhitespaceCharacterRegEx.test(this.message)) {
|
|
||||||
return cb(Errors.Invalid('Empty message'));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.isValid()) {
|
if (!this.isValid()) {
|
||||||
return cb(Errors.Invalid('Cannot persist invalid message!'));
|
return cb(Errors.Invalid('Cannot persist invalid message!'));
|
||||||
}
|
}
|
||||||
|
|
|
@ -332,10 +332,7 @@ exports.getModule = class MessageBaseQWKExport extends MenuModule {
|
||||||
publicExportAreas,
|
publicExportAreas,
|
||||||
(exportArea, nextExportArea) => {
|
(exportArea, nextExportArea) => {
|
||||||
const area = getMessageAreaByTag(exportArea.areaTag);
|
const area = getMessageAreaByTag(exportArea.areaTag);
|
||||||
let conf;
|
const conf = getMessageConferenceByTag(area.confTag);
|
||||||
if (area) {
|
|
||||||
conf = getMessageConferenceByTag(area.confTag);
|
|
||||||
}
|
|
||||||
if (!area || !conf) {
|
if (!area || !conf) {
|
||||||
// :TODO: remove from user properties - this area does not exist
|
// :TODO: remove from user properties - this area does not exist
|
||||||
this.client.log.warn(
|
this.client.log.warn(
|
||||||
|
|
|
@ -113,6 +113,7 @@ exports.getModule = class MessageBaseSearch extends MenuModule {
|
||||||
const returnNoResults = () => {
|
const returnNoResults = () => {
|
||||||
return this.gotoMenu(
|
return this.gotoMenu(
|
||||||
this.menuConfig.config.noResultsMenu || 'messageSearchNoResults',
|
this.menuConfig.config.noResultsMenu || 'messageSearchNoResults',
|
||||||
|
{ menuFlags: ['popParent'] },
|
||||||
cb
|
cb
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -159,6 +160,7 @@ exports.getModule = class MessageBaseSearch extends MenuModule {
|
||||||
messageList,
|
messageList,
|
||||||
noUpdateLastReadId: true,
|
noUpdateLastReadId: true,
|
||||||
},
|
},
|
||||||
|
menuFlags: ['popParent'],
|
||||||
};
|
};
|
||||||
|
|
||||||
return this.gotoMenu(
|
return this.gotoMenu(
|
||||||
|
|
54
core/mrc.js
54
core/mrc.js
|
@ -55,8 +55,6 @@ const helpText = `
|
||||||
|03/|11topic |03<message> |08- |07Set the room topic
|
|03/|11topic |03<message> |08- |07Set the room topic
|
||||||
|03/|11bbses |08& |03/|11info <id> |08- |07Info about BBS's connected
|
|03/|11bbses |08& |03/|11info <id> |08- |07Info about BBS's connected
|
||||||
|03/|11meetups |08- |07Info about MRC MeetUps
|
|03/|11meetups |08- |07Info about MRC MeetUps
|
||||||
|03/|11quote |08- |07Send raw command to server
|
|
||||||
|03/|11help |08- |07Server-side commands help
|
|
||||||
---
|
---
|
||||||
|03/|11l33t |03<your message> |08- |07l337 5p34k
|
|03/|11l33t |03<your message> |08- |07l337 5p34k
|
||||||
|03/|11kewl |03<your message> |08- |07BBS KeWL SPeaK
|
|03/|11kewl |03<your message> |08- |07BBS KeWL SPeaK
|
||||||
|
@ -377,18 +375,6 @@ exports.getModule = class mrcModule extends MenuModule {
|
||||||
'|08' + currentTime + '|00 ' + message.body + '|00'
|
'|08' + currentTime + '|00 ' + message.body + '|00'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deliver PrivMsg
|
|
||||||
else if (
|
|
||||||
message.to_user.toLowerCase() == this.state.alias.toLowerCase()
|
|
||||||
) {
|
|
||||||
const currentTime = moment().format(
|
|
||||||
this.client.currentTheme.helpers.getTimeFormat()
|
|
||||||
);
|
|
||||||
this.addMessageToChatLog(
|
|
||||||
'|08' + currentTime + '|00 ' + message.body + '|00'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.viewControllers.mrcChat.switchFocus(MciViewIds.mrcChat.inputArea);
|
this.viewControllers.mrcChat.switchFocus(MciViewIds.mrcChat.inputArea);
|
||||||
|
@ -554,46 +540,6 @@ exports.getModule = class mrcModule extends MenuModule {
|
||||||
this.sendServerMessage('LIST');
|
this.sendServerMessage('LIST');
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// Allow support for new server commands without change to client
|
|
||||||
case 'quote':
|
|
||||||
this.sendServerMessage(`${message.substr(7)}`);
|
|
||||||
break;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Process known additional server commands directly
|
|
||||||
*/
|
|
||||||
case 'afk':
|
|
||||||
this.sendServerMessage(`AFK ${message.substr(5)}`);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'roomconfig':
|
|
||||||
this.sendServerMessage(`ROOMCONFIG ${message.substr(12)}`);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'roompass':
|
|
||||||
this.sendServerMessage(`ROOMPASS ${message.substr(12)}`);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'status':
|
|
||||||
this.sendServerMessage(`STATUS ${message.substr(8)}`);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'lastseen':
|
|
||||||
this.sendServerMessage(`LASTSEEN ${message.substr(10)}`);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'help':
|
|
||||||
this.sendServerMessage(`HELP ${message.substr(6)}`);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'statistics':
|
|
||||||
case 'changelog':
|
|
||||||
case 'listbans':
|
|
||||||
case 'listmutes':
|
|
||||||
case 'routing':
|
|
||||||
this.sendServerMessage(cmd[0].toUpperCase());
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'quit':
|
case 'quit':
|
||||||
return this.prevMenu();
|
return this.prevMenu();
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
// ENiGMA½
|
// ENiGMA½
|
||||||
const { MenuModule, MenuFlags } = require('./menu_module.js');
|
const { MenuModule } = require('./menu_module.js');
|
||||||
const messageArea = require('./message_area.js');
|
const messageArea = require('./message_area.js');
|
||||||
const { Errors } = require('./enig_error.js');
|
const { Errors } = require('./enig_error.js');
|
||||||
const UserProps = require('./user_property.js');
|
const UserProps = require('./user_property.js');
|
||||||
|
@ -29,9 +29,6 @@ exports.getModule = class MessageAreaListModule extends MenuModule {
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
super(options);
|
super(options);
|
||||||
|
|
||||||
// always include noHistory flag
|
|
||||||
this.setMergedFlag(MenuFlags.NoHistory);
|
|
||||||
|
|
||||||
this.initList();
|
this.initList();
|
||||||
|
|
||||||
this.menuMethods = {
|
this.menuMethods = {
|
||||||
|
@ -52,7 +49,7 @@ exports.getModule = class MessageAreaListModule extends MenuModule {
|
||||||
extraArgs: {
|
extraArgs: {
|
||||||
areaTag: area.areaTag,
|
areaTag: area.areaTag,
|
||||||
},
|
},
|
||||||
menuFlags: [ MenuFlags.NoHistory ],
|
menuFlags: ['popParent', 'noHistory'],
|
||||||
};
|
};
|
||||||
|
|
||||||
return this.gotoMenu(
|
return this.gotoMenu(
|
||||||
|
|
|
@ -14,39 +14,6 @@ exports.moduleInfo = {
|
||||||
author: 'NuSkooler',
|
author: 'NuSkooler',
|
||||||
};
|
};
|
||||||
|
|
||||||
const MciViewIds = {
|
|
||||||
header: {
|
|
||||||
from: 1,
|
|
||||||
to: 2,
|
|
||||||
subject: 3,
|
|
||||||
errorMsg: 4,
|
|
||||||
modTimestamp: 5,
|
|
||||||
msgNum: 6,
|
|
||||||
msgTotal: 7,
|
|
||||||
|
|
||||||
customRangeStart: 10, // 10+ = customs
|
|
||||||
},
|
|
||||||
|
|
||||||
body: {
|
|
||||||
message: 1,
|
|
||||||
},
|
|
||||||
|
|
||||||
// :TODO: quote builder MCIs - remove all magic #'s
|
|
||||||
|
|
||||||
// :TODO: consolidate all footer MCI's - remove all magic #'s
|
|
||||||
ViewModeFooter: {
|
|
||||||
MsgNum: 6,
|
|
||||||
MsgTotal: 7,
|
|
||||||
// :TODO: Just use custom ranges
|
|
||||||
},
|
|
||||||
|
|
||||||
quoteBuilder: {
|
|
||||||
quotedMsg: 1,
|
|
||||||
// 2 NYI
|
|
||||||
quoteLines: 3,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.getModule = class AreaPostFSEModule extends FullScreenEditorModule {
|
exports.getModule = class AreaPostFSEModule extends FullScreenEditorModule {
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
super(options);
|
super(options);
|
||||||
|
@ -75,25 +42,19 @@ exports.getModule = class AreaPostFSEModule extends FullScreenEditorModule {
|
||||||
],
|
],
|
||||||
function complete(err) {
|
function complete(err) {
|
||||||
if (err) {
|
if (err) {
|
||||||
const errMsgView = self.viewControllers.header.getView(
|
// :TODO:... sooooo now what?
|
||||||
MciViewIds.header.errorMsg
|
} else {
|
||||||
|
// note: not logging 'from' here as it's part of client.log.xxxx()
|
||||||
|
self.client.log.info(
|
||||||
|
{
|
||||||
|
to: msg.toUserName,
|
||||||
|
subject: msg.subject,
|
||||||
|
uuid: msg.messageUuid,
|
||||||
|
},
|
||||||
|
`User "${self.client.user.username}" posted message to "${msg.toUserName}" (${msg.areaTag})`
|
||||||
);
|
);
|
||||||
if (errMsgView) {
|
|
||||||
errMsgView.setText(err.message);
|
|
||||||
}
|
|
||||||
return cb(err);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// note: not logging 'from' here as it's part of client.log.xxxx()
|
|
||||||
self.client.log.info(
|
|
||||||
{
|
|
||||||
to: msg.toUserName,
|
|
||||||
subject: msg.subject,
|
|
||||||
uuid: msg.messageUuid,
|
|
||||||
},
|
|
||||||
`User "${self.client.user.username}" posted message to "${msg.toUserName}" (${msg.areaTag})`
|
|
||||||
);
|
|
||||||
|
|
||||||
return self.nextMenu(cb);
|
return self.nextMenu(cb);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
// ENiGMA½
|
// ENiGMA½
|
||||||
const { MenuModule, MenuFlags } = require('./menu_module.js');
|
const { MenuModule } = require('./menu_module.js');
|
||||||
const messageArea = require('./message_area.js');
|
const messageArea = require('./message_area.js');
|
||||||
const { Errors } = require('./enig_error.js');
|
const { Errors } = require('./enig_error.js');
|
||||||
|
|
||||||
|
@ -26,9 +26,6 @@ exports.getModule = class MessageConfListModule extends MenuModule {
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
super(options);
|
super(options);
|
||||||
|
|
||||||
// always include noHistory flag
|
|
||||||
this.setMergedFlag(MenuFlags.NoHistory);
|
|
||||||
|
|
||||||
this.initList();
|
this.initList();
|
||||||
|
|
||||||
this.menuMethods = {
|
this.menuMethods = {
|
||||||
|
@ -52,7 +49,7 @@ exports.getModule = class MessageConfListModule extends MenuModule {
|
||||||
extraArgs: {
|
extraArgs: {
|
||||||
confTag: conf.confTag,
|
confTag: conf.confTag,
|
||||||
},
|
},
|
||||||
menuFlags: [ MenuFlags.NoHistory ],
|
menuFlags: ['popParent', 'noHistory'],
|
||||||
};
|
};
|
||||||
|
|
||||||
return this.gotoMenu(
|
return this.gotoMenu(
|
||||||
|
|
|
@ -70,14 +70,6 @@ exports.getModule = class MessageListModule extends (
|
||||||
|
|
||||||
this.menuMethods = {
|
this.menuMethods = {
|
||||||
selectMessage: (formData, extraArgs, cb) => {
|
selectMessage: (formData, extraArgs, cb) => {
|
||||||
if (!Array.isArray(this.config?.messageList)) {
|
|
||||||
this.client.log.error(
|
|
||||||
{ formData },
|
|
||||||
'No message list is available to select from!'
|
|
||||||
);
|
|
||||||
return cb(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (MciViewIds.allViews.msgList === formData.submitId) {
|
if (MciViewIds.allViews.msgList === formData.submitId) {
|
||||||
// 'messageIndex' or older deprecated 'message' member
|
// 'messageIndex' or older deprecated 'message' member
|
||||||
this.initialFocusIndex = _.get(
|
this.initialFocusIndex = _.get(
|
||||||
|
@ -323,16 +315,9 @@ exports.getModule = class MessageListModule extends (
|
||||||
let msgNum = 1;
|
let msgNum = 1;
|
||||||
self.config.messageList.forEach((listItem, index) => {
|
self.config.messageList.forEach((listItem, index) => {
|
||||||
listItem.msgNum = msgNum++;
|
listItem.msgNum = msgNum++;
|
||||||
try {
|
listItem.ts = moment(listItem.modTimestamp).format(
|
||||||
listItem.ts = moment(listItem.modTimestamp).format(
|
dateTimeFormat
|
||||||
dateTimeFormat
|
);
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
self.client.log.warn(
|
|
||||||
`Error parsing "${listItem.modTimestamp}"; expected timestamp: ${e.message}`
|
|
||||||
);
|
|
||||||
listItem.ts = moment().format(dateTimeFormat);
|
|
||||||
}
|
|
||||||
const isNew = _.isBoolean(listItem.isNew)
|
const isNew = _.isBoolean(listItem.isNew)
|
||||||
? listItem.isNew
|
? listItem.isNew
|
||||||
: listItem.messageId > self.lastReadId;
|
: listItem.messageId > self.lastReadId;
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
// ENiGMA½
|
// ENiGMA½
|
||||||
const { MenuModule, MenuFlags } = require('./menu_module');
|
const MenuModule = require('./menu_module.js').MenuModule;
|
||||||
const Message = require('./message.js');
|
const Message = require('./message.js');
|
||||||
const UserProps = require('./user_property.js');
|
const UserProps = require('./user_property.js');
|
||||||
const { filterMessageListByReadACS } = require('./message_area.js');
|
const { filterMessageListByReadACS } = require('./message_area.js');
|
||||||
|
@ -16,12 +16,14 @@ exports.moduleInfo = {
|
||||||
exports.getModule = class MyMessagesModule extends MenuModule {
|
exports.getModule = class MyMessagesModule extends MenuModule {
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
super(options);
|
super(options);
|
||||||
this.setMergedFlag(MenuFlags.NoHistory);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
initSequence() {
|
initSequence() {
|
||||||
const filter = {
|
const filter = {
|
||||||
toUserName: [this.client.user.username, this.client.user.realName()],
|
toUserName: [
|
||||||
|
this.client.user.username,
|
||||||
|
this.client.user.getProperty(UserProps.RealName),
|
||||||
|
],
|
||||||
sort: 'modTimestamp',
|
sort: 'modTimestamp',
|
||||||
resultType: 'messageList',
|
resultType: 'messageList',
|
||||||
limit: 1024 * 16, // we want some sort of limit...
|
limit: 1024 * 16, // we want some sort of limit...
|
||||||
|
@ -47,6 +49,7 @@ exports.getModule = class MyMessagesModule extends MenuModule {
|
||||||
if (!this.messageList || 0 === this.messageList.length) {
|
if (!this.messageList || 0 === this.messageList.length) {
|
||||||
return this.gotoMenu(
|
return this.gotoMenu(
|
||||||
this.menuConfig.config.noResultsMenu || 'messageSearchNoResults',
|
this.menuConfig.config.noResultsMenu || 'messageSearchNoResults',
|
||||||
|
{ menuFlags: ['popParent'] }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,6 +58,7 @@ exports.getModule = class MyMessagesModule extends MenuModule {
|
||||||
messageList: this.messageList,
|
messageList: this.messageList,
|
||||||
noUpdateLastReadId: true,
|
noUpdateLastReadId: true,
|
||||||
},
|
},
|
||||||
|
menuFlags: ['popParent'],
|
||||||
};
|
};
|
||||||
|
|
||||||
return this.gotoMenu(
|
return this.gotoMenu(
|
||||||
|
|
15
core/nua.js
15
core/nua.js
|
@ -13,7 +13,6 @@ const UserProps = require('./user_property.js');
|
||||||
|
|
||||||
// deps
|
// deps
|
||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
const moment = require('moment');
|
|
||||||
|
|
||||||
exports.moduleInfo = {
|
exports.moduleInfo = {
|
||||||
name: 'NUA',
|
name: 'NUA',
|
||||||
|
@ -96,15 +95,15 @@ exports.getModule = class NewUserAppModule extends MenuModule {
|
||||||
areaTag = areaTag || '';
|
areaTag = areaTag || '';
|
||||||
|
|
||||||
newUser.properties = {
|
newUser.properties = {
|
||||||
[UserProps.RealName]: formData.value.realName || '',
|
[UserProps.RealName]: formData.value.realName,
|
||||||
[UserProps.Birthdate]: getISOTimestampString(
|
[UserProps.Birthdate]: getISOTimestampString(
|
||||||
formData.value.birthdate || moment()
|
formData.value.birthdate
|
||||||
),
|
),
|
||||||
[UserProps.Sex]: formData.value.sex || '',
|
[UserProps.Sex]: formData.value.sex,
|
||||||
[UserProps.Location]: formData.value.location || '',
|
[UserProps.Location]: formData.value.location,
|
||||||
[UserProps.Affiliations]: formData.value.affils || '',
|
[UserProps.Affiliations]: formData.value.affils,
|
||||||
[UserProps.EmailAddress]: formData.value.email || '',
|
[UserProps.EmailAddress]: formData.value.email,
|
||||||
[UserProps.WebAddress]: formData.value.web || '',
|
[UserProps.WebAddress]: formData.value.web,
|
||||||
[UserProps.AccountCreated]: getISOTimestampString(),
|
[UserProps.AccountCreated]: getISOTimestampString(),
|
||||||
|
|
||||||
[UserProps.MessageConfTag]: confTag,
|
[UserProps.MessageConfTag]: confTag,
|
||||||
|
|
|
@ -182,14 +182,6 @@ General Information:
|
||||||
MessageBase: `usage: oputil.js mb <action> [<arguments>]
|
MessageBase: `usage: oputil.js mb <action> [<arguments>]
|
||||||
|
|
||||||
Actions:
|
Actions:
|
||||||
list-confs List conferences and areas
|
|
||||||
|
|
||||||
post PATH Posts a message file specified in PATH.
|
|
||||||
PATH must point to a UTF-8 encoded JSON file
|
|
||||||
containing 'to', 'from', 'subject', 'areaTag', and
|
|
||||||
'body'. If 'timestamp' is present, the system will
|
|
||||||
attempt to use it.
|
|
||||||
|
|
||||||
areafix CMD1 CMD2 ... ADDR Sends an AreaFix NetMail
|
areafix CMD1 CMD2 ... ADDR Sends an AreaFix NetMail
|
||||||
|
|
||||||
NetMail is sent to supplied address with the supplied command(s). Multi-part commands
|
NetMail is sent to supplied address with the supplied command(s). Multi-part commands
|
||||||
|
@ -202,9 +194,6 @@ Actions:
|
||||||
packet in the directory specified by PATH. The QWK
|
packet in the directory specified by PATH. The QWK
|
||||||
BBS ID will be obtained by the final component of PATH.
|
BBS ID will be obtained by the final component of PATH.
|
||||||
|
|
||||||
list-confs arguments:
|
|
||||||
--areas Include areas within each message conference.
|
|
||||||
|
|
||||||
import-areas arguments:
|
import-areas arguments:
|
||||||
--conf CONF_TAG Conference tag in which to import areas
|
--conf CONF_TAG Conference tag in which to import areas
|
||||||
--network NETWORK Network name/key to associate FTN areas
|
--network NETWORK Network name/key to associate FTN areas
|
||||||
|
|
|
@ -692,162 +692,6 @@ function exportQWKPacket() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const listConferences = () => {
|
|
||||||
initConfigAndDatabases(err => {
|
|
||||||
if (err) {
|
|
||||||
return console.error(err.reason ? err.reason : err.message);
|
|
||||||
}
|
|
||||||
|
|
||||||
const { getSortedAvailMessageConferences } = require('../../core/message_area');
|
|
||||||
|
|
||||||
const conferences = getSortedAvailMessageConferences(null, { noClient: true });
|
|
||||||
|
|
||||||
for (let conf of conferences) {
|
|
||||||
console.info(`${conf.confTag} - ${conf.conf.name}`);
|
|
||||||
|
|
||||||
if (!argv.areas) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let areaTag of Object.keys(conf.conf.areas)) {
|
|
||||||
console.info(` ${areaTag} - ${conf.conf.areas[areaTag].name}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const postMessage = () => {
|
|
||||||
const inputFile = argv._[argv._.length - 1];
|
|
||||||
if (argv._.length < 3 || !inputFile || 0 === inputFile.length) {
|
|
||||||
return printUsageAndSetExitCode(getHelpFor('MessageBase'), ExitCodes.ERROR);
|
|
||||||
}
|
|
||||||
|
|
||||||
async.waterfall(
|
|
||||||
[
|
|
||||||
callback => {
|
|
||||||
return initConfigAndDatabases(callback);
|
|
||||||
},
|
|
||||||
callback => {
|
|
||||||
fs.readFile(inputFile, { encoding: 'utf-8' }, (err, jsonData) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
let messageJson;
|
|
||||||
try {
|
|
||||||
messageJson = JSON.parse(jsonData);
|
|
||||||
} catch (e) {
|
|
||||||
return callback(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let f of ['to', 'from', 'subject', 'body', 'areaTag']) {
|
|
||||||
if (!_.isString(messageJson[f])) {
|
|
||||||
return callback(
|
|
||||||
Errors.MissingConfig(
|
|
||||||
`Missing "${f}" field in message JSON`
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
messageJson[f] = messageJson[f].trim();
|
|
||||||
if (messageJson[f].length === 0 && f !== 'subject') {
|
|
||||||
return callback(
|
|
||||||
Errors.Invalid(
|
|
||||||
`"${messageJson[f]}" is not a valid value for the "${f}" field`
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const { getMessageAreaByTag } = require('../../core/message_area');
|
|
||||||
|
|
||||||
const area = getMessageAreaByTag(messageJson.areaTag);
|
|
||||||
if (!area) {
|
|
||||||
return callback(
|
|
||||||
Errors.DoesNotExist(
|
|
||||||
`Area "${messageJson.areaTag}" does not exist`
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const { getAddressedToInfo } = require('../../core/mail_util');
|
|
||||||
const Message = require('../../core/message');
|
|
||||||
|
|
||||||
const toInfo = getAddressedToInfo(messageJson.to);
|
|
||||||
const fromInfo = getAddressedToInfo(messageJson.from);
|
|
||||||
|
|
||||||
if (fromInfo.flavor !== Message.AddressFlavor.Local) {
|
|
||||||
return callback(
|
|
||||||
Errors.Invalid(
|
|
||||||
'Only local "from" users are currently supported'
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let modTimestamp;
|
|
||||||
if (_.isString(messageJson.timestamp)) {
|
|
||||||
modTimestamp = moment(messageJson.timestamp);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!modTimestamp || !modTimestamp.isValid()) {
|
|
||||||
modTimestamp = moment();
|
|
||||||
}
|
|
||||||
|
|
||||||
const message = new Message({
|
|
||||||
toUserName: messageJson.to,
|
|
||||||
fromUserName: messageJson.from,
|
|
||||||
subject: messageJson.subject,
|
|
||||||
message: messageJson.body,
|
|
||||||
areaTag: messageJson.areaTag,
|
|
||||||
modTimestamp,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (toInfo.flavor !== Message.AddressFlavor.Local) {
|
|
||||||
message.setExternalFlavor(toInfo.flavor);
|
|
||||||
message.setRemoteToUser(toInfo.remote);
|
|
||||||
|
|
||||||
return callback(null, area, message);
|
|
||||||
}
|
|
||||||
|
|
||||||
const User = require('../../core/user');
|
|
||||||
User.getUserIdAndNameByLookup(
|
|
||||||
message.toUserName,
|
|
||||||
(err, toUserId, toUserName) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(
|
|
||||||
Errors.DoesNotExist(
|
|
||||||
`User "${message.toUserName}" does not exist.`
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
message.to = toUserName; // adjust case/etc.
|
|
||||||
message.setLocalToUserId(toUserId);
|
|
||||||
|
|
||||||
return callback(null, area, message);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
(area, message, callback) => {
|
|
||||||
message.persist(err => {
|
|
||||||
if (!err) {
|
|
||||||
console.info(
|
|
||||||
`Message from ${message.fromUserName} to ${message.toUserName}: "${message.subject}" in ${area.name}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return callback(err);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
],
|
|
||||||
err => {
|
|
||||||
if (err) {
|
|
||||||
return console.error(err.reason ? err.reason : err.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
function handleMessageBaseCommand() {
|
function handleMessageBaseCommand() {
|
||||||
function errUsage() {
|
function errUsage() {
|
||||||
return printUsageAndSetExitCode(getHelpFor('MessageBase'), ExitCodes.ERROR);
|
return printUsageAndSetExitCode(getHelpFor('MessageBase'), ExitCodes.ERROR);
|
||||||
|
@ -865,8 +709,6 @@ function handleMessageBaseCommand() {
|
||||||
'import-areas': importAreas,
|
'import-areas': importAreas,
|
||||||
'qwk-dump': dumpQWKPacket,
|
'qwk-dump': dumpQWKPacket,
|
||||||
'qwk-export': exportQWKPacket,
|
'qwk-export': exportQWKPacket,
|
||||||
'list-confs': listConferences,
|
|
||||||
post: postMessage,
|
|
||||||
}[action] || errUsage
|
}[action] || errUsage
|
||||||
)();
|
)();
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,6 @@ const moment = require('moment');
|
||||||
const async = require('async');
|
const async = require('async');
|
||||||
|
|
||||||
exports.getPredefinedMCIValue = getPredefinedMCIValue;
|
exports.getPredefinedMCIValue = getPredefinedMCIValue;
|
||||||
exports.getPredefinedMCIFormatObject = getPredefinedMCIFormatObject;
|
|
||||||
exports.init = init;
|
exports.init = init;
|
||||||
|
|
||||||
function init(cb) {
|
function init(cb) {
|
||||||
|
@ -533,21 +532,3 @@ function getPredefinedMCIValue(client, code, extra) {
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getPredefinedMCIFormatObject(client, text) {
|
|
||||||
const re = /\{([A-Z]{2})(?:[!:][^}]+)?\}/g;
|
|
||||||
let m;
|
|
||||||
const formatObj = {};
|
|
||||||
while ((m = re.exec(text))) {
|
|
||||||
const v = getPredefinedMCIValue(client, m[1]);
|
|
||||||
if (v) {
|
|
||||||
if (!isNaN(v)) {
|
|
||||||
formatObj[m[1]] = parseInt(v);
|
|
||||||
} else {
|
|
||||||
formatObj[m[1]] = v;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return _.isEmpty(formatObj) ? null : formatObj;
|
|
||||||
}
|
|
||||||
|
|
|
@ -172,7 +172,7 @@ exports.getModule = class RumorzModule extends MenuModule {
|
||||||
|
|
||||||
StatLog.getSystemLogEntries(
|
StatLog.getSystemLogEntries(
|
||||||
SystemLogKeys.UserAddedRumorz,
|
SystemLogKeys.UserAddedRumorz,
|
||||||
StatLog.Order.TimestampDesc,
|
StatLog.Order.Timestamp,
|
||||||
(err, entries) => {
|
(err, entries) => {
|
||||||
return callback(err, entriesView, entries);
|
return callback(err, entriesView, entries);
|
||||||
}
|
}
|
||||||
|
|
|
@ -219,8 +219,7 @@ exports.getModule = class MrcModule extends ServerModule {
|
||||||
connectedSockets.forEach(client => {
|
connectedSockets.forEach(client => {
|
||||||
if (
|
if (
|
||||||
message.to_user == '' ||
|
message.to_user == '' ||
|
||||||
// Fix PrivMSG delivery on case mismatch
|
message.to_user == client.username ||
|
||||||
message.to_user.toUpperCase() == client.username.toUpperCase() ||
|
|
||||||
message.to_user == 'CLIENT' ||
|
message.to_user == 'CLIENT' ||
|
||||||
message.from_user == client.username ||
|
message.from_user == client.username ||
|
||||||
message.to_user == 'NOTME'
|
message.to_user == 'NOTME'
|
||||||
|
|
|
@ -557,24 +557,17 @@ exports.getModule = class GopherModule extends ServerModule {
|
||||||
this.makeItem(ItemTypes.InfoMessage, `Messages in ${area.name}`),
|
this.makeItem(ItemTypes.InfoMessage, `Messages in ${area.name}`),
|
||||||
this.makeItem(ItemTypes.InfoMessage, '(newest first)'),
|
this.makeItem(ItemTypes.InfoMessage, '(newest first)'),
|
||||||
this.makeItem(ItemTypes.InfoMessage, '-'.repeat(70)),
|
this.makeItem(ItemTypes.InfoMessage, '-'.repeat(70)),
|
||||||
...msgList.map(msg => {
|
...msgList.map(msg =>
|
||||||
let m;
|
this.makeItem(
|
||||||
try {
|
|
||||||
m = moment(msg.modTimestamp);
|
|
||||||
} catch (e) {
|
|
||||||
this.log.warn(
|
|
||||||
`Error parsing "${msg.modTimestamp}"; expected timestamp: ${e.message}`
|
|
||||||
);
|
|
||||||
m = moment();
|
|
||||||
}
|
|
||||||
return this.makeItem(
|
|
||||||
ItemTypes.TextFile,
|
ItemTypes.TextFile,
|
||||||
`${m.format('YYYY-MM-DD hh:mma')}: ${this.shortenSubject(
|
`${moment(msg.modTimestamp).format(
|
||||||
msg.subject
|
'YYYY-MM-DD hh:mma'
|
||||||
)} (${msg.fromUserName} to ${msg.toUserName})`,
|
)}: ${this.shortenSubject(msg.subject)} (${
|
||||||
|
msg.fromUserName
|
||||||
|
} to ${msg.toUserName})`,
|
||||||
`/msgarea/${confTag}/${areaTag}/${msg.messageUuid}`
|
`/msgarea/${confTag}/${areaTag}/${msg.messageUuid}`
|
||||||
);
|
)
|
||||||
}),
|
),
|
||||||
].join('');
|
].join('');
|
||||||
|
|
||||||
this.log.debug({ confTag, areaTag }, 'Gopher serving message list');
|
this.log.debug({ confTag, areaTag }, 'Gopher serving message list');
|
||||||
|
|
|
@ -15,7 +15,6 @@ const fs = require('graceful-fs');
|
||||||
const paths = require('path');
|
const paths = require('path');
|
||||||
const mimeTypes = require('mime-types');
|
const mimeTypes = require('mime-types');
|
||||||
const forEachSeries = require('async/forEachSeries');
|
const forEachSeries = require('async/forEachSeries');
|
||||||
const findSeries = require('async/findSeries');
|
|
||||||
|
|
||||||
const ModuleInfo = (exports.moduleInfo = {
|
const ModuleInfo = (exports.moduleInfo = {
|
||||||
name: 'Web',
|
name: 'Web',
|
||||||
|
@ -75,6 +74,14 @@ exports.getModule = class WebServerModule extends ServerModule {
|
||||||
this.enableHttps = config.contentServers.web.https.enabled || false;
|
this.enableHttps = config.contentServers.web.https.enabled || false;
|
||||||
|
|
||||||
this.routes = {};
|
this.routes = {};
|
||||||
|
|
||||||
|
if (this.isEnabled() && config.contentServers.web.staticRoot) {
|
||||||
|
this.addRoute({
|
||||||
|
method: 'GET',
|
||||||
|
path: '/static/.*$',
|
||||||
|
handler: this.routeStaticFile.bind(this),
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
buildUrl(pathAndQuery) {
|
buildUrl(pathAndQuery) {
|
||||||
|
@ -203,21 +210,13 @@ exports.getModule = class WebServerModule extends ServerModule {
|
||||||
}
|
}
|
||||||
|
|
||||||
routeRequest(req, resp) {
|
routeRequest(req, resp) {
|
||||||
let route = _.find(this.routes, r => r.matchesRequest(req));
|
const route = _.find(this.routes, r => r.matchesRequest(req));
|
||||||
|
|
||||||
if (route) {
|
if (!route && '/' === req.url) {
|
||||||
return route.handler(req, resp);
|
return this.routeIndex(req, resp);
|
||||||
} else {
|
|
||||||
this.tryStaticRoute(req, resp, wasHandled => {
|
|
||||||
if (!wasHandled) {
|
|
||||||
this.tryRouteIndex(req, resp, wasHandled => {
|
|
||||||
if (!wasHandled) {
|
|
||||||
return this.fileNotFound(resp);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return route ? route.handler(req, resp) : this.accessDenied(resp);
|
||||||
}
|
}
|
||||||
|
|
||||||
respondWithError(resp, code, bodyText, title) {
|
respondWithError(resp, code, bodyText, title) {
|
||||||
|
@ -257,57 +256,27 @@ exports.getModule = class WebServerModule extends ServerModule {
|
||||||
return this.respondWithError(resp, 404, 'File not found.', 'File Not Found');
|
return this.respondWithError(resp, 404, 'File not found.', 'File Not Found');
|
||||||
}
|
}
|
||||||
|
|
||||||
tryRouteIndex(req, resp, cb) {
|
routeIndex(req, resp) {
|
||||||
const tryFiles = Config().contentServers.web.tryFiles || [
|
const filePath = paths.join(Config().contentServers.web.staticRoot, 'index.html');
|
||||||
'index.html',
|
return this.returnStaticPage(filePath, resp);
|
||||||
'index.htm',
|
|
||||||
];
|
|
||||||
|
|
||||||
findSeries(
|
|
||||||
tryFiles,
|
|
||||||
(tryFile, nextTryFile) => {
|
|
||||||
const fileName = paths.join(
|
|
||||||
req.url.substr(req.url.lastIndexOf('/', 1)),
|
|
||||||
tryFile
|
|
||||||
);
|
|
||||||
const filePath = this.resolveStaticPath(fileName);
|
|
||||||
|
|
||||||
fs.stat(filePath, (err, stats) => {
|
|
||||||
if (err || !stats.isFile()) {
|
|
||||||
return nextTryFile(null, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
const headers = {
|
|
||||||
'Content-Type':
|
|
||||||
mimeTypes.contentType(paths.basename(filePath)) ||
|
|
||||||
mimeTypes.contentType('.bin'),
|
|
||||||
'Content-Length': stats.size,
|
|
||||||
};
|
|
||||||
|
|
||||||
const readStream = fs.createReadStream(filePath);
|
|
||||||
resp.writeHead(200, headers);
|
|
||||||
readStream.pipe(resp);
|
|
||||||
|
|
||||||
return nextTryFile(null, true);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
(_, wasHandled) => {
|
|
||||||
return cb(wasHandled);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tryStaticRoute(req, resp, cb) {
|
routeStaticFile(req, resp) {
|
||||||
const fileName = req.url.substr(req.url.lastIndexOf('/', 1));
|
const fileName = req.url.substr(req.url.indexOf('/', 1));
|
||||||
const filePath = this.resolveStaticPath(fileName);
|
const filePath = this.resolveStaticPath(fileName);
|
||||||
|
return this.returnStaticPage(filePath, resp);
|
||||||
|
}
|
||||||
|
|
||||||
|
returnStaticPage(filePath, resp) {
|
||||||
|
const self = this;
|
||||||
|
|
||||||
if (!filePath) {
|
if (!filePath) {
|
||||||
return cb(false);
|
return this.fileNotFound(resp);
|
||||||
}
|
}
|
||||||
|
|
||||||
fs.stat(filePath, (err, stats) => {
|
fs.stat(filePath, (err, stats) => {
|
||||||
if (err || !stats.isFile()) {
|
if (err || !stats.isFile()) {
|
||||||
return cb(false);
|
return self.fileNotFound(resp);
|
||||||
}
|
}
|
||||||
|
|
||||||
const headers = {
|
const headers = {
|
||||||
|
@ -319,9 +288,7 @@ exports.getModule = class WebServerModule extends ServerModule {
|
||||||
|
|
||||||
const readStream = fs.createReadStream(filePath);
|
const readStream = fs.createReadStream(filePath);
|
||||||
resp.writeHead(200, headers);
|
resp.writeHead(200, headers);
|
||||||
readStream.pipe(resp);
|
return readStream.pipe(resp);
|
||||||
|
|
||||||
return cb(true);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
// ENiGMA½
|
// ENiGMA½
|
||||||
const MenuModule = require('./menu_module.js').MenuModule;
|
const MenuModule = require('./menu_module.js').MenuModule;
|
||||||
const Errors = require('../core/enig_error.js').Errors;
|
const Errors = require('../core/enig_error.js').Errors;
|
||||||
|
const ANSI = require('./ansi_term.js');
|
||||||
const Config = require('./config.js').get;
|
const Config = require('./config.js').get;
|
||||||
const { getMessageAreaByTag } = require('./message_area.js');
|
const { getMessageAreaByTag } = require('./message_area.js');
|
||||||
|
|
||||||
|
@ -20,7 +21,6 @@ exports.moduleInfo = {
|
||||||
exports.getModule = class ShowArtModule extends MenuModule {
|
exports.getModule = class ShowArtModule extends MenuModule {
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
super(options);
|
super(options);
|
||||||
|
|
||||||
this.config = Object.assign({}, _.get(options, 'menuConfig.config'), {
|
this.config = Object.assign({}, _.get(options, 'menuConfig.config'), {
|
||||||
extraArgs: options.extraArgs,
|
extraArgs: options.extraArgs,
|
||||||
});
|
});
|
||||||
|
|
|
@ -21,10 +21,8 @@ exports.validateEmailAvail = validateEmailAvail;
|
||||||
exports.validateBirthdate = validateBirthdate;
|
exports.validateBirthdate = validateBirthdate;
|
||||||
exports.validatePasswordSpec = validatePasswordSpec;
|
exports.validatePasswordSpec = validatePasswordSpec;
|
||||||
|
|
||||||
const emptyFieldError = () => new Error('Field cannot be empty');
|
|
||||||
|
|
||||||
function validateNonEmpty(data, cb) {
|
function validateNonEmpty(data, cb) {
|
||||||
return cb(data && data.length > 0 ? null : emptyFieldError);
|
return cb(data && data.length > 0 ? null : new Error('Field cannot be empty'));
|
||||||
}
|
}
|
||||||
|
|
||||||
function validateMessageSubject(data, cb) {
|
function validateMessageSubject(data, cb) {
|
||||||
|
@ -93,11 +91,7 @@ function validateGeneralMailAddressedTo(data, cb) {
|
||||||
// :TODO: remove hard-coded FTN check here. We need a decent way to register global supported flavors with modules.
|
// :TODO: remove hard-coded FTN check here. We need a decent way to register global supported flavors with modules.
|
||||||
const addressedToInfo = getAddressedToInfo(data);
|
const addressedToInfo = getAddressedToInfo(data);
|
||||||
|
|
||||||
if (addressedToInfo.name.length === 0) {
|
if (Message.AddressFlavor.FTN === addressedToInfo.flavor) {
|
||||||
return cb(emptyFieldError());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Message.AddressFlavor.Local !== addressedToInfo.flavor) {
|
|
||||||
return cb(null);
|
return cb(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,9 +11,6 @@ const renderSubstr = require('./string_util.js').renderSubstr;
|
||||||
const renderStringLength = require('./string_util.js').renderStringLength;
|
const renderStringLength = require('./string_util.js').renderStringLength;
|
||||||
const pipeToAnsi = require('./color_codes.js').pipeToAnsi;
|
const pipeToAnsi = require('./color_codes.js').pipeToAnsi;
|
||||||
const stripAllLineFeeds = require('./string_util.js').stripAllLineFeeds;
|
const stripAllLineFeeds = require('./string_util.js').stripAllLineFeeds;
|
||||||
const getPredefinedMCIFormatObject =
|
|
||||||
require('./predefined_mci').getPredefinedMCIFormatObject;
|
|
||||||
const stringFormat = require('./string_format');
|
|
||||||
|
|
||||||
// deps
|
// deps
|
||||||
const util = require('util');
|
const util = require('util');
|
||||||
|
@ -156,12 +153,6 @@ TextView.prototype.setText = function (text, redraw) {
|
||||||
text = text.toString();
|
text = text.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
const formatObj = getPredefinedMCIFormatObject(this.client, text);
|
|
||||||
if (formatObj) {
|
|
||||||
// expand before converting
|
|
||||||
text = stringFormat(text, formatObj);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.text = pipeToAnsi(stripAllLineFeeds(text), this.client); // expand MCI/etc.
|
this.text = pipeToAnsi(stripAllLineFeeds(text), this.client); // expand MCI/etc.
|
||||||
if (this.maxLength > 0) {
|
if (this.maxLength > 0) {
|
||||||
this.text = renderSubstr(this.text, 0, this.maxLength);
|
this.text = renderSubstr(this.text, 0, this.maxLength);
|
||||||
|
@ -179,10 +170,6 @@ TextView.prototype.setText = function (text, redraw) {
|
||||||
};
|
};
|
||||||
|
|
||||||
TextView.prototype.clearText = function () {
|
TextView.prototype.clearText = function () {
|
||||||
if (this.text) {
|
|
||||||
this.setText(this.fillChar.repeat(this.text.length));
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setText('');
|
this.setText('');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
// enigma-bbs
|
// enigma-bbs
|
||||||
const { MenuModule, MenuFlags } = require('./menu_module');
|
const MenuModule = require('./menu_module.js').MenuModule;
|
||||||
const stringFormat = require('./string_format.js');
|
const stringFormat = require('./string_format.js');
|
||||||
const getSortedAvailableFileAreas =
|
const getSortedAvailableFileAreas =
|
||||||
require('./file_base_area.js').getSortedAvailableFileAreas;
|
require('./file_base_area.js').getSortedAvailableFileAreas;
|
||||||
|
@ -76,8 +76,6 @@ exports.getModule = class UploadModule extends MenuModule {
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
super(options);
|
super(options);
|
||||||
|
|
||||||
this.setMergedFlag(MenuFlags.NoHistory);
|
|
||||||
|
|
||||||
this.interrupt = MenuModule.InterruptTypes.Never;
|
this.interrupt = MenuModule.InterruptTypes.Never;
|
||||||
|
|
||||||
if (_.has(options, 'lastMenuResult.recvFilePaths')) {
|
if (_.has(options, 'lastMenuResult.recvFilePaths')) {
|
||||||
|
|
21
core/user.js
21
core/user.js
|
@ -124,29 +124,12 @@ module.exports = class User {
|
||||||
return isMember;
|
return isMember;
|
||||||
}
|
}
|
||||||
|
|
||||||
realName(withUsernameFallback = true) {
|
|
||||||
const realName = this.getProperty(UserProps.RealName);
|
|
||||||
if (realName) {
|
|
||||||
return realName;
|
|
||||||
}
|
|
||||||
if (withUsernameFallback) {
|
|
||||||
return this.username;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getSanitizedName(type = 'username') {
|
getSanitizedName(type = 'username') {
|
||||||
const name = 'real' === type ? this.realName(true) : this.username;
|
const name =
|
||||||
|
'real' === type ? this.getProperty(UserProps.RealName) : this.username;
|
||||||
return sanatizeFilename(name) || `user${this.userId.toString()}`;
|
return sanatizeFilename(name) || `user${this.userId.toString()}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
emailAddress() {
|
|
||||||
const email = this.getProperty(UserProps.EmailAddress);
|
|
||||||
if (email) {
|
|
||||||
const realName = this.realName(false);
|
|
||||||
return realName ? `${realName} <${email}>` : email;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
isAvailable() {
|
isAvailable() {
|
||||||
return (this.statusFlags & User.StatusFlags.NotAvailable) == 0;
|
return (this.statusFlags & User.StatusFlags.NotAvailable) == 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -76,7 +76,7 @@ module.exports = class User2FA_OTPWebRegister {
|
||||||
(token, textTemplate, htmlTemplate, callback) => {
|
(token, textTemplate, htmlTemplate, callback) => {
|
||||||
const webServer = getWebServer();
|
const webServer = getWebServer();
|
||||||
const registerUrl = webServer.instance.buildUrl(
|
const registerUrl = webServer.instance.buildUrl(
|
||||||
`/_internal/enable_2fa_otp?token=${token}&otpType=${otpType}`
|
`/enable_2fa_otp?token=${token}&otpType=${otpType}`
|
||||||
);
|
);
|
||||||
|
|
||||||
const replaceTokens = s => {
|
const replaceTokens = s => {
|
||||||
|
@ -93,7 +93,9 @@ module.exports = class User2FA_OTPWebRegister {
|
||||||
}
|
}
|
||||||
|
|
||||||
const message = {
|
const message = {
|
||||||
to: user.emailAddress(),
|
to: `${
|
||||||
|
user.getProperty(UserProps.RealName) || user.username
|
||||||
|
} <${user.getProperty(UserProps.EmailAddress)}>`,
|
||||||
// from will be filled in
|
// from will be filled in
|
||||||
subject: '2-Factor Authentication Registration',
|
subject: '2-Factor Authentication Registration',
|
||||||
text: textTemplate,
|
text: textTemplate,
|
||||||
|
@ -168,7 +170,7 @@ module.exports = class User2FA_OTPWebRegister {
|
||||||
return User2FA_OTPWebRegister.accessDenied(webServer, resp);
|
return User2FA_OTPWebRegister.accessDenied(webServer, resp);
|
||||||
}
|
}
|
||||||
|
|
||||||
const postUrl = webServer.instance.buildUrl('/_internal/enable_2fa_otp');
|
const postUrl = webServer.instance.buildUrl('/enable_2fa_otp');
|
||||||
const config = Config();
|
const config = Config();
|
||||||
return webServer.instance.routeTemplateFilePage(
|
return webServer.instance.routeTemplateFilePage(
|
||||||
_.get(config, 'users.twoFactorAuth.otp.registerPageTemplate'),
|
_.get(config, 'users.twoFactorAuth.otp.registerPageTemplate'),
|
||||||
|
@ -294,12 +296,12 @@ ${backupCodes}
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
path: /^\/_internal\/enable_2fa_otp\?token=[a-f0-9]+&otpType=[a-zA-Z0-9_]+$/,
|
path: '^\\/enable_2fa_otp\\?token\\=[a-f0-9]+&otpType\\=[a-zA-Z0-9_]+$',
|
||||||
handler: User2FA_OTPWebRegister.routeRegisterGet,
|
handler: User2FA_OTPWebRegister.routeRegisterGet,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
path: /^\/_internal\/enable_2fa_otp$/,
|
path: '^\\/enable_2fa_otp$',
|
||||||
handler: User2FA_OTPWebRegister.routeRegisterPost,
|
handler: User2FA_OTPWebRegister.routeRegisterPost,
|
||||||
},
|
},
|
||||||
].forEach(r => {
|
].forEach(r => {
|
||||||
|
|
|
@ -8,10 +8,10 @@ const theme = require('./theme.js');
|
||||||
const sysValidate = require('./system_view_validate.js');
|
const sysValidate = require('./system_view_validate.js');
|
||||||
const UserProps = require('./user_property.js');
|
const UserProps = require('./user_property.js');
|
||||||
const { getISOTimestampString } = require('./database.js');
|
const { getISOTimestampString } = require('./database.js');
|
||||||
const EnigAssert = require('./enigma_assert');
|
|
||||||
|
|
||||||
// deps
|
// deps
|
||||||
const async = require('async');
|
const async = require('async');
|
||||||
|
const assert = require('assert');
|
||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
const moment = require('moment');
|
const moment = require('moment');
|
||||||
|
|
||||||
|
@ -109,27 +109,24 @@ exports.getModule = class UserConfigModule extends MenuModule {
|
||||||
// Handlers
|
// Handlers
|
||||||
//
|
//
|
||||||
saveChanges: function (formData, extraArgs, cb) {
|
saveChanges: function (formData, extraArgs, cb) {
|
||||||
EnigAssert(formData.value.password === formData.value.passwordConfirm);
|
assert(formData.value.password === formData.value.passwordConfirm);
|
||||||
|
|
||||||
// cache a copy of |formData| as changing a theme below can invalidate it
|
|
||||||
formData = _.clone(formData);
|
|
||||||
|
|
||||||
const newProperties = {
|
const newProperties = {
|
||||||
[UserProps.RealName]: formData.value.realName || '',
|
[UserProps.RealName]: formData.value.realName,
|
||||||
[UserProps.Birthdate]: getISOTimestampString(
|
[UserProps.Birthdate]: getISOTimestampString(
|
||||||
formData.value.birthdate || moment()
|
formData.value.birthdate
|
||||||
),
|
),
|
||||||
[UserProps.Sex]: formData.value.sex || '',
|
[UserProps.Sex]: formData.value.sex,
|
||||||
[UserProps.Location]: formData.value.location || '',
|
[UserProps.Location]: formData.value.location,
|
||||||
[UserProps.Affiliations]: formData.value.affils || '',
|
[UserProps.Affiliations]: formData.value.affils,
|
||||||
[UserProps.EmailAddress]: formData.value.email || '',
|
[UserProps.EmailAddress]: formData.value.email,
|
||||||
[UserProps.WebAddress]: formData.value.web || '',
|
[UserProps.WebAddress]: formData.value.web,
|
||||||
[UserProps.TermHeight]: formData.value.termHeight.toString(),
|
[UserProps.TermHeight]: formData.value.termHeight.toString(),
|
||||||
[UserProps.ThemeId]:
|
[UserProps.ThemeId]:
|
||||||
self.availThemeInfo[formData.value.theme].themeId,
|
self.availThemeInfo[formData.value.theme].themeId,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Runtime set theme
|
// runtime set theme
|
||||||
theme.setClientTheme(self.client, newProperties.theme_id);
|
theme.setClientTheme(self.client, newProperties.theme_id);
|
||||||
|
|
||||||
// persist all changes
|
// persist all changes
|
||||||
|
@ -233,7 +230,11 @@ exports.getModule = class UserConfigModule extends MenuModule {
|
||||||
function populateViews(callback) {
|
function populateViews(callback) {
|
||||||
const user = self.client.user;
|
const user = self.client.user;
|
||||||
|
|
||||||
self.setViewText('menu', MciCodeIds.RealName, user.realName(false) || '');
|
self.setViewText(
|
||||||
|
'menu',
|
||||||
|
MciCodeIds.RealName,
|
||||||
|
user.properties[UserProps.RealName]
|
||||||
|
);
|
||||||
self.setViewText(
|
self.setViewText(
|
||||||
'menu',
|
'menu',
|
||||||
MciCodeIds.BirthDate,
|
MciCodeIds.BirthDate,
|
||||||
|
|
|
@ -22,7 +22,7 @@ const _ = require('lodash');
|
||||||
|
|
||||||
const PW_RESET_EMAIL_TEXT_TEMPLATE_DEFAULT = `%USERNAME%:
|
const PW_RESET_EMAIL_TEXT_TEMPLATE_DEFAULT = `%USERNAME%:
|
||||||
A password reset has been requested for your account on %BOARDNAME%.
|
A password reset has been requested for your account on %BOARDNAME%.
|
||||||
|
|
||||||
* If this was not you, please ignore this email.
|
* If this was not you, please ignore this email.
|
||||||
* Otherwise, follow this link: %RESET_URL%
|
* Otherwise, follow this link: %RESET_URL%
|
||||||
`;
|
`;
|
||||||
|
@ -121,7 +121,7 @@ class WebPasswordReset {
|
||||||
const sendMail = require('./email.js').sendMail;
|
const sendMail = require('./email.js').sendMail;
|
||||||
|
|
||||||
const resetUrl = webServer.instance.buildUrl(
|
const resetUrl = webServer.instance.buildUrl(
|
||||||
`/_internal/reset_password?token=${
|
`/reset_password?token=${
|
||||||
user.properties[UserProps.EmailPwResetToken]
|
user.properties[UserProps.EmailPwResetToken]
|
||||||
}`
|
}`
|
||||||
);
|
);
|
||||||
|
@ -143,7 +143,9 @@ class WebPasswordReset {
|
||||||
}
|
}
|
||||||
|
|
||||||
const message = {
|
const message = {
|
||||||
to: user.emailAddress(),
|
to: `${user.properties[UserProps.RealName] || user.username} <${
|
||||||
|
user.properties[UserProps.EmailAddress]
|
||||||
|
}>`,
|
||||||
// from will be filled in
|
// from will be filled in
|
||||||
subject: 'Forgot Password',
|
subject: 'Forgot Password',
|
||||||
text: textTemplate,
|
text: textTemplate,
|
||||||
|
@ -192,13 +194,13 @@ class WebPasswordReset {
|
||||||
{
|
{
|
||||||
// this is the page displayed to user when they GET it
|
// this is the page displayed to user when they GET it
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
path: /^\/_internal\/reset_password\?token=[a-f0-9]+$/,
|
path: '^\\/reset_password\\?token\\=[a-f0-9]+$', // Config.contentServers.web.forgotPasswordPageTemplate
|
||||||
handler: WebPasswordReset.routeResetPasswordGet,
|
handler: WebPasswordReset.routeResetPasswordGet,
|
||||||
},
|
},
|
||||||
// POST handler for performing the actual reset
|
// POST handler for performing the actual reset
|
||||||
{
|
{
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
path: /^\/_internal\/reset_password$/,
|
path: '^\\/reset_password$',
|
||||||
handler: WebPasswordReset.routeResetPasswordPost,
|
handler: WebPasswordReset.routeResetPasswordPost,
|
||||||
},
|
},
|
||||||
].forEach(r => {
|
].forEach(r => {
|
||||||
|
@ -267,7 +269,7 @@ class WebPasswordReset {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const postResetUrl = webServer.instance.buildUrl('/_internal/reset_password');
|
const postResetUrl = webServer.instance.buildUrl('/reset_password');
|
||||||
|
|
||||||
const config = Config();
|
const config = Config();
|
||||||
return webServer.instance.routeTemplateFilePage(
|
return webServer.instance.routeTemplateFilePage(
|
||||||
|
|
|
@ -89,7 +89,6 @@ exports.getModule = class WaitingForCallerModule extends MenuModule {
|
||||||
toggleVisible: (formData, extraArgs, cb) => {
|
toggleVisible: (formData, extraArgs, cb) => {
|
||||||
const visible = this.client.user.isVisible();
|
const visible = this.client.user.isVisible();
|
||||||
this.client.user.setVisibility(!visible);
|
this.client.user.setVisibility(!visible);
|
||||||
this.visibilityToggled = true; // we won't restore it in this case
|
|
||||||
return this._refreshAll(cb);
|
return this._refreshAll(cb);
|
||||||
},
|
},
|
||||||
displayHelp: (formData, extraArgs, cb) => {
|
displayHelp: (formData, extraArgs, cb) => {
|
||||||
|
@ -367,9 +366,7 @@ exports.getModule = class WaitingForCallerModule extends MenuModule {
|
||||||
}
|
}
|
||||||
|
|
||||||
_restoreOpVisibility() {
|
_restoreOpVisibility() {
|
||||||
if (!this.visibilityToggled) {
|
this.client.user.setVisibility(this.restoreUserIsVisible);
|
||||||
this.client.user.setVisibility(this.restoreUserIsVisible);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_startRefreshing() {
|
_startRefreshing() {
|
||||||
|
@ -506,7 +503,9 @@ exports.getModule = class WaitingForCallerModule extends MenuModule {
|
||||||
|
|
||||||
// Current
|
// Current
|
||||||
currentUserName: this.client.user.username,
|
currentUserName: this.client.user.username,
|
||||||
currentUserRealName: this.client.user.realName(false) || 'N/A',
|
currentUserRealName:
|
||||||
|
this.client.user.getProperty(UserProps.RealName) ||
|
||||||
|
this.client.user.username,
|
||||||
availIndicator: availIndicator,
|
availIndicator: availIndicator,
|
||||||
visIndicator: visIndicator,
|
visIndicator: visIndicator,
|
||||||
lastLoginUserName: lastLoginStats.userName,
|
lastLoginUserName: lastLoginStats.userName,
|
||||||
|
|
|
@ -1,25 +1,19 @@
|
||||||
FROM --platform=${BUILDPLATFORM:-linux/amd64} node:20-bookworm-slim
|
FROM node:14-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 \
|
||||||
|
@ -27,22 +21,8 @@ RUN apt-get update \
|
||||||
lhasa \
|
lhasa \
|
||||||
unrar-free \
|
unrar-free \
|
||||||
p7zip-full \
|
p7zip-full \
|
||||||
dos2unix \
|
|
||||||
&& npm set progress=false && npm config set depth 0 \
|
|
||||||
&& npm install -g npm@latest \
|
|
||||||
&& npm install -g pm2 \
|
&& npm install -g pm2 \
|
||||||
&& cd /enigma-bbs && npm install
|
&& cd /enigma-bbs && npm install --only=production \
|
||||||
|
|
||||||
|
|
||||||
# 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 +30,15 @@ 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 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
|
||||||
|
|
|
@ -30,4 +30,3 @@ end
|
||||||
# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
|
# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
|
||||||
gem "tzinfo-data", platforms: [:mingw, :mswin, :x64_mingw, :jruby]
|
gem "tzinfo-data", platforms: [:mingw, :mswin, :x64_mingw, :jruby]
|
||||||
|
|
||||||
gem "webrick"
|
|
|
@ -1,15 +1,15 @@
|
||||||
GEM
|
GEM
|
||||||
remote: https://rubygems.org/
|
remote: https://rubygems.org/
|
||||||
specs:
|
specs:
|
||||||
activesupport (7.0.7.2)
|
activesupport (7.0.1)
|
||||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||||
i18n (>= 1.6, < 2)
|
i18n (>= 1.6, < 2)
|
||||||
minitest (>= 5.1)
|
minitest (>= 5.1)
|
||||||
tzinfo (~> 2.0)
|
tzinfo (~> 2.0)
|
||||||
addressable (2.8.5)
|
addressable (2.8.0)
|
||||||
public_suffix (>= 2.0.2, < 6.0)
|
public_suffix (>= 2.0.2, < 5.0)
|
||||||
colorator (1.1.0)
|
colorator (1.1.0)
|
||||||
concurrent-ruby (1.2.2)
|
concurrent-ruby (1.1.9)
|
||||||
cssminify2 (2.0.1)
|
cssminify2 (2.0.1)
|
||||||
em-websocket (0.5.3)
|
em-websocket (0.5.3)
|
||||||
eventmachine (>= 0.12.9)
|
eventmachine (>= 0.12.9)
|
||||||
|
@ -19,14 +19,14 @@ GEM
|
||||||
ffi (1.15.5)
|
ffi (1.15.5)
|
||||||
forwardable-extended (2.6.0)
|
forwardable-extended (2.6.0)
|
||||||
gemoji (3.0.1)
|
gemoji (3.0.1)
|
||||||
html-pipeline (2.14.3)
|
html-pipeline (2.14.0)
|
||||||
activesupport (>= 2)
|
activesupport (>= 2)
|
||||||
nokogiri (>= 1.4)
|
nokogiri (>= 1.4)
|
||||||
htmlcompressor (0.4.0)
|
htmlcompressor (0.4.0)
|
||||||
http_parser.rb (0.8.0)
|
http_parser.rb (0.8.0)
|
||||||
i18n (1.14.1)
|
i18n (1.9.1)
|
||||||
concurrent-ruby (~> 1.0)
|
concurrent-ruby (~> 1.0)
|
||||||
jekyll (4.2.2)
|
jekyll (4.2.1)
|
||||||
addressable (~> 2.4)
|
addressable (~> 2.4)
|
||||||
colorator (~> 1.0)
|
colorator (~> 1.0)
|
||||||
em-websocket (~> 0.5)
|
em-websocket (~> 0.5)
|
||||||
|
@ -49,7 +49,7 @@ GEM
|
||||||
uglifier (~> 4.1)
|
uglifier (~> 4.1)
|
||||||
jekyll-relative-links (0.6.1)
|
jekyll-relative-links (0.6.1)
|
||||||
jekyll (>= 3.3, < 5.0)
|
jekyll (>= 3.3, < 5.0)
|
||||||
jekyll-sass-converter (2.2.0)
|
jekyll-sass-converter (2.1.0)
|
||||||
sassc (> 2.0.1, < 3.0)
|
sassc (> 2.0.1, < 3.0)
|
||||||
jekyll-seo-tag (2.7.1)
|
jekyll-seo-tag (2.7.1)
|
||||||
jekyll (>= 3.8, < 5.0)
|
jekyll (>= 3.8, < 5.0)
|
||||||
|
@ -64,46 +64,42 @@ GEM
|
||||||
gemoji (~> 3.0)
|
gemoji (~> 3.0)
|
||||||
html-pipeline (~> 2.2)
|
html-pipeline (~> 2.2)
|
||||||
jekyll (>= 3.0, < 5.0)
|
jekyll (>= 3.0, < 5.0)
|
||||||
json (2.6.3)
|
json (2.6.1)
|
||||||
json-minify (0.0.3)
|
json-minify (0.0.3)
|
||||||
json (> 0)
|
json (> 0)
|
||||||
kramdown (2.4.0)
|
kramdown (2.3.1)
|
||||||
rexml
|
rexml
|
||||||
kramdown-parser-gfm (1.1.0)
|
kramdown-parser-gfm (1.1.0)
|
||||||
kramdown (~> 2.0)
|
kramdown (~> 2.0)
|
||||||
liquid (4.0.4)
|
liquid (4.0.3)
|
||||||
listen (3.8.0)
|
listen (3.7.1)
|
||||||
rb-fsevent (~> 0.10, >= 0.10.3)
|
rb-fsevent (~> 0.10, >= 0.10.3)
|
||||||
rb-inotify (~> 0.9, >= 0.9.10)
|
rb-inotify (~> 0.9, >= 0.9.10)
|
||||||
mercenary (0.4.0)
|
mercenary (0.4.0)
|
||||||
minitest (5.19.0)
|
minitest (5.15.0)
|
||||||
nokogiri (1.15.4-aarch64-linux)
|
nokogiri (1.13.6-x86_64-linux)
|
||||||
racc (~> 1.4)
|
|
||||||
nokogiri (1.15.4-x86_64-linux)
|
|
||||||
racc (~> 1.4)
|
racc (~> 1.4)
|
||||||
pathutil (0.16.2)
|
pathutil (0.16.2)
|
||||||
forwardable-extended (~> 2.6)
|
forwardable-extended (~> 2.6)
|
||||||
public_suffix (5.0.3)
|
public_suffix (4.0.6)
|
||||||
racc (1.7.1)
|
racc (1.6.0)
|
||||||
rb-fsevent (0.11.2)
|
rb-fsevent (0.11.0)
|
||||||
rb-inotify (0.10.1)
|
rb-inotify (0.10.1)
|
||||||
ffi (~> 1.0)
|
ffi (~> 1.0)
|
||||||
rexml (3.2.6)
|
rexml (3.2.5)
|
||||||
rouge (3.30.0)
|
rouge (3.28.0)
|
||||||
safe_yaml (1.0.5)
|
safe_yaml (1.0.5)
|
||||||
sassc (2.4.0)
|
sassc (2.4.0)
|
||||||
ffi (~> 1.9)
|
ffi (~> 1.9)
|
||||||
terminal-table (2.0.0)
|
terminal-table (2.0.0)
|
||||||
unicode-display_width (~> 1.1, >= 1.1.1)
|
unicode-display_width (~> 1.1, >= 1.1.1)
|
||||||
tzinfo (2.0.6)
|
tzinfo (2.0.4)
|
||||||
concurrent-ruby (~> 1.0)
|
concurrent-ruby (~> 1.0)
|
||||||
uglifier (4.2.0)
|
uglifier (4.2.0)
|
||||||
execjs (>= 0.3.0, < 3)
|
execjs (>= 0.3.0, < 3)
|
||||||
unicode-display_width (1.8.0)
|
unicode-display_width (1.8.0)
|
||||||
webrick (1.8.1)
|
|
||||||
|
|
||||||
PLATFORMS
|
PLATFORMS
|
||||||
aarch64-linux
|
|
||||||
x86_64-linux
|
x86_64-linux
|
||||||
|
|
||||||
DEPENDENCIES
|
DEPENDENCIES
|
||||||
|
@ -115,7 +111,6 @@ DEPENDENCIES
|
||||||
jekyll-theme-hacker (~> 0.2.0)
|
jekyll-theme-hacker (~> 0.2.0)
|
||||||
jemoji (~> 0.12.0)
|
jemoji (~> 0.12.0)
|
||||||
tzinfo-data
|
tzinfo-data
|
||||||
webrick
|
|
||||||
|
|
||||||
BUNDLED WITH
|
BUNDLED WITH
|
||||||
2.4.19
|
2.3.5
|
||||||
|
|
|
@ -54,7 +54,6 @@ collections:
|
||||||
- installation/network.md
|
- installation/network.md
|
||||||
- installation/testing.md
|
- installation/testing.md
|
||||||
- installation/production.md
|
- installation/production.md
|
||||||
- installation/development.md
|
|
||||||
- configuration/creating-config.md
|
- configuration/creating-config.md
|
||||||
- configuration/sysop-setup.md
|
- configuration/sysop-setup.md
|
||||||
- configuration/config-files.md
|
- configuration/config-files.md
|
||||||
|
@ -93,6 +92,7 @@ collections:
|
||||||
- art/views/horizontal_menu_view.md
|
- art/views/horizontal_menu_view.md
|
||||||
- art/views/mask_edit_text_view.md
|
- art/views/mask_edit_text_view.md
|
||||||
- art/views/multi_line_edit_text_view.md
|
- art/views/multi_line_edit_text_view.md
|
||||||
|
- art/views/predefined_label_view.md
|
||||||
- art/views/spinner_menu_view.md
|
- art/views/spinner_menu_view.md
|
||||||
- art/views/text_view.md
|
- art/views/text_view.md
|
||||||
- art/views/toggle_menu_view.md
|
- art/views/toggle_menu_view.md
|
||||||
|
@ -131,5 +131,4 @@ collections:
|
||||||
- admin/oputil.md
|
- admin/oputil.md
|
||||||
- admin/updating.md
|
- admin/updating.md
|
||||||
- troubleshooting/monitoring-logs.md
|
- troubleshooting/monitoring-logs.md
|
||||||
- troubleshooting/ssh-troubleshooting.md
|
|
||||||
|
|
||||||
|
|
|
@ -323,7 +323,7 @@ qwk-export arguments:
|
||||||
|
|
||||||
| Action | Description | Examples |
|
| Action | Description | Examples |
|
||||||
|-----------|-------------------|---------------------------------------|
|
|-----------|-------------------|---------------------------------------|
|
||||||
| `import-areas` | Imports areas using a FidoNet style *.NA or AREAS.BBS formatted file. Optionally maps areas to FTN networks. | `./oputil.js mb import-areas /some/path/l33tnet.na` |
|
| `import-areas` | Imports areas using a FidoNet style *.NA or AREAS.BBS formatted file. Optionally maps areas to FTN networks. | `./oputil.js config import-areas /some/path/l33tnet.na` |
|
||||||
| `areafix` | Utility for sending AreaFix mails without logging into the system | |
|
| `areafix` | Utility for sending AreaFix mails without logging into the system | |
|
||||||
| `qwk-dump` | Dump a QWK packet to stdout | `./oputil.js mb qwk-dump /path/to/XIBALBA.QWK` |
|
| `qwk-dump` | Dump a QWK packet to stdout | `./oputil.js mb qwk-dump /path/to/XIBALBA.QWK` |
|
||||||
| `qwk-export` | Export messages to a QWK packet | `./oputil.js mb qwk-export /path/to/XIBALBA.QWK` |
|
| `qwk-export` | Export messages to a QWK packet | `./oputil.js mb qwk-export /path/to/XIBALBA.QWK` |
|
||||||
|
|
|
@ -108,7 +108,7 @@ There are many predefined MCI codes that can be used anywhere on the system (pla
|
||||||
| `NM` | Count of new messages **address to the current user** across all message areas in which they have access |
|
| `NM` | Count of new messages **address to the current user** across all message areas in which they have access |
|
||||||
| `NP` | Count of new private mail to the current user |
|
| `NP` | Count of new private mail to the current user |
|
||||||
| `IA` | Indicator as to rather the current user is **available** or not. See also `getStatusAvailIndicators()` in [Themes](themes.md) |
|
| `IA` | Indicator as to rather the current user is **available** or not. See also `getStatusAvailIndicators()` in [Themes](themes.md) |
|
||||||
| `IV` | Indicator as to rather the current user is **visible** or not. See also `getStatusVisibleIndicators()` in [Themes](themes.md) |
|
| `IV` | Indicator as to rather the curent user is **visible** or not. See also `getStatusVisibleIndicators()` in [Themes](themes.md) |
|
||||||
| `PI` | Ingress bytes for the current process (since ENiGMA started up) |
|
| `PI` | Ingress bytes for the current process (since ENiGMA started up) |
|
||||||
| `PE` | Egress bytes for the current process (since ENiGMA started up) |
|
| `PE` | Egress bytes for the current process (since ENiGMA started up) |
|
||||||
|
|
||||||
|
@ -123,9 +123,11 @@ Some additional special case codes also exist:
|
||||||
| `XY` | A special code that may be utilized for placement identification when creating menus or to extend an otherwise empty space in an art file down the screen. |
|
| `XY` | A special code that may be utilized for placement identification when creating menus or to extend an otherwise empty space in an art file down the screen. |
|
||||||
|
|
||||||
|
|
||||||
> :information_source: More are added all the time so also check out [core/predefined_mci.js](https://github.com/NuSkooler/enigma-bbs/blob/master/core/mci_view_factory.js) for a full listing.
|
> :information_source: More are added all
|
||||||
|
the time so also check out [core/predefined_mci.js](https://github.com/NuSkooler/enigma-bbs/blob/master/core/mci_view_factory.js)
|
||||||
|
for a full listing.
|
||||||
|
|
||||||
> :memo: Many codes attempt to pay homage to Oblivion/2, iNiQUiTY, etc.
|
:memo: Many codes attempt to pay homage to Oblivion/2, iNiQUiTY, etc.
|
||||||
|
|
||||||
|
|
||||||
## Views
|
## Views
|
||||||
|
@ -144,6 +146,7 @@ a Vertical Menu (`%VM`): Old-school BBSers may recognize this as a lightbar menu
|
||||||
| `FM` | Full Menu | A menu that can go both vertical and horizontal. | See [Full Menu](views/full_menu_view.md) |
|
| `FM` | Full Menu | A menu that can go both vertical and horizontal. | See [Full Menu](views/full_menu_view.md) |
|
||||||
| `SM` | Spinner Menu | A spinner input control | Select *one* from multiple options. See [Spinner Menu](views/spinner_menu_view.md) |
|
| `SM` | Spinner Menu | A spinner input control | Select *one* from multiple options. See [Spinner Menu](views/spinner_menu_view.md) |
|
||||||
| `TM` | Toggle Menu | A toggle menu | Commonly used for Yes/No style input. See [Toggle Menu](views/toggle_menu_view.md)|
|
| `TM` | Toggle Menu | A toggle menu | Commonly used for Yes/No style input. See [Toggle Menu](views/toggle_menu_view.md)|
|
||||||
|
| `PL` | Predefined Label | Show environment information | See [Predefined Label](views/predefined_label_view.md)|
|
||||||
| `KE` | Key Entry | A *single* key input control | Think hotkeys |
|
| `KE` | Key Entry | A *single* key input control | Think hotkeys |
|
||||||
|
|
||||||
> :information_source: Peek at [/core/mci_view_factory.js](https://github.com/NuSkooler/enigma-bbs/blob/master/core/mci_view_factory.js) to see additional information.
|
> :information_source: Peek at [/core/mci_view_factory.js](https://github.com/NuSkooler/enigma-bbs/blob/master/core/mci_view_factory.js) to see additional information.
|
||||||
|
@ -180,21 +183,21 @@ Predefined MCI codes and other Views can have properties set via `menu.hjson` an
|
||||||
|
|
||||||
| Property | Description |
|
| Property | Description |
|
||||||
|-------------|--------------|
|
|-------------|--------------|
|
||||||
| `textStyle` | Sets the standard (non-focus) text style. See [Text Styles](#text-styles) below |
|
| `textStyle` | Sets the standard (non-focus) text style. See **Text Styles** below |
|
||||||
| `focusTextStyle` | Sets focus text style. See [Text Styles](#text-styles) below |
|
| `focusTextStyle` | Sets focus text style. See **Text Styles** below. |
|
||||||
| `itemSpacing` | Used to separate items in menus such as Vertical Menu and Horizontal Menu Views. |
|
| `itemSpacing` | Used to separate items in menus such as Vertical Menu and Horizontal Menu Views. |
|
||||||
| `height` | Sets the height of views such as menus that may be > 1 character in height |
|
| `height` | Sets the height of views such as menus that may be > 1 character in height |
|
||||||
| `width` | Sets the width of a view |
|
| `width` | Sets the width of a view |
|
||||||
| `focus` | If set to `true`, establishes initial focus |
|
| `focus` | If set to `true`, establishes initial focus |
|
||||||
| `text` | Set's the view's text if applicable, such as a [TextView](./views/text_view.md) or [EditTextView](./views/edit_text_view.md) amongst others. See [MCI Formatting](#mci-formatting) below for advanced formatting options using the |
|
| `text` | (initial) text of a view |
|
||||||
| `submit` | If set to `true` any `accept` action upon this view will submit the encompassing **form** |
|
| `submit` | If set to `true` any `accept` action upon this view will submit the encompassing **form** |
|
||||||
| `itemFormat` | Sets the format for a list entry. See [Entry Formatting](#entry-formatting) below |
|
| `itemFormat` | Sets the format for a list entry. See **Entry Formatting** below |
|
||||||
| `focusItemFormat` | Sets the format for a focused list entry. See [Entry Formatting](#entry-formatting) below |
|
| `focusItemFormat` | Sets the format for a focused list entry. See **Entry Formatting** below |
|
||||||
|
|
||||||
These are just a few of the properties set on various views. *Use the source Luke*, as well as taking a look at the default `menu.hjson` and `theme.hjson` files!
|
These are just a few of the properties set on various views. *Use the source Luke*, as well as taking a look at the default `menu.hjson` and `theme.hjson` files!
|
||||||
|
|
||||||
### Custom Properties
|
### Custom Properties
|
||||||
Often a module will provide custom properties that receive format objects (See [Entry Formatting](#entry-formatting) below). Custom property formatting can be declared in the `config` block. For example, `browseInfoFormat10`..._N_ (where _N_ is up to 99) in the `file_area_list` module received a fairly extensive format object that contains `{fileName}`, `{estReleaseYear}`, etc.
|
Often a module will provide custom properties that receive format objects (See **Entry Formatting** below). Custom property formatting can be declared in the `config` block. For example, `browseInfoFormat10`..._N_ (where _N_ is up to 99) in the `file_area_list` module received a fairly extensive format object that contains `{fileName}`, `{estReleaseYear}`, etc.
|
||||||
|
|
||||||
### Text Styles
|
### Text Styles
|
||||||
|
|
||||||
|
@ -216,21 +219,6 @@ Standard style types available for `textStyle` and `focusTextStyle`:
|
||||||
### Entry Formatting
|
### Entry Formatting
|
||||||
Various strings can be formatted using a syntax that allows width & precision specifiers, text styling, etc. Depending on the context, various elements can be referenced by `{name}`. Additional text styles can be supplied as well. The syntax is largely modeled after Python's [string format mini language](https://docs.python.org/3/library/string.html#format-specification-mini-language).
|
Various strings can be formatted using a syntax that allows width & precision specifiers, text styling, etc. Depending on the context, various elements can be referenced by `{name}`. Additional text styles can be supplied as well. The syntax is largely modeled after Python's [string format mini language](https://docs.python.org/3/library/string.html#format-specification-mini-language).
|
||||||
|
|
||||||
#### MCI Formatting
|
|
||||||
For more advanced layouts, you may want to apply formatting to MCI codes. In this case, an alternative syntax is supported similar to standard [Entry Formatting](#entry-formatting).
|
|
||||||
|
|
||||||
MCI codes can be surrounded by `{` and `}` in the `text` field in your `theme.hjson` for a Text Label (`%TL` aka [Text View](./views/text_view.md)). Some examples:
|
|
||||||
```hjson
|
|
||||||
text: "|00|07{BN!stylel33t}" // render board name in "l33t" text
|
|
||||||
|
|
||||||
// MCI codes that produce a number can use appropriate stylers as well
|
|
||||||
text: "|00|07{SD:,}"
|
|
||||||
// ...or perhaps
|
|
||||||
text: "|00:07{SD!countWithAbbr}"
|
|
||||||
```
|
|
||||||
|
|
||||||
> :bulb: MCI formatting also applies when programmatically calling [setText()](https://github.com/NuSkooler/enigma-bbs/blob/6710bf8c084487be2ee1d46d72a05d17a7b166f4/core/text_view.js#L148) of [TextView's](./views/text_view.md) and derived views.
|
|
||||||
|
|
||||||
### Additional Text Styles
|
### Additional Text Styles
|
||||||
Some of the text styles mentioned above are also available in the mini format language:
|
Some of the text styles mentioned above are also available in the mini format language:
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,49 @@
|
||||||
|
---
|
||||||
|
layout: page
|
||||||
|
title: Predefined Label View
|
||||||
|
---
|
||||||
|
## Predefined Label View
|
||||||
|
A predefined label view supports displaying a predefined MCI label on a screen.
|
||||||
|
|
||||||
|
## General Information
|
||||||
|
|
||||||
|
> :information_source: A predefined label view is defined with a percent (%) and the characters PL, followed by the view number and then the predefined MCI value in parenthesis. For example: `%PL1(VL)` to display the Version Label. *NOTE*: this is an alternate way of placing MCI codes, as the MCI can also be placed on the art page directly with the code. For example `%VL`. The difference between these is that the PL version can have additional formatting options applied to it.
|
||||||
|
|
||||||
|
> :information_source: See *Predefined Codes* in [MCI](../mci.md) for the list of available MCI codes.
|
||||||
|
|
||||||
|
> :information_source: See [MCI](../mci.md) for general information on how to use views and common configuration properties available for them.
|
||||||
|
|
||||||
|
### Properties
|
||||||
|
|
||||||
|
| Property | Description |
|
||||||
|
|-------------|--------------|
|
||||||
|
| `textStyle` | Sets the standard (non-focus) text style. See **Text Styles** in [MCI](../mci.md) |
|
||||||
|
| `justify` | Sets the justification of the MCI value text. Options: left (default), right, center |
|
||||||
|
| `fillChar` | Specifies a character to fill extra space in the view. Defaults to an empty space |
|
||||||
|
| `width` | Specifies the width that the value should be displayed in (default 3) |
|
||||||
|
| `textOverflow` | If the MCI is wider than width, set overflow characters. See **Text Overflow** below |
|
||||||
|
|
||||||
|
### Text Overflow
|
||||||
|
|
||||||
|
The `textOverflow` option is used to specify what happens when a predefined MCI string is too long to fit in the `width` defined.
|
||||||
|
|
||||||
|
> :information_source: If `textOverflow` is not specified at all, a predefined label view can become wider than the `width` if needed to display the MCI value.
|
||||||
|
|
||||||
|
> :information_source: Setting `textOverflow` to an empty string `textOverflow: ""` will cause the item to be truncated if necessary without any characters displayed
|
||||||
|
|
||||||
|
> :information_source: Otherwise, setting `textOverflow` to one or more characters will truncate the value if necessary and display those characters at the end. i.e. `textOverflow: ...`
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
![Example](../../assets/images/predefined_label_view_example1.png "Predefined label")
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Configuration fragment (expand to view)</summary>
|
||||||
|
<div markdown="1">
|
||||||
|
```
|
||||||
|
PL1: {
|
||||||
|
textStyle: upper
|
||||||
|
}
|
||||||
|
```
|
||||||
|
</div>
|
||||||
|
</details>
|
|
@ -15,7 +15,7 @@ A text label view supports displaying simple text on a screen.
|
||||||
|
|
||||||
| Property | Description |
|
| Property | Description |
|
||||||
|-------------|--------------|
|
|-------------|--------------|
|
||||||
| `text` | Sets the text to display on the label. See [MCI Formatting](../mci.md#mci-formatting) for information for advanced formatting. |
|
| `text` | Sets the text to display on the label |
|
||||||
| `textStyle` | Sets the standard (non-focus) text style. See **Text Styles** in [MCI](../mci.md) |
|
| `textStyle` | Sets the standard (non-focus) text style. See **Text Styles** in [MCI](../mci.md) |
|
||||||
| `width` | Sets the width of a view to display horizontally (default 15)|
|
| `width` | Sets the width of a view to display horizontally (default 15)|
|
||||||
| `justify` | Sets the justification of the text in the view. Options: left (default), right, center |
|
| `justify` | Sets the justification of the text in the view. Options: left (default), right, center |
|
||||||
|
|
|
@ -6,16 +6,6 @@ title: Access Condition System (ACS)
|
||||||
## Access Condition System (ACS)
|
## Access Condition System (ACS)
|
||||||
ENiGMA½ uses an Access Condition System (ACS) that is both familiar to oldschool BBS operators and has it's own style. With ACS, SysOp's are able to control access to various areas of the system based on various conditions such as group membership, connection type, etc. Various touch points in the system are configured to allow for `acs` checks. In some cases ACS is a simple boolean check while others (via ACS blocks) allow to define what conditions must be true for certain _rights_ such as `read` and `write` (though others exist as well).
|
ENiGMA½ uses an Access Condition System (ACS) that is both familiar to oldschool BBS operators and has it's own style. With ACS, SysOp's are able to control access to various areas of the system based on various conditions such as group membership, connection type, etc. Various touch points in the system are configured to allow for `acs` checks. In some cases ACS is a simple boolean check while others (via ACS blocks) allow to define what conditions must be true for certain _rights_ such as `read` and `write` (though others exist as well).
|
||||||
|
|
||||||
## Group Membership
|
|
||||||
ENiGMA½ does not utilize legacy "security levels" (see note below) but instead utilizes a group system. Users may be long to one or more groups which can be checked by the `GM` ACS (See [ACS Codes](#acs-codes) below). Two special groups exist out of the box:
|
|
||||||
1. `users`: Any regular user
|
|
||||||
2. `sysops`: System Operators. The first user (your root, or admin) will alwasy belong to this group.
|
|
||||||
|
|
||||||
You do not need to explicitly create groups: By checking for them via ACS, and adding members to a group, they implicitly exist within the system. You may use as many groups within your system as you would like. See ['optuil user group'](../admin/oputil.md#user) for information adding and removing users to groups.
|
|
||||||
|
|
||||||
> :information_source: Many dropfile formats require a security level. As such, the following apply: Root user or users in `sysops` group receive a security level of `100` while standard `users` receive `30`.
|
|
||||||
|
|
||||||
|
|
||||||
## ACS Codes
|
## ACS Codes
|
||||||
The following are ACS codes available as of this writing:
|
The following are ACS codes available as of this writing:
|
||||||
|
|
||||||
|
@ -34,7 +24,7 @@ The following are ACS codes available as of this writing:
|
||||||
| TW<i>width</i> | Terminal width is >= _width_ |
|
| TW<i>width</i> | Terminal width is >= _width_ |
|
||||||
| TM[_themeId_,...] | User's current theme ID is one of [_themeId_,...] (e.g. `luciano_blocktronics`) |
|
| TM[_themeId_,...] | User's current theme ID is one of [_themeId_,...] (e.g. `luciano_blocktronics`) |
|
||||||
| TT[_termType_,...] | User's current terminal type is one of [_termType_,...] (`ANSI-BBS`, `utf8`, `xterm`, etc.) |
|
| TT[_termType_,...] | User's current terminal type is one of [_termType_,...] (`ANSI-BBS`, `utf8`, `xterm`, etc.) |
|
||||||
| ID<i>id</i>, ID[_id_,...] | User's ID is _id_ or one of [_id_,...] |
|
| ID<i>id</i>, ID[_id_,...] | User's ID is _id_ or oen of [_id_,...] |
|
||||||
| WD<i>weekDay</i>, WD[_weekDay_,...] | Current day of week is _weekDay_ or one of [_weekDay_,...] where `0` is Sunday, `1` is Monday, and so on. |
|
| WD<i>weekDay</i>, WD[_weekDay_,...] | Current day of week is _weekDay_ or one of [_weekDay_,...] where `0` is Sunday, `1` is Monday, and so on. |
|
||||||
| AA<i>days</i> | Account is >= _days_ old |
|
| AA<i>days</i> | Account is >= _days_ old |
|
||||||
| BU<i>bytes</i> | User has uploaded >= _bytes_ |
|
| BU<i>bytes</i> | User has uploaded >= _bytes_ |
|
||||||
|
|
|
@ -59,15 +59,13 @@ The `config` block for a menu entry can contain common members as well as a per-
|
||||||
| `menuFlags` | An array of menu flag(s) controlling menu behavior. See **Menu Flags** below.
|
| `menuFlags` | An array of menu flag(s) controlling menu behavior. See **Menu Flags** below.
|
||||||
|
|
||||||
#### Menu Flags
|
#### Menu Flags
|
||||||
The `menuFlags` field of a `config` block can change default behavior of a particular menu:
|
The `menuFlags` field of a `config` block can change default behavior of a particular menu.
|
||||||
|
|
||||||
| Flag | Description |
|
| Flag | Description |
|
||||||
|------|-------------|
|
|------|-------------|
|
||||||
| `noHistory` | When leaving the current menu to load/chain to another, remove this menu from history. In other words, the fallback from the next menu would *not* be this one, but the previous. |
|
| `noHistory` | Prevents the menu from remaining in the menu stack / history. When this flag is set, when the **next** menu falls back, this menu will be skipped and the previous menu again displayed instead. Example: menuA -> menuB(noHistory) -> menuC: Exiting menuC returns the user to menuA. |
|
||||||
| `mergeFlags` | Generally used in code only: Request that any flags from `menu.hjson` |
|
| `popParent` | When *this* menu is exited, fall back beyond the parent as well. Often used in combination with `noHistory`. |
|
||||||
| `forwardArgs` | Forward this menu's `extraArgs` to the next. |
|
| `forwardArgs` | If set, when the next menu is entered, forward any `extraArgs` arguments to *this* menu on to it. |
|
||||||
|
|
||||||
> 💡 In JavaScript code, `MenuFlags` from `menu_module.js` contains constants for these flags.
|
|
||||||
|
|
||||||
|
|
||||||
## Forms
|
## Forms
|
||||||
|
|
|
@ -3,7 +3,7 @@ layout: page
|
||||||
title: TIC Support
|
title: TIC Support
|
||||||
---
|
---
|
||||||
## TIC Support
|
## TIC Support
|
||||||
ENiGMA½ supports FidoNet-Style TIC file attachments by mapping external TIC area tags to local file areas.
|
ENiGMA½ supports FidoNet-Style TIC file attachments by mapping TIC areas to local file areas.
|
||||||
|
|
||||||
Under a given node defined in the `ftn_bso` config section in `config.hjson` (see
|
Under a given node defined in the `ftn_bso` config section in `config.hjson` (see
|
||||||
[BSO Import/Export](../messageareas/bso-import-export.md)), TIC configuration may be supplied:
|
[BSO Import/Export](../messageareas/bso-import-export.md)), TIC configuration may be supplied:
|
||||||
|
@ -17,9 +17,9 @@ Under a given node defined in the `ftn_bso` config section in `config.hjson` (se
|
||||||
packetPassword: mypass
|
packetPassword: mypass
|
||||||
encoding: cp437
|
encoding: cp437
|
||||||
archiveType: zip
|
archiveType: zip
|
||||||
tic: { // <--- General TIC config for 46:*
|
tic: {
|
||||||
password: TESTY-TEST
|
password: TESTY-TEST
|
||||||
uploadBy: AgoraNet TIC
|
uploadBy: Agoranet TIC
|
||||||
allowReplace: true
|
allowReplace: true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,15 +29,7 @@ Under a given node defined in the `ftn_bso` config section in `config.hjson` (se
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Valid `tic` members:
|
You then need to configure the mapping between TIC areas you want to carry, and the file base area and storage tag for them to be tossed to. Optionally you can also add hashtags to the tossed files to assist users in searching for files:
|
||||||
|
|
||||||
| Item | Required | Description |
|
|
||||||
|--------|---------------|------------------|
|
|
||||||
| `password` | :-1: | TIC packet password, if required |
|
|
||||||
| `uploadedBy` | :-1: | Sets the "uploaded by" field for TIC attachments, for example "AgoraNet TIC" |
|
|
||||||
| `allowReplace` | :-1: | Set to `true` to allow TIC attachments to replace each other. This is especially handy for things like weekly node list attachments |
|
|
||||||
|
|
||||||
Next, we need to configure the mapping between TIC areas you want to carry, and the file base area (and, optionally, specific storage tag) for them to be tossed to. You can also add hashtags to the tossed files to assist users in searching for files:
|
|
||||||
|
|
||||||
```hjson
|
```hjson
|
||||||
ticAreas: {
|
ticAreas: {
|
||||||
|
@ -49,22 +41,10 @@ ticAreas: {
|
||||||
}
|
}
|
||||||
|
|
||||||
```
|
```
|
||||||
|
Multiple TIC areas can be mapped to a single file base area.
|
||||||
> :information_source: Note that in the example above `agn_node` represents the **external** network area tag, usually represented in all caps. In this case, `AGN_NODE`.
|
|
||||||
|
|
||||||
Valid `ticAreas` members under a given node mapping are as follows:
|
|
||||||
|
|
||||||
| Item | Required | Description |
|
|
||||||
|--------|---------------|------------------|
|
|
||||||
| `areaTag` | :+1: | Specifies the local areaTag in which to place TIC attachments |
|
|
||||||
| `storageTag` | :-1: | Optionally, set a specific storageTag. If not set, the default for this area will be used. |
|
|
||||||
| `hashTags` | :-1: | One or more optional hash tags to assign TIC attachments in this area. |
|
|
||||||
|
|
||||||
|
|
||||||
💡 Multiple TIC areas can be mapped to a single file base area.
|
|
||||||
|
|
||||||
### Example Configuration
|
### Example Configuration
|
||||||
Example configuration fragments mapping file base areas, FTN BSO node configuration and TIC area configuration.
|
An example configuration linking file base areas, FTN BSO node configuration and TIC area configuration.
|
||||||
|
|
||||||
```hjson
|
```hjson
|
||||||
fileBase: {
|
fileBase: {
|
||||||
|
@ -99,24 +79,22 @@ scannerTossers: {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ticAreas: {
|
|
||||||
// here we map AgoraNet AGN_NODE -> local msgNetworks file area
|
|
||||||
agn_node: {
|
|
||||||
areaTag: msgNetworks
|
|
||||||
storageTag: msg_network
|
|
||||||
hashTags: agoranet,nodelist
|
|
||||||
}
|
|
||||||
agn_info: {
|
|
||||||
areaTag: msgNetworks
|
|
||||||
storageTag: msg_network
|
|
||||||
hashTags: agoranet,infopack
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ticAreas: {
|
||||||
|
agn_node: {
|
||||||
|
areaTag: msgNetworks
|
||||||
|
storageTag: msg_network
|
||||||
|
hashTags: agoranet,nodelist
|
||||||
|
}
|
||||||
|
agn_info: {
|
||||||
|
areaTag: msgNetworks
|
||||||
|
storageTag: msg_network
|
||||||
|
hashTags: agoranet,infopack
|
||||||
|
}
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## See Also
|
## See Also
|
||||||
|
|
|
@ -1,38 +0,0 @@
|
||||||
---
|
|
||||||
layout: page
|
|
||||||
title: Development Environment Setup
|
|
||||||
---
|
|
||||||
_Note:_ This is only useful for people who are looking to contribute to the ENiGMA½ source base itself. Those that are just setting up a new BBS system do not need this section.
|
|
||||||
|
|
||||||
The easiest way to get started with development on ENiGMA½ is via the pre-configured Visual Studio Code remote docker container environment. This setup will download and configure everything needed with minimal interaction. It also works cross-platform.
|
|
||||||
|
|
||||||
* Install [Visual Studio Code](https://code.visualstudio.com/download)
|
|
||||||
* Install [Docker](https://docs.docker.com/engine/install/)
|
|
||||||
* Clone the [ENiGMA½](https://github.com/NuSkooler/enigma-bbs) repository.
|
|
||||||
* Choose "Open Folder" from Visual Studio Code and open the location where you cloned the repository.
|
|
||||||
|
|
||||||
That's it! Visual Studio Code should prompt you for everything else that is needed, including some useful extensions for development.
|
|
||||||
|
|
||||||
## Tasks
|
|
||||||
|
|
||||||
Once it completes, there are a few tasks and run-configs that are useful. Open up the command pallete and search/choose "Tasks> Run Task". From there you can run the following tasks:
|
|
||||||
|
|
||||||
### Start Jekyll (ENiGMA½ documentation server)
|
|
||||||
|
|
||||||
This task will start the Jekyll server to perform local testing of changes to documentation. After running this task, open a browser to (http://localhost:4000/enigma-bbs/) to see the documentation.
|
|
||||||
|
|
||||||
### (re)build Jekyll bundles
|
|
||||||
|
|
||||||
When the image is created the Jekyll bundles are installed, so in general there shouldn't be much need to run this task. This is available however in case soemthing goes wrong or you are working on the Jekyll setup itself.
|
|
||||||
|
|
||||||
### (re)build node modules
|
|
||||||
|
|
||||||
Used to re-generate the node modules. Generally shouldn't be necessary unless something is broken or you are adding/changing versions of dependencies.
|
|
||||||
|
|
||||||
### ENiGMA½ new configuration
|
|
||||||
|
|
||||||
This task executes `oputil.js` in order to create a new BBS configuration (useful if you have just checked out the code and haven't setup any configuration yet.)
|
|
||||||
|
|
||||||
## Run / Debug config
|
|
||||||
|
|
||||||
There is also a default "Launch Program" config (hotkey access via F5 / Ctrl-Shift-D.) This will launch ENiGMA½. Once it has launched, access the system via telnet, port 8888 as usual.
|
|
|
@ -71,9 +71,5 @@ Customising the Docker image is easy!
|
||||||
1. Clone the ENiGMA-BBS source.
|
1. Clone the ENiGMA-BBS source.
|
||||||
2. Build the image
|
2. Build the image
|
||||||
```bash
|
```bash
|
||||||
docker build -t enigmabbs -f ./docker/Dockerfile .
|
docker build -f ./docker/Dockerfile .
|
||||||
```
|
|
||||||
3. Run the image
|
|
||||||
```bash
|
|
||||||
docker run -it -p 8888:8888 --name "ENiGMABBS" -v "$(pwd)/config:/enigma-bbs/config" -v "$(pwd)/db:/enigma-bbs/db" -v "$(pwd)/logs:/enigma-bbs/logs" -v "$(pwd)/filebase:/enigma-bbs/filebase" -v "$(pwd)/art:/enigma-bbs/art" -v "$(pwd)/mods:/enigma-bbs/mods" -v "$(pwd)/mail:/mail" enigmabbs
|
|
||||||
```
|
```
|
||||||
|
|
|
@ -28,9 +28,9 @@ curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.11/install.sh |
|
||||||
|
|
||||||
Next, install Node.js with NVM:
|
Next, install Node.js with NVM:
|
||||||
```bash
|
```bash
|
||||||
nvm install 14
|
nvm install 12
|
||||||
nvm use 14
|
nvm use 12
|
||||||
nvm alias default 14
|
nvm alias default 12
|
||||||
```
|
```
|
||||||
|
|
||||||
If the above steps completed without errors, you should now have `nvm`, `node`, and `npm` installed and in your environment.
|
If the above steps completed without errors, you should now have `nvm`, `node`, and `npm` installed and in your environment.
|
||||||
|
|
|
@ -179,7 +179,7 @@ kill-old-partial-files 86400
|
||||||
prescan
|
prescan
|
||||||
|
|
||||||
# fsxNet - Agency HUB
|
# fsxNet - Agency HUB
|
||||||
node 21:1/100@fsxnet -md fsxnet.nz:24556 SOMEPASS c
|
node 21:1/100@fsxnet -md agency.bbs.nz:24556 SOMEPASS c
|
||||||
|
|
||||||
# ArakNet
|
# ArakNet
|
||||||
node 10:101/0@araknet -md whq.araknet.xyz:24556 SOMEPASS c
|
node 10:101/0@araknet -md whq.araknet.xyz:24556 SOMEPASS c
|
||||||
|
|
|
@ -40,5 +40,22 @@ doorParty: {
|
||||||
|
|
||||||
Fill in `username`, `password`, and `bbsTag` with credentials provided to you and you should be in business!
|
Fill in `username`, `password`, and `bbsTag` with credentials provided to you and you should be in business!
|
||||||
|
|
||||||
|
## The CombatNet Module
|
||||||
|
The `combatnet` module provides native support for [CombatNet](http://combatnet.us/). Add the following to your menu config:
|
||||||
|
|
||||||
|
````hjson
|
||||||
|
combatNet: {
|
||||||
|
desc: Using CombatNet
|
||||||
|
module: combatnet
|
||||||
|
config: {
|
||||||
|
bbsTag: CBNxxx
|
||||||
|
password: XXXXXXXXX
|
||||||
|
}
|
||||||
|
}
|
||||||
|
````
|
||||||
|
Update `bbsTag` (in the format CBNxxx) and `password` with the details provided when you register, then
|
||||||
|
you should be ready to rock!
|
||||||
|
|
||||||
## The Exodus Module
|
## The Exodus Module
|
||||||
|
|
||||||
TBC
|
TBC
|
|
@ -16,16 +16,14 @@ The `abracadabra` `config` block can contain the following members:
|
||||||
| Item | Required | Description |
|
| Item | Required | Description |
|
||||||
|------|----------|-------------|
|
|------|----------|-------------|
|
||||||
| `name` | :+1: | Used as a key for tracking number of clients using a particular door. |
|
| `name` | :+1: | Used as a key for tracking number of clients using a particular door. |
|
||||||
| `dropFileType` | :-1: | Specifies the type of dropfile to generate (See [Dropfile Types](#dropfile-types) below). Can be omitted or set to `none`. |
|
| `dropFileType` | :-1: | Specifies the type of dropfile to generate (See **Dropfile Types** below). Can be omitted or set to `none`. |
|
||||||
| `cmd` | :+1: | Path to executable to launch. |
|
| `cmd` | :+1: | Path to executable to launch. |
|
||||||
| `args` | :-1: | Array of argument(s) to pass to `cmd`. See [Argument Variables](#argument-variables) below for information on variables that can be utilized here. |
|
| `args` | :-1: | Array of argument(s) to pass to `cmd`. See **Argument Variables** below for information on variables that can be used here.
|
||||||
| `preCmd` | :-1: | Path to a pre-command executable or script to launch. Executes before `cmd`. |
|
|
||||||
| `preCmdArgs` | :-1: | Array of argument(s) to pass to `preCmd`. See [Argument Variables](#argument-variables) below for information on variables that can be utilized here. |
|
|
||||||
| `cwd` | :-1: | Sets the Current Working Directory (CWD) for `cmd`. Defaults to the directory of `cmd`. |
|
| `cwd` | :-1: | Sets the Current Working Directory (CWD) for `cmd`. Defaults to the directory of `cmd`. |
|
||||||
| `env` | :-1: | Sets the environment. Supplied in the form of an map: `{ SOME_VAR: "value" }`
|
| `env` | :-1: | Sets the environment. Supplied in the form of an map: `{ SOME_VAR: "value" }`
|
||||||
| `nodeMax` | :-1: | Max number of nodes that can access this door at once. Uses `name` as a tracking key. |
|
| `nodeMax` | :-1: | Max number of nodes that can access this door at once. Uses `name` as a tracking key. |
|
||||||
| `tooManyArt` | :-1: | Art spec to display if too many instances are already in use. |
|
| `tooManyArt` | :-1: | Art spec to display if too many instances are already in use. |
|
||||||
| `io` | :-1: | How to process input/output (I/O). Can be `stdio` or `socket`. When using `stdio`, I/O is handled via standard stdin/stdout. When using `socket` a temporary socket server is spawned that can be connected back to. The server listens on localhost on `{srvPort}` (See [Argument Variables](#argument-variables) below for more information). Default value is `stdio`. |
|
| `io` | :-1: | How to process input/output (I/O). Can be `stdio` or `socket`. When using `stdio`, I/O is handled via standard stdin/stdout. When using `socket` a temporary socket server is spawned that can be connected back to. The server listens on localhost on `{srvPort}` (See **Argument Variables** below for more information). Default value is `stdio`. |
|
||||||
| `encoding` | :-1: | Sets the **door's** encoding. Defaults to `cp437`. Linux binaries often produce `utf8`. |
|
| `encoding` | :-1: | Sets the **door's** encoding. Defaults to `cp437`. Linux binaries often produce `utf8`. |
|
||||||
|
|
||||||
#### Dropfile Types
|
#### Dropfile Types
|
||||||
|
@ -33,28 +31,23 @@ Dropfile types specified by `dropFileType`:
|
||||||
|
|
||||||
| Value | Description |
|
| Value | Description |
|
||||||
|-------|-------------|
|
|-------|-------------|
|
||||||
| `none` | No door file is needed |
|
|
||||||
| `DOOR` | [DOOR.SYS](https://web.archive.org/web/20160325192739/http://goldfndr.home.mindspring.com/dropfile/doorsys.htm)
|
| `DOOR` | [DOOR.SYS](https://web.archive.org/web/20160325192739/http://goldfndr.home.mindspring.com/dropfile/doorsys.htm)
|
||||||
| `DOOR32` | [DOOR32.SYS](https://raw.githubusercontent.com/NuSkooler/ansi-bbs/master/docs/dropfile_formats/door32_sys.txt)
|
| `DOOR32` | [DOOR32.SYS](https://raw.githubusercontent.com/NuSkooler/ansi-bbs/master/docs/dropfile_formats/door32_sys.txt)
|
||||||
| `DORINFO` | [DORINFOx.DEF](https://web.archive.org/web/20160321190038/http://goldfndr.home.mindspring.com/dropfile/dorinfo.htm)
|
| `DORINFO` | [DORINFOx.DEF](https://web.archive.org/web/20160321190038/http://goldfndr.home.mindspring.com/dropfile/dorinfo.htm)
|
||||||
|
|
||||||
#### Argument Variables
|
#### Argument Variables
|
||||||
The following variables may be used in `args` and `preCmdArgs` entries:
|
The following variables may be used in `args` entries:
|
||||||
|
|
||||||
| Variable | Description | Example |
|
| Variable | Description | Example |
|
||||||
|----------|-------------|---------|
|
|----------|-------------|---------|
|
||||||
| `{node}` | Current node number. | `1` |
|
| `{node}` | Current node number. | `1` |
|
||||||
| `{dropFile}` | Dropfile _filename_ only. | `DOOR.SYS` |
|
| `{dropFile}` | Dropfile _filename_ only. | `DOOR.SYS` |
|
||||||
| `{dropFilePath}` | Full path to generated dropfile. The system places dropfiles in the path set by `paths.dropFiles` in `config.hjson`. | `C:\enigma-bbs\drop\node1\DOOR.SYS` |
|
| `{dropFilePath}` | Full path to generated dropfile. The system places dropfiles in the path set by `paths.dropFiles` in `config.hjson`. | `C:\enigma-bbs\drop\node1\DOOR.SYS` |
|
||||||
| `{dropFileDir}` | Full path to **directory** containing the generated dropfile. | `/home/enigma-bbs/drop/node1/` |
|
|
||||||
| `{userAreaDir}` | Full path to a **directory** safe for user-specific save files/etc. | `/home/enigma-bbs/drop/node1/NuSkooler/lord/` |
|
|
||||||
| `{userId}` | Current user ID. | `420` |
|
| `{userId}` | Current user ID. | `420` |
|
||||||
| `{userName}` | [Sanitized](https://www.npmjs.com/package/sanitize-filename) username. Safe for filenames, etc. If the full username is sanitized away, this will resolve to something like "user_1234". | `izard` |
|
| `{userName}` | [Sanitized](https://www.npmjs.com/package/sanitize-filename) username. Safe for filenames, etc. If the full username is sanitized away, this will resolve to something like "user_1234". | `izard` |
|
||||||
| `{userNameRaw}` | _Raw_ username. May not be safe for filenames! | `\/\/izard` |
|
| `{userNameRaw}` | _Raw_ username. May not be safe for filenames! | `\/\/izard` |
|
||||||
| `{srvPort}` | Temporary server port when `io` is set to `socket`. | `1234` |
|
| `{srvPort}` | Temporary server port when `io` is set to `socket`. | `1234` |
|
||||||
| `{cwd}` | Current Working Directory. | `/home/enigma-bbs/doors/foo/` |
|
| `{cwd}` | Current Working Directory. | `/home/enigma-bbs/doors/foo/` |
|
||||||
| `{termHeight}` | Current client term height | `25` |
|
|
||||||
| `{termWidth}` | Current client term width | `80` |
|
|
||||||
|
|
||||||
Example `args` member using some variables described above:
|
Example `args` member using some variables described above:
|
||||||
```hjson
|
```hjson
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue