Updated everything
This commit is contained in:
parent
322c5f72d4
commit
3df5f1857c
264
README.md
264
README.md
@ -101,18 +101,6 @@ Commands:
|
||||
start Start this device as the swarm manager
|
||||
```
|
||||
|
||||
#### Examples
|
||||
```bash
|
||||
# Start the manager
|
||||
run scripts/botnet-manager.js start
|
||||
# Add a single server to the botnet
|
||||
run scripts/botnet-manager.js join --device n00dles
|
||||
# Add all rooted servers to the botnet
|
||||
run scripts/crawler.js -r /scripts/botnet-manager.js join --device {{TARGET}}
|
||||
# Distribute & run a script on the entire botnet network
|
||||
run scripts/botnet-manager.js run /scripts/miner.js n00dles
|
||||
```
|
||||
|
||||
### [connect.js](./scripts/connect.js)
|
||||
**RAM:** 1.85 GB
|
||||
|
||||
@ -124,26 +112,19 @@ you some time.
|
||||
Running script with 1 thread(s), pid 1 and args: ["--help"].
|
||||
/scripts/connect.js:
|
||||
|
||||
Search the network for a device and connect to it.
|
||||
Connect to a server anywhere in the network without a backdoor.
|
||||
|
||||
Usage: run connect.js DEVICE
|
||||
Usage: run connect.js [OPTIONS] SERVER
|
||||
run connect.js --help
|
||||
|
||||
DEVICE Device to connect to
|
||||
SERVER Server to connect to
|
||||
|
||||
Options:
|
||||
-h --help Display this help message
|
||||
```
|
||||
|
||||
#### Examples
|
||||
```bash
|
||||
# Connect to a server without knowing where it is
|
||||
run scripts/connect.js CSEC
|
||||
run scripts/connect.js I.I.I.I
|
||||
-h, --help Display this help message
|
||||
```
|
||||
|
||||
### [copy.js](./scripts/copy.js)
|
||||
**RAM:** 3.50 GB
|
||||
**RAM:** 4.20 GB
|
||||
|
||||
Scripts often import other scripts requiring multiple `scp` calls before it can be run on a remote machine. This script
|
||||
will automatically scan the file being copied for imports & recursively scan & copy the dependencies. Plus it has a
|
||||
@ -153,30 +134,25 @@ fancy animated loading bar.
|
||||
Running script with 1 thread(s), pid 1 and args: ["--help"].
|
||||
/scripts/copy.js:
|
||||
|
||||
Copy a file/script to a device along with any dependencies.
|
||||
Copy a file & it's dependencies to a server.
|
||||
|
||||
Usage: run copy.js [OPTIONS] FILE DEVICE
|
||||
Usage: run copy.js [OPTIONS] FILE SERVER [ARGS]...
|
||||
run copy.js --help
|
||||
|
||||
FILE File to copy
|
||||
DEVICE Device to copy file(s) to
|
||||
SERVER Server to copy file(s) to
|
||||
ARGS Arguments to start file/script with
|
||||
|
||||
Options:
|
||||
-n --no-deps Skip copying dependencies
|
||||
-s --silent Surpress program output
|
||||
-h --help Display this help message
|
||||
```
|
||||
|
||||
#### Examples
|
||||
```bash
|
||||
# Copy the miner script with it's dependencies
|
||||
run scripts/copy.js /scripts/miner.js n00dles
|
||||
# Copy without the animated bar & dependencies
|
||||
run scripts/copy.js -sn /scripts/miner.js n00dles
|
||||
-c, --cpu Number of CPU threads to start script with, will use maximum if not specified
|
||||
-e, --execute Start script after copying
|
||||
-n, --no-deps Skip copying dependencies
|
||||
-q, --quite Suppress program output
|
||||
-h, --help Display this help message
|
||||
```
|
||||
|
||||
### [crawler.js](./scripts/crawler.js)
|
||||
**RAM:** 4.15 GB
|
||||
**RAM:** 5.80 GB
|
||||
|
||||
Mid-game solution to distribute & run scripts across the network.
|
||||
```
|
||||
@ -184,40 +160,30 @@ Mid-game solution to distribute & run scripts across the network.
|
||||
Running script with 1 thread(s), pid 1 and args: ["--help"].
|
||||
/scripts/crawler.js:
|
||||
|
||||
Search the network for targets to execute a script against.
|
||||
Search the network for servers to execute a script against.
|
||||
|
||||
Usage: run crawler.js [OPTIONS] SCRIPT [ARGS]...
|
||||
run crawler.js --help
|
||||
|
||||
SCRIPT Script to copy & execute
|
||||
ARGS Arguments for script. Forward the current target with: {{TARGET}}
|
||||
ARGS Arguments for script. Forward the discovered server with: {{SERVER}}
|
||||
|
||||
Options:
|
||||
-c --cpu Number of CPU threads to use with script
|
||||
-d --depth Depth to scan to, defaults to 3
|
||||
-k --kill Kill all scripts running on device
|
||||
-l --level Exclude targets with higher hack level, defaults to current hack level
|
||||
-e --remote-exec Copy script to remote device & run there
|
||||
-r --rooted Filter to devices that have been rooted
|
||||
-n --not-rooted Filter to devices that have not been rooted
|
||||
-p --ports Exclude targets with too many closed ports
|
||||
-s --silent Suppress program output
|
||||
-v --verbose Display the device names in the final report
|
||||
-h --help Display this help message
|
||||
```
|
||||
|
||||
#### Examples
|
||||
```bash
|
||||
# Run a command on the local machine targeting discovered devices
|
||||
run scripts/crawler.js -n /scripts/rootkit.js {{TARGET}}
|
||||
# Chain the miner to the rootkit to automatically deploy it
|
||||
run scripts/crawler.js -n /scripts/rootkit.js {{TARGET}} /scripts/miner.js
|
||||
# Deploy a script on rooted devices
|
||||
run scripts/crawler.js -re /scripts/miner.js n00dles
|
||||
-c, --cpu Number of CPU threads to start script with, will use maximum if not specified
|
||||
-d, --depth Depth to scan to, defaults to 3
|
||||
-k, --kill Kill all running scripts on the server
|
||||
--level Skip servers with higher hack level, defaults to current hack level
|
||||
-e, --remote-exec Execute script on remote server
|
||||
-r, --rooted Only servers that have been rooted
|
||||
-n, --not-rooted Only servers that have not been rooted
|
||||
-p, --ports Skip servers with too many closed ports
|
||||
-q, --quite Suppress program output
|
||||
-v, --verbose Display the server names in the final report
|
||||
-h, --help Display this help message
|
||||
```
|
||||
|
||||
### [find-target.js](./scripts/find-target.js)
|
||||
**RAM:** 6.00 GB
|
||||
**RAM:** 4.05 GB
|
||||
|
||||
A utility to help figure out which server is worth hacking the most. It does this by estimating the financial yield per
|
||||
minute for each server & returns the servers in a sorted list.
|
||||
@ -226,25 +192,17 @@ minute for each server & returns the servers in a sorted list.
|
||||
Running script with 1 thread(s), pid 1 and args: ["--help"].
|
||||
/scripts/find-target.js:
|
||||
|
||||
Scan the network for the best device(s) to mine.
|
||||
Scan the network for the best servers(s) to hack.
|
||||
|
||||
Usage: run find-target.js [OPTIONS]
|
||||
run find-target.js --help
|
||||
|
||||
Options:
|
||||
-c --count Number of devices to return in order from best to worst
|
||||
-r --rooted Filter to devices that have been rooted
|
||||
-n --not-rooted Filter to devices that have not been rooted
|
||||
-v --verbose Display the estimated income per minute per core
|
||||
-h --help Display this help message
|
||||
```
|
||||
|
||||
#### Examples
|
||||
```bash
|
||||
# Rank all the servers on the network
|
||||
run scripts/find-target.js -v
|
||||
# Best server currently rooted
|
||||
run scripts/find-target.js -rc 1
|
||||
-c, --count Number of servers to return
|
||||
-r, --rooted Only servers that have been rooted
|
||||
-n, --not-rooted Only servers that have not been rooted
|
||||
-v, --verbose Display the estimated income per minute per core
|
||||
-h, --help Display this help message
|
||||
```
|
||||
|
||||
### [hacknet-manager.js](./scripts/hacknet-manager.js)
|
||||
@ -261,21 +219,13 @@ Buy, upgrade & manage Hacknet nodes automatically. Tail for live updates.
|
||||
Usage: run hacknet-manager.js [OPTIONS] [LIMIT]
|
||||
run hacknet-manager.js --help
|
||||
|
||||
LIMIT Limit the number of nodes the manager will buy, defaults to 8
|
||||
LIMIT Limit the number of nodes the manager will buy, defaults to 8 or the current number of nodes
|
||||
|
||||
Options:
|
||||
-a --auto-limit Automatically increase the node limit when there is nothing to do
|
||||
-b --balance Prevent spending bellow point
|
||||
-s --sleep Amount of time to wait between purchases, defaults to 1 (second)
|
||||
-h --help Display this help message
|
||||
```
|
||||
|
||||
#### Examples
|
||||
```bash
|
||||
# Start the manager to 8 nodes & prevent spending while we have less than $1 million
|
||||
run scripts/hacknet-manager.js -b 1E6 8
|
||||
# Let the hacknet manage & grow itself
|
||||
run scripts/hacknet-manager.js -a
|
||||
-a, --auto-limit Automatically increase the node limit when there is nothing to do
|
||||
-b, --balance Prevent spending bellow point
|
||||
-s, --sleep Amount of time to wait between purchases, defaults to 1 (second)
|
||||
-h, --help Display this help message
|
||||
```
|
||||
|
||||
### [miner.js](./scripts/miner.js)
|
||||
@ -288,78 +238,54 @@ or they can all target the server with the most money which is more efficient (s
|
||||
Running script with 1 thread(s), pid 1 and args: ["--help"].
|
||||
/scripts/miner.js:
|
||||
|
||||
Weaken, Grow, Hack loop to "mine" device for money. Tail for live updates.
|
||||
Buy, upgrade & manage Hacknet nodes automatically. Tail for live updates.
|
||||
|
||||
Usage: run miner.js [DEVICE]
|
||||
run miner.js --help
|
||||
Usage: run hacknet-manager.js [OPTIONS] [LIMIT]
|
||||
run hacknet-manager.js --help
|
||||
|
||||
DEVICE Device to mine, defaults to current machine
|
||||
LIMIT Limit the number of nodes the manager will buy, defaults to 8 or the current number of nodes
|
||||
|
||||
Options:
|
||||
-h --help Display this help message
|
||||
```
|
||||
|
||||
#### Examples
|
||||
```bash
|
||||
# Use home to hack another server
|
||||
run scripts/miner.js n00dles
|
||||
# Make remote server hack itself
|
||||
run scripts/rootkit.js noodles /scripts/miner.js
|
||||
# Make remote server hack another remote server
|
||||
run scripts/rootkit.js noodles /scripts/miner.js foodnstuff
|
||||
# Distribute the miner on entire network to hack a single server
|
||||
run scripts/crawler.js /scripts/rootkit.js {{TARGET}} /scripts/miner.js foodnstuff
|
||||
-a, --auto-limit Automatically increase the node limit when there is nothing to do
|
||||
-b, --balance Prevent spending bellow point
|
||||
-s, --sleep Amount of time to wait between purchases, defaults to 1 (second)
|
||||
-h, --help Display this help message
|
||||
```
|
||||
|
||||
### [network-graph.js](./scripts/network-graph.js)
|
||||
**RAM:** 3.85 GB
|
||||
|
||||
A utility to scan the network & build a visual tree of all the devices. It comes with lots of flags to narrow down the
|
||||
results. It's useful for figuring out where you are, manually finding targets & discovering the path to a server.
|
||||
results. It's useful for figuring out where you are, manually finding targets & discovering the path to a server &
|
||||
connecting to it.
|
||||
```
|
||||
[home ~/]> run /scripts/network-graph.js --help
|
||||
Running script with 1 thread(s), pid 1 and args: ["--help"].
|
||||
/scripts/network-graph.js:
|
||||
|
||||
Scan the network for devices and display as an ASCII tree:
|
||||
home
|
||||
├─ n00dles (ROOTED)
|
||||
| └─ max-hardware (80|1)
|
||||
| └─ neo-net (50|1)
|
||||
├─ foodnstuff (ROOTED)
|
||||
└─ sigma-cosmetics (ROOTED)
|
||||
Scan the network for servers and display as an ASCII tree. Servers with root access are highlighted & bold. Click to
|
||||
automatically connect.
|
||||
|
||||
Usage: run network-graph.js [OPTIONS] [DEVICE]
|
||||
Usage: run network-graph.js [OPTIONS] [SERVER]
|
||||
run network-graph.js --help
|
||||
|
||||
DEVICE Point to start scan from, defaults to current machine
|
||||
SERVER Point to start scan from, defaults to local server
|
||||
|
||||
Options:
|
||||
-d --depth Depth to scan to
|
||||
-f --filter Filter to device matching name
|
||||
-e --regex Filter to devices matching pattern
|
||||
-r --rooted Filter to devices that have been rooted
|
||||
-n --not-rooted Filter to devices that have not been rooted
|
||||
-v --verbose Display the required hack level & number of ports to root: (level|port)
|
||||
-h --help Display this help message
|
||||
```
|
||||
|
||||
#### Example
|
||||
```bash
|
||||
# Show the entire network
|
||||
run scripts/network-graph.js -v
|
||||
# Show servers within 3 hops that still need to be rooted
|
||||
run scripts/network-graph.js -nvd 3
|
||||
# Show servers you have rooted
|
||||
run scripts/network-graph.js -r
|
||||
# Find a specific server
|
||||
run scripts/network-graph.js -f CSEC
|
||||
-d, --depth Depth to scan to
|
||||
-f, --filter Filter to servers matching name
|
||||
-e, --regex Filter to servers matching pattern
|
||||
-l, --level Display the required hack level & number of ports to root: [level|port]
|
||||
-n, --not-rooted Filter to servers that have not been rooted
|
||||
-r, --rooted Filter to servers that have been rooted
|
||||
-s, --specs Display the server specifications: {CPU|RAM}
|
||||
-u, --usage Display the server utilization: (USG%)
|
||||
-v, --verbose Display level, specs & usage in that order: [HL|P] {CPU|RAM} (USG%)
|
||||
-h, --help Display this help message
|
||||
```
|
||||
|
||||
### [rootkit.js](./scripts/rootkit.js)
|
||||
**RAM:** 5.05 GB <small>(Can be reduced to 4.80 GB)</small>
|
||||
|
||||
Programs can be commented out to lower the cost of running.
|
||||
**RAM:** 4.65 GB
|
||||
|
||||
Automatically gains root on the local or remote server. A script can be passed as an additional argument; it will be
|
||||
copied and automatically executed with the maximum number of threads after being rooted.
|
||||
@ -368,57 +294,53 @@ copied and automatically executed with the maximum number of threads after being
|
||||
Running script with 1 thread(s), pid 1 and args: ["--help"].
|
||||
/scripts/rootkit.js:
|
||||
|
||||
Automatically gain root access to a device. A file can also be uploaded & executed.
|
||||
Scan the network for servers and display as an ASCII tree. Servers with root access are highlighted & bold.
|
||||
|
||||
Usage: run rootkit.js [OPTIONS] [DEVICE] [SCRIPT] [ARGS]...
|
||||
run rootkit.js --help
|
||||
Usage: run network-graph.js [OPTIONS] [SERVER]
|
||||
run network-graph.js --help
|
||||
|
||||
DEVICE Device to root, defaults to current machine
|
||||
SCRIPT Script to copy & execute
|
||||
ARGS Arguments for script. Forward the current target with: {{TARGET}}
|
||||
SERVER Point to start scan from, defaults to local server
|
||||
|
||||
Options:
|
||||
-c --cpu Number of CPU threads to use with script
|
||||
-s --silent Surpress program output
|
||||
-h --help Display this help message
|
||||
```
|
||||
|
||||
#### Examples
|
||||
```bash
|
||||
# Hack a remote server
|
||||
run scripts/rootkit.js n00dles
|
||||
# Start the miner after hacking
|
||||
run scripts/rootkit.js n00dles /scripts/miner.js foodnstuff
|
||||
-d, --depth Depth to scan to
|
||||
-f, --filter Filter to servers matching name
|
||||
-e, --regex Filter to servers matching pattern
|
||||
-l, --level Display the required hack level & number of ports to root: [level|port]
|
||||
-n, --not-rooted Filter to servers that have not been rooted
|
||||
-r, --rooted Filter to servers that have been rooted
|
||||
-s, --specs Display the server specifications: {CPU|RAM}
|
||||
-u, --usage Display the server utilization: (USG%)
|
||||
-v, --verbose Display level, specs & usage in that order: [HL|P] {CPU|RAM} (USG%)
|
||||
-h, --help Display this help message
|
||||
```
|
||||
|
||||
### [server-manager.js (WIP)](./scripts/server-manager.js)
|
||||
**RAM:** 9.35 GB
|
||||
**RAM:** 11.35 GB
|
||||
|
||||
Early game script to handle purchasing and upgrading servers for more computer power.
|
||||
Mid-game script to handle purchasing and upgrading servers for more computer power. You can also set a script to run
|
||||
automatically after purchase. Useful to chain with `miner.js` or `botnet.js`.
|
||||
```
|
||||
[home ~/]> run /scripts/server-manager.js --help
|
||||
Running script with 1 thread(s), pid 1 and args: ["--help"].
|
||||
/scripts/server-manager.js:
|
||||
|
||||
Automate the buying & upgrading of servers.
|
||||
Automate the buying & upgrading of servers. Automatically starts script after purchase. Tail for live updates.
|
||||
|
||||
Usage: run server-manager.js [OPTIONS]
|
||||
Usage: run server-manager.js [OPTIONS] [SCRIPT] [ARGS]...
|
||||
run server-manager.js --help
|
||||
|
||||
SCRIPT Script to copy & execute
|
||||
ARGS Arguments for script. Forward the discovered server with: {{SERVER}}
|
||||
|
||||
Options:
|
||||
-b, --balance Prevent spending bellow point
|
||||
-c, --cpu Number of CPU threads to start script with, will use maximum if not specified
|
||||
-l, --limit Limit the number of servers that can be purchased, defaults to 25
|
||||
-r, --ram Amount of RAM to purchase new servers with, defaults to 8 GB
|
||||
-s, --sleep Amount of time to wait between purchases, defaults to 1 (second)
|
||||
-h, --help Display this help message
|
||||
```
|
||||
|
||||
#### Examples
|
||||
```bash
|
||||
# Start automatically purchasing & upgrading servers
|
||||
run scripts/server-manager.js --ram 16
|
||||
```
|
||||
|
||||
### [update.js](./scripts/update.js)
|
||||
**RAM:** 2.65 GB
|
||||
|
||||
@ -439,13 +361,5 @@ Usage: run update.js [OPTIONS] [DEVICE]
|
||||
Options:
|
||||
--skip-self Skip updating self (for debugging & used internally)
|
||||
--no-banner Hide the banner (Used internally)
|
||||
-h --help Display this help message
|
||||
```
|
||||
|
||||
#### Examples
|
||||
```bash
|
||||
# Download the scripts to local computer
|
||||
run scripts/update.js
|
||||
# Download scripts to a remote computer
|
||||
run scripts/update.js n00dles
|
||||
-h, --help Display this help message
|
||||
```
|
||||
|
7249
index.d.ts
vendored
Normal file
7249
index.d.ts
vendored
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,6 @@
|
||||
import {ArgParser} from '/scripts/lib/arg-parser2';
|
||||
import {ArgParser} from '/scripts/lib/arg-parser';
|
||||
import {Logger} from '/scripts/lib/logger';
|
||||
import {copyWithDependencies} from '/scripts/lib/utils';
|
||||
import {copyWithDependencies} from '/scripts/copy';
|
||||
|
||||
class Manager {
|
||||
running;
|
||||
@ -125,32 +125,32 @@ export async function main(ns) {
|
||||
// Setup
|
||||
ns.disableLog('ALL');
|
||||
const hostname = ns.getHostname(), portNum = 1;
|
||||
const argParser = new ArgParser(ns, 'botnet-manager.js', 'Connect & manage a network of devices to launch distributed attacks.', [
|
||||
new ArgParser(ns, 'copy', 'Copy file & dependencies to swarm nodes', [
|
||||
const argParser = new ArgParser('botnet-manager.js', 'Connect & manage a network of devices to launch distributed attacks.', [
|
||||
new ArgParser('copy', 'Copy file & dependencies to swarm nodes', [
|
||||
{name: 'file', desc: 'File to copy', default: false},
|
||||
{name: 'manager', desc: 'Copy to manager node', flags: ['-m', '--manager'], default: false},
|
||||
{name: 'noDeps', desc: 'Skip copying dependencies', flags: ['-d', '--no-deps'], default: false},
|
||||
{name: 'workers', desc: 'Copy to worker nodes', flags: ['-w', '--workers'], default: false},
|
||||
]),
|
||||
new ArgParser(ns, 'join', 'Connect device as a worker node to the swarm', [
|
||||
new ArgParser('join', 'Connect device as a worker node to the swarm', [
|
||||
{name: 'device', desc: 'Device to connect, defaults to the current machine', optional: true, default: hostname}
|
||||
]),
|
||||
new ArgParser(ns, 'kill', 'Kill any scripts running on worker nodes'),
|
||||
new ArgParser(ns, 'leave', 'Disconnect worker node from swarm', [
|
||||
new ArgParser('kill', 'Kill any scripts running on worker nodes'),
|
||||
new ArgParser('leave', 'Disconnect worker node from swarm', [
|
||||
{name: 'device', desc: 'Device to disconnect, defaults to the current machine', optional: true, default: hostname}
|
||||
]),
|
||||
new ArgParser(ns, 'run', 'Copy & run script on all worker nodes', [
|
||||
new ArgParser('run', 'Copy & run script on all worker nodes', [
|
||||
{name: 'script', desc: 'Script to copy & execute', type: 'string'},
|
||||
{name: 'args', desc: 'Arguments for script. Forward the current target with: {{TARGET}}', optional: true, extras: true},
|
||||
]),
|
||||
new ArgParser(ns, 'start', 'Start this device as the swarm manager'),
|
||||
new ArgParser('start', 'Start this device as the swarm manager'),
|
||||
{name: 'silent', desc: 'Suppress program output', flags: ['-s', '--silent'], default: false},
|
||||
]);
|
||||
const args = argParser.parse(ns.args);
|
||||
|
||||
// Help
|
||||
if(args['help'] || args['_error'])
|
||||
return ns.tprint(argParser.help(args['help'] ? null : args['_error'], args['_command']));
|
||||
if(args['help'] || args['_error'].length)
|
||||
return ns.tprint(argParser.help(args['help'] ? null : args['_error'][0], args['_command']));
|
||||
|
||||
// Run command
|
||||
if(args['_command'] == 'start') { // Start botnet manager
|
||||
|
@ -1,39 +1,69 @@
|
||||
import {ArgError, ArgParser} from '/scripts/lib/arg-parser';
|
||||
import {pruneTree, scanNetwork, terminal} from '/scripts/lib/utils';
|
||||
import {ArgParser} from '/scripts/lib/arg-parser';
|
||||
import {pruneTree, terminal} from '/scripts/lib/utils';
|
||||
import {scanNetwork} from '/scripts/crawler';
|
||||
|
||||
/**
|
||||
* BitBurner autocomplete
|
||||
* @param data {server: string[], txts: string[], scripts: string[], flags: string[]} - Contextual information
|
||||
* Create connection string to get from one server to another.
|
||||
*
|
||||
* @param {NS} ns - BitBurner API
|
||||
* @param {string} end - Server to find path to
|
||||
* @param {string} start - Starting point, defaults to the local server
|
||||
* @returns {string} - Connection string: "home;connect n00dles;connect CSEC;"
|
||||
*/
|
||||
export function connectionString(ns, end, start = ns.getHostname()) {
|
||||
let path = pathToServer(ns, end, start);
|
||||
const homeShortcut = path.indexOf('home');
|
||||
if(homeShortcut) path = path.slice(homeShortcut);
|
||||
return path.map((p, i) => p == 'home' && i == 0 ? 'home' : 'connect ' + p).join(';');
|
||||
}
|
||||
|
||||
/**
|
||||
* Find path from one server to another.
|
||||
*
|
||||
* @param {NS} ns - BitBurner API
|
||||
* @param {string} end - Server to find path to
|
||||
* @param {string} start - Starting point, defaults to the local server
|
||||
* @returns {string[]} - Path to get to server
|
||||
*/
|
||||
export function pathToServer(ns, end, start = ns.getHostname()) {
|
||||
if(start == end) return [end];
|
||||
const [ignore, network] = scanNetwork(ns, start);
|
||||
pruneTree(network, d => d == end);
|
||||
let current = network, name, path = [start];
|
||||
while(name = Object.keys(current)[0]) {
|
||||
path.push(name);
|
||||
current = current[name];
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect to a server anywhere in the network without a backdoor.
|
||||
*
|
||||
* @param {NS} ns - BitBurner API
|
||||
*/
|
||||
export async function main(ns) {
|
||||
// Setup
|
||||
ns.disableLog('ALL');
|
||||
const argParser = new ArgParser('connect.js', 'Connect to a server anywhere in the network without a backdoor.', [
|
||||
{name: 'server', desc: 'Server to connect to'}
|
||||
]);
|
||||
const args = argParser.parse(ns.args);
|
||||
|
||||
// Help
|
||||
if(args['help'] || args['_error'].length)
|
||||
return ns.tprint(argParser.help(args['help'] ? null : args['_error'][0], args['_command']));
|
||||
|
||||
// Run
|
||||
await terminal(connectionString(ns, args['server']));
|
||||
}
|
||||
|
||||
/**
|
||||
* BitBurner autocomplete.
|
||||
*
|
||||
* @param {{servers: string[], txts: string[], scripts: string[], flags: string[]}} data - Contextual information
|
||||
* @returns {string[]} - Pool of autocomplete options
|
||||
*/
|
||||
export function autocomplete(data) {
|
||||
return [...data.servers];
|
||||
}
|
||||
|
||||
/**
|
||||
* Search the network for a device and connect to it.
|
||||
* @param ns {NS} - BitBurner API
|
||||
*/
|
||||
export async function main(ns) {
|
||||
// Setup
|
||||
ns.disableLog('ALL');
|
||||
const argParser = new ArgParser('connect.js', 'Search the network for a device and connect to it.', null, [
|
||||
{name: 'device', desc: 'Device to connect to', default: ns.getHostname(), type: 'string'}
|
||||
]);
|
||||
|
||||
try {
|
||||
// Run
|
||||
const args = argParser.parse(ns.args);
|
||||
const [devices, network] = scanNetwork(ns);
|
||||
pruneTree(network, d => d == args['device']);
|
||||
let current = network, name, path = [];
|
||||
while(name = Object.keys(current)[0]) {
|
||||
current = current[name];
|
||||
path.push(name);
|
||||
}
|
||||
await terminal('home; ' + path.map(p => `connect ${p}`).join('; '));
|
||||
} catch(err) {
|
||||
if(err instanceof ArgError) return ns.tprint(argParser.help(err.message));
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
102
scripts/copy.js
102
scripts/copy.js
@ -1,34 +1,58 @@
|
||||
import {ArgError, ArgParser} from '/scripts/lib/arg-parser';
|
||||
import {copyWithDependencies, progressBar} from '/scripts/lib/utils';
|
||||
import {ArgParser} from '/scripts/lib/arg-parser';
|
||||
import {maxThreads, progressBar} from '/scripts/lib/utils';
|
||||
|
||||
/**
|
||||
* BitBurner autocomplete
|
||||
* @param data {server: string[], txts: string[], scripts: string[], flags: string[]} - Contextual information
|
||||
* @returns {string[]} - Pool of autocomplete options
|
||||
* Copy a file & it's dependencies to a server.
|
||||
*
|
||||
* @param {NS} ns - BitBurner API
|
||||
* @param {string} src - File to scan & copy
|
||||
* @param {string} server - Device to copy files to
|
||||
* @returns {Promise<string[]>} - Array of copied files
|
||||
*/
|
||||
export function autocomplete(data) {
|
||||
return [...data.servers, ...data.scripts];
|
||||
export async function copyWithDependencies(ns, src, server) {
|
||||
const queue = [src], found = [src];
|
||||
while(queue.length) {
|
||||
const file = queue.splice(0, 1)[0];
|
||||
const imports = new RegExp(/from ["']\.?(\/.+)["']/g);
|
||||
const script = await ns.read(file);
|
||||
let match;
|
||||
while((match = imports.exec(script)) != null) {
|
||||
const path = `${match[1]}.js`;
|
||||
if(!found.includes(path)) found.push(path);
|
||||
queue.push(path);
|
||||
}
|
||||
}
|
||||
await ns.scp(found, server);
|
||||
return found.reverse();
|
||||
}
|
||||
|
||||
/** @param {NS} ns **/
|
||||
/**
|
||||
* Copy a file & it's dependencies to a server.
|
||||
*
|
||||
* @param {NS} ns - BitBurner API
|
||||
*/
|
||||
export async function main(ns) {
|
||||
// Setup
|
||||
ns.disableLog('ALL');
|
||||
const argParser = new ArgParser('copy.js', 'Copy a file/script to a device along with any dependencies.', null, [
|
||||
{name: 'file', desc: 'File to copy', type: 'string'},
|
||||
{name: 'device', desc: 'Device to copy file(s) to', type: 'string'},
|
||||
{name: 'noDeps', desc: 'Skip copying dependencies', flags: ['-n', '--no-deps'], type: 'bool'},
|
||||
{name: 'silent', desc: 'Suppress program output', flags: ['-s', '--silent'], type: 'bool'}
|
||||
], true);
|
||||
|
||||
try {
|
||||
// Run
|
||||
const argParser = new ArgParser('copy.js', 'Copy a file & it\'s dependencies to a server.', [
|
||||
{name: 'file', desc: 'File to copy'},
|
||||
{name: 'server', desc: 'Server to copy file(s) to'},
|
||||
{name: 'args', desc: 'Arguments to start file/script with', optional: true, extras: true},
|
||||
{name: 'cpu', desc: 'Number of CPU threads to start script with, will use maximum if not specified', flags: ['-c', '--cpu']},
|
||||
{name: 'execute', desc: 'Start script after copying', flags: ['-e', '--execute'], default: false},
|
||||
{name: 'noDeps', desc: 'Skip copying dependencies', flags: ['-n', '--no-deps'], default: false},
|
||||
{name: 'quite', desc: 'Suppress program output', flags: ['-q', '--quite'], default: false},
|
||||
]);
|
||||
const args = argParser.parse(ns.args);
|
||||
|
||||
// Help
|
||||
if(args['help'] || args['_error'].length)
|
||||
return ns.tprint(argParser.help(args['help'] ? null : args['_error'][0], args['_command']));
|
||||
|
||||
// Banner
|
||||
if(!args['silent']) {
|
||||
if(!args['quite']) {
|
||||
ns.tprint('===================================================');
|
||||
ns.tprint(`Copying: ${args['device']}`);
|
||||
ns.tprint(`Copying: ${args['server']}`);
|
||||
ns.tprint('===================================================');
|
||||
ns.tprint('');
|
||||
ns.tprint('Copying Files:');
|
||||
@ -37,25 +61,47 @@ export async function main(ns) {
|
||||
|
||||
// Copy files & create download bar
|
||||
if(args['noDeps']) {
|
||||
await ns.scp(args['file'], args['device']);
|
||||
if(!args['silent']) await progressBar(ns, args['file']);
|
||||
await ns.scp(args['file'], args['server']);
|
||||
if(!args['quite']) await progressBar(ns, args['file']);
|
||||
} else {
|
||||
const files = await copyWithDependencies(ns, args['file'], args['device']);
|
||||
if(!args['silent']) {
|
||||
const files = copyWithDependencies(ns, args['file'], args['server']);
|
||||
if(!args['quite']) {
|
||||
for(let file of files) {
|
||||
await progressBar(ns, file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Run the script if requested
|
||||
if(args['execute']) {
|
||||
const threads = args['cpu'] || maxThreads(ns, args['file'], args['server']) || 1;
|
||||
if(!args['quite']) {
|
||||
ns.tprint('');
|
||||
ns.tprint(`Executing with ${threads} thread${threads > 1 ? 's' : ''}...`);
|
||||
await ns.sleep(500);
|
||||
}
|
||||
ns.killall(args['server']);
|
||||
const pid = ns.exec(args['file'], args['server'], threads, args['args']);
|
||||
if(!args['quite']) {
|
||||
ns.tprint(!!pid ? 'Done!' : 'Failed to start');
|
||||
ns.tprint('');
|
||||
}
|
||||
}
|
||||
|
||||
// Done message
|
||||
if(!args['silent']) {
|
||||
if(!args['quite']) {
|
||||
ns.tprint('');
|
||||
ns.tprint('Done!');
|
||||
ns.tprint('');
|
||||
}
|
||||
} catch(err) {
|
||||
if(err instanceof ArgError) return ns.tprint(argParser.help(err.message));
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* BitBurner autocomplete.
|
||||
*
|
||||
* @param {{servers: string[], txts: string[], scripts: string[], flags: string[]}} data - Contextual information
|
||||
* @returns {string[]} - Pool of autocomplete options
|
||||
*/
|
||||
export function autocomplete(data) {
|
||||
return [...data.servers, ...data.scripts];
|
||||
}
|
||||
|
@ -1,77 +1,93 @@
|
||||
import {ArgError, ArgParser} from '/scripts/lib/arg-parser';
|
||||
import {copyWithDependencies, scanNetwork} from '/scripts/lib/utils';
|
||||
import {ArgParser} from '/scripts/lib/arg-parser';
|
||||
import {availableExploits} from '/scripts/rootkit';
|
||||
import {copyWithDependencies} from '/scripts/copy';
|
||||
import {maxThreads} from "/scripts/lib/utils";
|
||||
|
||||
/**
|
||||
* BitBurner autocomplete
|
||||
* @param data {server: string[], txts: string[], scripts: string[], flags: string[]} - Contextual information
|
||||
* @returns {string[]} - Pool of autocomplete options
|
||||
* Scan the network for servers.
|
||||
*
|
||||
* @param {NS} ns - BitBurner API
|
||||
* @param {string} server - Entrypoint to network
|
||||
* @param {number} maxDepth - Depth to scan to
|
||||
* @returns {[string[], Object]} - A tuple including an array of discovered servers & a tree of the network
|
||||
*/
|
||||
export function autocomplete(data) {
|
||||
return [...data.scripts, '{{TARGET}}'];
|
||||
export function scanNetwork(ns, server = ns.getHostname(), maxDepth = Infinity) {
|
||||
let discovered = [server];
|
||||
function scan (server, depth = 1) {
|
||||
if(depth > maxDepth) return {};
|
||||
const localTargets = ns.scan(server).filter(s => !discovered.includes(s));
|
||||
discovered = [...discovered, ...localTargets];
|
||||
return localTargets.reduce((acc, s) => ({...acc, [s]: scan(s, depth + 1)}), {});
|
||||
}
|
||||
const network = scan(server);
|
||||
return [discovered.slice(1), network];
|
||||
}
|
||||
|
||||
/**
|
||||
* Search the network for targets to execute a script against.
|
||||
* @param ns {NS} - BitBurner API
|
||||
* Search the network for servers to execute a script against.
|
||||
*
|
||||
* @param {NS} ns - BitBurner API
|
||||
*/
|
||||
export async function main(ns) {
|
||||
// Setup
|
||||
ns.disableLog('ALL');
|
||||
const argParser = new ArgParser('crawler.js', 'Search the network for targets to execute a script against.', null, [
|
||||
{name: 'script', desc: 'Script to copy & execute', type: 'string'},
|
||||
{name: 'args', desc: 'Arguments for script. Forward the current target with: {{TARGET}}', optional: true, extras: true, type: 'string'},
|
||||
{name: 'cpu', desc: 'Number of CPU threads to use with script', flags: ['-c', '--cpu'], type: 'num'},
|
||||
{name: 'depth', desc: 'Depth to scan to, defaults to 3', flags: ['-d', '--depth'], default: Infinity, type: 'num'},
|
||||
{name: 'kill', desc: 'Kill all scripts running on device', flags: ['-k', '--kill'], type: 'bool'},
|
||||
{name: 'level', desc: 'Exclude targets with higher hack level, defaults to current hack level', flags: ['--level'], default: ns.getHackingLevel(), type: 'num'},
|
||||
{name: 'remoteExec', desc: 'Copy script to remote device & run there', flags: ['-e', '--remote-exec'], type: 'bool'},
|
||||
{name: 'rooted', desc: 'Filter to devices that have been rooted', flags: ['-r', '--rooted'], type: 'bool'},
|
||||
{name: 'notRooted', desc: 'Filter to devices that have not been rooted', flags: ['-n', '--not-rooted'], type: 'bool'},
|
||||
{name: 'ports', desc: 'Exclude targets with too many closed ports', flags: ['-p', '--ports'], default: Infinity, type: 'num'},
|
||||
{name: 'silent', desc: 'Suppress program output', flags: ['-s', '--silent'], type: 'bool'},
|
||||
{name: 'verbose', desc: 'Display the device names in the final report', flags: ['-v', '--verbose'], type: 'bool'},
|
||||
], true);
|
||||
const argParser = new ArgParser('crawler.js', 'Search the network for servers to execute a script against.', [
|
||||
{name: 'script', desc: 'Script to copy & execute'},
|
||||
{name: 'args', desc: 'Arguments for script. Forward the discovered server with: {{SERVER}}', optional: true, extras: true, type: 'string'},
|
||||
{name: 'cpu', desc: 'Number of CPU threads to start script with, will use maximum if not specified', flags: ['-c', '--cpu']},
|
||||
{name: 'depth', desc: 'Depth to scan to, defaults to 3', flags: ['-d', '--depth'], default: Infinity},
|
||||
{name: 'kill', desc: 'Kill all running scripts on the server', flags: ['-k', '--kill'], default: false},
|
||||
{name: 'level', desc: 'Skip servers with higher hack level, defaults to current hack level', flags: ['--level'], default: ns.getHackingLevel()},
|
||||
{name: 'remoteExec', desc: 'Execute script on remote server', flags: ['-e', '--remote-exec'], default: false},
|
||||
{name: 'rooted', desc: 'Only servers that have been rooted', flags: ['-r', '--rooted'], default: false},
|
||||
{name: 'notRooted', desc: 'Only servers that have not been rooted', flags: ['-n', '--not-rooted'], default: false},
|
||||
{name: 'ports', desc: 'Skip servers with too many closed ports', flags: ['-p', '--ports'], default: availableExploits(ns).length},
|
||||
{name: 'quite', desc: 'Suppress program output', flags: ['-q', '--quite'], default: false},
|
||||
{name: 'verbose', desc: 'Display the server names in the final report', flags: ['-v', '--verbose'], default: false},
|
||||
]);
|
||||
const args = argParser.parse(ns.args);
|
||||
|
||||
// Help
|
||||
if(args['help'] || args['_error'].length)
|
||||
return ns.tprint(argParser.help(args['help'] ? null : args['_error'][0], args['_command']));
|
||||
|
||||
try {
|
||||
// Run
|
||||
const localhost = ns.getHostname();
|
||||
const args = argParser.parse(ns.args);
|
||||
const [devices, network] = scanNetwork(ns);
|
||||
const [servers, network] = scanNetwork(ns);
|
||||
let complete = [], failed = [], skipped = [];
|
||||
for(let device of devices) {
|
||||
for(let server of servers) {
|
||||
// Check root status if needed
|
||||
const rooted = ns.hasRootAccess(device);
|
||||
const rooted = ns.hasRootAccess(server);
|
||||
if(args['rooted'] && !rooted) continue;
|
||||
if(args['notRooted'] && rooted) continue;
|
||||
|
||||
// Skip invalid devices
|
||||
if(device == 'home' || args['level'] < ns.getServerRequiredHackingLevel(device) || args['ports'] < ns.getServerNumPortsRequired(device)) {
|
||||
skipped.push(device);
|
||||
// Skip invalid servers
|
||||
if(server == 'home' || args['level'] < ns.getServerRequiredHackingLevel(server) || args['ports'] < ns.getServerNumPortsRequired(server)) {
|
||||
skipped.push(server);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Start script
|
||||
if(args['kill']) ns.killall(device);
|
||||
const scriptArgs = args['args'].map(arg => arg.toUpperCase() == '{{TARGET}}' ? device : arg);
|
||||
const [totalRam, usedRam] = ns.getServerRam(args['remoteExec'] ? device : localhost);
|
||||
const threads = args['cpu'] || ~~((totalRam - usedRam) / ns.getScriptRam(args['script'], localhost)) || 1;
|
||||
if(args['remoteExec']) await copyWithDependencies(ns, args['script'], device);
|
||||
const pid = ns.exec(args['script'], args['remoteExec'] ? device : localhost, threads, ...scriptArgs);
|
||||
if(args['kill']) ns.killall(server);
|
||||
const scriptArgs = args['args'].map(arg => arg.toUpperCase() == '{{SERVER}}' ? server : arg);
|
||||
const threads = args['cpu'] || maxThreads(ns, args['script'], server) || 1;
|
||||
if(args['remoteExec']) await copyWithDependencies(ns, args['script'], server);
|
||||
const pid = ns.exec(args['script'], args['remoteExec'] ? server : localhost, threads, ...scriptArgs);
|
||||
if(pid == 0) {
|
||||
failed.push(device);
|
||||
failed.push(server);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Wait for script to finish if local
|
||||
if(!args['remoteExec'])
|
||||
while(ns.scriptRunning(args['script'], localhost)) await ns.sleep(1000);
|
||||
complete.push(device);
|
||||
complete.push(server);
|
||||
}
|
||||
|
||||
// Output report
|
||||
if(!args['silent']) {
|
||||
if(!args['quite']) {
|
||||
ns.tprint('===================================================');
|
||||
ns.tprint(`Crawler Report: ${complete.length + failed.length + skipped.length} Devices`);
|
||||
ns.tprint(`Crawler: ${complete.length + failed.length + skipped.length} Servers`);
|
||||
ns.tprint('===================================================');
|
||||
if(args['verbose']) {
|
||||
ns.tprint(`Complete (${complete.length}):`);
|
||||
@ -88,8 +104,14 @@ export async function main(ns) {
|
||||
ns.tprint('');
|
||||
}
|
||||
}
|
||||
} catch(err) {
|
||||
if(err instanceof ArgError) return ns.tprint(argParser.help(err.message));
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* BitBurner autocomplete.
|
||||
*
|
||||
* @param {{servers: string[], txts: string[], scripts: string[], flags: string[]}} data - Contextual information
|
||||
* @returns {string[]} - Pool of autocomplete options
|
||||
*/
|
||||
export function autocomplete(data) {
|
||||
return [...data.scripts, ...data.servers, '{{SERVER}}'];
|
||||
}
|
||||
|
@ -1,42 +1,68 @@
|
||||
import {ArgError, ArgParser} from '/scripts/lib/arg-parser';
|
||||
import {bestTarget} from '/scripts/lib/utils';
|
||||
import {ArgParser} from '/scripts/lib/arg-parser';
|
||||
import {toCurrency} from '/scripts/lib/utils';
|
||||
import {scanNetwork} from '/scripts/crawler';
|
||||
|
||||
/**
|
||||
* Scan the network for the best device(s) to mine.
|
||||
* Sort array of servers based on the potential return/yield.
|
||||
*
|
||||
* @param {NS} ns - BitBurner API
|
||||
* @param {string[]} servers - List of servers to sort based on yield
|
||||
* @returns {[string, number][]} - Sorted list of servers & their potential yield per minute
|
||||
*/
|
||||
export function bestTarget(ns, servers) {
|
||||
return servers.map(s => [s, serverYield(ns, s)]).sort((a, b) => {
|
||||
if(a[1] < b[1]) return 1;
|
||||
if(a[1] > b[1]) return -1;
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the average return per minute when hacking a server.
|
||||
*
|
||||
* **Disclaimer:** Does not take into account security or weaken time.
|
||||
*
|
||||
* @param {NS} ns - BitBurner API
|
||||
* @param {string} server - Server to calculate yield for
|
||||
* @returns {number} - $/minute
|
||||
*/
|
||||
export function serverYield(ns, server) {
|
||||
return (ns.hackAnalyze(server) * ns.getServerMaxMoney(server))
|
||||
* ((60 / (ns.getHackTime(server) / 1000)) * ns.hackAnalyzeChance(server));
|
||||
}
|
||||
|
||||
/**
|
||||
* Scan the network for the best server(s) to hack.
|
||||
*
|
||||
* @param ns {NS} - BitBurner API
|
||||
* @returns {*}
|
||||
*/
|
||||
export function main(ns) {
|
||||
// Setup
|
||||
ns.disableLog('ALL');
|
||||
const argParser = new ArgParser('find-target.js', 'Scan the network for the best device(s) to mine.', null, [
|
||||
{name: 'count', desc: 'Number of devices to return in order from best to worst', flags: ['-c', '--count'], default: Infinity, type: 'number'},
|
||||
{name: 'rooted', desc: 'Filter to devices that have been rooted', flags: ['-r', '--rooted'], type: 'bool'},
|
||||
{name: 'notRooted', desc: 'Filter to devices that have not been rooted', flags: ['-n', '--not-rooted'], type: 'bool'},
|
||||
{name: 'verbose', desc: 'Display the estimated income per minute per core', flags: ['-v', '--verbose'], type: 'bool'},
|
||||
const argParser = new ArgParser('find-target.js', 'Scan the network for the best servers(s) to hack.',[
|
||||
{name: 'count', desc: 'Number of servers to return', flags: ['-c', '--count'], default: Infinity},
|
||||
{name: 'rooted', desc: 'Only servers that have been rooted', flags: ['-r', '--rooted'], default: false},
|
||||
{name: 'notRooted', desc: 'Only servers that have not been rooted', flags: ['-n', '--not-rooted'], default: false},
|
||||
{name: 'verbose', desc: 'Display the estimated income per minute per core', flags: ['-v', '--verbose'], default: false},
|
||||
]);
|
||||
|
||||
try {
|
||||
// Run
|
||||
const args = argParser.parse(ns.args);
|
||||
|
||||
// Help
|
||||
if(args['help'] || args['_error'].length)
|
||||
return ns.tprint(argParser.help(args['help'] ? null : args['_error'][0], args['_command']));
|
||||
|
||||
// Banner
|
||||
ns.tprint('===================================================');
|
||||
ns.tprint(`Finding Targets:`);
|
||||
ns.tprint('===================================================');
|
||||
|
||||
// Search & display results
|
||||
bestTarget(ns).filter((t, i) => !args['count'] || i < args['count'])
|
||||
.filter(t => !args['rooted'] || t.hasAdminRights)
|
||||
.filter(t => !args['notRooted'] || !t.hasAdminRights)
|
||||
.map(t => `${t.hostname}${args['verbose'] ? ` (${t.moneyAMinute.toLocaleString('en-US', {
|
||||
style: 'currency',
|
||||
currency: 'USD',
|
||||
})})` : ''}`)
|
||||
.forEach((t, i) => ns.tprint(`${i + 1}) ${t}`));
|
||||
const [servers, ignore] = scanNetwork(ns);
|
||||
bestTarget(ns, servers).map(s => [...s, ns.hasRootAccess(s[0])])
|
||||
.filter(s => (!args['rooted'] || s[2]) || (!args['notRooted'] || !s[2]))
|
||||
.filter((s, i) => i < args['count'])
|
||||
.map(s => `${s[0]}${args['verbose'] ? ` (${toCurrency(s[1])})` : ''}`)
|
||||
.forEach((s, i) => ns.tprint(`${i + 1}) ${s}`));
|
||||
ns.tprint('');
|
||||
} catch(err) {
|
||||
if(err instanceof ArgError) return ns.tprint(argParser.help(err.message));
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
@ -1,67 +1,77 @@
|
||||
import {ArgError, ArgParser} from '/scripts/lib/arg-parser';
|
||||
import {ArgParser} from '/scripts/lib/arg-parser';
|
||||
import {Logger} from '/scripts/lib/logger';
|
||||
import {toCurrency} from '/scripts/lib/utils';
|
||||
|
||||
/**
|
||||
* Buy, upgrade & manage Hacknet nodes automatically.
|
||||
* @params ns {NS} - BitBurner API
|
||||
* Strategy is to buy a new node when ever we can & then resort to the cheapest upgrade. If auto-scale is on a new
|
||||
* server will be purchased when it becomes the cheapest option.
|
||||
*
|
||||
* @params {NS} ns - BitBurner API
|
||||
*/
|
||||
export async function main(ns) {
|
||||
// Setup
|
||||
ns.disableLog('ALL');
|
||||
const argParser = new ArgParser('hacknet-manager.js', 'Buy, upgrade & manage Hacknet nodes automatically. Tail for live updates.', null, [
|
||||
{name: 'limit', desc: 'Limit the number of nodes the manager will buy, defaults to 8', optional: true, default: 8, type: 'num'},
|
||||
{name: 'autoLimit', desc: 'Automatically increase the node limit when there is nothing to do', flags: ['-a', '--auto-limit'], type: 'bool'},
|
||||
{name: 'balance', desc: 'Prevent spending bellow point', flags: ['-b', '--balance'], type: 'num'},
|
||||
{name: 'sleep', desc: 'Amount of time to wait between purchases, defaults to 1 (second)', flags: ['-s', '--sleep'], default: 1, type: 'num'}
|
||||
const argParser = new ArgParser('hacknet-manager.js', 'Buy, upgrade & manage Hacknet nodes automatically. Tail for live updates.', [
|
||||
{name: 'limit', desc: 'Limit the number of nodes the manager will buy, defaults to 8 or the current number of nodes', optional: true, default: 8},
|
||||
{name: 'autoLimit', desc: 'Automatically increase the node limit when there is nothing to do', flags: ['-a', '--auto-limit'], default: false},
|
||||
{name: 'balance', desc: 'Prevent spending bellow point', flags: ['-b', '--balance'], default: false},
|
||||
{name: 'sleep', desc: 'Amount of time to wait between purchases, defaults to 1 (second)', flags: ['-s', '--sleep'], default: 1}
|
||||
]);
|
||||
|
||||
try {
|
||||
// Run
|
||||
const args = argParser.parse(ns.args);
|
||||
let nodeCount = ns.hacknet.numNodes();
|
||||
const args = argParser.parse(ns.args);
|
||||
if(nodeCount > args['limit']) args['limit'] = nodeCount;
|
||||
const logger = new Logger(ns, [() => `Hacknet Manager: ${nodeCount}/${args['limit']}`]);
|
||||
|
||||
// Help
|
||||
if(args['help'] || args['_error'].length)
|
||||
return ns.tprint(argParser.help(args['help'] ? null : args['_error'][0], args['_command']));
|
||||
|
||||
// Main loop
|
||||
// noinspection InfiniteLoopJS
|
||||
while(true) {
|
||||
const balance = ns.getServerMoneyAvailable('home');
|
||||
const newNodeCost = ns.hacknet.getPurchaseNodeCost();
|
||||
|
||||
// Check if we should buy a new node
|
||||
if(nodeCount < args['limit'] && balance - ns.hacknet.getPurchaseNodeCost() >= args['balance']) {
|
||||
// Check if we should wait to buy a node
|
||||
if(nodeCount < args['limit'] && balance - newNodeCost >= args['balance']) {
|
||||
nodeCount++;
|
||||
ns.hacknet.purchaseNode();
|
||||
logger.log(`Buying Node ${nodeCount}`);
|
||||
logger.log(`Node ${nodeCount} - Purchased - ${toCurrency(newNodeCost)}`);
|
||||
} else {
|
||||
// Create an ordered list of nodes by their cheapest upgrade
|
||||
const upgrades = Array(nodeCount).fill(null)
|
||||
.map((ignore, i) => ({ // Gather information
|
||||
index: i,
|
||||
cacheCost: ns.hacknet.getCacheUpgradeCost(i),
|
||||
coreCost: ns.hacknet.getCoreUpgradeCost(i),
|
||||
levelCost: ns.hacknet.getLevelUpgradeCost(i),
|
||||
ramCost: ns.hacknet.getRamUpgradeCost(i),
|
||||
cacheCost: ns.hacknet.getCacheUpgradeCost(i, 1),
|
||||
coreCost: ns.hacknet.getCoreUpgradeCost(i, 1),
|
||||
levelCost: ns.hacknet.getLevelUpgradeCost(i, 1),
|
||||
ramCost: ns.hacknet.getRamUpgradeCost(i, 1),
|
||||
...ns.hacknet.getNodeStats(i)
|
||||
})).map(node => { // Figure out cheapest upgrade
|
||||
if(node.cacheCost != 0 && node.cacheCost != Infinity && node.cacheCost <= node.coreCost && node.cacheCost <= node.levelCost && node.cacheCost <= node.ramCost) {
|
||||
node.bestUpgrade = {
|
||||
name: 'cache',
|
||||
cost: node.cacheCost,
|
||||
purchase: () => ns.hacknet.upgradeCache(node.index)
|
||||
purchase: () => ns.hacknet.upgradeCache(node.index,1 )
|
||||
};
|
||||
} else if(node.coreCost != 0 && node.coreCost != Infinity && node.coreCost <= node.cacheCost && node.coreCost <= node.levelCost && node.coreCost <= node.ramCost) {
|
||||
node.bestUpgrade = {
|
||||
name: 'cores',
|
||||
cost: node.coreCost,
|
||||
purchase: () => ns.hacknet.upgradeCore(node.index)
|
||||
purchase: () => ns.hacknet.upgradeCore(node.index, 1)
|
||||
};
|
||||
} else if(node.ramCost != 0 && node.ramCost != Infinity && node.ramCost <= node.cacheCost && node.ramCost <= node.levelCost && node.ramCost <= node.coreCost) {
|
||||
node.bestUpgrade = {
|
||||
name: 'ram',
|
||||
cost: node.ramCost,
|
||||
purchase: () => ns.hacknet.upgradeRam(node.index)
|
||||
purchase: () => ns.hacknet.upgradeRam(node.index, 1)
|
||||
};
|
||||
} else {
|
||||
node.bestUpgrade = {
|
||||
name: 'level',
|
||||
cost: node.levelCost,
|
||||
purchase: () => ns.hacknet.upgradeLevel(node.index)
|
||||
purchase: () => ns.hacknet.upgradeLevel(node.index, 1)
|
||||
};
|
||||
}
|
||||
return node;
|
||||
@ -71,24 +81,27 @@ export async function main(ns) {
|
||||
return 0;
|
||||
});
|
||||
|
||||
// Apply the cheapest upgrade
|
||||
if(upgrades.length) {
|
||||
if(args['autoLimit'] && nodeCount >= args['limit'] && upgrades[0].bestUpgrade.cost == Infinity) {
|
||||
// Apply the cheapest upgrade/purchase
|
||||
if((!upgrades.length || newNodeCost < upgrades[0].bestUpgrade.cost) && args['autoLimit'] && nodeCount >= args['limit']) {
|
||||
args['limit'] = Math.max(nodeCount, args['limit']) + 1;
|
||||
logger.log(`Increasing node limit to ${args['limit']}`);
|
||||
} else if(balance - upgrades[0].bestUpgrade.cost >= args['balance']) {
|
||||
const cost = Math.round(upgrades[0].bestUpgrade.cost * 100) / 100;
|
||||
logger.log(`Node ${upgrades[0].index} - ${upgrades[0].bestUpgrade.name} ${upgrades[0][upgrades[0].bestUpgrade.name] + 1} - $${cost}`);
|
||||
} else if(upgrades.length && upgrades[0].bestUpgrade.cost != Infinity) {
|
||||
logger.log(`Node ${upgrades[0].index} - ${upgrades[0].bestUpgrade.name} ${upgrades[0][upgrades[0].bestUpgrade.name] + 1} - ${toCurrency(upgrades[0].bestUpgrade.cost)}`);
|
||||
upgrades[0].bestUpgrade.purchase();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check again in 1s
|
||||
// Wait & then check again
|
||||
await ns.sleep(args['sleep'] * 1000);
|
||||
}
|
||||
} catch(err) {
|
||||
if(err instanceof ArgError) return ns.tprint(argParser.help(err.message));
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* BitBurner autocomplete.
|
||||
*
|
||||
* @param {{servers: string[], txts: string[], scripts: string[], flags: string[]}} data - Contextual information
|
||||
* @returns {string[]} - Pool of autocomplete options
|
||||
*/
|
||||
export function autocomplete(data) {
|
||||
return new Array(10).fill(null).map((ignore, i) => Math.pow(i, 2).toString());
|
||||
}
|
||||
|
@ -1,95 +1,120 @@
|
||||
export class ArgError extends Error {}
|
||||
|
||||
export class ArgParser {
|
||||
/**
|
||||
* Create a unix-like argument parser to extract flags from the argument list. Can also create help messages.
|
||||
* @param name {string} - Script name
|
||||
* @param desc {string} - Help text desciption
|
||||
* @param examples {string[]} - Help text examples
|
||||
* @param argList {name: string, desc: string, flags: string[], type: string, default: any}[] - Array of CLI arguments
|
||||
* @param allowUnknown {boolean} - Allow unknown flags
|
||||
*
|
||||
* @param {string} name - Script name
|
||||
* @param {string} desc - Help description
|
||||
* @param {(ArgParser | {name: string, desc: string, flags?: string[], optional?: boolean, default?: any})[]} argList - Array of CLI arguments
|
||||
* @param {string[]} examples - Additional examples to display
|
||||
*/
|
||||
constructor(name, desc, examples, argList, allowUnknown = false) {
|
||||
this.name = name ?? 'example.js';
|
||||
this.description = desc ?? 'Example description';
|
||||
this.examples = examples || [`${argList.find(arg => !!arg.flags) ? '[OPTIONS] ' : ''}${argList.filter(arg => !arg.flags).map(arg => (arg.optional ? `[${arg.name.toUpperCase()}]` : arg.name.toUpperCase()) + (arg.extras ? '...' : '')).join(' ')}`];
|
||||
this.examples.push('--help');
|
||||
this.argList = argList || [];
|
||||
this.argList.push({name: 'help', desc: 'Display this help message', flags: ['-h', '--help'], type: 'bool'});
|
||||
this.allowUnknown = allowUnknown;
|
||||
constructor(name, desc, argList = [], examples = []) {
|
||||
this.name = name;
|
||||
this.desc = desc;
|
||||
|
||||
// Arguments
|
||||
this.commands = argList.filter(arg => arg instanceof ArgParser);
|
||||
this.args = argList.filter(arg => !arg.flags || !arg.flags.length);
|
||||
this.flags = argList.filter(arg => !(arg instanceof ArgParser) && arg.flags && arg.flags.length);
|
||||
this.flags.push({name: 'help', desc: 'Display this help message', flags: ['-h', '--help'], default: false});
|
||||
this.defaults = argList.reduce((acc, arg) => ({...acc, [arg.name]: arg?.extras ? [] : arg.default ?? null}), {});
|
||||
|
||||
// Examples
|
||||
this.examples = [
|
||||
...examples,
|
||||
`[OPTIONS] ${this.args.map(arg => (arg.optional ? `[${arg.name.toUpperCase()}]` : arg.name.toUpperCase()) + (arg.extras ? '...' : '')).join(' ')}`,
|
||||
this.commands.length ? `[OPTIONS] COMMAND` : null,
|
||||
`--help ${this.commands.length ? '[COMMAND]' : ''}`
|
||||
].filter(e => !!e);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse an array into an arguments dictionary using the configuration.
|
||||
* @param args {string[]} - Array of arguments to be parsed
|
||||
*
|
||||
* @param {string[]} args - Array of arguments to be parsed
|
||||
* @returns {object} - Dictionary of arguments with defaults applied
|
||||
*/
|
||||
parse(args) {
|
||||
// Parse arguments
|
||||
const queue = [...args], extra = [];
|
||||
const parsed = this.argList.reduce((acc, arg) => ({...acc, [arg.name]: arg.default ?? (arg.type == 'bool' ? false : null)}), {});
|
||||
// Flags
|
||||
let extras = [], parsed = {...this.defaults, '_error': []}, queue = [...args];
|
||||
while(queue.length) {
|
||||
let parse = queue.splice(0, 1)[0];
|
||||
if(parse[0] == '-') {
|
||||
// Check combined flags
|
||||
if(parse[1] != '-' && parse.length > 2) {
|
||||
parse = `-${parse[1]}`;
|
||||
queue = parse.substring(1).split('').map(a => `-${a}`).concat(queue);
|
||||
let arg = queue.splice(0, 1)[0];
|
||||
if(arg[0] == '-') { // Flags
|
||||
// Check for combined shorthand
|
||||
if(arg[1] != '-' && arg.length > 2) {
|
||||
queue = [...arg.substring(2).split('').map(a => `-${a}`), ...queue];
|
||||
arg = `-${arg[1]}`;
|
||||
}
|
||||
// Find & add flag
|
||||
const split = parse.split('=');
|
||||
const arg = this.argList.find(arg => arg.flags && arg.flags.includes(split[0] || parse));
|
||||
if(arg == null) {
|
||||
if(!this.allowUnknown) throw new ArgError(`Option unknown: ${parse}`);
|
||||
extra.push(parse);
|
||||
const combined = arg.split('=');
|
||||
const argDef = this.flags.find(flag => flag.flags.includes(combined[0] || arg));
|
||||
if(argDef == null) { // Not found, add to extras
|
||||
extras.push(arg);
|
||||
continue;
|
||||
}
|
||||
if(arg.name == 'help') throw new ArgError('Help');
|
||||
const value = arg.type == 'bool' ? true : split[1] || queue.splice(queue.findIndex(q => q[0] != '-'), 1)[0];
|
||||
if(value == null) throw new ArgError(`Option missing value: ${arg.name}`);
|
||||
parsed[arg.name] = value;
|
||||
} else {
|
||||
// Save for required parsing
|
||||
extra.push(parse);
|
||||
const value = argDef.default === false ? true : argDef.default === true ? false : queue.splice(queue.findIndex(q => q[0] != '-'), 1)[0] || argDef.default;
|
||||
if(value == null) parsed['_error'].push(`Option missing value: ${arg.name}`);
|
||||
parsed[argDef.name] = value;
|
||||
} else { // Command
|
||||
const c = this.commands.find(command => command.name == arg);
|
||||
if(!!c) {
|
||||
const parsedCommand = c.parse(queue.splice(0, queue.length));
|
||||
Object.keys(parsedCommand).forEach(key => {
|
||||
if(parsed[key] != parsedCommand[key] && parsedCommand[key] == c.defaults[key])
|
||||
delete parsedCommand[key];
|
||||
});
|
||||
parsed = {
|
||||
...parsed,
|
||||
...parsedCommand,
|
||||
_command: c.name
|
||||
};
|
||||
} else extras.push(arg); // Not found, add to extras
|
||||
}
|
||||
}
|
||||
// Arguments
|
||||
this.argList.filter(arg => !arg.flags && !arg.extras).forEach(arg => {
|
||||
if(!arg.optional && !extra.length) throw new ArgError(`Argument missing: ${arg.name.toUpperCase()}`);
|
||||
const value = extra.splice(0, 1)[0];
|
||||
if(value != null) parsed[arg.name] = value;
|
||||
this.args.filter(arg => !arg.extras).forEach(arg => {
|
||||
if(!arg.optional && !extras.length) parsed['_error'].push(`Argument missing: ${arg.name.toUpperCase()}`);
|
||||
if(extras.length) parsed[arg.name] = extras.splice(0, 1)[0];
|
||||
});
|
||||
// Extras
|
||||
const extraKey = this.argList.find(arg => arg.extras)?.name || 'extra';
|
||||
parsed[extraKey] = extra;
|
||||
const extraKey = this.args.find(arg => arg.extras)?.name || '_extra';
|
||||
parsed[extraKey] = extras;
|
||||
return parsed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create help message from the provided description, examples & argument list.
|
||||
* @param message {string} - Message to display, defaults to the description
|
||||
* Create help message from the provided description & argument list.
|
||||
*
|
||||
* @param {string} message - Message to display, defaults to the description
|
||||
* @param {string} command - Command help message to show
|
||||
* @returns {string} - Help message
|
||||
*/
|
||||
help(msg) {
|
||||
help(message = '', command = '') {
|
||||
const spacer = (text) => Array(24 - text.length || 1).fill(' ').join('');
|
||||
|
||||
// Help with specific command
|
||||
if(command) {
|
||||
const argParser = this.commands.find(parser => parser.name == command);
|
||||
if(!argParser) throw new Error(`${command.toUpperCase()} does not have a help`)
|
||||
return argParser.help(message);
|
||||
}
|
||||
|
||||
// Description
|
||||
let message = '\n\n' + (msg && msg.toLowerCase() != 'help' ? msg : this.description);
|
||||
// Usage
|
||||
if(this.examples.length) message += '\n\nUsage:\t' + this.examples.map(ex => `run ${this.name} ${ex}`).join('\n\t');
|
||||
let msg = `\n\n${message || this.desc}`;
|
||||
// Examples
|
||||
msg += '\n\nUsage:\t' + this.examples.map(ex => `run ${this.name} ${ex}`).join('\n\t');
|
||||
// Arguments
|
||||
const req = this.argList.filter(a => !a.flags);
|
||||
if(req.length) message += '\n\n\t' + req.map(arg => {
|
||||
const padding = 3 - ~~(arg.name.length / 8);
|
||||
return `${arg.name.toUpperCase()}${Array(padding).fill('\t').join('')} ${arg.desc}`;
|
||||
}).join('\n\t');
|
||||
if(this.args.length) msg += '\n\n\t' + this.args
|
||||
.map(arg => `${arg.name.toUpperCase()}${spacer(arg.name)}${arg.desc}`)
|
||||
.join('\n\t');
|
||||
// Flags
|
||||
const opts = this.argList.filter(a => a.flags);
|
||||
if(opts.length) message += '\n\nOptions:\n\t' + opts.map(a => {
|
||||
const flgs = a.flags.join(' ');
|
||||
const padding = 3 - ~~(flgs.length / 8);
|
||||
return `${flgs}${Array(padding).fill('\t').join('')} ${a.desc}`;
|
||||
msg += '\n\nOptions:\n\t' + this.flags.map(flag => {
|
||||
const flags = flag.flags.join(', ');
|
||||
return `${flags}${spacer(flags)}${flag.desc}`;
|
||||
}).join('\n\t');
|
||||
// Print final message
|
||||
return `${message}\n\n`;
|
||||
// Commands
|
||||
if(this.commands.length) msg += '\n\nCommands:\n\t' + this.commands
|
||||
.map(command => `${command.name}${spacer(command.name)}${command.desc}`)
|
||||
.join('\n\t');
|
||||
return `${msg}\n\n`;
|
||||
}
|
||||
}
|
||||
|
@ -1,119 +0,0 @@
|
||||
export class ArgError extends Error {}
|
||||
|
||||
export class ArgParser {
|
||||
/**
|
||||
* Create a unix-like argument parser to extract flags from the argument list. Can also create help messages.
|
||||
* @param name {string} - Script name
|
||||
* @param desc {string} - Help description
|
||||
* @param argList {(ArgParser || {name: string, desc: string, flags: string[], optional: boolean, default: boolean})[]} - Array of CLI arguments
|
||||
* @param examples {string[]} - Additional examples to display
|
||||
*/
|
||||
constructor(ns, name, desc, argList = [], examples = []) {
|
||||
this.ns = ns;
|
||||
this.name = name;
|
||||
this.desc = desc;
|
||||
|
||||
// Arguments
|
||||
this.commands = argList.filter(arg => arg instanceof ArgParser);
|
||||
this.args = argList.filter(arg => !arg.flags || !arg.flags.length);
|
||||
this.flags = argList.filter(arg => !(arg instanceof ArgParser) && arg.flags && arg.flags.length);
|
||||
this.flags.push({name: 'help', desc: 'Display this help message', flags: ['-h', '--help'], default: false});
|
||||
this.defaults = argList.reduce((acc, arg) => ({...acc, [arg.name]: arg?.extras ? [] : arg.default ?? null}), {});
|
||||
|
||||
// Examples
|
||||
this.examples = [
|
||||
...examples,
|
||||
`[OPTIONS] ${this.args.map(arg => (arg.optional ? `[${arg.name.toUpperCase()}]` : arg.name.toUpperCase()) + (arg.extras ? '...' : '')).join(' ')}`,
|
||||
this.commands.length ? `[OPTIONS] COMMAND` : null,
|
||||
`--help ${this.commands.length ? '[COMMAND]' : ''}`
|
||||
].filter(e => !!e);
|
||||
}
|
||||
/**
|
||||
* Parse an array into an arguments dictionary using the configuration.
|
||||
* @param args {string[]} - Array of arguments to be parsed
|
||||
* @returns {object} - Dictionary of arguments with defaults applied
|
||||
*/
|
||||
parse(args = this.ns.args) {
|
||||
// Parse arguments
|
||||
let extras = [], parsed = {...this.defaults}, queue = [...args];
|
||||
while(queue.length) {
|
||||
let arg = queue.splice(0, 1)[0];
|
||||
if(arg[0] == '-') { // Flags
|
||||
// Check for combined shorthand
|
||||
if(arg[1] != '-' && arg.length > 2) {
|
||||
queue = [...arg.substring(2).split('').map(a => `-${a}`), ...queue];
|
||||
arg = `-${arg[1]}`;
|
||||
}
|
||||
// Find & add flag
|
||||
const combined = arg.split('=');
|
||||
const argDef = this.flags.find(flag => flag.flags.includes(combined[0] || arg));
|
||||
if(argDef == null) { // Not found, add to extras
|
||||
extras.push(arg);
|
||||
continue;
|
||||
}
|
||||
const value = argDef.default === false ? true : argDef.default === true ? false : queue.splice(queue.findIndex(q => q[0] != '-'), 1)[0] || argDef.default;
|
||||
if(value == null) parsed['_error'] = `Option missing value: ${arg.name}`;
|
||||
parsed[argDef.name] = value;
|
||||
} else { // Command
|
||||
const c = this.commands.find(command => command.name == arg);
|
||||
if(!!c) {
|
||||
const parsedCommand = c.parse(queue.splice(0, queue.length));
|
||||
Object.keys(parsedCommand).forEach(key => {
|
||||
if(parsed[key] != parsedCommand[key] && parsedCommand[key] == c.defaults[key])
|
||||
delete parsedCommand[key];
|
||||
});
|
||||
parsed = {
|
||||
...parsed,
|
||||
...parsedCommand,
|
||||
_command: c.name
|
||||
};
|
||||
} else extras.push(arg); // Not found, add to extras
|
||||
}
|
||||
}
|
||||
// Arguments
|
||||
this.args.filter(arg => !arg.extras).forEach(arg => {
|
||||
if(!arg.optional && !extras.length) parsed['_error'] = `Argument missing: ${arg.name.toUpperCase()}`;
|
||||
parsed[arg.name] = extras.splice(0, 1)[0];
|
||||
});
|
||||
// Extras
|
||||
const extraKey = this.args.find(arg => arg.extras)?.name || '_extra';
|
||||
parsed[extraKey] = extras;
|
||||
return parsed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create help message from the provided description & argument list.
|
||||
* @param message {string} - Message to display, defaults to the description
|
||||
* @param command {string} - Command help message to show
|
||||
* @returns {string} - Help message
|
||||
*/
|
||||
help(message = '', command = '') {
|
||||
const spacer = (text) => Array(24 - text.length || 1).fill(' ').join('');
|
||||
|
||||
// Help with specific command
|
||||
if(command) {
|
||||
const argParser = this.commands.find(parser => parser.name == command);
|
||||
if(!argParser) throw new Error(`${command.toUpperCase()} does not have a help`)
|
||||
return argParser.help(message);
|
||||
}
|
||||
|
||||
// Description
|
||||
let msg = `\n\n${message || this.desc}`;
|
||||
// Examples
|
||||
msg += '\n\nUsage:\t' + this.examples.map(ex => `run ${this.name} ${ex}`).join('\n\t');
|
||||
// Arguments
|
||||
if(this.args.length) msg += '\n\n\t' + this.args
|
||||
.map(arg => `${arg.name.toUpperCase()}${spacer(arg.name)}${arg.desc}`)
|
||||
.join('\n\t');
|
||||
// Flags
|
||||
msg += '\n\nOptions:\n\t' + this.flags.map(flag => {
|
||||
const flags = flag.flags.join(', ');
|
||||
return `${flags}${spacer(flags)}${flag.desc}`;
|
||||
}).join('\n\t');
|
||||
// Commands
|
||||
if(this.commands.length) msg += '\n\nCommands:\n\t' + this.commands
|
||||
.map(command => `${command.name}${spacer(command.name)}${command.desc}`)
|
||||
.join('\n\t');
|
||||
this.ns.tprint(`${msg}\n\n`);
|
||||
}
|
||||
}
|
@ -4,8 +4,9 @@ export class Logger {
|
||||
|
||||
/**
|
||||
* Create a nicer log with a banner.
|
||||
* @param ns {NS} - BitBurner API
|
||||
* @param lineFns {Function[]} - Functions to generate a line (Seperated by a linebreak)
|
||||
*
|
||||
* @param {NS} ns - BitBurner API
|
||||
* @param {Function[]} lineFns - Functions to generate a line (Seperated by a linebreak)
|
||||
*/
|
||||
constructor(ns, lineFns = []) {
|
||||
this.ns = ns;
|
||||
@ -16,8 +17,9 @@ export class Logger {
|
||||
}
|
||||
|
||||
/**
|
||||
* Add red error message to logs
|
||||
* @param message {string} - Text that will be added
|
||||
* Add red error message to logs.
|
||||
*
|
||||
* @param {string} message - Text that will be added
|
||||
*/
|
||||
error(message) { this.log(`ERROR: ${message}`); }
|
||||
|
||||
@ -29,7 +31,7 @@ export class Logger {
|
||||
}
|
||||
|
||||
/**
|
||||
* Print the header using the provided functions
|
||||
* Print the header using the provided functions.
|
||||
*/
|
||||
header() {
|
||||
this.lineBreak();
|
||||
@ -40,8 +42,9 @@ export class Logger {
|
||||
}
|
||||
|
||||
/**
|
||||
* Add message to the logs
|
||||
* @param message {string} - Text that will be added
|
||||
* Add message to the logs.
|
||||
*
|
||||
* @param {string} message - Text that will be added
|
||||
*/
|
||||
log(message = '') {
|
||||
this.ns.clearLog();
|
||||
@ -52,8 +55,9 @@ export class Logger {
|
||||
}
|
||||
|
||||
/**
|
||||
* Add orange warning to the logs
|
||||
* @param message {string} - Text that will be added
|
||||
* Add orange warning to the logs.
|
||||
*
|
||||
* @param {string} message - Text that will be added
|
||||
*/
|
||||
warn(message) { this.log(`WARN: ${message}`); }
|
||||
}
|
||||
|
@ -1,42 +1,60 @@
|
||||
/**
|
||||
* Scan the entire network for the best device to hack.
|
||||
* @param ns {NS} - BitBurner API
|
||||
* @returns {string[]} - Sorted list of targets to hack based on financial return
|
||||
* Add CSS to DOM.
|
||||
*
|
||||
* @param {string} id - An ID so we can make sure we only inject it once
|
||||
* @param {string} css - CSS to inject
|
||||
*/
|
||||
export function bestTarget(ns) {
|
||||
const [devices, network] = scanNetwork(ns, 'home');
|
||||
return devices.map(d => ns.getServer(d)).filter(d => d.hasAdminRights).map(d => ({
|
||||
...d,
|
||||
moneyAMinute: (ns.hackAnalyze(d.hostname) * ns.getServerMaxMoney(d.hostname)) * ((60 / (ns.getHackTime(d.hostname) / 1000)) * ns.hackAnalyzeChance(d.hostname))}
|
||||
)).sort((a, b) => {
|
||||
if(a.moneyAMinute < b.moneyAMinute) return 1;
|
||||
if(a.moneyAMinute > b.moneyAMinute) return -1;
|
||||
return 0;
|
||||
export function addCSS(id, css) {
|
||||
const doc = eval('document');
|
||||
id = `dynamic-css-${id}`;
|
||||
const exists = doc.querySelector(`#${id}`);
|
||||
if(exists) exists.outerHTML = '';
|
||||
doc.head.insertAdjacentHTML('beforeend', `<style id="${id}">${css}</style`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format number to look like a dollar value ($1,000.00).
|
||||
*
|
||||
* @param {number} num - Number to format
|
||||
* @returns {string} - formatted value with dollar sign
|
||||
*/
|
||||
export function toCurrency(num) {
|
||||
return Number(num).toLocaleString('en-US', {
|
||||
style: 'currency',
|
||||
currency: 'USD',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy a file & scan it for dependencies copying them as well.
|
||||
* @param ns {NS} - BitBurner API
|
||||
* @param src {string} - File to scan & copy
|
||||
* @param device {string} - Device to copy files to
|
||||
* @returns {string[]} - Array of coppied files
|
||||
* Injects HTML into the terminal as a new line.
|
||||
*
|
||||
* **Disclaimer:** React will wipe out anything injected by this function.
|
||||
*
|
||||
* @param {string} html - HTML to inject into terminal
|
||||
* @param {boolean} wrap - Wrap in a list-item & paragraph to match default style
|
||||
*/
|
||||
export async function copyWithDependencies(ns, src, device) {
|
||||
const queue = [src], found = [src];
|
||||
while(queue.length) {
|
||||
const file = queue.splice(0, 1)[0];
|
||||
const imports = new RegExp(/from ["']\.?(\/.+)["']/g);
|
||||
const script = await ns.read(file);
|
||||
let match;
|
||||
while((match = imports.exec(script)) != null) {
|
||||
const path = `${match[1]}.js`;
|
||||
queue.push(path);
|
||||
found.push(path);
|
||||
export function htmlPrint(html, wrap = true) {
|
||||
setTimeout(() => {
|
||||
const doc = eval('document');
|
||||
if(wrap) {
|
||||
const liClass = doc.querySelector('#terminal li').classList.value;
|
||||
const pClass = doc.querySelector('#terminal li p').classList.value;
|
||||
html = `<li class="${liClass}"><p class="${pClass}">${html}</p></li>`
|
||||
}
|
||||
}
|
||||
await ns.scp(found, device);
|
||||
return found.reverse();
|
||||
eval('document').getElementById('terminal').insertAdjacentHTML('beforeend', html)
|
||||
}, 25);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the maximum number of threads a script can be executed with.
|
||||
*
|
||||
* @param {NS} ns - BitBurner API
|
||||
* @param {string} script - Full path to script
|
||||
* @param {string} server - Server script will run on
|
||||
* @returns {number} - Number of threads the server will be able to support
|
||||
*/
|
||||
export function maxThreads(ns, script, server = ns.getHostname()) {
|
||||
return ~~(ns.getServerMaxRam(server) / ns.getScriptRam(script, ns.getHostname()))
|
||||
}
|
||||
|
||||
/**
|
||||
@ -46,10 +64,10 @@ export async function copyWithDependencies(ns, src, device) {
|
||||
*
|
||||
* `/script/test.js [||||||||||----------] 50% (24.2 MB/s)`
|
||||
*
|
||||
* @param ns {NS} - BitBurner API
|
||||
* @param name {string} - Name to display at the begging of bar
|
||||
* @param showSpeed {boolean} - Show the speed in the progress bar
|
||||
* @param time {number} - Time it takes for bar to fill
|
||||
* @param {NS} ns - BitBurner API
|
||||
* @param {string} name - Name to display at the begging of bar
|
||||
* @param {boolean} showSpeed - Show the speed in the progress bar
|
||||
* @param {number} time - Time it takes for bar to fill
|
||||
*/
|
||||
export async function progressBar(ns, name, showSpeed = true, time = Math.random() + 0.5) {
|
||||
const text = (percentage, speed) => {
|
||||
@ -77,8 +95,9 @@ export async function progressBar(ns, name, showSpeed = true, time = Math.random
|
||||
|
||||
/**
|
||||
* **Impure:** Prune tree down to keys which pass function
|
||||
* @param tree {object} - Tree to search
|
||||
* @param fn {(key: string) => boolean} - Function to test each key with
|
||||
*
|
||||
* @param {Object} tree - Tree to search
|
||||
* @param {(key: string) => boolean} fn - Function to test each key with
|
||||
* @returns {boolean} - True if a match was found
|
||||
*/
|
||||
export function pruneTree(tree, fn) {
|
||||
@ -91,56 +110,65 @@ export function pruneTree(tree, fn) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Scan the network of a given device.
|
||||
* @param ns {NS} - BitBurner API
|
||||
* @param device {string} - Device network that will be scanned
|
||||
* @param maxDepth - Depth to scan to
|
||||
* @returns {[string[], Object]} - A tuple including an array of discovered devices & a tree of the network
|
||||
* Pause for a random amount of time.
|
||||
* @param {number} min - minimum amount of time to wait after printing text
|
||||
* @param {number} max - maximum amount of time to wait after printing text
|
||||
*/
|
||||
export function scanNetwork(ns, device = ns.getHostname(), maxDepth = Infinity) {
|
||||
let discovered = [device];
|
||||
function scan (device, depth = 1) {
|
||||
if(depth > maxDepth) return {};
|
||||
const localTargets = ns.scan(device).filter(newDevice => !discovered.includes(newDevice));
|
||||
discovered = [...discovered, ...localTargets];
|
||||
return localTargets.reduce((acc, device) => ({...acc, [device]: scan(device, depth + 1)}), {});
|
||||
}
|
||||
const network = scan(device);
|
||||
return [discovered.slice(1), network];
|
||||
export function randomSleep(min = 0.5, max = 1.5) {
|
||||
return new Promise(res => setTimeout(res, ~~(Math.random() * (max * 1000 - min * 1000)) + min * 1000));
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts function into HTML friendly string with it's arguments. Meant to be used
|
||||
* with `onclick` to execute code.
|
||||
*
|
||||
* @param {Function} fn - function that will be serialized
|
||||
* @param {...any} args - Arguments passed to function
|
||||
* @returns {string} - Serialized function with arguments: "(function(arg1, arg2, ...) {...})(arg1, arg2, ...)"
|
||||
*/
|
||||
export function serializeFunction(fn, ...args) {
|
||||
let serialized = fn.toString().replace(/function .+\W?\(/, 'function(');
|
||||
serialized = `(${serialized})(${args.map(a => JSON.stringify(a)).join()})`;
|
||||
serialized = serialized.replace(/"/g, '"');
|
||||
serialized = serialized.replace(/'/g, ''');
|
||||
return serialized;
|
||||
}
|
||||
|
||||
/**
|
||||
* Print text to the terminal & then delay for a random amount of time to emulate execution time.
|
||||
* @param ns {NS} - BitBurner API
|
||||
* @param message {string} - Text to display
|
||||
* @param min {number} - minimum amount of time to wait after printing text
|
||||
* @param max {number} - maximum amount of time to wait after printing text
|
||||
*
|
||||
* @param {NS} ns - BitBurner API
|
||||
* @param {string} message - Text to display
|
||||
* @param {boolean} first - Pause first or wait until text is displayed
|
||||
* @param {number} min - minimum amount of time to wait after printing text
|
||||
* @param {number} max - maximum amount of time to wait after printing text
|
||||
*/
|
||||
export async function slowPrint(ns, message, min = 0.5, max = 1.5) {
|
||||
const time = ~~(Math.random() * (max * 1000 - min * 1000)) + min * 1000;
|
||||
export async function slowPrint(ns, message, first = false, min = 0.5, max = 0.5) {
|
||||
if(first) await randomSleep(min, max);
|
||||
ns.tprint(message);
|
||||
await ns.sleep(time);
|
||||
await randomSleep(min, max);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a command to the terminal.
|
||||
* @param command {string} - Command that will be run
|
||||
* @returns {Promise<string>} - Command line response
|
||||
*
|
||||
* @param {string} command - Command that will be run
|
||||
* @returns {Promise<string[]>} - Any new output
|
||||
*/
|
||||
export function terminal(command) {
|
||||
// Get the terminal
|
||||
const terminalInput = document.getElementById("terminal-input");
|
||||
const doc = eval('document');
|
||||
const terminalInput = doc.getElementById("terminal-input");
|
||||
const handler = Object.keys(terminalInput)[1];
|
||||
|
||||
// Send command
|
||||
terminalInput.value = command; // Enter the command
|
||||
terminalInput[handler].onChange({target:terminalInput}); // React on change
|
||||
terminalInput[handler].onChange({target: terminalInput}); // React on change
|
||||
terminalInput[handler].onKeyDown({key: 'Enter', preventDefault: () => null}); // Enter 'keystroke'
|
||||
|
||||
// Return any new terminal output
|
||||
return new Promise(res => setTimeout(() => {
|
||||
const terminalOutput = Array.from(eval('document')
|
||||
.querySelectorAll('#terminal li p')).map(out => out.innerText);
|
||||
const terminalOutput = Array.from(doc.querySelectorAll('#terminal li p')).map(out => out.innerText);
|
||||
const i = terminalOutput.length - terminalOutput.reverse().findIndex(o => o.indexOf(command) != -1);
|
||||
res(terminalOutput.reverse().slice(i));
|
||||
}, 25));
|
||||
|
111
scripts/miner.js
111
scripts/miner.js
@ -1,60 +1,65 @@
|
||||
import {ArgError, ArgParser} from '/scripts/lib/arg-parser';
|
||||
import {Logger} from "/scripts/lib/logger";
|
||||
import {ArgParser} from '/scripts/lib/arg-parser';
|
||||
import {Logger} from '/scripts/lib/logger';
|
||||
import {toCurrency} from '/scripts/lib/utils';
|
||||
|
||||
/**
|
||||
* BitBurner autocomplete
|
||||
* @param data {server: string[], txts: string[], scripts: string[], flags: string[]} - Contextual information
|
||||
* Hack, Grow, Weaken loop to "mine" a server for money. Tail for live updates.
|
||||
*
|
||||
* @params {NS} ns - BitBurner API
|
||||
*/
|
||||
export async function main(ns) {
|
||||
// Setup
|
||||
ns.disableLog('ALL');
|
||||
const argParser = new ArgParser('miner.js', 'Hack, Grow, Weaken loop to "mine" a server for money. Tail for live updates.', [
|
||||
{name: 'server', desc: 'Server to mine, defaults to the local server', optional: true, default: ns.getHostname()}
|
||||
]);
|
||||
const args = argParser.parse(ns.args);
|
||||
let maxBalance, balance, minSecurity, security;
|
||||
maxBalance = ns.getServerMaxMoney(args['server']);
|
||||
balance = ns.getServerMoneyAvailable(args['server']);
|
||||
minSecurity = ns.getServerMinSecurityLevel(args['server']) + 2;
|
||||
security = ns.getServerSecurityLevel(args['server']);
|
||||
|
||||
// Help
|
||||
if(args['help'] || args['_error'].length)
|
||||
return ns.tprint(argParser.help(args['help'] ? null : args['_error'][0], args['_command']));
|
||||
|
||||
// Logger
|
||||
const logger = new Logger(ns, [
|
||||
() => `Mining: ${args['server']}`,
|
||||
() => `Security: ${Math.round(security)}/${minSecurity}\tBalance: \$${Math.round(balance * 100) / 100}`
|
||||
]);
|
||||
|
||||
// Main loop
|
||||
// noinspection InfiniteLoopJS
|
||||
while(true) {
|
||||
// Update information
|
||||
security = ns.getServerSecurityLevel(args['server']);
|
||||
balance = ns.getServerMoneyAvailable(args['server']);
|
||||
|
||||
// Pick step
|
||||
if(security > minSecurity) { // Weaken
|
||||
logger.log('Attacking Security...');
|
||||
const w = await ns.weaken(args['server']);
|
||||
logger.log(`Security: -${w}`);
|
||||
} else if(balance < maxBalance) { // Grow
|
||||
logger.log('Spoofing Balance...');
|
||||
const g = await ns.grow(args['server']);
|
||||
logger.log(`Balance: +${toCurrency(g * balance - balance)}`);
|
||||
} else { // Hack
|
||||
logger.log('Hacking Account...');
|
||||
const h = await ns.hack(args['server']);
|
||||
logger.log(`Balance: -$${h}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* BitBurner autocomplete.
|
||||
*
|
||||
* @param {{servers: string[], txts: string[], scripts: string[], flags: string[]}} data - Contextual information
|
||||
* @returns {string[]} - Pool of autocomplete options
|
||||
*/
|
||||
export function autocomplete(data) {
|
||||
return [...data.servers];
|
||||
}
|
||||
|
||||
/**
|
||||
* Weaken, Grow, Hack loop to "mine" target machine for money.
|
||||
* @params ns {NS} - BitBurner API
|
||||
*/
|
||||
export async function main(ns) {
|
||||
// Setup
|
||||
ns.disableLog('ALL');
|
||||
const argParser = new ArgParser('miner.js', 'Weaken, Grow, Hack loop to "mine" device for money. Tail for live updates.', null, [
|
||||
{name: 'device', desc: 'Device to mine, defaults to current machine', optional: true, default: ns.getHostname(), type: 'string'}
|
||||
]);
|
||||
|
||||
try {
|
||||
// Run
|
||||
const args = argParser.parse(ns.args);
|
||||
let maxBalance, balance, minSecurity, security;
|
||||
maxBalance = await ns.getServerMaxMoney(args['device']);
|
||||
balance = await ns.getServerMoneyAvailable(args['device']);
|
||||
minSecurity = await ns.getServerMinSecurityLevel(args['device']) + 2;
|
||||
security = await ns.getServerSecurityLevel(args['device']);
|
||||
const logger = new Logger(ns, [
|
||||
() => `Mining: ${args['device']}`,
|
||||
() => `Security: ${Math.round(security)}/${minSecurity}\tBalance: \$${Math.round(balance * 100) / 100}`
|
||||
]);
|
||||
while(true) {
|
||||
// Update information
|
||||
security = await ns.getServerSecurityLevel(args['device']);
|
||||
balance = await ns.getServerMoneyAvailable(args['device']);
|
||||
|
||||
// Pick step
|
||||
if(security > minSecurity) { // Weaken
|
||||
logger.log('Attacking Security...');
|
||||
const w = await ns.weaken(args['device']);
|
||||
logger.log(`Security: -${w}`);
|
||||
} else if(balance < maxBalance) { // Grow
|
||||
logger.log('Spoofing Balance...');
|
||||
const g = await ns.grow(args['device']);
|
||||
logger.log(`Balance: +$${Math.round((g * balance - balance) * 100) / 100}`);
|
||||
} else { // Hack
|
||||
logger.log('Hacking Account...');
|
||||
const h = await ns.hack(args['device']);
|
||||
logger.log(`Balance: -$${h}`);
|
||||
}
|
||||
}
|
||||
} catch(err) {
|
||||
if(err instanceof ArgError) return ns.tprint(argParser.help(err.message));
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
@ -1,63 +1,115 @@
|
||||
import {ArgError, ArgParser} from '/scripts/lib/arg-parser';
|
||||
import {pruneTree, scanNetwork} from '/scripts/lib/utils';
|
||||
import {ArgParser} from '/scripts/lib/arg-parser';
|
||||
import {addCSS, htmlPrint, pruneTree, serializeFunction, terminal} from '/scripts/lib/utils';
|
||||
import {connectionString} from '/scripts/connect';
|
||||
import {scanNetwork} from '/scripts/crawler';
|
||||
|
||||
const CSS = `
|
||||
#terminal a:not([href]):hover {
|
||||
cursor:pointer;
|
||||
text-decoration:underline;
|
||||
}
|
||||
.srv-fnr { color: #BBBB11; }
|
||||
.srv-fr {
|
||||
color: #FFFF44;
|
||||
font-weight: bold;
|
||||
}
|
||||
.srv-nr { color: #11BB11; }
|
||||
.srv-r {
|
||||
color: #00FF00;
|
||||
font-weight: bold;
|
||||
}`
|
||||
|
||||
export const factionServers = ['CSEC', 'avmnite-02h', 'I.I.I.I', 'run4theh111z', 'w0r1d_d43m0n'];
|
||||
|
||||
/**
|
||||
* BitBurner autocomplete
|
||||
* @param data {server: string[], txts: string[], scripts: string[], flags: string[]} - Contextual information
|
||||
* Scan the network for servers and display as an ASCII tree. Servers with root access are highlighted & bold.
|
||||
*
|
||||
* @param {NS} ns - BitBurner API
|
||||
*/
|
||||
export async function main(ns) {
|
||||
// Setup
|
||||
ns.disableLog('ALL');
|
||||
const argParser = new ArgParser('network-graph.js', 'Scan the network for servers and display as an ASCII tree. Servers with root access are highlighted & bold. Click to automatically connect.', [
|
||||
{name: 'server', desc: 'Point to start scan from, defaults to local server', optional: true, default: ns.getHostname()},
|
||||
{name: 'depth', desc: 'Depth to scan to', flags: ['-d', '--depth'], default: Infinity},
|
||||
{name: 'filter', desc: 'Filter to servers matching name', flags: ['-f', '--filter'], default: false},
|
||||
{name: 'regex', desc: 'Filter to servers matching pattern', flags: ['-e', '--regex'], default: false},
|
||||
{name: 'level', desc: 'Display the required hack level & number of ports to root: [level|port]', flags: ['-l', '--level'], default: false},
|
||||
{name: 'notRooted', desc: 'Filter to servers that have not been rooted', flags: ['-n', '--not-rooted'], default: false},
|
||||
{name: 'rooted', desc: 'Filter to servers that have been rooted', flags: ['-r', '--rooted'], default: false},
|
||||
{name: 'specs', desc: 'Display the server specifications: {CPU|RAM}', flags: ['-s', '--specs'], default: false},
|
||||
{name: 'usage', desc: 'Display the server utilization: (USG%)', flags: ['-u', '--usage'], default: false},
|
||||
{name: 'verbose', desc: 'Display level, specs & usage in that order: [HL|P] {CPU|RAM} (USG%)', flags: ['-v', '--verbose'], default: false},
|
||||
]);
|
||||
const args = argParser.parse(ns.args);
|
||||
|
||||
/**
|
||||
* Get the color class for the server.
|
||||
*
|
||||
* @param {string} server - Server to figure out color for.
|
||||
*/
|
||||
function color(server) {
|
||||
const rooted = ns.getServer(server).hasAdminRights; // Already using getServer so we might as well keep using it
|
||||
if(factionServers.includes(server)) return rooted ? 'srv-fr' : 'srv-fnr';
|
||||
return rooted ? 'srv-r' : 'srv-nr';
|
||||
}
|
||||
|
||||
/**
|
||||
* Create serialized connection command.
|
||||
*
|
||||
* @param {string} server - server to connect to.
|
||||
*/
|
||||
function connectFn(server) {
|
||||
return serializeFunction(terminal, connectionString(ns, server, 'home'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterate tree & convert to ascii.
|
||||
*
|
||||
* @param {Object} tree - Tree to parse
|
||||
* @param {string} spacer - Spacer text for tree formatting
|
||||
*/
|
||||
function render(tree, spacer = ' ') {
|
||||
const nodes = Object.keys(tree);
|
||||
for(let i = 0; i < nodes.length; i++) {
|
||||
const server = nodes[i], info = ns.getServer(server);
|
||||
let stats = '';
|
||||
if(args['level'] || args['verbose']) stats += ` [${info.requiredHackingSkill}|${info.openPortCount}]`;
|
||||
if(args['specs'] || args['verbose']) stats += ` {${info.cpuCores}|${info.maxRam}}`;
|
||||
if(args['usage'] || args['verbose']) stats += ` (${Math.round(info.ramUsed / info.maxRam * 100) || 0}%)`;
|
||||
const last = i == nodes.length - 1;
|
||||
const branch = last ? '└─ ' : '├─ ';
|
||||
htmlPrint(spacer + branch + `<a class="${color(server)}" onclick="${connectFn(server)}">${server + stats}</a>`);
|
||||
render(tree[server], spacer + (last ? ' ' : '| '));
|
||||
}
|
||||
}
|
||||
|
||||
// Help
|
||||
if(args['help'] || args['_error'].length)
|
||||
return ns.tprint(argParser.help(args['help'] ? null : args['_error'][0], args['_command']));
|
||||
|
||||
// Gather network information
|
||||
const [ignore, network] = scanNetwork(ns, args['server'], args['depth']);
|
||||
|
||||
// Add flags filters
|
||||
if(args['regex']) pruneTree(network, s => RegExp(args['regex']).test(s));
|
||||
if(args['filter']) pruneTree(network, s => s == args['filter']);
|
||||
if(args['rooted']) pruneTree(network, s => ns.getServer(s).hasAdminRights); // Already using getServer so we might as well keep using it
|
||||
if(args['notRooted']) pruneTree(network, s => !ns.getServer(s).hasAdminRights); // Already using getServer so we might as well keep using it
|
||||
|
||||
// Output
|
||||
addCSS('network-graph', CSS);
|
||||
htmlPrint(`\n<a class="srv-tree-span ${color(args['server'])}" onclick="${connectFn(args['server'])}">${args['server']}</a>`);
|
||||
render(network);
|
||||
htmlPrint('\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* BitBurner autocomplete.
|
||||
*
|
||||
* @param {{servers: string[], txts: string[], scripts: string[], flags: string[]}} data - Contextual information
|
||||
* @returns {string[]} - Pool of autocomplete options
|
||||
*/
|
||||
export function autocomplete(data) {
|
||||
return [...data.servers];
|
||||
}
|
||||
|
||||
/**
|
||||
* Scan the network for devices and display as an ASCII tree.
|
||||
* @param ns {NS} - BitBurner API
|
||||
*/
|
||||
export async function main(ns) {
|
||||
/**
|
||||
* Iterate tree & print to screen
|
||||
* @param tree {Object} - Tree to parse
|
||||
* @param stats {Object} - Pool of stats to pull extra information from
|
||||
* @param spacer {string} - Spacer text for tree formatting
|
||||
*/
|
||||
function render(tree, stats, spacer = ' ') {
|
||||
Object.keys(tree).forEach((device, i, arr) => {
|
||||
const deviceStats = stats ? stats[device] : null;
|
||||
const stat = deviceStats ? ` (${deviceStats.hasAdminRights ? 'ROOTED' : `${deviceStats.requiredHackingSkill}|${deviceStats.numOpenPortsRequired}`})` : '';
|
||||
const last = i == arr.length - 1;
|
||||
const branch = last ? '└─ ' : '├─ ';
|
||||
ns.tprint(spacer + branch + device + stat);
|
||||
render(tree[device], stats, spacer + (last ? ' ' : '| '));
|
||||
});
|
||||
}
|
||||
|
||||
// Setup
|
||||
ns.disableLog('ALL');
|
||||
const argParser = new ArgParser('network-graph.js', 'Scan the network for devices and display as an ASCII tree:\n home\n ├─ n00dles (ROOTED)\n | └─ max-hardware (80|1)\n | └─ neo-net (50|1)\n ├─ foodnstuff (ROOTED)\n └─ sigma-cosmetics (ROOTED)', null, [
|
||||
{name: 'device', desc: 'Point to start scan from, defaults to current machine', optional: true, default: ns.getHostname(), type: 'string'},
|
||||
{name: 'depth', desc: 'Depth to scan to', flags: ['-d', '--depth'], default: Infinity, type: 'num'},
|
||||
{name: 'filter', desc: 'Filter to device matching name', flags: ['-f', '--filter'], type: 'string'},
|
||||
{name: 'regex', desc: 'Filter to devices matching pattern', flags: ['-e', '--regex'], type: 'string'},
|
||||
{name: 'rooted', desc: 'Filter to devices that have been rooted', flags: ['-r', '--rooted'], type: 'bool'},
|
||||
{name: 'notRooted', desc: 'Filter to devices that have not been rooted', flags: ['-n', '--not-rooted'], type: 'bool'},
|
||||
{name: 'verbose', desc: 'Display the required hack level & number of ports to root: (level|port)', flags: ['-v', '--verbose'], type: 'bool'},
|
||||
]);
|
||||
|
||||
try {
|
||||
// Run
|
||||
const args = argParser.parse(ns.args);
|
||||
const [devices, network] = scanNetwork(ns, args['device'], args['depth']);
|
||||
const stats = devices.reduce((acc, d) => ({...acc, [d]: ns.getServer(d)}), {});
|
||||
if(args['regex']) pruneTree(network, d => RegExp(args['regex']).test(d)); // Regex flag
|
||||
else if(args['filter']) pruneTree(network, d => d == args['filter']); // Filter flag
|
||||
if(args['rooted']) pruneTree(network, d => stats[d].hasAdminRights); // Rooted flag
|
||||
else if(args['notRooted']) pruneTree(network, d => !stats[d].hasAdminRights); // Not rooted flag
|
||||
ns.tprint(args['device']);
|
||||
render(network, args['verbose'] ? stats : null);
|
||||
ns.tprint('');
|
||||
} catch(err) {
|
||||
if(err instanceof ArgError) return ns.tprint(argParser.help(err.message));
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
@ -1,104 +1,121 @@
|
||||
import {ArgError, ArgParser} from '/scripts/lib/arg-parser';
|
||||
import {copyWithDependencies, progressBar, slowPrint} from '/scripts/lib/utils';
|
||||
import {ArgParser} from '/scripts/lib/arg-parser';
|
||||
import {maxThreads, progressBar, slowPrint} from '/scripts/lib/utils';
|
||||
import {copyWithDependencies} from '/scripts/copy';
|
||||
|
||||
/**
|
||||
* BitBurner autocomplete
|
||||
* @param data {server: string[], txts: string[], scripts: string[], flags: string[]} - Contextual information
|
||||
* @returns {string[]} - Pool of autocomplete options
|
||||
* Check if exploit is available for use.
|
||||
*
|
||||
* @param {NS} ns - BitBurner API
|
||||
* @returns {string[]} - Available exploits
|
||||
*/
|
||||
export function autocomplete(data) {
|
||||
return [...data.servers, ...data.scripts];
|
||||
export function availableExploits(ns) {
|
||||
return ['BruteSSH.exe', 'FTPCrack.exe', 'relaySMTP.exe', 'HTTPWorm.exe', 'SQLInject.exe']
|
||||
.filter(e => ns.fileExists(e));
|
||||
}
|
||||
|
||||
/**
|
||||
* Pwn a target server with availible tools. Additionally can copy & execute a script after pwning.
|
||||
* @param ns {NS} - Bitburner API
|
||||
* Attempt to root a server.
|
||||
*
|
||||
* @param {NS} ns - BitBurner API
|
||||
* @param {string} server - Server to attempt to root
|
||||
* @returns {[boolean, string[]]} - Tuple of whether the server was successfully rooted & the exploits which where ran
|
||||
*/
|
||||
export function root(ns, server) {
|
||||
function runExploit(name) {
|
||||
if(name == 'BruteSSH.exe') ns.brutessh(server);
|
||||
else if(name == 'FTPCrack.exe') ns.ftpcrack(server);
|
||||
else if(name == 'relaySMTP.exe') ns.relaysmtp(server);
|
||||
else if(name == 'HTTPWorm.exe') ns.httpworm(server);
|
||||
else if(name == 'SQLInject.exe') ns.sqlinject(server);
|
||||
}
|
||||
|
||||
const exploits = availableExploits(ns);
|
||||
exploits.forEach(e => runExploit(e));
|
||||
try {
|
||||
ns.nuke(server)
|
||||
return [true, exploits];
|
||||
} catch {
|
||||
return [false, exploits];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Automatically gain root access to a server. A file can also be uploaded & executed.
|
||||
*
|
||||
* @param {NS} ns - BitBurner API
|
||||
*/
|
||||
export async function main(ns) {
|
||||
// Setup
|
||||
ns.disableLog('ALL');
|
||||
const argParser = new ArgParser('rootkit.js', 'Automatically gain root access to a device. A file can also be uploaded & executed.', null, [
|
||||
{name: 'device', desc: 'Device to root, defaults to current machine', optional: true, default: ns.getHostname(), type: 'string'},
|
||||
{name: 'script', desc: 'Script to copy & execute', optional: true, type: 'string'},
|
||||
{name: 'args', desc: 'Arguments for script. Forward the current target with: {{TARGET}}', optional: true, extras: true, type: 'string'},
|
||||
{name: 'cpu', desc: 'Number of CPU threads to use with script', flags: ['-c', '--cpu'], type: 'num'},
|
||||
{name: 'silent', desc: 'Surpress program output', flags: ['-s', '--silent'], type: 'bool'}
|
||||
], true);
|
||||
|
||||
try {
|
||||
const argParser = new ArgParser('rootkit.js', 'Automatically gain root access to a server. A file can also be uploaded & executed.', [
|
||||
{name: 'server', desc: 'Server to root, defaults to local server', optional: true, default: ns.getHostname()},
|
||||
{name: 'script', desc: 'Script to copy & execute', optional: true},
|
||||
{name: 'args', desc: 'Arguments for script. Forward the discovered server with: {{SERVER}}', optional: true, extras: true},
|
||||
{name: 'cpu', desc: 'Number of CPU threads to start script with, will use maximum if not specified', flags: ['-c', '--cpu'], default: false},
|
||||
{name: 'quite', desc: 'Suppress program output', flags: ['-q', '--quite'], default: false},
|
||||
]);
|
||||
const args = argParser.parse(ns.args);
|
||||
if(args['script'] && !args['cpu']) args['cpu'] = ~~(ns.getServerMaxRam(args['device']) / ns.getScriptRam(args['script'], 'home')) || 1;
|
||||
if(args['script'] && !args['cpu']) args['cpu'] =
|
||||
~~(ns.getServerMaxRam(args['server']) / ns.getScriptRam(args['script'], 'home')) || 1;
|
||||
|
||||
// Help
|
||||
if(args['help'] || args['_error'].length)
|
||||
return ns.tprint(argParser.help(args['help'] ? null : args['_error'][0], args['_command']));
|
||||
|
||||
// Banner
|
||||
if(!args['silent']) {
|
||||
if(!args['quite']) {
|
||||
ns.tprint('===================================================');
|
||||
ns.tprint(`Rooting: ${args['device']}`);
|
||||
ns.tprint(`Rooting: ${args['server']}`);
|
||||
ns.tprint('===================================================');
|
||||
}
|
||||
|
||||
// Check if we already have root
|
||||
if(ns.hasRootAccess(args['device'])) {
|
||||
if(!args['silent']) ns.tprint('Root: Skipped');
|
||||
// Root server if access is restricted
|
||||
if(!ns.hasRootAccess(args['server'])) {
|
||||
const [rooted, exploits] = root(ns, args['server']);
|
||||
if(exploits.length >= 1) await slowPrint(ns, 'Exploiting SSH (*:22)...');
|
||||
if(exploits.length >= 2) await slowPrint(ns, 'Exploiting FTP (*:24)...');
|
||||
if(exploits.length >= 3) await slowPrint(ns, 'Exploiting SMTP (*:25)...');
|
||||
if(exploits.length >= 4) await slowPrint(ns, 'Exploiting HTTP (*:80)...');
|
||||
if(exploits.length >= 5) await slowPrint(ns, 'Exploiting MSQL (*:3306)...');
|
||||
ns.tprint(`Root: ${rooted ? 'Success!' : 'Failed'}`);
|
||||
if(!rooted) ns.exit();
|
||||
} else {
|
||||
let spacer = false;
|
||||
try {
|
||||
// Run exploits
|
||||
ns.brutessh(args['device']);
|
||||
if(!args['silent']) {
|
||||
await slowPrint(ns, `Checking SSH (:22)...`, 0.5, 1.5);
|
||||
spacer = true;
|
||||
}
|
||||
ns.ftpcrack(args['device']);
|
||||
if(!args['silent']) await slowPrint(ns, `Checking FTP (:24)...`, 0.5, 1.5);
|
||||
ns.relaysmtp(args['device']);
|
||||
if(!args['silent']) await slowPrint(ns, `Checking SMTP (:25)...`, 0.5, 1.5);
|
||||
ns.httpworm(args['device']);
|
||||
if(!args['silent']) await slowPrint(ns, `Checking HTTP (:80)...`, 0.5, 1.5);
|
||||
ns.sqlinject(args['device']);
|
||||
if(!args['silent']) await slowPrint(ns, `Checking SQL (:3306)...`, 0.5, 1.5);
|
||||
} catch {
|
||||
} finally {
|
||||
try {
|
||||
// Attempt root
|
||||
if(spacer) ns.tprint('');
|
||||
ns.nuke(args['device']);
|
||||
if(!args['silent']) ns.tprint(`Root: Success!`);
|
||||
} catch {
|
||||
if(!args['silent']) {
|
||||
ns.tprint(`Root: Failed`);
|
||||
ns.tprint('');
|
||||
}
|
||||
ns.exit();
|
||||
}
|
||||
}
|
||||
ns.tprint(`Root: Skipped`);
|
||||
}
|
||||
ns.tprint('');
|
||||
|
||||
// Start script if required
|
||||
if(args['script']) {
|
||||
// Detect script dependencies & copy everything to target
|
||||
const files = await copyWithDependencies(ns, args['script'], args['device']);
|
||||
if(!args['silent']) {
|
||||
// Copy script & it's dependencies
|
||||
const files = await copyWithDependencies(ns, args['script'], args['server']);
|
||||
if(!args['quite']) {
|
||||
await ns.sleep(500);
|
||||
ns.tprint('Copying files:');
|
||||
for(let file of files) await progressBar(ns, file);
|
||||
}
|
||||
|
||||
// Run script
|
||||
if(!args['silent']) {
|
||||
// Start the script
|
||||
const threads = args['cpu'] || maxThreads(ns, args['file'], args['server']) || 1;
|
||||
if(!args['quite']) {
|
||||
ns.tprint('');
|
||||
ns.tprint(`Executing with ${args['cpu']} thread${args['cpu'] > 1 ? 's' : ''}...`);
|
||||
ns.tprint(`Executing with ${threads} thread${threads > 1 ? 's' : ''}...`);
|
||||
await ns.sleep(500);
|
||||
}
|
||||
ns.scriptKill(args['script'], args['device']);
|
||||
const pid = ns.exec(args['script'], args['device'], args['cpu'], ...args['args']
|
||||
.map(a => a == '{{TARGET}}' ? args['device'] : a));
|
||||
if(!args['silent']) {
|
||||
ns.killall(args['server']);
|
||||
const pid = ns.exec(args['file'], args['server'], threads, args['args']);
|
||||
if(!args['quite']) {
|
||||
ns.tprint(!!pid ? 'Done!' : 'Failed to start');
|
||||
ns.tprint('');
|
||||
}
|
||||
}
|
||||
} catch(err) {
|
||||
if(err instanceof ArgError) return ns.tprint(argParser.help(err.message));
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* BitBurner autocomplete.
|
||||
*
|
||||
* @param {{servers: string[], txts: string[], scripts: string[], flags: string[]}} data - Contextual information
|
||||
* @returns {string[]} - Pool of autocomplete options
|
||||
*/
|
||||
export function autocomplete(data) {
|
||||
return [...data.servers, ...data.scripts];
|
||||
}
|
||||
|
@ -1,9 +1,12 @@
|
||||
import {ArgParser} from '/scripts/lib/arg-parser2';
|
||||
import {ArgParser} from '/scripts/lib/arg-parser';
|
||||
import {Logger} from '/scripts/lib/logger';
|
||||
import {maxThreads, toCurrency} from '/scripts/lib/utils';
|
||||
import {copyWithDependencies} from "/scripts/copy";
|
||||
|
||||
/**
|
||||
* Automate the buying & upgrading of servers.
|
||||
* @param ns {NS} - BitBurner API
|
||||
*
|
||||
* @param {NS} ns - BitBurner API
|
||||
*/
|
||||
export async function main(ns) {
|
||||
// Setup
|
||||
@ -12,34 +15,48 @@ export async function main(ns) {
|
||||
const logger = new Logger(ns, [
|
||||
() => `Server Manager: ${servers.length}`
|
||||
]);
|
||||
const argParser = new ArgParser(ns, 'server-manager.js', 'Automate the buying & upgrading of servers.', [
|
||||
const argParser = new ArgParser('server-manager.js', 'Automate the buying & upgrading of servers. Automatically starts script after purchase. Tail for live updates.', [
|
||||
{name: 'script', desc: 'Script to copy & execute', optional: true},
|
||||
{name: 'args', desc: 'Arguments for script. Forward the discovered server with: {{SERVER}}', optional: true, extras: true},
|
||||
{name: 'balance', desc: 'Prevent spending bellow point', flags: ['-b', '--balance'], default: 0},
|
||||
{name: 'cpu', desc: 'Number of CPU threads to start script with, will use maximum if not specified', flags: ['-c', '--cpu'], default: false},
|
||||
{name: 'limit', desc: 'Limit the number of servers that can be purchased, defaults to 25', flags: ['-l', '--limit'], default: 25},
|
||||
{name: 'ram', desc: 'Amount of RAM to purchase new servers with, defaults to 8 GB', flags: ['-r', '--ram'], default: 8},
|
||||
{name: 'sleep', desc: 'Amount of time to wait between purchases, defaults to 1 (second)', flags: ['-s', '--sleep'], default: 1}
|
||||
]);
|
||||
const args = argParser.parse();
|
||||
const args = argParser.parse(ns.args);
|
||||
const serverPrefix = 'botnet_'
|
||||
const maxRam = ns.getPurchasedServerMaxRam();
|
||||
const minRamCost = ns.getPurchasedServerCost(args['ram']);
|
||||
|
||||
// Help
|
||||
if(args['help'] || args['_error'])
|
||||
return argParser.help(args['help'] ? null : args['_error'], args['_command']);
|
||||
async function startScript(server) {
|
||||
await copyWithDependencies(ns, args['script'], server);
|
||||
const threads = args['cpu'] || maxThreads(ns, args['script'], server) || 1;
|
||||
const pid = ns.exec(args['script'], server, threads, ...args['args']);
|
||||
logger.log(`Starting "${args['script']}" with ${threads} thread${threads > 1 ? 's' : ''}`);
|
||||
logger[pid == -1 ? 'warn' : 'log'](pid == -1 ? 'Done!' : 'Failed to start');
|
||||
}
|
||||
|
||||
// Run
|
||||
// Help
|
||||
if(args['help'] || args['_error'].length)
|
||||
return ns.tprint(argParser.help(args['help'] ? null : args['_error'][0], args['_command']));
|
||||
|
||||
// Main loop
|
||||
// noinspection InfiniteLoopJS
|
||||
while(true) {
|
||||
servers = ns.getPurchasedServers();
|
||||
const balance = ns.getServerMoneyAvailable('home');
|
||||
let targ
|
||||
// Purchase new server if we can afford it
|
||||
if(servers.length < args['limit'] && balance - minRamCost > args['balance']) {
|
||||
logger.log(`Buying server (${args['ram']} GB): ${minRamCost.toLocaleString('en-US', {style: 'currency', currency: 'USD'})}`);
|
||||
logger.log(`Buying server (${args['ram']} GB): ${toCurrency(minRamCost)}`);
|
||||
ns.purchaseServer(`${serverPrefix}${servers.length}`, args['ram']);
|
||||
|
||||
// Run the script if requested
|
||||
if(args['script']) await startScript(`${serverPrefix}${servers.length - 1}`);
|
||||
} else { // Check for upgrades
|
||||
const upgrades = servers.map(server => {
|
||||
let upgrades = servers.map(server => {
|
||||
// Calculate next RAM upgrades (must be a power of two: 2, 4, 8, 16, 32...)
|
||||
let ram = Math.pow(2, Math.log2(ns.getServerRam(server)[0]) + 1);
|
||||
let ram = Math.pow(2, Math.log2(ns.getServerMaxRam(server)) + 1);
|
||||
if(ram > maxRam) ram = null;
|
||||
return {
|
||||
server,
|
||||
@ -47,7 +64,7 @@ export async function main(ns) {
|
||||
cost: ram ? ns.getPurchasedServerCost(ram) : null
|
||||
}
|
||||
});
|
||||
upgrades.sort((a, b) => { // Sort by price
|
||||
upgrades = upgrades.sort((a, b) => { // Sort by price
|
||||
if(a.cost < b.cost) return 1;
|
||||
if(a.cost < b.cost) return -1;
|
||||
return 0;
|
||||
@ -56,10 +73,13 @@ export async function main(ns) {
|
||||
// Do the cheapest upgrade if we can afford it
|
||||
const upgrade = upgrades[0];
|
||||
if(upgrade && !!upgrade.ram && balance - upgrade.cost > args['balance']) {
|
||||
logger.log(`Upgrading ${upgrade.server}: ${upgrade.ram} GB/${upgrade.cost.toLocaleString('en-US', {style: 'currency', currency: 'USD'})}`);
|
||||
logger.log(`Upgrading ${upgrade.server}: ${upgrade.ram} GB / ${toCurrency(upgrade.cost)}`);
|
||||
ns.killall(upgrade.server);
|
||||
ns.deleteServer(upgrade.server);
|
||||
ns.purchaseServer(upgrade.server, upgrade.ram);
|
||||
|
||||
// Run the script if requested
|
||||
if(args['script']) await startScript(upgrade.server);
|
||||
}
|
||||
}
|
||||
await ns.sleep(args['sleep'] * 1000);
|
||||
|
@ -1,96 +1,121 @@
|
||||
class ArgError extends Error {}
|
||||
|
||||
class ArgParser {
|
||||
/**
|
||||
* Create a unix-like argument parser to extract flags from the argument list. Can also create help messages.
|
||||
* @param name {string} - Script name
|
||||
* @param desc {string} - Help text desciption
|
||||
* @param examples {string[]} - Help text examples
|
||||
* @param argList {name: string, desc: string, flags: string[], type: string, default: any}[] - Array of CLI arguments
|
||||
* @param allowUnknown {boolean} - Allow unknown flags
|
||||
*
|
||||
* @param {string} name - Script name
|
||||
* @param {string} desc - Help description
|
||||
* @param {(ArgParser | {name: string, desc: string, flags?: string[], optional?: boolean, default?: any})[]} argList - Array of CLI arguments
|
||||
* @param {string[]} examples - Additional examples to display
|
||||
*/
|
||||
constructor(name, desc, examples, argList, allowUnknown = false) {
|
||||
this.name = name ?? 'example.js';
|
||||
this.description = desc ?? 'Example description';
|
||||
this.examples = examples || [`${argList.find(arg => !!arg.flags) ? '[OPTIONS] ' : ''}${argList.filter(arg => !arg.flags).map(arg => (arg.optional ? `[${arg.name.toUpperCase()}]` : arg.name.toUpperCase()) + (arg.extras ? '...' : '')).join(' ')}`];
|
||||
this.examples.push('--help');
|
||||
this.argList = argList || [];
|
||||
this.argList.push({name: 'help', desc: 'Display this help message', flags: ['-h', '--help'], type: 'bool'});
|
||||
this.allowUnknown = allowUnknown;
|
||||
constructor(name, desc, argList = [], examples = []) {
|
||||
this.name = name;
|
||||
this.desc = desc;
|
||||
|
||||
// Arguments
|
||||
this.commands = argList.filter(arg => arg instanceof ArgParser);
|
||||
this.args = argList.filter(arg => !arg.flags || !arg.flags.length);
|
||||
this.flags = argList.filter(arg => !(arg instanceof ArgParser) && arg.flags && arg.flags.length);
|
||||
this.flags.push({name: 'help', desc: 'Display this help message', flags: ['-h', '--help'], default: false});
|
||||
this.defaults = argList.reduce((acc, arg) => ({...acc, [arg.name]: arg?.extras ? [] : arg.default ?? null}), {});
|
||||
|
||||
// Examples
|
||||
this.examples = [
|
||||
...examples,
|
||||
`[OPTIONS] ${this.args.map(arg => (arg.optional ? `[${arg.name.toUpperCase()}]` : arg.name.toUpperCase()) + (arg.extras ? '...' : '')).join(' ')}`,
|
||||
this.commands.length ? `[OPTIONS] COMMAND` : null,
|
||||
`--help ${this.commands.length ? '[COMMAND]' : ''}`
|
||||
].filter(e => !!e);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse an array into an arguments dictionary using the configuration.
|
||||
* @param args {string[]} - Array of arguments to be parsed
|
||||
*
|
||||
* @param {string[]} args - Array of arguments to be parsed
|
||||
* @returns {object} - Dictionary of arguments with defaults applied
|
||||
*/
|
||||
parse(args) {
|
||||
// Parse arguments
|
||||
const queue = [...args], extra = [];
|
||||
const parsed = this.argList.reduce((acc, arg) => ({...acc, [arg.name]: arg.default ?? (arg.type == 'bool' ? false : null)}), {});
|
||||
// Flags
|
||||
let extras = [], parsed = {...this.defaults, '_error': []}, queue = [...args];
|
||||
while(queue.length) {
|
||||
let parse = queue.splice(0, 1)[0];
|
||||
if(parse[0] == '-') {
|
||||
// Check combined flags
|
||||
if(parse[1] != '-' && parse.length > 2) {
|
||||
parse = `-${parse[1]}`;
|
||||
queue = parse.substring(1).split('').map(a => `-${a}`).concat(queue);
|
||||
let arg = queue.splice(0, 1)[0];
|
||||
if(arg[0] == '-') { // Flags
|
||||
// Check for combined shorthand
|
||||
if(arg[1] != '-' && arg.length > 2) {
|
||||
queue = [...arg.substring(2).split('').map(a => `-${a}`), ...queue];
|
||||
arg = `-${arg[1]}`;
|
||||
}
|
||||
// Find & add flag
|
||||
const split = parse.split('=');
|
||||
const arg = this.argList.find(arg => arg.flags && arg.flags.includes(split[0] || parse));
|
||||
if(arg == null) {
|
||||
if(!this.allowUnknown) throw new ArgError(`Option unknown: ${parse}`);
|
||||
extra.push(parse);
|
||||
const combined = arg.split('=');
|
||||
const argDef = this.flags.find(flag => flag.flags.includes(combined[0] || arg));
|
||||
if(argDef == null) { // Not found, add to extras
|
||||
extras.push(arg);
|
||||
continue;
|
||||
}
|
||||
if(arg.name == 'help') throw new ArgError('Help');
|
||||
const value = arg.type == 'bool' ? true : split[1] || queue.splice(queue.findIndex(q => q[0] != '-'), 1)[0];
|
||||
if(value == null) throw new ArgError(`Option missing value: ${arg.name}`);
|
||||
parsed[arg.name] = value;
|
||||
} else {
|
||||
// Save for required parsing
|
||||
extra.push(parse);
|
||||
const value = argDef.default === false ? true : argDef.default === true ? false : queue.splice(queue.findIndex(q => q[0] != '-'), 1)[0] || argDef.default;
|
||||
if(value == null) parsed['_error'].push(`Option missing value: ${arg.name}`);
|
||||
parsed[argDef.name] = value;
|
||||
} else { // Command
|
||||
const c = this.commands.find(command => command.name == arg);
|
||||
if(!!c) {
|
||||
const parsedCommand = c.parse(queue.splice(0, queue.length));
|
||||
Object.keys(parsedCommand).forEach(key => {
|
||||
if(parsed[key] != parsedCommand[key] && parsedCommand[key] == c.defaults[key])
|
||||
delete parsedCommand[key];
|
||||
});
|
||||
parsed = {
|
||||
...parsed,
|
||||
...parsedCommand,
|
||||
_command: c.name
|
||||
};
|
||||
} else extras.push(arg); // Not found, add to extras
|
||||
}
|
||||
}
|
||||
// Arguments
|
||||
this.argList.filter(arg => !arg.flags && !arg.extras).forEach(arg => {
|
||||
if(!arg.optional && !extra.length) throw new ArgError(`Argument missing: ${arg.name.toUpperCase()}`);
|
||||
const value = extra.splice(0, 1)[0];
|
||||
if(value != null) parsed[arg.name] = value;
|
||||
this.args.filter(arg => !arg.extras).forEach(arg => {
|
||||
if(!arg.optional && !extras.length) parsed['_error'].push(`Argument missing: ${arg.name.toUpperCase()}`);
|
||||
if(extras.length) parsed[arg.name] = extras.splice(0, 1)[0];
|
||||
});
|
||||
// Extras
|
||||
const extraKey = this.argList.find(arg => arg.extras)?.name || 'extra';
|
||||
parsed[extraKey] = extra;
|
||||
const extraKey = this.args.find(arg => arg.extras)?.name || '_extra';
|
||||
parsed[extraKey] = extras;
|
||||
return parsed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create help message from the provided description, examples & argument list.
|
||||
* @param message {string} - Message to display, defaults to the description
|
||||
* Create help message from the provided description & argument list.
|
||||
*
|
||||
* @param {string} message - Message to display, defaults to the description
|
||||
* @param {string} command - Command help message to show
|
||||
* @returns {string} - Help message
|
||||
*/
|
||||
help(msg) {
|
||||
help(message = '', command = '') {
|
||||
const spacer = (text) => Array(24 - text.length || 1).fill(' ').join('');
|
||||
|
||||
// Help with specific command
|
||||
if(command) {
|
||||
const argParser = this.commands.find(parser => parser.name == command);
|
||||
if(!argParser) throw new Error(`${command.toUpperCase()} does not have a help`)
|
||||
return argParser.help(message);
|
||||
}
|
||||
|
||||
// Description
|
||||
let message = '\n\n' + (msg && msg.toLowerCase() != 'help' ? msg : this.description);
|
||||
// Usage
|
||||
if(this.examples.length) message += '\n\nUsage:\t' + this.examples.map(ex => `run ${this.name} ${ex}`).join('\n\t');
|
||||
let msg = `\n\n${message || this.desc}`;
|
||||
// Examples
|
||||
msg += '\n\nUsage:\t' + this.examples.map(ex => `run ${this.name} ${ex}`).join('\n\t');
|
||||
// Arguments
|
||||
const req = this.argList.filter(a => !a.flags);
|
||||
if(req.length) message += '\n\n\t' + req.map(arg => {
|
||||
const padding = 3 - ~~(arg.name.length / 8);
|
||||
return `${arg.name.toUpperCase()}${Array(padding).fill('\t').join('')} ${arg.desc}`;
|
||||
}).join('\n\t');
|
||||
if(this.args.length) msg += '\n\n\t' + this.args
|
||||
.map(arg => `${arg.name.toUpperCase()}${spacer(arg.name)}${arg.desc}`)
|
||||
.join('\n\t');
|
||||
// Flags
|
||||
const opts = this.argList.filter(a => a.flags);
|
||||
if(opts.length) message += '\n\nOptions:\n\t' + opts.map(a => {
|
||||
const flgs = a.flags.join(' ');
|
||||
const padding = 3 - ~~(flgs.length / 8);
|
||||
return `${flgs}${Array(padding).fill('\t').join('')} ${a.desc}`;
|
||||
msg += '\n\nOptions:\n\t' + this.flags.map(flag => {
|
||||
const flags = flag.flags.join(', ');
|
||||
return `${flags}${spacer(flags)}${flag.desc}`;
|
||||
}).join('\n\t');
|
||||
// Print final message
|
||||
return `${message}\n\n`;
|
||||
// Commands
|
||||
if(this.commands.length) msg += '\n\nCommands:\n\t' + this.commands
|
||||
.map(command => `${command.name}${spacer(command.name)}${command.desc}`)
|
||||
.join('\n\t');
|
||||
return `${msg}\n\n`;
|
||||
}
|
||||
}
|
||||
|
||||
@ -101,10 +126,10 @@ class ArgParser {
|
||||
*
|
||||
* `/script/test.js [||||||||||----------] 50% (24.2 MB/s)`
|
||||
*
|
||||
* @param ns {NS} - BitBurner API
|
||||
* @param name {string} - Name to display at the begging of bar
|
||||
* @param showSpeed {boolean} - Show the speed in the progress bar
|
||||
* @param time {number} - Time it takes for bar to fill
|
||||
* @param {NS} ns - BitBurner API
|
||||
* @param {string} name - Name to display at the begging of bar
|
||||
* @param {boolean} showSpeed - Show the speed in the progress bar
|
||||
* @param {number} time - Time it takes for bar to fill
|
||||
*/
|
||||
export async function progressBar(ns, name, showSpeed = true, time = Math.random() + 0.5) {
|
||||
const text = (percentage, speed) => {
|
||||
@ -132,10 +157,11 @@ export async function progressBar(ns, name, showSpeed = true, time = Math.random
|
||||
|
||||
/**
|
||||
* Print text to the terminal & then delay for a random amount of time to emulate execution time.
|
||||
* @param ns {NS} - BitBurner API
|
||||
* @param message {string} - Text to display
|
||||
* @param min {number} - minimum amount of time to wait after printing text
|
||||
* @param max {number} - maximum amount of time to wait after printing text
|
||||
*
|
||||
* @param {NS} ns - BitBurner API
|
||||
* @param {string} message - Text to display
|
||||
* @param {number} min - minimum amount of time to wait after printing text
|
||||
* @param {number} max - maximum amount of time to wait after printing text
|
||||
*/
|
||||
async function slowPrint(ns, message, min = 0.5, max = 1.5) {
|
||||
const time = ~~(Math.random() * (max * 1000 - min * 1000)) + min * 1000;
|
||||
@ -151,13 +177,11 @@ export async function main(ns) {
|
||||
// Setup
|
||||
ns.disableLog('ALL');
|
||||
const updateFile = 'update.js';
|
||||
const argParser = new ArgParser(updateFile, 'Download the latest script updates from the repository using wget.', null, [
|
||||
{name: 'device', desc: 'Device to update, defaults to current machine', optional: true, default: ns.getHostname(), type: 'string'},
|
||||
{name: 'skip-self', desc: 'Skip updating self (for debugging & used internally)', flags: ['--skip-self'], type: 'bool'},
|
||||
{name: 'no-banner', desc: 'Hide the banner (Used internally)', flags: ['--no-banner'], type: 'bool'}
|
||||
const argParser = new ArgParser(updateFile, 'Download the latest script updates from the repository using wget.', [
|
||||
{name: 'device', desc: 'Device to update, defaults to current machine', optional: true, default: ns.getHostname()},
|
||||
{name: 'skip-self', desc: 'Skip updating self (for debugging & used internally)', flags: ['--skip-self'], default: false},
|
||||
{name: 'no-banner', desc: 'Hide the banner (Used internally)', flags: ['--no-banner'], default: false}
|
||||
]);
|
||||
|
||||
try {
|
||||
const args = argParser.parse(ns.args || []);
|
||||
const src = 'https://gitlab.zakscode.com/ztimson/BitBurner/-/raw/develop/scripts/';
|
||||
const dest = '/scripts/';
|
||||
@ -176,6 +200,10 @@ export async function main(ns) {
|
||||
'rootkit.js'
|
||||
];
|
||||
|
||||
// Help
|
||||
if(args['help'] || args['_error'].length)
|
||||
return ns.tprint(argParser.help(args['help'] ? null : args['_error'][0], args['_command']));
|
||||
|
||||
if(!args['no-banner']) {
|
||||
// Banner
|
||||
ns.tprint('===================================================');
|
||||
@ -204,8 +232,4 @@ export async function main(ns) {
|
||||
ns.tprint('Done!');
|
||||
ns.tprint('');
|
||||
}
|
||||
} catch(err) {
|
||||
if(err instanceof ArgError) return ns.tprint(argParser.help(err.message));
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user