dankgrinder 8.73.0 → 8.75.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/ui.js +179 -330
- package/package.json +1 -1
package/lib/ui.js
CHANGED
|
@@ -1,370 +1,219 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
*/
|
|
1
|
+
const logUpdate = require('log-update');
|
|
2
|
+
const chalk = require('chalk');
|
|
3
|
+
const figlet = require('figlet');
|
|
5
4
|
|
|
6
|
-
let _startTime = Date.now();
|
|
7
5
|
let _workers = [];
|
|
8
6
|
let _version = '0.0.0';
|
|
9
7
|
let _live = false;
|
|
10
|
-
let
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
let
|
|
14
|
-
let _inner = 98;
|
|
15
|
-
let _maxAccounts = 4;
|
|
8
|
+
let _startTime = Date.now();
|
|
9
|
+
let _events = [];
|
|
10
|
+
const MAX_EVENTS = 5;
|
|
11
|
+
let _refreshTimer = null;
|
|
16
12
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
let
|
|
13
|
+
function applyGradient(str) {
|
|
14
|
+
const lines = str.split('\n');
|
|
15
|
+
const maxL = Math.max(...lines.map(l => l.length));
|
|
16
|
+
let res = '';
|
|
17
|
+
lines.forEach(line => {
|
|
18
|
+
for (let c = 0; c < line.length; c++) {
|
|
19
|
+
let t = c / (maxL || 1);
|
|
20
|
+
res += chalk.rgb(
|
|
21
|
+
Math.floor(255 - (t * 255)),
|
|
22
|
+
Math.floor(t * 200),
|
|
23
|
+
Math.floor(t * 255) + 100 > 255 ? 255 : Math.floor(t * 255) + 100
|
|
24
|
+
)(line[c]);
|
|
25
|
+
}
|
|
26
|
+
res += '\n';
|
|
27
|
+
});
|
|
28
|
+
return res;
|
|
29
|
+
}
|
|
21
30
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
function spinnerFrame() { return SPINNER[Math.floor(Date.now() / 150) % SPINNER.length]; }
|
|
31
|
+
const SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
32
|
+
function getSpinner() { return SPINNER_FRAMES[Math.floor(Date.now() / 80) % SPINNER_FRAMES.length]; }
|
|
25
33
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
' ██████╗ ██╗ ██╗███╗ ██╗ ██████╗ ███████╗ ██████╗ ███╗ ██╗ DANKGRINDER',
|
|
29
|
-
' ██╔══██╗██║ ██║████╗ ██║██╔════╝ ██╔════╝██╔═══██╗████╗ ██║ vPLACEHOLDER',
|
|
30
|
-
' ██║ ██║██║ ██║██╔██╗ ██║██║ ███╗█████╗ ██║ ██║██╔██╗ ██║',
|
|
31
|
-
' ██║ ██║██║ ██║██║╚██╗██║██║ ██║██╔══╝ ██║ ██║██║╚██╗██║',
|
|
32
|
-
' ██████╔╝╚██████╔╝██║ ╚████║╚██████╔╝███████╗╚██████╔╝██║ ╚████║',
|
|
33
|
-
' ╚═════╝ ╚═════╝ ╚═╝ ╚═══╝ ╚═════╝ ╚══════╝ ╚═════╝ ╚═╝ ╚═══╝',
|
|
34
|
-
];
|
|
34
|
+
const LOAD_FRAMES = ['[= ]', '[== ]', '[=== ]', '[ ===]', '[ ==]', '[ =]', '[ ==]', '[ ===]', '[=== ]', '[== ]'];
|
|
35
|
+
function getLoader() { return LOAD_FRAMES[Math.floor(Date.now() / 150) % LOAD_FRAMES.length]; }
|
|
35
36
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
];
|
|
43
|
-
function wc(idx) { return PALETTE[idx % PALETTE.length]; }
|
|
37
|
+
const ACC_COLORS = ['#ff0054', '#ffbd00', '#390099', '#9e0059', '#ff5400', '#00f5d4', '#00bbf9', '#fee440', '#f15bb5', '#9b5de5'];
|
|
38
|
+
function getAccountColor(str) {
|
|
39
|
+
let hash = 0;
|
|
40
|
+
for (let i = 0; i < str.length; i++) hash = str.charCodeAt(i) + ((hash << 5) - hash);
|
|
41
|
+
return chalk.hex(ACC_COLORS[Math.abs(hash) % ACC_COLORS.length]);
|
|
42
|
+
}
|
|
44
43
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
cyan: '\x1b[38;2;80;220;255m',
|
|
52
|
-
white: '\x1b[37m',
|
|
44
|
+
const STATUS_DOT = {
|
|
45
|
+
online: { dot: '●', color: chalk.hex('#50ff78') },
|
|
46
|
+
busy: { dot: '◐', color: chalk.hex('#ffdc50') },
|
|
47
|
+
paused: { dot: '○', color: chalk.hex('#b4b4b4') },
|
|
48
|
+
dead: { dot: '✗', color: chalk.hex('#ff5064') },
|
|
49
|
+
connect: { dot: '◯', color: chalk.hex('#ffb450') }
|
|
53
50
|
};
|
|
54
|
-
const DIM = c.dim;
|
|
55
|
-
const B = '\x1b[38;2;77;212;238m'; // box border color
|
|
56
51
|
|
|
57
|
-
function
|
|
58
|
-
|
|
59
|
-
function
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
52
|
+
function init({ workers }) { _workers = workers; _startTime = Date.now(); _events = []; _live = false; }
|
|
53
|
+
|
|
54
|
+
function drawBanner(version) {
|
|
55
|
+
_version = version || '0.0.0';
|
|
56
|
+
console.clear();
|
|
57
|
+
const titleStr = figlet.textSync('DANK GRINDER', { font: 'ANSI Shadow' });
|
|
58
|
+
console.log(chalk.bold(applyGradient(titleStr)));
|
|
59
|
+
console.log(chalk.bold.magenta(`v${_version} — ${getLoader()} Booting... `));
|
|
60
|
+
console.log();
|
|
61
|
+
}
|
|
64
62
|
|
|
65
63
|
function fmtUptime() {
|
|
66
64
|
const s = Math.floor((Date.now() - _startTime) / 1000);
|
|
67
65
|
if (s < 60) return `${s}s`;
|
|
68
66
|
const m = Math.floor(s / 60);
|
|
69
67
|
const h = Math.floor(m / 60);
|
|
70
|
-
if (h > 0) return `${h}h${m % 60}m`;
|
|
68
|
+
if (h > 0) return `${h}h ${m % 60}m`;
|
|
71
69
|
return `${m}m`;
|
|
72
70
|
}
|
|
73
71
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
if (
|
|
77
|
-
if (w.
|
|
78
|
-
if (w.busy || w._invRunning || w._sellRunning) return
|
|
79
|
-
if (Date.now() < w.globalCooldownUntil) return
|
|
80
|
-
return
|
|
72
|
+
function getDot(w) {
|
|
73
|
+
if (!w.channel) return STATUS_DOT.connect;
|
|
74
|
+
if (w.paused || w.dashboardPaused) return STATUS_DOT.paused;
|
|
75
|
+
if (w._alert?.type === 'death') return STATUS_DOT.dead;
|
|
76
|
+
if (w.busy || w._invRunning || w._sellRunning) return STATUS_DOT.busy;
|
|
77
|
+
if (w.globalCooldownUntil && Date.now() < w.globalCooldownUntil) return STATUS_DOT.busy;
|
|
78
|
+
return STATUS_DOT.online;
|
|
81
79
|
}
|
|
82
80
|
|
|
83
|
-
function
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
if (
|
|
89
|
-
if (
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
81
|
+
function addEvent(type, msg) {
|
|
82
|
+
const now = new Date();
|
|
83
|
+
const ts = `${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}:${String(now.getSeconds()).padStart(2, '0')}`;
|
|
84
|
+
const icons = { info: chalk.blue('ℹ'), warn: chalk.yellow('⚠'), error: chalk.red('✗'), success: chalk.green('✔') };
|
|
85
|
+
_events.unshift({ icon: icons[type] || icons.info, msg: chalk.bold(msg), ts });
|
|
86
|
+
if (_events.length > MAX_EVENTS) _events.pop();
|
|
87
|
+
if (_live) render();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function logGlobal(msg) { addEvent('info', msg); }
|
|
91
|
+
function log(accountIdx, msg) { if (!_live && accountIdx === -1) console.log(chalk.bold(msg)); }
|
|
92
|
+
function updateAccountRow() { }
|
|
93
|
+
|
|
94
|
+
// Precision width padding
|
|
95
|
+
function padFixed(str, len, isAnsiPrepared = false) {
|
|
96
|
+
if (!isAnsiPrepared) {
|
|
97
|
+
if (str.length > len) return str.substring(0, len - 1) + '…';
|
|
98
|
+
return str.padEnd(len, ' ');
|
|
99
|
+
} else {
|
|
100
|
+
const rawLen = str.replace(/\x1b\[[0-9;]*m/g, '').length;
|
|
101
|
+
if (rawLen < len) return str + ' '.repeat(len - rawLen);
|
|
102
|
+
return str; // Assume caller pre-truncated the inner text
|
|
93
103
|
}
|
|
94
|
-
return '● READY';
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// ── Format helpers ─────────────────────────────────────────────
|
|
98
|
-
function fmtCoins(n) {
|
|
99
|
-
if (!n && n !== 0) return DIM + '—' + c.reset;
|
|
100
|
-
if (n >= 1_000_000) return c.green + '+' + (n / 1_000_000).toFixed(1) + 'M' + c.reset;
|
|
101
|
-
if (n >= 1_000) return c.green + '+' + (n / 1_000).toFixed(1) + 'k' + c.reset;
|
|
102
|
-
if (n > 0) return c.green + '+' + n + c.reset;
|
|
103
|
-
return DIM + '—' + c.reset;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
function fmtLifesavers(w) {
|
|
107
|
-
const ls = w._lifesavers;
|
|
108
|
-
if (ls === undefined || ls === null) return DIM + '—' + c.reset;
|
|
109
|
-
if (ls === 0) return c.red + '0' + c.reset;
|
|
110
|
-
if (ls <= 2) return c.yellow + ls + c.reset;
|
|
111
|
-
return c.green + ls + c.reset;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
function fmtLevel(w) {
|
|
115
|
-
const lv = w._level;
|
|
116
|
-
if (!lv) return DIM + '—' + c.reset;
|
|
117
|
-
return c.cyan + lv + c.reset;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// ── Layout ────────────────────────────────────────────────────
|
|
121
|
-
function layout() {
|
|
122
|
-
_W = Math.min(process.stdout.columns || 100, 120);
|
|
123
|
-
_inner = _W - 2;
|
|
124
|
-
const rows = process.stdout.rows || 40;
|
|
125
|
-
_maxAccounts = Math.min(_workers.length, Math.max(3, rows - 16));
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
// ── Column layout ─────────────────────────────────────────────
|
|
129
|
-
function getCol() {
|
|
130
|
-
const col = { st: 9, name: 18, cmd: 18, bal: 10, ls: 3, lv: 3, earned: 10 };
|
|
131
|
-
// Account row visual parts: '│ ' + '# ' + status + ' ' + name + ' ' + doing + ' ' + bal + ' ' + ls + ' ' + lv + ' ' + earned + ' │'
|
|
132
|
-
const rowPadding = 1 + 2 + 1 + col.st + 1 + col.name + 1 + col.cmd + 1 + col.bal + 1 + col.ls + 1 + col.lv + 1 + col.earned + 2;
|
|
133
|
-
const nameExtra = Math.max(0, _inner - rowPadding);
|
|
134
|
-
col.name += nameExtra;
|
|
135
|
-
return col;
|
|
136
104
|
}
|
|
137
105
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
const
|
|
141
|
-
const
|
|
142
|
-
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
const
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
const
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
return (
|
|
157
|
-
`${B}│\x1b[0m ` +
|
|
158
|
-
`${DIM}${padL(wi + 1, 2)}${c.reset} ` +
|
|
159
|
-
`${stCol}${padR(stTxt, col.st)}${c.reset} ` +
|
|
160
|
-
`${col2}${name}${c.reset} ` +
|
|
161
|
-
`${DIM}${doing}${c.reset} ` +
|
|
162
|
-
`${balPadded} ` +
|
|
163
|
-
`${padL(ls, col.ls)} ` +
|
|
164
|
-
`${padL(lv, col.lv)} ` +
|
|
165
|
-
`${earnedPadded} ` +
|
|
166
|
-
`${B}│\x1b[0m`
|
|
167
|
-
);
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
// ── Draw the FULL box (called once) ─────────────────────────
|
|
171
|
-
function draw() {
|
|
172
|
-
layout();
|
|
173
|
-
const col = getCol();
|
|
174
|
-
const bannerH = BANNER_LINES.length;
|
|
175
|
-
|
|
176
|
-
// Clear entire screen + home
|
|
177
|
-
process.stdout.write('\x1b[2J\x1b[H');
|
|
178
|
-
|
|
179
|
-
let r = 1;
|
|
180
|
-
|
|
181
|
-
// ── Top border ──
|
|
182
|
-
row(r++); clrLine();
|
|
183
|
-
process.stdout.write(`${B}┌${'─'.repeat(_inner)}┐`);
|
|
184
|
-
ln();
|
|
185
|
-
|
|
186
|
-
// ── Banner ──
|
|
187
|
-
for (let i = 0; i < bannerH; i++) {
|
|
188
|
-
row(r++); clrLine();
|
|
189
|
-
const line = i === 1 ? BANNER_LINES[1].replace('PLACEHOLDER', _version) : BANNER_LINES[i];
|
|
190
|
-
if (i < 2) {
|
|
191
|
-
const gradLine = gradientLine(line, 77, 212, 238, 255, 92, 147);
|
|
192
|
-
const ansiLen = stripAnsi(gradLine).length;
|
|
193
|
-
const totalContent = 2 + ansiLen; // left spaces + content
|
|
194
|
-
process.stdout.write(`${B}│\x1b[0m ${gradLine}${' '.repeat(Math.max(0, _inner - totalContent - 1))}${B}│`);
|
|
195
|
-
} else {
|
|
196
|
-
const ansiLen = stripAnsi(line).length;
|
|
197
|
-
const totalContent = 2 + ansiLen;
|
|
198
|
-
process.stdout.write(`${B}│\x1b[0m ${DIM}${line}${c.reset}${' '.repeat(Math.max(0, _inner - totalContent - 1))}${B}│`);
|
|
199
|
-
}
|
|
200
|
-
ln();
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
// ── Status bar ──
|
|
204
|
-
const running = _workers.filter(w => w.channel && !w.paused && !w.dashboardPaused).length;
|
|
205
|
-
const paused = _workers.filter(w => w.paused || w.dashboardPaused).length;
|
|
206
|
-
const errors = _workers.filter(w => !w.channel).length;
|
|
207
|
-
const phaseLabel = _phase === 'grinding' ? '' : ` ${DIM}[${_phase}]${c.reset}`;
|
|
208
|
-
const statusParts = [
|
|
209
|
-
`${c.green}●${c.reset} ${running} online`,
|
|
210
|
-
paused > 0 ? `${c.yellow}~${c.reset} ${paused} paused` : null,
|
|
211
|
-
errors > 0 ? `${c.red}E${c.reset} ${errors} error` : null,
|
|
212
|
-
`${DIM}Ctrl+C${c.reset}`,
|
|
213
|
-
].filter(Boolean);
|
|
214
|
-
const statusStr = statusParts.join(' ') + phaseLabel;
|
|
215
|
-
|
|
216
|
-
row(r++); clrLine();
|
|
217
|
-
process.stdout.write(`${B}│\x1b[0m ${statusStr}${' '.repeat(_inner - 1 - stripAnsi(statusStr))}${B}│`);
|
|
218
|
-
ln();
|
|
219
|
-
|
|
220
|
-
// ── Divider ──
|
|
221
|
-
row(r++); clrLine();
|
|
222
|
-
process.stdout.write(`${B}├${'─'.repeat(_inner)}┤`);
|
|
223
|
-
ln();
|
|
224
|
-
|
|
225
|
-
// ── Header ──
|
|
226
|
-
row(r++); clrLine();
|
|
227
|
-
process.stdout.write(`${B}│\x1b[0m ${c.bold}#${c.reset} ${c.bold}${padR('STATUS', col.st)}${c.reset} ${c.bold}${padR('ACCOUNT', col.name)}${c.reset} ${c.bold}${padR('DOING', col.cmd)}${c.reset} ${c.bold}${padL('BAL', col.bal)}${c.reset} ${c.bold}${padL('LS', col.ls)}${c.reset} ${c.bold}${padL('LV', col.lv)}${c.reset} ${c.bold}${padL('EARNED', col.earned)}${c.reset} ${B}│`);
|
|
228
|
-
ln();
|
|
229
|
-
|
|
230
|
-
// ── HR ──
|
|
231
|
-
row(r++); clrLine();
|
|
232
|
-
process.stdout.write(`${B}│\x1b[0m ${'─'.repeat(_inner)} ${B}│`);
|
|
233
|
-
ln();
|
|
234
|
-
|
|
235
|
-
// ── Account rows ──
|
|
106
|
+
function render() {
|
|
107
|
+
if (!_live) return;
|
|
108
|
+
const cols = process.stdout.columns || 110;
|
|
109
|
+
const rows = process.stdout.rows || 30;
|
|
110
|
+
|
|
111
|
+
const titleStr = figlet.textSync('DANK GRINDER', { font: 'ANSI Shadow' });
|
|
112
|
+
let out = chalk.bold(applyGradient(titleStr));
|
|
113
|
+
|
|
114
|
+
const sep = chalk.hex('#4dd4ee').bold('━'.repeat(cols));
|
|
115
|
+
out += sep + '\n';
|
|
116
|
+
|
|
117
|
+
const wCols = { num: 4, name: 18, bal: 14, ls: 6, lv: 6 };
|
|
118
|
+
let logW = Math.max(15, cols - (wCols.num + wCols.name + wCols.bal + wCols.ls + wCols.lv + 12));
|
|
119
|
+
|
|
120
|
+
out += `${chalk.bold(' #').padEnd(wCols.num)} ${chalk.bold('ACCOUNT').padEnd(wCols.name)} ${chalk.bold('BAL').padEnd(wCols.bal)} ${chalk.bold('LS').padEnd(wCols.ls)} ${chalk.bold('LV').padEnd(wCols.lv)} ${chalk.bold('LOGS')} \n`;
|
|
121
|
+
out += sep + '\n';
|
|
122
|
+
|
|
123
|
+
const maxShown = Math.max(3, rows - 24);
|
|
236
124
|
const sorted = [..._workers].sort((a, b) => {
|
|
237
|
-
if (!a.channel !== !b.channel) return
|
|
125
|
+
if (!a.channel !== !b.channel) return a.channel ? -1 : 1;
|
|
238
126
|
const aA = a.channel && !a.paused && !a.dashboardPaused;
|
|
239
|
-
const
|
|
240
|
-
if (aA !==
|
|
127
|
+
const bB = b.channel && !b.paused && !b.dashboardPaused;
|
|
128
|
+
if (aA !== bB) return aA ? -1 : 1;
|
|
241
129
|
return (b.stats.commands || 0) - (a.stats.commands || 0);
|
|
242
130
|
});
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
const w = shown[si];
|
|
248
|
-
const wi = _workers.indexOf(w);
|
|
249
|
-
_accountRows[wi] = r;
|
|
250
|
-
row(r++); clrLine();
|
|
251
|
-
process.stdout.write(accountRow(w, wi, col));
|
|
252
|
-
ln();
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
// ── Totals divider ──
|
|
256
|
-
_totalsRow = r;
|
|
257
|
-
row(r++); clrLine();
|
|
258
|
-
process.stdout.write(`${B}│\x1b[0m ${'─'.repeat(_inner)} ${B}│`);
|
|
259
|
-
ln();
|
|
260
|
-
|
|
261
|
-
// ── Totals ──
|
|
262
|
-
let totalCoins = 0, totalBal = 0;
|
|
263
|
-
for (const w of _workers) {
|
|
131
|
+
|
|
132
|
+
let totalCoins = 0, totalBal = 0, onlineCount = 0;
|
|
133
|
+
for (let i = 0; i < _workers.length; i++) {
|
|
134
|
+
const w = _workers[i];
|
|
264
135
|
totalCoins += w.stats.coins || 0;
|
|
265
136
|
totalBal += w.stats.balance || 0;
|
|
137
|
+
if (w.channel && !w.paused && !w.dashboardPaused) onlineCount++;
|
|
266
138
|
}
|
|
267
|
-
const memMB = Math.round((process.memoryUsage?.rss?.() ?? process.memoryUsage().rss) / 1048576);
|
|
268
|
-
const balStr = totalBal > 0 ? '⏣' + totalBal.toLocaleString() : '—';
|
|
269
|
-
const balPadded = balStr.padEnd(col.bal);
|
|
270
|
-
const earnedStr = stripAnsi(fmtCoins(totalCoins));
|
|
271
|
-
|
|
272
|
-
row(r++); clrLine();
|
|
273
|
-
process.stdout.write(
|
|
274
|
-
`${B}│\x1b[0m ${c.bold}Σ${c.reset} ${DIM}${padL(_workers.length, 2)} acc${c.reset} ${' '.repeat(col.name + col.cmd)} ${balPadded} ${' '.repeat(col.ls + col.lv)} ${fmtCoins(totalCoins)}${' '.repeat(_inner - 2 - stripAnsi(` ${c.bold}Σ${c.reset} ${DIM}${padL(_workers.length, 2)} acc${c.reset} ${' '.repeat(col.name + col.cmd)} ${balPadded} ${' '.repeat(col.ls + col.lv)} ${fmtCoins(totalCoins)}`))}${B}│`
|
|
275
|
-
);
|
|
276
|
-
ln();
|
|
277
|
-
|
|
278
|
-
// ── Bottom ──
|
|
279
|
-
_bottomRow = r;
|
|
280
|
-
row(r++); clrLine();
|
|
281
|
-
process.stdout.write(`${B}└${'─'.repeat(_inner)}┘`);
|
|
282
|
-
ln();
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
// ── Update ONE account row in-place ─────────────────────────
|
|
286
|
-
function updateAccountRow(accountIdx) {
|
|
287
|
-
if (!_live) return;
|
|
288
|
-
const rowNum = _accountRows[accountIdx];
|
|
289
|
-
if (!rowNum) return; // not visible
|
|
290
|
-
const w = _workers[accountIdx];
|
|
291
|
-
if (!w) return;
|
|
292
|
-
const col = getCol();
|
|
293
139
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
140
|
+
const shown = sorted.slice(0, maxShown);
|
|
141
|
+
for (let i = 0; i < shown.length; i++) {
|
|
142
|
+
const w = shown[i];
|
|
143
|
+
const rawIdx = String(_workers.indexOf(w) + 1);
|
|
144
|
+
|
|
145
|
+
let logText = w.lastStatus || 'idle';
|
|
146
|
+
let activity = '';
|
|
147
|
+
if (w.globalCooldownUntil && Date.now() < w.globalCooldownUntil) {
|
|
148
|
+
const s = Math.ceil((w.globalCooldownUntil - Date.now()) / 1000);
|
|
149
|
+
logText = s > 60 ? `cooldown ${Math.ceil(s/60)}m` : `cd ${s}s`;
|
|
150
|
+
activity = chalk.yellow(getSpinner());
|
|
151
|
+
} else if (w.paused || w.dashboardPaused) {
|
|
152
|
+
logText = 'paused';
|
|
153
|
+
} else if (w.channel) {
|
|
154
|
+
activity = chalk.cyan(getSpinner());
|
|
155
|
+
} else {
|
|
156
|
+
activity = chalk.gray(getLoader());
|
|
157
|
+
}
|
|
298
158
|
|
|
299
|
-
//
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
const
|
|
304
|
-
|
|
159
|
+
// Fixed widths construction
|
|
160
|
+
const numPart = padFixed(` ${rawIdx}`, wCols.num);
|
|
161
|
+
|
|
162
|
+
// Name Part
|
|
163
|
+
const dot = getDot(w);
|
|
164
|
+
let uName = w.username || '?';
|
|
165
|
+
// dot + space + name -> subtract 2 from allowed length
|
|
166
|
+
if (uName.length > wCols.name - 2) uName = uName.substring(0, wCols.name - 3) + '…';
|
|
167
|
+
const cName = `${dot.color(dot.dot)} ${getAccountColor(uName)(chalk.bold(uName))}`;
|
|
168
|
+
const namePart = padFixed(cName, wCols.name, true);
|
|
169
|
+
|
|
170
|
+
// Bal Part
|
|
171
|
+
let bVal = w.stats.balance !== undefined ? (w.stats.balance >= 1000 ? w.stats.balance.toLocaleString() : String(w.stats.balance)) : '—';
|
|
172
|
+
bVal = bVal.length > wCols.bal - 2 ? bVal.substring(0, wCols.bal - 3) + '…' : bVal;
|
|
173
|
+
const cBal = bVal === '—' ? chalk.dim('—') : chalk.bold.green(`⏣${bVal}`);
|
|
174
|
+
const balPart = padFixed(cBal, wCols.bal, true);
|
|
175
|
+
|
|
176
|
+
// LS Part
|
|
177
|
+
let lVal = w._lifesavers !== undefined ? String(w._lifesavers) : '—';
|
|
178
|
+
const lColor = lVal === '—' ? chalk.dim : (w._lifesavers === 0 ? chalk.bold.red : (w._lifesavers <= 2 ? chalk.bold.yellow : chalk.bold.cyan));
|
|
179
|
+
const lsPart = padFixed(lColor(lVal.substring(0, wCols.ls)), wCols.ls, true);
|
|
180
|
+
|
|
181
|
+
// LV Part
|
|
182
|
+
let lvVal = w._level !== undefined ? String(w._level) : '—';
|
|
183
|
+
const lvPart = padFixed(chalk.bold.magenta(lvVal.substring(0, wCols.lv)), wCols.lv, true);
|
|
184
|
+
|
|
185
|
+
// Log Part
|
|
186
|
+
let lStr = logText;
|
|
187
|
+
if (lStr.length > logW - 3) lStr = lStr.substring(0, logW - 4) + '…';
|
|
188
|
+
const cLog = `${activity} ${chalk.dim.bold(lStr)}`;
|
|
189
|
+
const logPart = padFixed(cLog, logW, true);
|
|
190
|
+
|
|
191
|
+
out += `${chalk.dim(numPart)} ${namePart} ${balPart} ${lsPart} ${lvPart} ${logPart}\n`;
|
|
305
192
|
}
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
// ── Event tracking ────────────────────────────────────────────
|
|
310
|
-
let _eventLines = [];
|
|
311
|
-
const MAX_EVENTS = 15;
|
|
312
|
-
|
|
313
|
-
// ── Public API ────────────────────────────────────────────────
|
|
314
|
-
|
|
315
|
-
function init({ workers, isShuttingDown }) {
|
|
316
|
-
_startTime = Date.now();
|
|
317
|
-
_workers = workers;
|
|
318
|
-
_version = '0.0.0';
|
|
319
|
-
_eventLines = [];
|
|
320
|
-
_live = false;
|
|
321
|
-
_phase = 'init';
|
|
322
|
-
_accountRows = [];
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
function drawBanner(version) { _version = version || '0.0.0'; }
|
|
326
|
-
function start() {}
|
|
327
|
-
|
|
328
|
-
function stop() {
|
|
329
|
-
_live = false;
|
|
330
|
-
_phase = 'init';
|
|
331
|
-
process.stdout.write('\x1b[2J\x1b[H' + c.reset + '\n');
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
function setLive(val) { _live = val; }
|
|
335
|
-
function setPhase(phase) { _phase = phase; }
|
|
336
|
-
|
|
337
|
-
function log(accountIdx, msg) {
|
|
338
|
-
const now = new Date();
|
|
339
|
-
const ts = `${padL(now.getHours(), 2, '0')}:${padL(now.getMinutes(), 2, '0')}:${padL(now.getSeconds(), 2, '0')}`;
|
|
340
|
-
|
|
341
|
-
if (accountIdx >= 0) {
|
|
342
|
-
if (!_eventLines[accountIdx]) _eventLines[accountIdx] = [];
|
|
343
|
-
_eventLines[accountIdx].push({ text: msg, ts });
|
|
344
|
-
if (_eventLines[accountIdx].length > MAX_EVENTS) _eventLines[accountIdx].shift();
|
|
345
|
-
if (_live) updateAccountRow(accountIdx);
|
|
193
|
+
|
|
194
|
+
if (_workers.length > maxShown) {
|
|
195
|
+
out += chalk.bold.dim(` ... and ${(_workers.length - maxShown).toLocaleString()} more accounts running ${getSpinner()}\n`);
|
|
346
196
|
}
|
|
347
197
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
198
|
+
out += sep + '\n';
|
|
199
|
+
const totC = totalCoins >= 1000000 ? (totalCoins/1000000).toFixed(1)+'M' : (totalCoins >= 1000 ? (totalCoins/1000).toFixed(1)+'k' : totalCoins);
|
|
200
|
+
out += ` ${chalk.bold('Σ ACCOUNTS:')} ${chalk.bold.magenta(_workers.length)} │ ${chalk.bold('⚡ ONLINE:')} ${chalk.bold.cyan(onlineCount)} │ ${chalk.bold('💰 TOTAL:')} ${chalk.bold.green('⏣' + totalBal.toLocaleString())} │ ${chalk.bold('📈 GAINED:')} ${chalk.bold.yellow('+' + totC)} │ ${chalk.bold('⏱ UP:')} ${chalk.bold.dim(fmtUptime())}\n`;
|
|
201
|
+
out += sep + '\n';
|
|
202
|
+
|
|
203
|
+
out += chalk.bold.cyan(` 📻 LATEST EVENTS \n`) + chalk.dim.bold('─'.repeat(cols)) + '\n';
|
|
204
|
+
if (_events.length === 0) out += chalk.dim(' Quiet here...\n');
|
|
205
|
+
_events.forEach(ev => { out += chalk.dim(` [${ev.ts}] `) + `${ev.icon} ${ev.msg}\n`; });
|
|
206
|
+
out += chalk.dim.bold('─'.repeat(cols)) + '\n';
|
|
356
207
|
|
|
357
|
-
|
|
358
|
-
let _refreshTimer = null;
|
|
359
|
-
function startRefresh() {
|
|
360
|
-
if (_refreshTimer) return;
|
|
361
|
-
_refreshTimer = setInterval(() => {
|
|
362
|
-
if (!_live) return;
|
|
363
|
-
for (let i = 0; i < _workers.length; i++) updateAccountRow(i);
|
|
364
|
-
}, 1500);
|
|
365
|
-
}
|
|
366
|
-
function stopRefresh() {
|
|
367
|
-
if (_refreshTimer) { clearInterval(_refreshTimer); _refreshTimer = null; }
|
|
208
|
+
logUpdate(out);
|
|
368
209
|
}
|
|
369
210
|
|
|
370
|
-
|
|
211
|
+
function start() {}
|
|
212
|
+
function draw() { render(); }
|
|
213
|
+
function setLive(val) { _live = val; if (val) render(); }
|
|
214
|
+
function setPhase(phase) {}
|
|
215
|
+
function startRefresh() { if (!_refreshTimer) _refreshTimer = setInterval(() => { if (_live) render(); }, 80); }
|
|
216
|
+
function stopRefresh() { if (_refreshTimer) { clearInterval(_refreshTimer); _refreshTimer = null; } }
|
|
217
|
+
function stop() { _live = false; stopRefresh(); logUpdate.clear(); }
|
|
218
|
+
|
|
219
|
+
module.exports = { init, drawBanner, start, draw, log, logGlobal, stop, setLive, setPhase, updateAccountRow, startRefresh, stopRefresh, addEvent };
|