dankgrinder 8.40.0 → 8.42.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 +15 -6
- package/lib/rawLogger.js +10 -6
- package/lib/ui.js +52 -66
- package/package.json +1 -1
package/lib/grinder.js
CHANGED
|
@@ -13,12 +13,21 @@ const ui = require('./ui');
|
|
|
13
13
|
// Global shutdown flag
|
|
14
14
|
let shutdownCalled = false;
|
|
15
15
|
|
|
16
|
-
// Catch silent Discord client errors
|
|
16
|
+
// Catch silent Discord client errors — route through log() once available
|
|
17
17
|
process.on('unhandledRejection', (reason) => {
|
|
18
|
-
|
|
18
|
+
const msg = reason?.message || reason;
|
|
19
|
+
if (typeof log === 'function') {
|
|
20
|
+
log('error', msg);
|
|
21
|
+
} else {
|
|
22
|
+
process.stderr.write(`[UNHANDLED REJECTION] ${msg}\n`);
|
|
23
|
+
}
|
|
19
24
|
});
|
|
20
25
|
process.on('uncaughtException', (err) => {
|
|
21
|
-
|
|
26
|
+
if (typeof log === 'function') {
|
|
27
|
+
log('error', err.message);
|
|
28
|
+
} else {
|
|
29
|
+
process.stderr.write(`[UNCAUGHT] ${err.message}\n`);
|
|
30
|
+
}
|
|
22
31
|
});
|
|
23
32
|
|
|
24
33
|
// ── Memory-Optimized Client Factory ──────────────────────────
|
|
@@ -2768,7 +2777,7 @@ async function start(apiKey, apiUrl, opts = {}) {
|
|
|
2768
2777
|
|
|
2769
2778
|
// Init rawLogger Redis (uses same URL — logs all raw gateway data)
|
|
2770
2779
|
if (REDIS_URL) {
|
|
2771
|
-
rawLogger.init(REDIS_URL).catch(() => {});
|
|
2780
|
+
rawLogger.init(REDIS_URL, { silent: true }).catch(() => {});
|
|
2772
2781
|
// Listen for DM events across all accounts — update worker state + dashboard LIVE
|
|
2773
2782
|
rawLogger.onDmEvent((event, raw) => {
|
|
2774
2783
|
const channelId = raw.channel_id;
|
|
@@ -2863,7 +2872,6 @@ async function start(apiKey, apiUrl, opts = {}) {
|
|
|
2863
2872
|
if (timedOutWorkers.length > 0) {
|
|
2864
2873
|
ui.log(-1, `${timedOutWorkers.length} timed out (retrying in background)`);
|
|
2865
2874
|
}
|
|
2866
|
-
ui.draw();
|
|
2867
2875
|
|
|
2868
2876
|
const activeWorkers = workers.filter(w => !w._tokenInvalid);
|
|
2869
2877
|
|
|
@@ -2919,10 +2927,11 @@ async function start(apiKey, apiUrl, opts = {}) {
|
|
|
2919
2927
|
|
|
2920
2928
|
// ── Phase 3: Start grind loops ───────────────────────────────────
|
|
2921
2929
|
ui.log(-1, `Starting ${activeWorkers.length} grind loops...`);
|
|
2930
|
+
ui.setLive(true);
|
|
2931
|
+
ui.draw();
|
|
2922
2932
|
for (const w of activeWorkers) {
|
|
2923
2933
|
if (!shutdownCalled) w.grindLoop();
|
|
2924
2934
|
}
|
|
2925
|
-
ui.draw();
|
|
2926
2935
|
|
|
2927
2936
|
// Cluster heartbeat — lets other nodes see this node is alive
|
|
2928
2937
|
if (CLUSTER_ENABLED) {
|
package/lib/rawLogger.js
CHANGED
|
@@ -28,11 +28,15 @@ const memStore = new Map();
|
|
|
28
28
|
const channelLast = new Map();
|
|
29
29
|
const memRing = [];
|
|
30
30
|
let memIdx = 0;
|
|
31
|
+
let _silent = false;
|
|
32
|
+
function _log(...args) { if (!_silent) console.log(...args); }
|
|
33
|
+
function _err(...args) { if (!_silent) console.error(...args); }
|
|
31
34
|
|
|
32
35
|
// ── Redis init ──
|
|
33
|
-
async function init(redisUrl) {
|
|
36
|
+
async function init(redisUrl, opts = {}) {
|
|
37
|
+
if (opts.silent) _silent = true;
|
|
34
38
|
if (!redisUrl) {
|
|
35
|
-
|
|
39
|
+
_log('[rawLogger] No Redis URL — raw logging disabled');
|
|
36
40
|
return;
|
|
37
41
|
}
|
|
38
42
|
try {
|
|
@@ -44,7 +48,7 @@ async function init(redisUrl) {
|
|
|
44
48
|
});
|
|
45
49
|
// Skip if already connecting or connected
|
|
46
50
|
if (redis.status === 'connecting' || redis.status === 'connect' || redis.status === 'ready') {
|
|
47
|
-
|
|
51
|
+
_log('[rawLogger] Redis already connecting — skipping');
|
|
48
52
|
redisReady = false;
|
|
49
53
|
return;
|
|
50
54
|
}
|
|
@@ -58,12 +62,12 @@ async function init(redisUrl) {
|
|
|
58
62
|
redis.connect().catch(reject);
|
|
59
63
|
});
|
|
60
64
|
redisReady = true;
|
|
61
|
-
|
|
65
|
+
_log('[rawLogger] Redis connected');
|
|
62
66
|
redis.on('error', (e) => {
|
|
63
67
|
// Suppress common transient network errors from spamming stderr
|
|
64
68
|
const msg = e?.message || '';
|
|
65
69
|
if (msg.includes('ETIMEDOUT') || msg.includes('ECONNRESET') || msg.includes('ENOTFOUND') || msg.includes('read') || msg.includes('connect')) return;
|
|
66
|
-
|
|
70
|
+
_err(`[rawLogger] Redis error: ${msg}`);
|
|
67
71
|
redisReady = false;
|
|
68
72
|
});
|
|
69
73
|
redis.on('close', () => {
|
|
@@ -79,7 +83,7 @@ async function init(redisUrl) {
|
|
|
79
83
|
// Suppress "already connecting" errors — happens when Redis reconnects mid-init
|
|
80
84
|
const msg = e?.message || '';
|
|
81
85
|
if (!msg.includes('already connecting') && !msg.includes('already connected')) {
|
|
82
|
-
|
|
86
|
+
_err(`[rawLogger] Redis connect failed: ${msg}`);
|
|
83
87
|
}
|
|
84
88
|
redis = null;
|
|
85
89
|
redisReady = false;
|
package/lib/ui.js
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* CLI Live Dashboard — box
|
|
3
|
-
*
|
|
2
|
+
* CLI Live Dashboard — fixed box at top, events stream below.
|
|
3
|
+
* Box drawn once. Events appended line-by-line. No flicker.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
let _startTime = Date.now();
|
|
7
7
|
let _workers = [];
|
|
8
8
|
let _isShuttingDown = () => false;
|
|
9
9
|
let _version = '0.0.0';
|
|
10
|
+
let _live = false;
|
|
11
|
+
let _boxHeight = 0; // rows consumed by the box
|
|
10
12
|
|
|
11
13
|
// ── Spinner frames ────────────────────────────────────────────
|
|
12
14
|
const SPINNER = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
@@ -60,11 +62,6 @@ const DIM = c.dim;
|
|
|
60
62
|
function trunc(s, n) { s = String(s || ''); return s.length <= n ? s : s.slice(0, n - 1) + '…'; }
|
|
61
63
|
function padR(s, n) { return trunc(s, n).padEnd(n); }
|
|
62
64
|
function padL(s, n, char) { return String(s).padStart(n, char || ' '); }
|
|
63
|
-
function padC(s, n) {
|
|
64
|
-
s = String(s || '');
|
|
65
|
-
const pad = Math.max(0, n - s.length);
|
|
66
|
-
return ' '.repeat(Math.floor(pad / 2)) + s + ' '.repeat(Math.ceil(pad / 2));
|
|
67
|
-
}
|
|
68
65
|
|
|
69
66
|
function fmtUptime() {
|
|
70
67
|
const s = Math.floor((Date.now() - _startTime) / 1000);
|
|
@@ -104,25 +101,25 @@ function statusText(w) {
|
|
|
104
101
|
function layout() {
|
|
105
102
|
const W = Math.min(process.stdout.columns || 120, 120);
|
|
106
103
|
const rows = process.stdout.rows || 40;
|
|
107
|
-
const bannerH = BANNER_LINES.length + 1; // +1
|
|
104
|
+
const bannerH = BANNER_LINES.length + 1; // +1 blank
|
|
108
105
|
const statusH = 1;
|
|
109
|
-
const headerH =
|
|
110
|
-
const
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
const eventH = Math.max(3, rows - bannerH - statusH - headerH - totalsH - footerH - maxAccounts);
|
|
114
|
-
return { W, bannerH, statusH, headerH, totalsH, footerH, maxAccounts, eventH };
|
|
106
|
+
const headerH = 2; // headers + hr
|
|
107
|
+
const footerH = 2; // totals + hr
|
|
108
|
+
const maxAccounts = Math.min(_workers.length, Math.max(3, rows - bannerH - statusH - headerH - footerH - 5));
|
|
109
|
+
return { W, bannerH, statusH, headerH, footerH, maxAccounts };
|
|
115
110
|
}
|
|
116
111
|
|
|
117
|
-
// ── Draw the
|
|
112
|
+
// ── Draw the fixed box ────────────────────────────────────────
|
|
118
113
|
function draw() {
|
|
119
|
-
const { W, bannerH, statusH, headerH,
|
|
120
|
-
const T = '─'.repeat(W - 2);
|
|
114
|
+
const { W, bannerH, statusH, headerH, footerH, maxAccounts } = layout();
|
|
115
|
+
const T = '─'.repeat(W - 2);
|
|
116
|
+
|
|
117
|
+
// ── Save cursor, clear screen, go to top ──
|
|
118
|
+
process.stdout.write('\x1b[s'); // save cursor
|
|
119
|
+
process.stdout.write('\x1b[2J\x1b[H'); // clear screen + home
|
|
120
|
+
process.stdout.write('\x1b[1G'); // column 1
|
|
121
121
|
|
|
122
122
|
// ── Top of box ──
|
|
123
|
-
// Move cursor to top-left of box area and overwrite in place (no full clear)
|
|
124
|
-
process.stdout.write(`\x1b[${1};1H`);
|
|
125
|
-
process.stdout.write(`\x1b[0J`); // clear from cursor to end of screen
|
|
126
123
|
process.stdout.write(`\x1b[38;2;77;212;238m┌─${T}─┐\x1b[0m\n`);
|
|
127
124
|
|
|
128
125
|
// ── Banner inside box ──
|
|
@@ -144,18 +141,13 @@ function draw() {
|
|
|
144
141
|
errors > 0 ? `${c.red}E${c.reset}${errors}` : null,
|
|
145
142
|
`${DIM}Ctrl+C${c.reset}`,
|
|
146
143
|
].filter(Boolean).join(' ');
|
|
147
|
-
const statusPad = W - 4 - statusLine.length;
|
|
144
|
+
const statusPad = W - 4 - stripAnsi(statusLine).length;
|
|
148
145
|
process.stdout.write(`\x1b[38;2;77;212;238m│\x1b[0m ${statusLine}${' '.repeat(Math.max(0, statusPad))} \x1b[38;2;77;212;238m│\x1b[0m\n`);
|
|
149
146
|
process.stdout.write(`\x1b[38;2;77;212;238m├─${T}─┤\x1b[0m\n`);
|
|
150
147
|
|
|
151
148
|
// ── Table header ──
|
|
152
149
|
const col = {
|
|
153
|
-
st:
|
|
154
|
-
name: 18, // Account name
|
|
155
|
-
last: 20, // Last command
|
|
156
|
-
cmds: 6, // Commands
|
|
157
|
-
ok: 4, // OK%
|
|
158
|
-
earned: 10, // Earned
|
|
150
|
+
st: 9, name: 20, last: 22, cmds: 6, ok: 5, earned: 10,
|
|
159
151
|
};
|
|
160
152
|
const nameExtra = Math.max(0, W - 4 - col.st - col.name - col.last - col.cmds - col.ok - col.earned - 14);
|
|
161
153
|
col.name += nameExtra;
|
|
@@ -164,7 +156,7 @@ function draw() {
|
|
|
164
156
|
process.stdout.write(`${c.bold}#${c.reset} `);
|
|
165
157
|
process.stdout.write(`${c.bold}${padR('STATUS', col.st)}${c.reset} `);
|
|
166
158
|
process.stdout.write(`${c.bold}${padR('ACCOUNT', col.name)}${c.reset} `);
|
|
167
|
-
process.stdout.write(`${c.bold}${padR('LAST
|
|
159
|
+
process.stdout.write(`${c.bold}${padR('LAST CMD', col.last)}${c.reset} `);
|
|
168
160
|
process.stdout.write(`${c.bold}${padL('CMDS', col.cmds)}${c.reset} `);
|
|
169
161
|
process.stdout.write(`${c.bold}${padL('OK%', col.ok)}${c.reset} `);
|
|
170
162
|
process.stdout.write(`${c.bold}${padL('EARNED', col.earned)}${c.reset} `);
|
|
@@ -186,14 +178,14 @@ function draw() {
|
|
|
186
178
|
const wi = _workers.indexOf(w);
|
|
187
179
|
const col2 = wc(wi);
|
|
188
180
|
const stCol = statusColor(w);
|
|
189
|
-
const
|
|
181
|
+
const stTxt = statusText(w);
|
|
190
182
|
const earned = w.stats.coins > 0 ? `${c.green}+${w.stats.coins.toLocaleString()}${c.reset}` : DIM + '—' + c.reset;
|
|
191
183
|
const cmds = w.stats.commands || 0;
|
|
192
184
|
const rate = w.stats.commands > 0 ? Math.round((w.stats.successes / w.stats.commands) * 100) : 0;
|
|
193
185
|
|
|
194
186
|
process.stdout.write(`\x1b[38;2;77;212;238m│\x1b[0m `);
|
|
195
187
|
process.stdout.write(`${DIM}${padL(wi + 1, 2)}${c.reset} `);
|
|
196
|
-
process.stdout.write(`${stCol}${padR(
|
|
188
|
+
process.stdout.write(`${stCol}${padR(stTxt, col.st)}${c.reset} `);
|
|
197
189
|
process.stdout.write(`${col2}${padR(w.username || '?', col.name)}${c.reset} `);
|
|
198
190
|
process.stdout.write(`${DIM}${padR(w.lastStatus || 'idle', col.last)}${c.reset} `);
|
|
199
191
|
process.stdout.write(`${padL(cmds, col.cmds)} `);
|
|
@@ -203,7 +195,7 @@ function draw() {
|
|
|
203
195
|
}
|
|
204
196
|
|
|
205
197
|
if (sorted.length > maxAccounts) {
|
|
206
|
-
process.stdout.write(`\x1b[38;2;77;212;238m│\x1b[0m ${DIM}+${sorted.length - maxAccounts} more
|
|
198
|
+
process.stdout.write(`\x1b[38;2;77;212;238m│\x1b[0m ${DIM}+${sorted.length - maxAccounts} more${' '.repeat(Math.max(0, W - 20 - String(sorted.length - maxAccounts).length))}${c.reset} \x1b[38;2;77;212;238m│\x1b[0m\n`);
|
|
207
199
|
}
|
|
208
200
|
|
|
209
201
|
// ── Totals ──
|
|
@@ -221,50 +213,31 @@ function draw() {
|
|
|
221
213
|
process.stdout.write(`${c.bold}Σ${c.reset} `);
|
|
222
214
|
process.stdout.write(`${DIM}${padL(_workers.length, 2)} acc${c.reset} `);
|
|
223
215
|
process.stdout.write(`${' '.repeat(col.name)} `);
|
|
224
|
-
process.stdout.write(`${
|
|
216
|
+
process.stdout.write(`${' '.repeat(col.last)} `);
|
|
225
217
|
process.stdout.write(`${padL(totalCmds, col.cmds)} `);
|
|
226
218
|
process.stdout.write(`${padL(rate, col.ok)}% `);
|
|
227
219
|
process.stdout.write(`${totalCoins > 0 ? c.green + padL('+' + totalCoins.toLocaleString(), col.earned) + c.reset : DIM + padL('—', col.earned) + c.reset} `);
|
|
228
220
|
process.stdout.write(`${DIM}${fmtUptime()} | ${memMB}MB${c.reset} `.padEnd(W - 4));
|
|
229
221
|
process.stdout.write(`\x1b[38;2;77;212;238m│\x1b[0m\n`);
|
|
230
222
|
|
|
231
|
-
// ──
|
|
232
|
-
process.stdout.write(`\x1b[38;2;77;212;238m
|
|
233
|
-
process.stdout.write(`\x1b[38;2;77;212;238m│\x1b[0m ${c.bold}EVENTS${c.reset}${' '.repeat(Math.max(0, W - 10))}\x1b[38;2;77;212;238m│\x1b[0m\n`);
|
|
234
|
-
process.stdout.write(`\x1b[38;2;77;212;238m│\x1b[0m ${'─'.repeat(W - 4)} \x1b[38;2;77;212;238m│\x1b[0m\n`);
|
|
223
|
+
// ── Bottom of box ──
|
|
224
|
+
process.stdout.write(`\x1b[38;2;77;212;238m└─${T}─┘\x1b[0m\n`);
|
|
235
225
|
|
|
236
|
-
//
|
|
237
|
-
|
|
238
|
-
for (let i = 0; i < _workers.length; i++) {
|
|
239
|
-
if (_eventLines[i] && _eventLines[i].length > 0) {
|
|
240
|
-
const latest = _eventLines[i][_eventLines[i].length - 1];
|
|
241
|
-
const col2 = wc(i);
|
|
242
|
-
const name = trunc(_workers[i]?.username || '?', 14);
|
|
243
|
-
evLines.push({ i, text: latest.text, ts: latest.ts, col: col2, name });
|
|
244
|
-
}
|
|
245
|
-
}
|
|
226
|
+
// Record how many rows the box consumed
|
|
227
|
+
_boxHeight = bannerH + statusH + 1 + headerH + shown.length + (sorted.length > maxAccounts ? 1 : 0) + footerH + 1;
|
|
246
228
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
const ts = `${padL(now.getHours(), 2, '0')}:${padL(now.getMinutes(), 2, '0')}:${padL(now.getSeconds(), 2, '0')}`;
|
|
251
|
-
process.stdout.write(`\x1b[38;2;77;212;238m│\x1b[0m `);
|
|
252
|
-
process.stdout.write(`${ev.col}${padR(ev.name, 14)}${c.reset} `);
|
|
253
|
-
process.stdout.write(`${DIM}[${ts}]${c.reset} ${ev.text}${' '.repeat(Math.max(0, W - 20 - ev.name.length - ev.text.length))}`);
|
|
254
|
-
process.stdout.write(`\x1b[38;2;77;212;238m│\x1b[0m\n`);
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
for (let i = 0; i < Math.max(0, eventH - evLines.length); i++) {
|
|
258
|
-
process.stdout.write(`\x1b[38;2;77;212;238m│\x1b[0m ${' '.repeat(W - 4)} \x1b[38;2;77;212;238m│\x1b[0m\n`);
|
|
259
|
-
}
|
|
229
|
+
// Restore cursor to just below the box
|
|
230
|
+
process.stdout.write(`\x1b[u`); // restore cursor
|
|
231
|
+
}
|
|
260
232
|
|
|
261
|
-
|
|
262
|
-
|
|
233
|
+
// ── Strip ANSI for length calc ───────────────────────────────
|
|
234
|
+
function stripAnsi(s) {
|
|
235
|
+
return String(s).replace(/\x1b\[[0-9;]*m/g, '');
|
|
263
236
|
}
|
|
264
237
|
|
|
265
238
|
// ── Event tracking ────────────────────────────────────────────
|
|
266
239
|
let _eventLines = []; // [accountIdx] = [{text, ts}]
|
|
267
|
-
const MAX_EVENTS =
|
|
240
|
+
const MAX_EVENTS = 2;
|
|
268
241
|
|
|
269
242
|
// ── Public API ────────────────────────────────────────────────
|
|
270
243
|
|
|
@@ -274,28 +247,41 @@ function init({ workers, isShuttingDown }) {
|
|
|
274
247
|
_isShuttingDown = isShuttingDown || (() => false);
|
|
275
248
|
_version = '0.0.0';
|
|
276
249
|
_eventLines = [];
|
|
250
|
+
_live = false;
|
|
277
251
|
}
|
|
278
252
|
|
|
279
253
|
function drawBanner(version) { _version = version; }
|
|
280
254
|
function start() {}
|
|
281
|
-
function stop() { process.stdout.write(c.reset + '\n'); }
|
|
255
|
+
function stop() { _live = false; process.stdout.write(c.reset + '\n'); }
|
|
256
|
+
|
|
257
|
+
// Enable live mode — from this point, log() appends below the box
|
|
258
|
+
function setLive(val) { _live = val; }
|
|
282
259
|
|
|
283
260
|
function log(accountIdx, msg) {
|
|
284
261
|
const now = new Date();
|
|
285
262
|
const ts = `${padL(now.getHours(), 2, '0')}:${padL(now.getMinutes(), 2, '0')}:${padL(now.getSeconds(), 2, '0')}`;
|
|
286
263
|
|
|
264
|
+
// Track event per account
|
|
287
265
|
if (accountIdx >= 0) {
|
|
288
266
|
if (!_eventLines[accountIdx]) _eventLines[accountIdx] = [];
|
|
289
267
|
_eventLines[accountIdx].push({ text: msg, ts });
|
|
290
268
|
if (_eventLines[accountIdx].length > MAX_EVENTS) _eventLines[accountIdx].shift();
|
|
291
269
|
}
|
|
292
270
|
|
|
293
|
-
|
|
271
|
+
if (!_live) return; // during login: just track, don't output yet
|
|
272
|
+
|
|
273
|
+
// ── Append event LINE BY LINE below the box ──
|
|
274
|
+
const col2 = accountIdx >= 0 ? wc(accountIdx) : '';
|
|
275
|
+
const name = accountIdx >= 0 ? trunc(_workers[accountIdx]?.username || '?', 12) : 'GLOBAL';
|
|
276
|
+
|
|
277
|
+
process.stdout.write(`\x1b[38;2;77;212;238m│\x1b[0m `);
|
|
278
|
+
process.stdout.write(`${col2}${padR(name, 12)}${c.reset} `);
|
|
279
|
+
process.stdout.write(`${DIM}[${ts}]${c.reset} ${msg}`);
|
|
280
|
+
process.stdout.write(`\n`);
|
|
294
281
|
}
|
|
295
282
|
|
|
296
283
|
function logGlobal(msg) {
|
|
297
|
-
// Global events go to account 0
|
|
298
284
|
log(-1, msg);
|
|
299
285
|
}
|
|
300
286
|
|
|
301
|
-
module.exports = { init, drawBanner, start, draw, log, logGlobal, workerColor: wc, stop };
|
|
287
|
+
module.exports = { init, drawBanner, start, draw, log, logGlobal, workerColor: wc, stop, setLive };
|