@hotbunny/hackhub-content-sdk 0.5.2 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +160 -13
- package/build.mjs +122 -11
- package/index.d.ts +279 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @hotbunny/hackhub-content-sdk
|
|
2
2
|
|
|
3
|
-
Type definitions and
|
|
3
|
+
Type definitions and build tools for creating HackHub mods.
|
|
4
4
|
|
|
5
5
|
## Quick Start
|
|
6
6
|
|
|
@@ -26,18 +26,28 @@ import {
|
|
|
26
26
|
Quest,
|
|
27
27
|
Events,
|
|
28
28
|
Network,
|
|
29
|
+
Random,
|
|
29
30
|
RegisterModPackage,
|
|
30
31
|
RegisterQuest,
|
|
31
32
|
} from "@hotbunny/hackhub-content-sdk";
|
|
32
33
|
|
|
33
|
-
@RegisterModPackage
|
|
34
|
+
@RegisterModPackage
|
|
34
35
|
class MyMod extends Bootstrap {
|
|
36
|
+
Settings = [
|
|
37
|
+
{ key: "difficulty", label: "Difficulty", type: "select" as const, default: "normal", options: [
|
|
38
|
+
{ label: "Easy", value: "easy" },
|
|
39
|
+
{ label: "Normal", value: "normal" },
|
|
40
|
+
{ label: "Hard", value: "hard" },
|
|
41
|
+
]},
|
|
42
|
+
{ key: "showHints", label: "Show Hints", type: "toggle" as const, default: true },
|
|
43
|
+
];
|
|
44
|
+
|
|
35
45
|
OnModPackageLoaded() {
|
|
36
46
|
console.log("My mod loaded!");
|
|
37
47
|
}
|
|
38
48
|
}
|
|
39
49
|
|
|
40
|
-
@RegisterQuest
|
|
50
|
+
@RegisterQuest
|
|
41
51
|
class MyQuest extends Quest {
|
|
42
52
|
definition = {
|
|
43
53
|
name: "MyQuest",
|
|
@@ -57,11 +67,13 @@ class MyQuest extends Quest {
|
|
|
57
67
|
};
|
|
58
68
|
|
|
59
69
|
OnStart() {
|
|
70
|
+
const targetIp = Random.pick(["10.0.0.1", "10.0.0.2", "10.0.0.3"]);
|
|
71
|
+
|
|
60
72
|
Network.createSubnetNetwork({
|
|
61
|
-
ip:
|
|
73
|
+
ip: targetIp,
|
|
62
74
|
type: "ROUTER",
|
|
63
75
|
ports: [{ external: 80, internal: 80, active: true, service: "http" }],
|
|
64
|
-
users: [Network.createUser({ username: "admin" })],
|
|
76
|
+
users: [Network.createUser({ username: "admin", password: Random.password() })],
|
|
65
77
|
children: [],
|
|
66
78
|
});
|
|
67
79
|
}
|
|
@@ -74,8 +86,8 @@ class MyQuest extends Quest {
|
|
|
74
86
|
|
|
75
87
|
| Class | Purpose |
|
|
76
88
|
|-------|---------|
|
|
77
|
-
| `Bootstrap` | Mod entry point
|
|
78
|
-
| `Quest` | Define custom quests with objectives and
|
|
89
|
+
| `Bootstrap` | Mod entry point with lifecycle callbacks, settings, and manifest |
|
|
90
|
+
| `Quest` | Define custom quests with objectives, rewards, mail, and dialogs |
|
|
79
91
|
| `Website` | Add in-game websites to the browser |
|
|
80
92
|
| `Command` | Register custom terminal commands |
|
|
81
93
|
| `App` | Create desktop applications |
|
|
@@ -84,11 +96,12 @@ class MyQuest extends Quest {
|
|
|
84
96
|
|
|
85
97
|
| Namespace | Purpose |
|
|
86
98
|
|-----------|---------|
|
|
87
|
-
| `Events` | Listen to game events, emit custom cross-mod events |
|
|
99
|
+
| `Events` | Listen to 60+ game events, emit custom cross-mod events |
|
|
88
100
|
| `Files` | Create, read, write, delete files in the game filesystem |
|
|
89
101
|
| `Network` | Create networks, manage ports, firewalls, domains |
|
|
90
102
|
| `Mail` | Send in-game emails |
|
|
91
103
|
| `Bank` | Manage bank transactions |
|
|
104
|
+
| `Random` | Random generation helpers (id, uuid, number, password, username, pick, sleep) |
|
|
92
105
|
| `Storage` | Persistent key-value storage (per-mod) |
|
|
93
106
|
| `Variables` | Session-only variables (per-mod, reset on game close) |
|
|
94
107
|
| `SharedStorage` | Persistent storage shared between mods |
|
|
@@ -102,11 +115,67 @@ class MyQuest extends Quest {
|
|
|
102
115
|
### Decorators
|
|
103
116
|
|
|
104
117
|
```typescript
|
|
105
|
-
@RegisterModPackage
|
|
106
|
-
@RegisterQuest
|
|
107
|
-
@RegisterWebsite
|
|
108
|
-
@RegisterCommand
|
|
109
|
-
@RegisterApp
|
|
118
|
+
@RegisterModPackage // Register the mod's Bootstrap class
|
|
119
|
+
@RegisterQuest // Register a Quest
|
|
120
|
+
@RegisterWebsite // Register a Website
|
|
121
|
+
@RegisterCommand // Register a terminal Command
|
|
122
|
+
@RegisterApp // Register a desktop App
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Mod Settings
|
|
126
|
+
|
|
127
|
+
Mods can define settings that players can configure from the in-game Mods menu.
|
|
128
|
+
|
|
129
|
+
### Declarative Settings
|
|
130
|
+
|
|
131
|
+
Define a `Settings` array on your Bootstrap class:
|
|
132
|
+
|
|
133
|
+
```typescript
|
|
134
|
+
@RegisterModPackage
|
|
135
|
+
class MyMod extends Bootstrap {
|
|
136
|
+
Settings = [
|
|
137
|
+
{ key: "difficulty", label: "Difficulty", type: "select" as const, default: "normal", options: [
|
|
138
|
+
{ label: "Easy", value: "easy" },
|
|
139
|
+
{ label: "Normal", value: "normal" },
|
|
140
|
+
{ label: "Hard", value: "hard" },
|
|
141
|
+
]},
|
|
142
|
+
{ key: "showHints", label: "Show Hints", type: "toggle" as const, default: true },
|
|
143
|
+
{ key: "volume", label: "Volume", type: "slider" as const, default: 50, min: 0, max: 100, step: 5 },
|
|
144
|
+
{ key: "playerName", label: "Player Name", type: "text" as const, default: "Player" },
|
|
145
|
+
];
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Setting values are stored in mod-scoped storage and can be read with `Storage.get("key")`.
|
|
150
|
+
|
|
151
|
+
Available types: `toggle`, `select`, `text`, `number`, `slider`.
|
|
152
|
+
|
|
153
|
+
### Custom HTML Settings
|
|
154
|
+
|
|
155
|
+
For advanced settings panels, provide an HTML file:
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
@RegisterModPackage
|
|
159
|
+
class MyMod extends Bootstrap {
|
|
160
|
+
SettingsHTML = "settings.html";
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
The HTML file is rendered in a sandboxed iframe within the Mods menu.
|
|
165
|
+
|
|
166
|
+
## Random Helpers
|
|
167
|
+
|
|
168
|
+
```typescript
|
|
169
|
+
import { Random } from "@hotbunny/hackhub-content-sdk";
|
|
170
|
+
|
|
171
|
+
Random.id(); // Short unique ID (default 10 chars)
|
|
172
|
+
Random.uuid(); // UUID v4
|
|
173
|
+
Random.number(1, 100); // Random integer between 1-100
|
|
174
|
+
Random.password(); // Random password from game's list
|
|
175
|
+
Random.username(); // Random username from game's list
|
|
176
|
+
Random.pick(["a", "b", "c"]); // Random element from array
|
|
177
|
+
Random.pickMultiple(arr, 3); // 3 unique random elements
|
|
178
|
+
await Random.sleep(2000); // Wait 2 seconds
|
|
110
179
|
```
|
|
111
180
|
|
|
112
181
|
## Events
|
|
@@ -117,6 +186,14 @@ class MyQuest extends Quest {
|
|
|
117
186
|
Events.on("Terminal.NmapScan", (data) => {
|
|
118
187
|
console.log(`Player scanned ${data.ip}`);
|
|
119
188
|
});
|
|
189
|
+
|
|
190
|
+
Events.on("Bettercap.Open", () => {
|
|
191
|
+
console.log("Player opened Bettercap");
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
Events.on("Process.Killed", (data) => {
|
|
195
|
+
console.log(`Process ${data.name} was killed`);
|
|
196
|
+
});
|
|
120
197
|
```
|
|
121
198
|
|
|
122
199
|
### Custom Cross-Mod Events
|
|
@@ -136,6 +213,76 @@ Events.on("MyMod.BossDefeated", (data) => {
|
|
|
136
213
|
});
|
|
137
214
|
```
|
|
138
215
|
|
|
216
|
+
## Quest Mail & Dialogs
|
|
217
|
+
|
|
218
|
+
Quests can send in-game emails and start phone call dialogs:
|
|
219
|
+
|
|
220
|
+
```typescript
|
|
221
|
+
@RegisterQuest
|
|
222
|
+
class MyQuest extends Quest {
|
|
223
|
+
Mails = [
|
|
224
|
+
{ title: "Mission Briefing", content: "Your target is...", replyable: false },
|
|
225
|
+
];
|
|
226
|
+
|
|
227
|
+
Dialog = {
|
|
228
|
+
default: [
|
|
229
|
+
{ speaker: "Handler", text: "Are you ready?", options: [
|
|
230
|
+
{ label: "Yes", text: "I'm ready.", switchBranch: "briefing" },
|
|
231
|
+
{ label: "No", text: "Not yet.", isEnd: true },
|
|
232
|
+
]},
|
|
233
|
+
],
|
|
234
|
+
briefing: [
|
|
235
|
+
{ speaker: "Handler", text: "Good luck.", isEnd: true },
|
|
236
|
+
],
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
OnStart() {
|
|
240
|
+
this.sendMail(0);
|
|
241
|
+
this.createDialog("default");
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
## Manifest
|
|
247
|
+
|
|
248
|
+
Each mod requires a `manifest.json`:
|
|
249
|
+
|
|
250
|
+
```json
|
|
251
|
+
{
|
|
252
|
+
"id": "my-unique-mod-id",
|
|
253
|
+
"name": "My Mod",
|
|
254
|
+
"version": "1.0.0",
|
|
255
|
+
"author": "Your Name",
|
|
256
|
+
"description": "What the mod does",
|
|
257
|
+
"apiVersion": 1,
|
|
258
|
+
"permissions": ["filesystem", "network", "events"],
|
|
259
|
+
"cover": "cover.png",
|
|
260
|
+
"tags": ["quest", "network"],
|
|
261
|
+
"dependencies": [],
|
|
262
|
+
"workshopId": ""
|
|
263
|
+
}
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
### Permissions
|
|
267
|
+
|
|
268
|
+
| Permission | Required for |
|
|
269
|
+
|------------|-------------|
|
|
270
|
+
| `filesystem` | Files API |
|
|
271
|
+
| `network` | Network API |
|
|
272
|
+
| `events` | Events API |
|
|
273
|
+
| `mail` | Mail API |
|
|
274
|
+
| `bank` | Bank API |
|
|
275
|
+
| `shell` | Shell API |
|
|
276
|
+
| `ui` | UI API |
|
|
277
|
+
|
|
278
|
+
## Steam Workshop
|
|
279
|
+
|
|
280
|
+
Mods can be published to and downloaded from Steam Workshop directly from the in-game Mods menu.
|
|
281
|
+
|
|
282
|
+
- **Browse**: View and manage your Workshop subscriptions
|
|
283
|
+
- **Upload**: Publish local mods to Workshop with cover image, tags, and change notes
|
|
284
|
+
- **Auto-discovery**: Subscribed Workshop mods are automatically loaded on game start
|
|
285
|
+
|
|
139
286
|
## License
|
|
140
287
|
|
|
141
288
|
MIT
|
package/build.mjs
CHANGED
|
@@ -4,6 +4,12 @@ import path from "node:path";
|
|
|
4
4
|
const ASSET_EXTS = /\.(png|jpg|jpeg|gif|svg|ico|webp|css|js|cur|woff|woff2|ttf|eot|mp3|wav|ogg|mp4|webm|json|pdf)$/i;
|
|
5
5
|
const SRC_ASSET_PATTERN = /["'](\.[^"']+?\.(png|jpg|jpeg|gif|svg|ico|webp|css|js|cur|woff|woff2|ttf|eot|mp3|wav|ogg|mp4|webm|json|pdf))["']/gi;
|
|
6
6
|
|
|
7
|
+
// ─── SDK project schema version ─────────────────────────────────────
|
|
8
|
+
// Bump this when the required project structure changes (new files,
|
|
9
|
+
// new manifest fields, tsconfig options, etc.). buildMod() compares
|
|
10
|
+
// it against the project's saved version and runs migrations.
|
|
11
|
+
const PROJECT_SCHEMA_VERSION = 1;
|
|
12
|
+
|
|
7
13
|
function copyDirSync(src, dest) {
|
|
8
14
|
fs.mkdirSync(dest, { recursive: true });
|
|
9
15
|
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
@@ -40,11 +46,6 @@ function collectFilesRecursive(dir, ext, results = []) {
|
|
|
40
46
|
return results;
|
|
41
47
|
}
|
|
42
48
|
|
|
43
|
-
/**
|
|
44
|
-
* Scans all .ts/.tsx files in src/ for relative path strings that
|
|
45
|
-
* reference asset files (images, scripts, styles, etc.) and copies
|
|
46
|
-
* them into dist/ so they're available via mod-asset:// at runtime.
|
|
47
|
-
*/
|
|
48
49
|
function scanSourceAssets() {
|
|
49
50
|
const sourceFiles = collectFilesRecursive("src", [".ts", ".tsx"]);
|
|
50
51
|
let copied = 0;
|
|
@@ -71,10 +72,112 @@ function scanSourceAssets() {
|
|
|
71
72
|
return copied;
|
|
72
73
|
}
|
|
73
74
|
|
|
75
|
+
// ─── Required file templates ────────────────────────────────────────
|
|
76
|
+
// Each entry defines a file that must exist in the mod project.
|
|
77
|
+
// If missing, it's auto-created from the template.
|
|
78
|
+
|
|
79
|
+
const REQUIRED_FILES = {
|
|
80
|
+
"src/types.d.ts": {
|
|
81
|
+
content: `declare module "*.html" {\n const value: string;\n export default value;\n}\n`,
|
|
82
|
+
description: "HTML import type declarations",
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
// Fields that must exist in manifest.json (with defaults)
|
|
87
|
+
const REQUIRED_MANIFEST_FIELDS = {
|
|
88
|
+
apiVersion: "1.0",
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
// Required tsconfig.json compilerOptions
|
|
92
|
+
const REQUIRED_TSCONFIG_OPTIONS = {
|
|
93
|
+
experimentalDecorators: true,
|
|
94
|
+
strict: true,
|
|
95
|
+
};
|
|
96
|
+
|
|
74
97
|
/**
|
|
75
|
-
*
|
|
76
|
-
*
|
|
98
|
+
* Validates project structure and auto-fixes missing/outdated files.
|
|
99
|
+
* Returns a list of actions taken.
|
|
77
100
|
*/
|
|
101
|
+
function validateProject() {
|
|
102
|
+
const actions = [];
|
|
103
|
+
|
|
104
|
+
// 1. Check required files
|
|
105
|
+
for (const [filePath, spec] of Object.entries(REQUIRED_FILES)) {
|
|
106
|
+
if (!fs.existsSync(filePath)) {
|
|
107
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
108
|
+
fs.writeFileSync(filePath, spec.content, "utf-8");
|
|
109
|
+
actions.push(`Created missing file: ${filePath} (${spec.description})`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// 2. Check manifest.json required fields
|
|
114
|
+
if (fs.existsSync("manifest.json")) {
|
|
115
|
+
const raw = fs.readFileSync("manifest.json", "utf-8");
|
|
116
|
+
const manifest = JSON.parse(raw);
|
|
117
|
+
let changed = false;
|
|
118
|
+
|
|
119
|
+
for (const [key, defaultValue] of Object.entries(REQUIRED_MANIFEST_FIELDS)) {
|
|
120
|
+
if (!(key in manifest)) {
|
|
121
|
+
manifest[key] = defaultValue;
|
|
122
|
+
changed = true;
|
|
123
|
+
actions.push(`Added missing manifest field: "${key}" = "${defaultValue}"`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (changed) {
|
|
128
|
+
fs.writeFileSync("manifest.json", JSON.stringify(manifest, null, 4) + "\n", "utf-8");
|
|
129
|
+
}
|
|
130
|
+
} else {
|
|
131
|
+
actions.push("WARNING: manifest.json not found!");
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// 3. Check tsconfig.json required options
|
|
135
|
+
if (fs.existsSync("tsconfig.json")) {
|
|
136
|
+
const raw = fs.readFileSync("tsconfig.json", "utf-8");
|
|
137
|
+
let tsconfig;
|
|
138
|
+
try {
|
|
139
|
+
tsconfig = JSON.parse(raw);
|
|
140
|
+
} catch {
|
|
141
|
+
actions.push("WARNING: tsconfig.json has invalid JSON");
|
|
142
|
+
return actions;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (!tsconfig.compilerOptions) tsconfig.compilerOptions = {};
|
|
146
|
+
let changed = false;
|
|
147
|
+
|
|
148
|
+
for (const [key, value] of Object.entries(REQUIRED_TSCONFIG_OPTIONS)) {
|
|
149
|
+
if (tsconfig.compilerOptions[key] !== value) {
|
|
150
|
+
tsconfig.compilerOptions[key] = value;
|
|
151
|
+
changed = true;
|
|
152
|
+
actions.push(`Fixed tsconfig option: "${key}" → ${value}`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (changed) {
|
|
157
|
+
fs.writeFileSync("tsconfig.json", JSON.stringify(tsconfig, null, 4) + "\n", "utf-8");
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// 4. Check public/ directory exists
|
|
162
|
+
if (!fs.existsSync("public")) {
|
|
163
|
+
fs.mkdirSync("public", { recursive: true });
|
|
164
|
+
actions.push("Created missing public/ directory");
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// 5. Update schema version stamp
|
|
168
|
+
const stampFile = "node_modules/.hackhub-schema-version";
|
|
169
|
+
const currentStamp = fs.existsSync(stampFile) ? parseInt(fs.readFileSync(stampFile, "utf-8").trim(), 10) : 0;
|
|
170
|
+
if (currentStamp < PROJECT_SCHEMA_VERSION) {
|
|
171
|
+
fs.mkdirSync(path.dirname(stampFile), { recursive: true });
|
|
172
|
+
fs.writeFileSync(stampFile, String(PROJECT_SCHEMA_VERSION), "utf-8");
|
|
173
|
+
if (currentStamp > 0) {
|
|
174
|
+
actions.push(`Project schema updated: v${currentStamp} → v${PROJECT_SCHEMA_VERSION}`);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return actions;
|
|
179
|
+
}
|
|
180
|
+
|
|
78
181
|
export function htmlAssetsPlugin() {
|
|
79
182
|
const assetPattern = /(?:src|href)\s*=\s*["'](\.[^"']+)["']/g;
|
|
80
183
|
|
|
@@ -107,10 +210,10 @@ export function htmlAssetsPlugin() {
|
|
|
107
210
|
|
|
108
211
|
function log(msg) { process.stdout.write(`\x1b[36m[HackHub]\x1b[0m ${msg}\n`); }
|
|
109
212
|
function logSuccess(msg) { process.stdout.write(`\x1b[32m[HackHub]\x1b[0m ${msg}\n`); }
|
|
213
|
+
function logWarn(msg) { process.stdout.write(`\x1b[33m[HackHub]\x1b[0m ${msg}\n`); }
|
|
110
214
|
|
|
111
215
|
/**
|
|
112
216
|
* Builds a HackHub mod project.
|
|
113
|
-
* Call this from your esbuild.config.ts — all build logic is handled here.
|
|
114
217
|
*
|
|
115
218
|
* @param {object} [options]
|
|
116
219
|
* @param {string} [options.entryPoint] - Entry file (default: "src/index.ts")
|
|
@@ -122,12 +225,18 @@ export async function buildMod(options = {}) {
|
|
|
122
225
|
const isWatch = process.argv.includes("--watch");
|
|
123
226
|
const start = Date.now();
|
|
124
227
|
|
|
125
|
-
|
|
228
|
+
// Validate & auto-fix project structure
|
|
229
|
+
const fixes = validateProject();
|
|
230
|
+
for (const fix of fixes) {
|
|
231
|
+
if (fix.startsWith("WARNING")) logWarn(fix);
|
|
232
|
+
else logWarn(`Auto-fix: ${fix}`);
|
|
233
|
+
}
|
|
234
|
+
|
|
126
235
|
prepareDist();
|
|
127
236
|
|
|
128
237
|
const assetCount = scanSourceAssets();
|
|
129
238
|
if (assetCount > 0) {
|
|
130
|
-
log(`Copied ${assetCount} asset(s)
|
|
239
|
+
log(`Copied ${assetCount} asset(s)`);
|
|
131
240
|
}
|
|
132
241
|
|
|
133
242
|
const outfile = options.outfile || "dist/mod.js";
|
|
@@ -152,11 +261,13 @@ export async function buildMod(options = {}) {
|
|
|
152
261
|
{
|
|
153
262
|
name: "rebuild-notify",
|
|
154
263
|
setup(build) {
|
|
264
|
+
let rebuildStart = Date.now();
|
|
265
|
+
build.onStart(() => { rebuildStart = Date.now(); });
|
|
155
266
|
build.onEnd((result) => {
|
|
156
267
|
if (result.errors.length === 0) {
|
|
157
268
|
prepareDist();
|
|
158
269
|
scanSourceAssets();
|
|
159
|
-
logSuccess(`Rebuilt in ${Date.now() -
|
|
270
|
+
logSuccess(`Rebuilt in ${Date.now() - rebuildStart}ms → ${outfile}`);
|
|
160
271
|
}
|
|
161
272
|
});
|
|
162
273
|
},
|
package/index.d.ts
CHANGED
|
@@ -180,6 +180,61 @@ export interface WeeChatMessageDefinition {
|
|
|
180
180
|
/** Callback fired after this message is sent. */
|
|
181
181
|
onSent?: () => void | Promise<void>;
|
|
182
182
|
}
|
|
183
|
+
/** A mail message sent during a quest (e.g. from an employer NPC). */
|
|
184
|
+
export interface QuestMailDefinition {
|
|
185
|
+
/** Mail subject line. */
|
|
186
|
+
title: string;
|
|
187
|
+
/** Mail body content (plain text or HTML string). */
|
|
188
|
+
content: string;
|
|
189
|
+
/** If true, the player can reply to this mail. */
|
|
190
|
+
replyable?: boolean;
|
|
191
|
+
/** Attachment file to include with the mail. */
|
|
192
|
+
attachment?: {
|
|
193
|
+
name: string;
|
|
194
|
+
extension?: string;
|
|
195
|
+
content?: string;
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
/** A phone-call dialog tree for quests. Keys are branch names. */
|
|
199
|
+
export interface QuestDialogDefinition {
|
|
200
|
+
[branchName: string]: QuestDialogSpeech[];
|
|
201
|
+
}
|
|
202
|
+
/** A single speech line in a phone-call dialog. */
|
|
203
|
+
export interface QuestDialogSpeech {
|
|
204
|
+
/** Who is speaking (NPC name or "player"). */
|
|
205
|
+
speaker: string;
|
|
206
|
+
/** The spoken text. */
|
|
207
|
+
text: string;
|
|
208
|
+
/** Audio file path (resolved via mod-asset://). Empty string if none. */
|
|
209
|
+
audio?: string;
|
|
210
|
+
/** If true, this is the last line and the call ends. */
|
|
211
|
+
isEnd?: boolean;
|
|
212
|
+
/** Delay in ms before the next line auto-plays. */
|
|
213
|
+
timeout?: number;
|
|
214
|
+
/** Callback fired when this speech line ends. */
|
|
215
|
+
onEnd?: () => void;
|
|
216
|
+
/** Player response options (branching). */
|
|
217
|
+
options?: QuestDialogOption[];
|
|
218
|
+
}
|
|
219
|
+
/** A selectable response option in a dialog. */
|
|
220
|
+
export interface QuestDialogOption {
|
|
221
|
+
/** Short label shown on the button. */
|
|
222
|
+
label: string;
|
|
223
|
+
/** Full text the player says. */
|
|
224
|
+
text: string;
|
|
225
|
+
/** Audio file path. Empty string if none. */
|
|
226
|
+
audio?: string;
|
|
227
|
+
/** Switch to a different dialog branch by name. */
|
|
228
|
+
switchBranch?: string;
|
|
229
|
+
/** Jump to a specific index within the current/target branch. */
|
|
230
|
+
nextIndex?: number;
|
|
231
|
+
/** If true, the call ends after this option. */
|
|
232
|
+
isEnd?: boolean;
|
|
233
|
+
/** Delay in ms before proceeding. */
|
|
234
|
+
timeout?: number;
|
|
235
|
+
/** Callback fired when this option is selected. */
|
|
236
|
+
onSelect?: () => void;
|
|
237
|
+
}
|
|
183
238
|
/**
|
|
184
239
|
* Game events that mods can listen to.
|
|
185
240
|
* This is a curated subset of the internal GameEvents, exposing only
|
|
@@ -390,6 +445,120 @@ export interface RemoteConnectionDisconnectedEvent {
|
|
|
390
445
|
ip: string;
|
|
391
446
|
service?: string;
|
|
392
447
|
}
|
|
448
|
+
export interface TerminalCdEvent {
|
|
449
|
+
path: string;
|
|
450
|
+
}
|
|
451
|
+
export interface TerminalLsEvent {
|
|
452
|
+
id: string;
|
|
453
|
+
name: string;
|
|
454
|
+
}
|
|
455
|
+
export interface TerminalOpensslEvent {
|
|
456
|
+
file: string;
|
|
457
|
+
}
|
|
458
|
+
export interface TerminalSSHShutdownEvent {
|
|
459
|
+
ip: string;
|
|
460
|
+
}
|
|
461
|
+
export interface TerminalLynxSearchEvent {
|
|
462
|
+
query: string;
|
|
463
|
+
}
|
|
464
|
+
export interface TerminalLynxLookupEvent {
|
|
465
|
+
data: any;
|
|
466
|
+
}
|
|
467
|
+
export interface TerminalMxlookupEvent {
|
|
468
|
+
domain: string;
|
|
469
|
+
ip: string;
|
|
470
|
+
}
|
|
471
|
+
export interface TerminalIfconfigEvent {
|
|
472
|
+
terminalIp: string;
|
|
473
|
+
}
|
|
474
|
+
export interface MetasploitMsfconsoleEvent {
|
|
475
|
+
}
|
|
476
|
+
export interface MetasploitShowOptionsEvent {
|
|
477
|
+
name: string;
|
|
478
|
+
type: string;
|
|
479
|
+
}
|
|
480
|
+
export interface MetasploitSetOptionEvent {
|
|
481
|
+
name: string;
|
|
482
|
+
value: string | number;
|
|
483
|
+
allFilled: boolean;
|
|
484
|
+
}
|
|
485
|
+
export interface MetasploitRootgrabEvent {
|
|
486
|
+
ip: string;
|
|
487
|
+
file: {
|
|
488
|
+
id: string;
|
|
489
|
+
name: string;
|
|
490
|
+
};
|
|
491
|
+
}
|
|
492
|
+
export interface MeterpreterDownloadEvent {
|
|
493
|
+
host: string;
|
|
494
|
+
file: {
|
|
495
|
+
id: string;
|
|
496
|
+
name: string;
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
export interface SubfinderResultsEvent {
|
|
500
|
+
domain: string;
|
|
501
|
+
subdomains: any[];
|
|
502
|
+
}
|
|
503
|
+
export interface NucleiResultsEvent {
|
|
504
|
+
file: any;
|
|
505
|
+
hosts: string[];
|
|
506
|
+
}
|
|
507
|
+
export interface HashcatEvent {
|
|
508
|
+
id: string;
|
|
509
|
+
name: string;
|
|
510
|
+
}
|
|
511
|
+
export interface JohnDecryptHashEvent {
|
|
512
|
+
hash: string;
|
|
513
|
+
password: string;
|
|
514
|
+
}
|
|
515
|
+
export interface FernFindPasswordEvent {
|
|
516
|
+
user: any;
|
|
517
|
+
model: string;
|
|
518
|
+
}
|
|
519
|
+
export interface BettercapOpenEvent {
|
|
520
|
+
}
|
|
521
|
+
export interface BettercapCloseEvent {
|
|
522
|
+
}
|
|
523
|
+
export interface BettercapNetProbeEvent {
|
|
524
|
+
active: boolean;
|
|
525
|
+
}
|
|
526
|
+
export interface BettercapNetShowEvent {
|
|
527
|
+
}
|
|
528
|
+
export interface BettercapWifiReconEvent {
|
|
529
|
+
}
|
|
530
|
+
export interface BettercapWifiDeAuthEvent {
|
|
531
|
+
}
|
|
532
|
+
export interface MailMailboxOpenedEvent {
|
|
533
|
+
from: string;
|
|
534
|
+
to: string;
|
|
535
|
+
subject: string;
|
|
536
|
+
}
|
|
537
|
+
export interface TwotterAccountLogoutEvent {
|
|
538
|
+
id: string;
|
|
539
|
+
username: string;
|
|
540
|
+
}
|
|
541
|
+
export interface BrowserMetaEvent {
|
|
542
|
+
title: string;
|
|
543
|
+
url: string;
|
|
544
|
+
}
|
|
545
|
+
export interface ProcessKilledEvent {
|
|
546
|
+
pid: number;
|
|
547
|
+
name: string;
|
|
548
|
+
}
|
|
549
|
+
export interface WiresharkStartedEvent {
|
|
550
|
+
interface: string;
|
|
551
|
+
}
|
|
552
|
+
export interface WiresharkStoppedEvent {
|
|
553
|
+
interface: string;
|
|
554
|
+
}
|
|
555
|
+
export interface Python3ExecFileEvent {
|
|
556
|
+
args: string[];
|
|
557
|
+
file: any;
|
|
558
|
+
}
|
|
559
|
+
export interface BCCNewsOpenedEvent {
|
|
560
|
+
articleId: string;
|
|
561
|
+
}
|
|
393
562
|
/**
|
|
394
563
|
* Map of event names to their payload types.
|
|
395
564
|
* Mod authors use these as: Events.on("Terminal.NmapScan", (data) => { ... })
|
|
@@ -461,6 +630,38 @@ export interface ModEventMap {
|
|
|
461
630
|
"RemoteConnection.Established": RemoteConnectionEstablishedEvent;
|
|
462
631
|
"RemoteConnection.Disconnected": RemoteConnectionDisconnectedEvent;
|
|
463
632
|
"AppStore.Downloaded": string;
|
|
633
|
+
"Terminal.Cd": TerminalCdEvent;
|
|
634
|
+
"Terminal.Ls": TerminalLsEvent;
|
|
635
|
+
"Terminal.Openssl": TerminalOpensslEvent;
|
|
636
|
+
"Terminal.SSH.Shutdown": TerminalSSHShutdownEvent;
|
|
637
|
+
"Terminal.Lynx.Search": TerminalLynxSearchEvent;
|
|
638
|
+
"Terminal.Lynx.Lookup": TerminalLynxLookupEvent;
|
|
639
|
+
"Terminal.Mxlookup": TerminalMxlookupEvent;
|
|
640
|
+
"Terminal.Ifconfig": TerminalIfconfigEvent;
|
|
641
|
+
"Metasploit.Msfconsole": MetasploitMsfconsoleEvent;
|
|
642
|
+
"Metasploit.ShowOptions": MetasploitShowOptionsEvent;
|
|
643
|
+
"Metasploit.SetOption": MetasploitSetOptionEvent;
|
|
644
|
+
"Metasploit.Rootgrab": MetasploitRootgrabEvent;
|
|
645
|
+
"Meterpreter.Download": MeterpreterDownloadEvent;
|
|
646
|
+
"Subfinder.Results": SubfinderResultsEvent;
|
|
647
|
+
"Nuclei.Results": NucleiResultsEvent;
|
|
648
|
+
"Hashcat": HashcatEvent;
|
|
649
|
+
"John.DecryptHash": JohnDecryptHashEvent;
|
|
650
|
+
"Fern.FindPassword": FernFindPasswordEvent;
|
|
651
|
+
"Bettercap.Open": BettercapOpenEvent;
|
|
652
|
+
"Bettercap.Close": BettercapCloseEvent;
|
|
653
|
+
"Bettercap.NetProbe": BettercapNetProbeEvent;
|
|
654
|
+
"Bettercap.NetShow": BettercapNetShowEvent;
|
|
655
|
+
"Bettercap.WifiRecon": BettercapWifiReconEvent;
|
|
656
|
+
"Bettercap.WifiDeAuth": BettercapWifiDeAuthEvent;
|
|
657
|
+
"Mail.MailboxOpened": MailMailboxOpenedEvent;
|
|
658
|
+
"Twotter.AccountLogout": TwotterAccountLogoutEvent;
|
|
659
|
+
"Browser.Meta": BrowserMetaEvent;
|
|
660
|
+
"Process.Killed": ProcessKilledEvent;
|
|
661
|
+
"Wireshark.Started": WiresharkStartedEvent;
|
|
662
|
+
"Wireshark.Stopped": WiresharkStoppedEvent;
|
|
663
|
+
"Python3.ExecFile": Python3ExecFileEvent;
|
|
664
|
+
"BCC.News.Opened": BCCNewsOpenedEvent;
|
|
464
665
|
}
|
|
465
666
|
export type ModEventName = keyof ModEventMap;
|
|
466
667
|
export type ModEventCallback<T extends ModEventName> = (data: ModEventMap[T]) => void;
|
|
@@ -711,6 +912,9 @@ export interface ModManifest {
|
|
|
711
912
|
dependencies?: string[];
|
|
712
913
|
permissions?: ModPermission[];
|
|
713
914
|
icon?: string;
|
|
915
|
+
cover?: string;
|
|
916
|
+
workshopId?: string;
|
|
917
|
+
tags?: string[];
|
|
714
918
|
}
|
|
715
919
|
export type ModPermission = "filesystem" | "network" | "events" | "mail" | "bank" | "shell" | "ui";
|
|
716
920
|
export interface ModInfo {
|
|
@@ -720,8 +924,13 @@ export interface ModInfo {
|
|
|
720
924
|
author: string;
|
|
721
925
|
description: string;
|
|
722
926
|
icon?: string;
|
|
927
|
+
cover?: string;
|
|
723
928
|
enabled: boolean;
|
|
929
|
+
status?: ModStatus;
|
|
724
930
|
error?: string;
|
|
931
|
+
permissions?: ModPermission[];
|
|
932
|
+
workshopId?: string;
|
|
933
|
+
tags?: string[];
|
|
725
934
|
}
|
|
726
935
|
export declare enum ModStatus {
|
|
727
936
|
Pending = "PENDING",
|
|
@@ -730,6 +939,19 @@ export declare enum ModStatus {
|
|
|
730
939
|
Error = "ERROR",
|
|
731
940
|
Disabled = "DISABLED"
|
|
732
941
|
}
|
|
942
|
+
export interface ModSettingDefinition {
|
|
943
|
+
key: string;
|
|
944
|
+
label: string;
|
|
945
|
+
type: "toggle" | "select" | "text" | "number" | "slider";
|
|
946
|
+
default: any;
|
|
947
|
+
options?: {
|
|
948
|
+
label: string;
|
|
949
|
+
value: string;
|
|
950
|
+
}[];
|
|
951
|
+
min?: number;
|
|
952
|
+
max?: number;
|
|
953
|
+
step?: number;
|
|
954
|
+
}
|
|
733
955
|
export interface TwotterCreateUserOptions {
|
|
734
956
|
id?: string;
|
|
735
957
|
username?: string;
|
|
@@ -828,19 +1050,28 @@ export interface WeeChatServer {
|
|
|
828
1050
|
*
|
|
829
1051
|
* @RegisterModPackage
|
|
830
1052
|
* export class MyMod extends Bootstrap {
|
|
1053
|
+
* Settings = [
|
|
1054
|
+
* { key: "difficulty", label: "Difficulty", type: "select" as const, default: "normal", options: [
|
|
1055
|
+
* { label: "Easy", value: "easy" },
|
|
1056
|
+
* { label: "Normal", value: "normal" },
|
|
1057
|
+
* { label: "Hard", value: "hard" },
|
|
1058
|
+
* ]},
|
|
1059
|
+
* { key: "showHints", label: "Show Hints", type: "toggle" as const, default: true },
|
|
1060
|
+
* ];
|
|
1061
|
+
*
|
|
831
1062
|
* OnModPackageLoaded() {
|
|
832
1063
|
* console.log("My mod has been loaded!");
|
|
833
1064
|
* }
|
|
834
|
-
*
|
|
835
|
-
* OnModPackageUnloaded() {
|
|
836
|
-
* console.log("My mod has been unloaded.");
|
|
837
|
-
* }
|
|
838
1065
|
* }
|
|
839
1066
|
* ```
|
|
840
1067
|
*/
|
|
841
1068
|
export declare abstract class Bootstrap {
|
|
842
1069
|
/** The parsed manifest.json of this mod. Set by the loader at runtime. */
|
|
843
1070
|
Manifest: ModManifest;
|
|
1071
|
+
/** Declarative settings definitions rendered in the Mods UI. */
|
|
1072
|
+
Settings?: ModSettingDefinition[];
|
|
1073
|
+
/** Path to an HTML file for a custom settings panel (rendered in iframe). */
|
|
1074
|
+
SettingsHTML?: string;
|
|
844
1075
|
/** Called after the mod package and all its registered content are loaded. */
|
|
845
1076
|
OnModPackageLoaded(): void | Promise<void>;
|
|
846
1077
|
/** Called when the mod is being unloaded (e.g. disabled by user). */
|
|
@@ -894,6 +1125,10 @@ export declare abstract class Quest {
|
|
|
894
1125
|
KisscordChats?: KisscordChatDefinition[];
|
|
895
1126
|
/** WeeChat (IRC) chat chains for this quest. */
|
|
896
1127
|
WeeChatChats?: WeeChatChatDefinition[];
|
|
1128
|
+
/** Mail messages that can be sent during the quest via this.sendMail(index). */
|
|
1129
|
+
Mails?: QuestMailDefinition[];
|
|
1130
|
+
/** Phone-call dialog tree for this quest. Use this.createDialog(branch) to start. */
|
|
1131
|
+
Dialog?: QuestDialogDefinition;
|
|
897
1132
|
/**
|
|
898
1133
|
* Called when the quest is first claimed/started.
|
|
899
1134
|
* Use this to set up event listeners for objective completion.
|
|
@@ -909,6 +1144,12 @@ export declare abstract class Quest {
|
|
|
909
1144
|
CreateData(): any;
|
|
910
1145
|
/** Complete an objective by its name. */
|
|
911
1146
|
completeObjective(name: string): void;
|
|
1147
|
+
/** Send a mail from the Mails array by index. */
|
|
1148
|
+
sendMail(index: number, from?: string, to?: string): void;
|
|
1149
|
+
/** Start a phone-call dialog. Defaults to "default" branch. */
|
|
1150
|
+
createDialog(branch?: string, startIndex?: number): void;
|
|
1151
|
+
/** @internal */ _sendMailInternal?: (index: number, from?: string, to?: string) => void;
|
|
1152
|
+
/** @internal */ _createDialogInternal?: (branch: string, startIndex: number) => void;
|
|
912
1153
|
}
|
|
913
1154
|
/**
|
|
914
1155
|
* Base class for mod websites. Mod authors extend this to create
|
|
@@ -1576,6 +1817,40 @@ export declare namespace WeeChat {
|
|
|
1576
1817
|
export function getHistory(host?: string): WeeChatMessage[];
|
|
1577
1818
|
export {};
|
|
1578
1819
|
}
|
|
1820
|
+
/**
|
|
1821
|
+
* Random generation helpers for mod authors.
|
|
1822
|
+
*
|
|
1823
|
+
* @example
|
|
1824
|
+
* ```ts
|
|
1825
|
+
* import { Random } from "@hotbunny/hackhub-content-sdk";
|
|
1826
|
+
*
|
|
1827
|
+
* const uniqueId = Random.id();
|
|
1828
|
+
* const pin = Random.number(1000, 9999);
|
|
1829
|
+
* const pass = Random.password();
|
|
1830
|
+
* const user = Random.username();
|
|
1831
|
+
* const winner = Random.pick(["Alice", "Bob", "Charlie"]);
|
|
1832
|
+
* await Random.sleep(2000);
|
|
1833
|
+
* ```
|
|
1834
|
+
*/
|
|
1835
|
+
export declare namespace Random {
|
|
1836
|
+
/** Generate a short unique ID. Default length is 10. */
|
|
1837
|
+
export function id(length?: number): string;
|
|
1838
|
+
/** Generate a UUID (v4-like). */
|
|
1839
|
+
export function uuid(): string;
|
|
1840
|
+
/** Generate a random integer between min and max (inclusive). */
|
|
1841
|
+
export function number(min?: number, max?: number): number;
|
|
1842
|
+
/** Get a random password from the game's password list. */
|
|
1843
|
+
export function password(): string;
|
|
1844
|
+
/** Get a random username from the game's username list. */
|
|
1845
|
+
export function username(): string;
|
|
1846
|
+
/** Pick a random element from an array. */
|
|
1847
|
+
export function pick<T>(array: T[]): T;
|
|
1848
|
+
/** Pick multiple unique random elements from an array. */
|
|
1849
|
+
export function pickMultiple<T>(array: T[], count: number): T[];
|
|
1850
|
+
/** Sleep for the given number of milliseconds. */
|
|
1851
|
+
export function sleep(ms: number): Promise<void>;
|
|
1852
|
+
export {};
|
|
1853
|
+
}
|
|
1579
1854
|
/**
|
|
1580
1855
|
* Marks a class as the mod's entry point.
|
|
1581
1856
|
* Each mod must have exactly one @RegisterModPackage class extending Bootstrap.
|