dankgrinder 8.28.0 → 8.32.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 +61 -42
- package/lib/ui.js +144 -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;
|
|
@@ -88,14 +89,13 @@ async function sendWebhook(title, description, color = 0x5865f2) {
|
|
|
88
89
|
}
|
|
89
90
|
|
|
90
91
|
// ── Terminal Colors & ANSI ───────────────────────────────────
|
|
91
|
-
// All colors stripped — plain text output only
|
|
92
92
|
const c = {
|
|
93
|
-
reset: '', bold: '', dim: '', italic: '',
|
|
94
|
-
green: '', red: '', yellow: '', cyan: '',
|
|
95
|
-
magenta: '', white: '', blue: '',
|
|
96
|
-
bgGreen: '', bgRed: '', bgYellow: '', bgCyan: '',
|
|
97
|
-
bgMagenta: '', bgBlue: '', bgWhite: '',
|
|
98
|
-
// Cursor control
|
|
93
|
+
reset: '\x1b[0m', bold: '\x1b[1m', dim: '\x1b[2m', italic: '\x1b[3m',
|
|
94
|
+
green: '\x1b[32m', red: '\x1b[31m', yellow: '\x1b[33m', cyan: '\x1b[36m',
|
|
95
|
+
magenta: '\x1b[35m', white: '\x1b[37m', blue: '\x1b[34m',
|
|
96
|
+
bgGreen: '\x1b[42m', bgRed: '\x1b[41m', bgYellow: '\x1b[43m', bgCyan: '\x1b[46m',
|
|
97
|
+
bgMagenta: '\x1b[45m', bgBlue: '\x1b[44m', bgWhite: '\x1b[47m',
|
|
98
|
+
// Cursor control
|
|
99
99
|
clearLine: '\x1b[2K',
|
|
100
100
|
cursorUp: (n) => `\x1b[${n}A`,
|
|
101
101
|
cursorTo: (col) => `\x1b[${col}G`,
|
|
@@ -230,21 +230,34 @@ async function filterClaimableAccounts(accounts) {
|
|
|
230
230
|
return claimable;
|
|
231
231
|
}
|
|
232
232
|
|
|
233
|
-
// ── Truecolor gradient helpers
|
|
234
|
-
function rgb(r, g, b) { return
|
|
235
|
-
function bgRgb(r, g, b) { return
|
|
233
|
+
// ── Truecolor gradient helpers ─────────────────────────────────
|
|
234
|
+
function rgb(r, g, b) { return `\x1b[38;2;${r};${g};${b}m`; }
|
|
235
|
+
function bgRgb(r, g, b) { return `\x1b[48;2;${r};${g};${b}m`; }
|
|
236
236
|
function lerp(a, b, t) { return Math.round(a + (b - a) * t); }
|
|
237
237
|
|
|
238
238
|
function gradientLine(text, from, to) {
|
|
239
|
-
|
|
239
|
+
// from/to are [r,g,b] arrays
|
|
240
|
+
const fr = Array.isArray(from) ? from[0] : 128;
|
|
241
|
+
const fg = Array.isArray(from) ? from[1] : 128;
|
|
242
|
+
const fb = Array.isArray(from) ? from[2] : 128;
|
|
243
|
+
const tr = Array.isArray(to) ? to[0] : 255;
|
|
244
|
+
const tg = Array.isArray(to) ? to[1] : 255;
|
|
245
|
+
const tb = Array.isArray(to) ? to[2] : 255;
|
|
246
|
+
let out = '';
|
|
247
|
+
for (let i = 0; i < text.length; i++) {
|
|
248
|
+
const t = text.length <= 1 ? 0 : i / (text.length - 1);
|
|
249
|
+
out += `\x1b[38;2;${lerp(fr, tr, t)};${lerp(fg, tg, t)};${lerp(fb, tb, t)}m${text[i]}`;
|
|
250
|
+
}
|
|
251
|
+
return out + c.reset;
|
|
240
252
|
}
|
|
241
253
|
|
|
242
254
|
function gradientText(text, from, to) {
|
|
243
|
-
return text;
|
|
255
|
+
return gradientLine(text, from, to);
|
|
244
256
|
}
|
|
245
257
|
|
|
246
258
|
function colorBanner() {
|
|
247
|
-
|
|
259
|
+
const title = `DANKGRINDER v${PKG_VERSION}`;
|
|
260
|
+
return gradientLine(title, [77, 142, 255], [255, 92, 147]);
|
|
248
261
|
}
|
|
249
262
|
|
|
250
263
|
// ── Simple Logging ─────────────────────────────────────────────
|
|
@@ -2647,7 +2660,8 @@ captchaDetector.build();
|
|
|
2647
2660
|
// ══════════════════════════════════════════════════════════════
|
|
2648
2661
|
|
|
2649
2662
|
async function start(apiKey, apiUrl, opts = {}) {
|
|
2650
|
-
|
|
2663
|
+
ui.init({ workers, getUptime: formatUptime, isShuttingDown: () => shutdownCalled });
|
|
2664
|
+
ui.drawBanner(PKG_VERSION);
|
|
2651
2665
|
CLOUD_ADMIN_KEY = process.env.CLOUD_ADMIN_KEY || '';
|
|
2652
2666
|
API_KEY = apiKey;
|
|
2653
2667
|
API_URL = apiUrl || process.env.DANKGRINDER_URL || 'http://localhost:3000';
|
|
@@ -2870,7 +2884,8 @@ async function start(apiKey, apiUrl, opts = {}) {
|
|
|
2870
2884
|
for (const w of activeWorkers) {
|
|
2871
2885
|
if (!shutdownCalled) w.grindLoop();
|
|
2872
2886
|
}
|
|
2873
|
-
|
|
2887
|
+
ui.start();
|
|
2888
|
+
console.log(`${c.dim}All grind loops started. | Ctrl+C to stop${c.reset}`);
|
|
2874
2889
|
|
|
2875
2890
|
// Cluster heartbeat — lets other nodes see this node is alive
|
|
2876
2891
|
if (CLUSTER_ENABLED) {
|
|
@@ -2931,64 +2946,68 @@ async function start(apiKey, apiUrl, opts = {}) {
|
|
|
2931
2946
|
} catch {}
|
|
2932
2947
|
}, 10_000);
|
|
2933
2948
|
|
|
2934
|
-
let
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2949
|
+
let shutdownInProgress = false;
|
|
2950
|
+
|
|
2951
|
+
async function gracefulShutdown(signal) {
|
|
2952
|
+
if (shutdownInProgress) return;
|
|
2953
|
+
shutdownInProgress = true;
|
|
2938
2954
|
shutdownCalled = true;
|
|
2939
2955
|
setDashboardActive(false);
|
|
2940
|
-
|
|
2956
|
+
ui.stop();
|
|
2957
|
+
process.stdout.write(c.show + '\n');
|
|
2941
2958
|
|
|
2942
2959
|
console.log('');
|
|
2943
|
-
console.log(
|
|
2960
|
+
console.log(`${c.yellow}[${signal}] Shutting down...${c.reset}`);
|
|
2944
2961
|
|
|
2945
|
-
// Collect stats from all workers
|
|
2962
|
+
// Collect stats from all workers
|
|
2946
2963
|
let finalCoins = 0;
|
|
2947
2964
|
let finalCmds = 0;
|
|
2948
2965
|
for (const wk of workers) {
|
|
2949
2966
|
const rate = wk.stats.commands > 0 ? ((wk.stats.successes / wk.stats.commands) * 100).toFixed(0) : 0;
|
|
2950
|
-
console.log(` ${
|
|
2967
|
+
console.log(` ${c.dim}${wk.username || '?'}${c.reset} ${c.green}+⏣${wk.stats.coins.toLocaleString()}${c.reset} ${wk.stats.commands}cmds ${rate}%ok`);
|
|
2951
2968
|
finalCoins += wk.stats.coins || 0;
|
|
2952
2969
|
finalCmds += wk.stats.commands || 0;
|
|
2953
2970
|
}
|
|
2954
2971
|
|
|
2955
2972
|
const memFinal = Math.round((process.memoryUsage?.rss?.() ?? process.memoryUsage().rss) / 1048576);
|
|
2956
|
-
const avgEarn = globalEarningsEMA.get();
|
|
2957
2973
|
const cpm = globalCmdRate.getRate().toFixed(1);
|
|
2958
|
-
console.log(
|
|
2959
|
-
console.log('');
|
|
2974
|
+
console.log(`${c.bold}Total:${c.reset} +⏣${finalCoins.toLocaleString()} in ${formatUptime()} | ${finalCmds}cmds | ~${cpm}cmd/m | ${memFinal}MB`);
|
|
2960
2975
|
|
|
2961
|
-
//
|
|
2962
|
-
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
|
|
2976
|
+
// Stop workers — max 1s per worker so one hung client doesn't block shutdown
|
|
2977
|
+
await Promise.all(workers.map(wk => Promise.race([
|
|
2978
|
+
new Promise(resolve => { wk.stop(); resolve(true); }),
|
|
2979
|
+
new Promise(resolve => setTimeout(() => resolve(false), 1000)),
|
|
2980
|
+
])));
|
|
2966
2981
|
workerMap.clear();
|
|
2967
2982
|
|
|
2968
|
-
//
|
|
2983
|
+
// Release cluster claims
|
|
2984
|
+
await Promise.allSettled(workers.map(wk => releaseClaim(wk.account.id)));
|
|
2985
|
+
|
|
2986
|
+
// Remove heartbeat
|
|
2969
2987
|
if (redis && CLUSTER_ENABLED) {
|
|
2970
|
-
|
|
2988
|
+
await Promise.allSettled([redis.del(`${CLUSTER_PREFIX}node:${NODE_ID}`)]);
|
|
2971
2989
|
}
|
|
2972
2990
|
|
|
2973
2991
|
const totalRecoveries = workers.reduce((sum, wk) => sum + (wk._totalRecoveries || 0), 0);
|
|
2974
2992
|
const totalDisconnects = workers.reduce((sum, wk) => sum + (wk._disconnectCount || 0), 0);
|
|
2975
2993
|
const totalRateLimits = workers.reduce((sum, wk) => sum + (wk._rateLimitHits || 0), 0);
|
|
2976
2994
|
|
|
2995
|
+
if (totalRecoveries > 0 || totalDisconnects > 0) {
|
|
2996
|
+
console.log(` ${c.dim}${totalRecoveries} recoveries, ${totalDisconnects} disconnects, ${totalRateLimits} rate-limits${c.reset}`);
|
|
2997
|
+
}
|
|
2998
|
+
|
|
2977
2999
|
const webhookMsg = `+⏣ ${finalCoins.toLocaleString()} | ${finalCmds} cmds | ${formatUptime()}` +
|
|
2978
3000
|
(totalRecoveries > 0 ? ` | ${totalRecoveries} auto-recoveries` : '') +
|
|
2979
3001
|
(CLUSTER_ENABLED ? ` | node: ${NODE_ID.substring(0, 12)}` : '');
|
|
2980
3002
|
sendWebhook('Session Ended', webhookMsg, 0x8b5cf6);
|
|
2981
3003
|
|
|
2982
|
-
if (
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
console.log(` ${c.dim}Cluster node: ${NODE_ID} — claims released${c.reset}`);
|
|
2987
|
-
}
|
|
3004
|
+
if (redis) { redis.disconnect().catch(() => {}); }
|
|
3005
|
+
console.log(`${c.green}Goodbye!${c.reset}\n`);
|
|
3006
|
+
process.exit(0);
|
|
3007
|
+
}
|
|
2988
3008
|
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
});
|
|
3009
|
+
process.on('SIGINT', () => gracefulShutdown('SIGINT'));
|
|
3010
|
+
process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
|
|
2992
3011
|
}
|
|
2993
3012
|
|
|
2994
3013
|
// ══════════════════════════════════════════════════════════════
|
package/lib/ui.js
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
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 _workers = [];
|
|
8
|
+
let _getUptime = () => '0s';
|
|
9
|
+
let _isShuttingDown = () => false;
|
|
10
|
+
|
|
11
|
+
// ANSI helpers
|
|
12
|
+
const c = {
|
|
13
|
+
reset: '\x1b[0m', bold: '\x1b[1m', dim: '\x1b[2m',
|
|
14
|
+
green: '\x1b[32m', red: '\x1b[31m', yellow: '\x1b[33m',
|
|
15
|
+
cyan: '\x1b[36m', blue: '\x1b[34m',
|
|
16
|
+
};
|
|
17
|
+
const DIM = c.dim;
|
|
18
|
+
|
|
19
|
+
function rgb(r, g, b) { return `\x1b[38;2;${r};${g};${b}m`; }
|
|
20
|
+
function lerp(a, b, t) { return Math.round(a + (b - a) * t); }
|
|
21
|
+
function gradientLine(text, from, to) {
|
|
22
|
+
const fr = Array.isArray(from) ? from[0] : 128;
|
|
23
|
+
const fg = Array.isArray(from) ? from[1] : 128;
|
|
24
|
+
const fb = Array.isArray(from) ? from[2] : 128;
|
|
25
|
+
const tr = Array.isArray(to) ? to[0] : 255;
|
|
26
|
+
const tg = Array.isArray(to) ? to[1] : 255;
|
|
27
|
+
const tb = Array.isArray(to) ? to[2] : 255;
|
|
28
|
+
let out = '';
|
|
29
|
+
for (let i = 0; i < text.length; i++) {
|
|
30
|
+
const t = text.length <= 1 ? 0 : i / (text.length - 1);
|
|
31
|
+
out += `\x1b[38;2;${lerp(fr, tr, t)};${lerp(fg, tg, t)};${lerp(fb, tb, t)}m${text[i]}`;
|
|
32
|
+
}
|
|
33
|
+
return out + c.reset;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
let _lineCount = 0;
|
|
37
|
+
let _interval = null;
|
|
38
|
+
|
|
39
|
+
function _statusIcon(w) {
|
|
40
|
+
if (!w.running || !w.channel) return `${c.red}✗${c.reset}`;
|
|
41
|
+
if (w.paused || w.dashboardPaused) return `${c.yellow}⏸${c.reset}`;
|
|
42
|
+
if (w.busy || w._invRunning || w._sellRunning) return `${c.cyan}⚙${c.reset}`;
|
|
43
|
+
if (Date.now() < w.globalCooldownUntil) return `${c.blue}⏳${c.reset}`;
|
|
44
|
+
return `${c.green}●${c.reset}`;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function _render() {
|
|
48
|
+
const workers = _workers;
|
|
49
|
+
const W = Math.min(process.stdout.columns || 120, 120);
|
|
50
|
+
const lines = [];
|
|
51
|
+
|
|
52
|
+
lines.push(`${c.bold}${'─'.repeat(W)}${c.reset}`);
|
|
53
|
+
lines.push(
|
|
54
|
+
` ${c.bold}#${c.reset} ${c.bold}Account${c.reset}${' '.repeat(Math.max(1, 20 - 7))}` +
|
|
55
|
+
`${c.bold}Status${c.reset}${' '.repeat(Math.max(1, 14 - 6))}` +
|
|
56
|
+
`${c.bold}Earned${c.reset}${' '.repeat(Math.max(1, 12 - 5))}` +
|
|
57
|
+
`${c.bold}Cmds${c.reset}${' '.repeat(Math.max(1, 7 - 4))}` +
|
|
58
|
+
`${c.bold}OK%${c.reset}`
|
|
59
|
+
);
|
|
60
|
+
lines.push(`${c.bold}${'─'.repeat(W)}${c.reset}`);
|
|
61
|
+
|
|
62
|
+
for (let i = 0; i < workers.length; i++) {
|
|
63
|
+
const w = workers[i];
|
|
64
|
+
const name = (w.username || '?').substring(0, 20);
|
|
65
|
+
const status = (w.lastStatus || 'idle').substring(0, 14).padEnd(14);
|
|
66
|
+
const earned = w.stats.coins > 0 ? `+⏣${w.stats.coins.toLocaleString()}` : DIM + '—' + c.reset;
|
|
67
|
+
const cmds = String(w.stats.commands || 0);
|
|
68
|
+
const rate = w.stats.commands > 0 ? Math.round((w.stats.successes / w.stats.commands) * 100) : 0;
|
|
69
|
+
|
|
70
|
+
lines.push(
|
|
71
|
+
` ${DIM}${String(i + 1).padStart(2)}${c.reset} ${name.padEnd(20)} ${status}` +
|
|
72
|
+
`${String(earned).padStart(12)} ${cmds.padStart(6)} ${String(rate).padStart(3)}%`
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
lines.push(`${c.bold}${'─'.repeat(W)}${c.reset}`);
|
|
77
|
+
|
|
78
|
+
// Totals
|
|
79
|
+
let totalCoins = 0, totalCmds = 0;
|
|
80
|
+
for (const w of workers) {
|
|
81
|
+
totalCoins += w.stats.coins || 0;
|
|
82
|
+
totalCmds += w.stats.commands || 0;
|
|
83
|
+
}
|
|
84
|
+
const memMB = Math.round((process.memoryUsage?.rss?.() ?? process.memoryUsage().rss) / 1048576);
|
|
85
|
+
lines.push(
|
|
86
|
+
` ${c.bold}Total${c.reset} ` +
|
|
87
|
+
`${totalCoins > 0 ? c.green + '+⏣' + totalCoins.toLocaleString() + c.reset : DIM + '—' + c.reset}` +
|
|
88
|
+
` ${totalCmds} cmds ${_getUptime()} ${memMB}MB`
|
|
89
|
+
);
|
|
90
|
+
lines.push('');
|
|
91
|
+
|
|
92
|
+
// Redraw in place
|
|
93
|
+
process.stdout.write(`\x1b[${_lineCount}A`);
|
|
94
|
+
process.stdout.write(lines.join('\n') + '\n');
|
|
95
|
+
_lineCount = lines.length - 1;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function _clear() {
|
|
99
|
+
if (_lineCount > 0) {
|
|
100
|
+
process.stdout.write(`\x1b[${_lineCount}A`);
|
|
101
|
+
for (let i = 0; i < _lineCount; i++) {
|
|
102
|
+
process.stdout.write(`\x1b[2K\r${i < _lineCount - 1 ? '\x1b[1A' : ''}`);
|
|
103
|
+
}
|
|
104
|
+
_lineCount = 0;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// ── Public API ────────────────────────────────────────────────
|
|
109
|
+
|
|
110
|
+
function init({ workers, getUptime, isShuttingDown }) {
|
|
111
|
+
_workers = workers;
|
|
112
|
+
_getUptime = getUptime || (() => '0s');
|
|
113
|
+
_isShuttingDown = isShuttingDown || (() => false);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function drawBanner(version) {
|
|
117
|
+
const title = `DANKGRINDER v${version}`;
|
|
118
|
+
const gradient = gradientLine(title, [77, 142, 255], [255, 92, 147]);
|
|
119
|
+
console.log('');
|
|
120
|
+
console.log(` ${gradient}${c.reset}`);
|
|
121
|
+
console.log(` ${DIM}Ctrl+C to stop${c.reset}`);
|
|
122
|
+
console.log('');
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function start() {
|
|
126
|
+
if (_workers.length > 30) return; // too many accounts — stay quiet
|
|
127
|
+
_lineCount = 0;
|
|
128
|
+
_render();
|
|
129
|
+
_interval = setInterval(() => {
|
|
130
|
+
if (_isShuttingDown()) {
|
|
131
|
+
clearInterval(_interval);
|
|
132
|
+
_interval = null;
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
_render();
|
|
136
|
+
}, 10_000);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function stop() {
|
|
140
|
+
if (_interval) { clearInterval(_interval); _interval = null; }
|
|
141
|
+
_clear();
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
module.exports = { init, drawBanner, start, stop };
|