dankgrinder 8.31.0 → 8.33.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/grinder.js +8 -3
- package/lib/ui.js +155 -0
- package/package.json +1 -1
package/lib/grinder.js
CHANGED
|
@@ -8,6 +8,7 @@ const {
|
|
|
8
8
|
AhoCorasick, LRUCache, StringPool, AsyncBatchQueue, JitterBackoff,
|
|
9
9
|
} = require('./structures');
|
|
10
10
|
const PKG_VERSION = require('../package.json').version;
|
|
11
|
+
const ui = require('./ui');
|
|
11
12
|
|
|
12
13
|
// Global shutdown flag
|
|
13
14
|
let shutdownCalled = false;
|
|
@@ -255,7 +256,8 @@ function gradientText(text, from, to) {
|
|
|
255
256
|
}
|
|
256
257
|
|
|
257
258
|
function colorBanner() {
|
|
258
|
-
|
|
259
|
+
const title = `DANKGRINDER v${PKG_VERSION}`;
|
|
260
|
+
return gradientLine(title, [77, 142, 255], [255, 92, 147]);
|
|
259
261
|
}
|
|
260
262
|
|
|
261
263
|
// ── Simple Logging ─────────────────────────────────────────────
|
|
@@ -2658,7 +2660,8 @@ captchaDetector.build();
|
|
|
2658
2660
|
// ══════════════════════════════════════════════════════════════
|
|
2659
2661
|
|
|
2660
2662
|
async function start(apiKey, apiUrl, opts = {}) {
|
|
2661
|
-
|
|
2663
|
+
ui.init({ workers, isShuttingDown: () => shutdownCalled });
|
|
2664
|
+
ui.drawBanner(PKG_VERSION);
|
|
2662
2665
|
CLOUD_ADMIN_KEY = process.env.CLOUD_ADMIN_KEY || '';
|
|
2663
2666
|
API_KEY = apiKey;
|
|
2664
2667
|
API_URL = apiUrl || process.env.DANKGRINDER_URL || 'http://localhost:3000';
|
|
@@ -2881,7 +2884,8 @@ async function start(apiKey, apiUrl, opts = {}) {
|
|
|
2881
2884
|
for (const w of activeWorkers) {
|
|
2882
2885
|
if (!shutdownCalled) w.grindLoop();
|
|
2883
2886
|
}
|
|
2884
|
-
|
|
2887
|
+
ui.start();
|
|
2888
|
+
console.log(`${c.dim}All grind loops started. | Ctrl+C to stop${c.reset}`);
|
|
2885
2889
|
|
|
2886
2890
|
// Cluster heartbeat — lets other nodes see this node is alive
|
|
2887
2891
|
if (CLUSTER_ENABLED) {
|
|
@@ -2949,6 +2953,7 @@ async function start(apiKey, apiUrl, opts = {}) {
|
|
|
2949
2953
|
shutdownInProgress = true;
|
|
2950
2954
|
shutdownCalled = true;
|
|
2951
2955
|
setDashboardActive(false);
|
|
2956
|
+
ui.stop();
|
|
2952
2957
|
process.stdout.write(c.show + '\n');
|
|
2953
2958
|
|
|
2954
2959
|
console.log('');
|
package/lib/ui.js
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI Live Dashboard
|
|
3
|
+
* Renders a terminal table that updates in place every 10s.
|
|
4
|
+
* Shows per-account status, earned coins, commands, and session uptime.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
let _startTime = Date.now();
|
|
8
|
+
let _workers = [];
|
|
9
|
+
let _isShuttingDown = () => false;
|
|
10
|
+
|
|
11
|
+
function formatUptime() {
|
|
12
|
+
const s = Math.floor((Date.now() - _startTime) / 1000);
|
|
13
|
+
if (s < 60) return `${s}s`;
|
|
14
|
+
const m = Math.floor(s / 60);
|
|
15
|
+
const h = Math.floor(m / 60);
|
|
16
|
+
const d = Math.floor(h / 24);
|
|
17
|
+
if (d > 0) return `${d}d ${h % 24}h`;
|
|
18
|
+
if (h > 0) return `${h}h ${m % 60}m`;
|
|
19
|
+
return `${m}m`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// ANSI helpers
|
|
23
|
+
const c = {
|
|
24
|
+
reset: '\x1b[0m', bold: '\x1b[1m', dim: '\x1b[2m',
|
|
25
|
+
green: '\x1b[32m', red: '\x1b[31m', yellow: '\x1b[33m',
|
|
26
|
+
cyan: '\x1b[36m', blue: '\x1b[34m',
|
|
27
|
+
};
|
|
28
|
+
const DIM = c.dim;
|
|
29
|
+
|
|
30
|
+
function rgb(r, g, b) { return `\x1b[38;2;${r};${g};${b}m`; }
|
|
31
|
+
function lerp(a, b, t) { return Math.round(a + (b - a) * t); }
|
|
32
|
+
function gradientLine(text, from, to) {
|
|
33
|
+
const fr = Array.isArray(from) ? from[0] : 128;
|
|
34
|
+
const fg = Array.isArray(from) ? from[1] : 128;
|
|
35
|
+
const fb = Array.isArray(from) ? from[2] : 128;
|
|
36
|
+
const tr = Array.isArray(to) ? to[0] : 255;
|
|
37
|
+
const tg = Array.isArray(to) ? to[1] : 255;
|
|
38
|
+
const tb = Array.isArray(to) ? to[2] : 255;
|
|
39
|
+
let out = '';
|
|
40
|
+
for (let i = 0; i < text.length; i++) {
|
|
41
|
+
const t = text.length <= 1 ? 0 : i / (text.length - 1);
|
|
42
|
+
out += `\x1b[38;2;${lerp(fr, tr, t)};${lerp(fg, tg, t)};${lerp(fb, tb, t)}m${text[i]}`;
|
|
43
|
+
}
|
|
44
|
+
return out + c.reset;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
let _lineCount = 0;
|
|
48
|
+
let _interval = null;
|
|
49
|
+
|
|
50
|
+
function _statusIcon(w) {
|
|
51
|
+
if (!w.running || !w.channel) return `${c.red}✗${c.reset}`;
|
|
52
|
+
if (w.paused || w.dashboardPaused) return `${c.yellow}⏸${c.reset}`;
|
|
53
|
+
if (w.busy || w._invRunning || w._sellRunning) return `${c.cyan}⚙${c.reset}`;
|
|
54
|
+
if (Date.now() < w.globalCooldownUntil) return `${c.blue}⏳${c.reset}`;
|
|
55
|
+
return `${c.green}●${c.reset}`;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function _render() {
|
|
59
|
+
const workers = _workers;
|
|
60
|
+
const W = Math.min(process.stdout.columns || 120, 120);
|
|
61
|
+
const lines = [];
|
|
62
|
+
|
|
63
|
+
lines.push(`${c.bold}${'─'.repeat(W)}${c.reset}`);
|
|
64
|
+
lines.push(
|
|
65
|
+
` ${c.bold}#${c.reset} ${c.bold}Account${c.reset}${' '.repeat(Math.max(1, 20 - 7))}` +
|
|
66
|
+
`${c.bold}Status${c.reset}${' '.repeat(Math.max(1, 14 - 6))}` +
|
|
67
|
+
`${c.bold}Earned${c.reset}${' '.repeat(Math.max(1, 12 - 5))}` +
|
|
68
|
+
`${c.bold}Cmds${c.reset}${' '.repeat(Math.max(1, 7 - 4))}` +
|
|
69
|
+
`${c.bold}OK%${c.reset}`
|
|
70
|
+
);
|
|
71
|
+
lines.push(`${c.bold}${'─'.repeat(W)}${c.reset}`);
|
|
72
|
+
|
|
73
|
+
for (let i = 0; i < workers.length; i++) {
|
|
74
|
+
const w = workers[i];
|
|
75
|
+
const name = (w.username || '?').substring(0, 20);
|
|
76
|
+
const status = (w.lastStatus || 'idle').substring(0, 14).padEnd(14);
|
|
77
|
+
const earned = w.stats.coins > 0 ? `+⏣${w.stats.coins.toLocaleString()}` : DIM + '—' + c.reset;
|
|
78
|
+
const cmds = String(w.stats.commands || 0);
|
|
79
|
+
const rate = w.stats.commands > 0 ? Math.round((w.stats.successes / w.stats.commands) * 100) : 0;
|
|
80
|
+
|
|
81
|
+
lines.push(
|
|
82
|
+
` ${DIM}${String(i + 1).padStart(2)}${c.reset} ${name.padEnd(20)} ${status}` +
|
|
83
|
+
`${String(earned).padStart(12)} ${cmds.padStart(6)} ${String(rate).padStart(3)}%`
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
lines.push(`${c.bold}${'─'.repeat(W)}${c.reset}`);
|
|
88
|
+
|
|
89
|
+
// Totals
|
|
90
|
+
let totalCoins = 0, totalCmds = 0;
|
|
91
|
+
for (const w of workers) {
|
|
92
|
+
totalCoins += w.stats.coins || 0;
|
|
93
|
+
totalCmds += w.stats.commands || 0;
|
|
94
|
+
}
|
|
95
|
+
const memMB = Math.round((process.memoryUsage?.rss?.() ?? process.memoryUsage().rss) / 1048576);
|
|
96
|
+
lines.push(
|
|
97
|
+
` ${c.bold}Total${c.reset} ` +
|
|
98
|
+
`${totalCoins > 0 ? c.green + '+⏣' + totalCoins.toLocaleString() + c.reset : DIM + '—' + c.reset}` +
|
|
99
|
+
` ${totalCmds} cmds ${formatUptime()} ${memMB}MB`
|
|
100
|
+
);
|
|
101
|
+
lines.push('');
|
|
102
|
+
|
|
103
|
+
// Redraw in place
|
|
104
|
+
process.stdout.write(`\x1b[${_lineCount}A`);
|
|
105
|
+
process.stdout.write(lines.join('\n') + '\n');
|
|
106
|
+
_lineCount = lines.length - 1;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function _clear() {
|
|
110
|
+
if (_lineCount > 0) {
|
|
111
|
+
process.stdout.write(`\x1b[${_lineCount}A`);
|
|
112
|
+
for (let i = 0; i < _lineCount; i++) {
|
|
113
|
+
process.stdout.write(`\x1b[2K\r${i < _lineCount - 1 ? '\x1b[1A' : ''}`);
|
|
114
|
+
}
|
|
115
|
+
_lineCount = 0;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// ── Public API ────────────────────────────────────────────────
|
|
120
|
+
|
|
121
|
+
function init({ workers, isShuttingDown }) {
|
|
122
|
+
_startTime = Date.now();
|
|
123
|
+
_workers = workers;
|
|
124
|
+
_isShuttingDown = isShuttingDown || (() => false);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function drawBanner(version) {
|
|
128
|
+
const title = `DANKGRINDER v${version}`;
|
|
129
|
+
const gradient = gradientLine(title, [77, 142, 255], [255, 92, 147]);
|
|
130
|
+
console.log('');
|
|
131
|
+
console.log(` ${gradient}${c.reset}`);
|
|
132
|
+
console.log(` ${DIM}Ctrl+C to stop${c.reset}`);
|
|
133
|
+
console.log('');
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function start() {
|
|
137
|
+
if (_workers.length > 30) return; // too many accounts — stay quiet
|
|
138
|
+
_lineCount = 0;
|
|
139
|
+
_render();
|
|
140
|
+
_interval = setInterval(() => {
|
|
141
|
+
if (_isShuttingDown()) {
|
|
142
|
+
clearInterval(_interval);
|
|
143
|
+
_interval = null;
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
_render();
|
|
147
|
+
}, 10_000);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function stop() {
|
|
151
|
+
if (_interval) { clearInterval(_interval); _interval = null; }
|
|
152
|
+
_clear();
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
module.exports = { init, drawBanner, start, stop };
|