dankgrinder 6.21.0 → 6.27.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/commands/farm.js +499 -502
- package/lib/grinder.js +166 -245
- package/package.json +1 -1
package/lib/grinder.js
CHANGED
|
@@ -458,7 +458,7 @@ function renderDashboard() {
|
|
|
458
458
|
lines.push(bRow(` ${c.bold}${titleGrad}${c.reset} ${D}v${PKG_VERSION}${c.reset} ${G}${spin}${c.reset}`));
|
|
459
459
|
}
|
|
460
460
|
|
|
461
|
-
lines.push(bRow(` ${D}v${PKG_VERSION}${c.reset} ${G}${spin}${c.reset}`));
|
|
461
|
+
lines.push(bRow(` ${D}v${PKG_VERSION}${c.reset} ${G}${spin}${c.reset} ${Y}◷${c.reset} ${D}UP${c.reset} ${c.bold}${Y}${formatUptime()}${c.reset}`));
|
|
462
462
|
|
|
463
463
|
// Subtitle info
|
|
464
464
|
const activeCount = workers.filter(w => w.running && !w.paused && !w.dashboardPaused).length;
|
|
@@ -487,52 +487,54 @@ function renderDashboard() {
|
|
|
487
487
|
lines.push(bEmpty);
|
|
488
488
|
|
|
489
489
|
// ═══════════════════════════════════════════════════════════════
|
|
490
|
-
// STATS PANEL
|
|
490
|
+
// STATS PANEL — left: all metrics | right: big trend + rate
|
|
491
491
|
// ═══════════════════════════════════════════════════════════════
|
|
492
492
|
lines.push(bSep);
|
|
493
493
|
lines.push(bEmpty);
|
|
494
494
|
|
|
495
|
-
// Earnings sparkline data
|
|
496
495
|
const now = Date.now();
|
|
497
496
|
if (now - lastEarningsSample > 8000) { earningsHistory.push(totalCoins); lastEarningsSample = now; }
|
|
498
497
|
const elapsedHrs = (Date.now() - startTime) / 3_600_000;
|
|
499
498
|
const perHr = elapsedHrs > 0.01 ? Math.round(totalCoins / elapsedHrs) : 0;
|
|
500
|
-
const peakFlag = isNewHigh ? ` ${R}${c.bold}* NEW HIGH *${c.reset}` : '';
|
|
501
499
|
|
|
502
|
-
//
|
|
500
|
+
// ── Compute metric values ─────────────────────────────────────
|
|
503
501
|
const cpmVal = globalCmdRate.getRate().toFixed(1);
|
|
504
502
|
const srColor = successRate >= 95 ? G : successRate >= 80 ? Y : R;
|
|
505
|
-
const
|
|
506
|
-
const srBar = progressBar(successRate, 100, srBarW, successRate >= 95 ? [52, 211, 153] : successRate >= 80 ? [251, 191, 36] : [239, 68, 68]);
|
|
503
|
+
const srBar = progressBar(successRate, 100, 10, successRate >= 95 ? [52, 211, 153] : successRate >= 80 ? [251, 191, 36] : [239, 68, 68]);
|
|
507
504
|
const memMB = Math.round((process.memoryUsage?.rss?.() ?? process.memoryUsage().rss) / 1048576);
|
|
508
505
|
const memCol = memMB > 900 ? [239, 68, 68] : memMB > 600 ? [251, 191, 36] : [52, 211, 153];
|
|
509
|
-
const
|
|
510
|
-
const
|
|
506
|
+
const memBar = progressBar(memMB, 1024, 10, memCol, [40, 40, 55]);
|
|
507
|
+
const perHrColor = perHr >= 0 ? G : R;
|
|
508
|
+
const perHrSign = perHr >= 0 ? '+' : '';
|
|
509
|
+
const newHighFlag = isNewHigh ? ` ${R}${c.bold}★ NEW HIGH ★${c.reset}` : '';
|
|
511
510
|
|
|
512
|
-
//
|
|
513
|
-
const sparkW = Math.floor(iw * 0.
|
|
511
|
+
// ── Big trend sparkline (takes ~40% of inner width) ─────────
|
|
512
|
+
const sparkW = Math.max(28, Math.floor(iw * 0.4));
|
|
514
513
|
const spark = drawSparkline(earningsHistory.toArray(), sparkW);
|
|
514
|
+
const sparkLabel = `${A}~${c.reset} ${D}TREND${c.reset}`;
|
|
515
515
|
|
|
516
|
-
//
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
const combined1 = `${leftRow1}${' '.repeat(Math.max(2, leftHalf - leftRow1.replace(RE, '').length))}${rightRow1}`;
|
|
523
|
-
lines.push(bRow(` ${combined1}`));
|
|
516
|
+
// ── Left metric rows (each left-aligned, ANSI-aware padding) ─
|
|
517
|
+
// Helper: ANSI-strip-aware pad — strip ANSI then pad the visible content
|
|
518
|
+
const padRow = (content, totalVis) => {
|
|
519
|
+
const vis = content.replace(RE, '').length;
|
|
520
|
+
return content + ' '.repeat(Math.max(0, totalVis - vis));
|
|
521
|
+
};
|
|
524
522
|
|
|
525
|
-
//
|
|
526
|
-
const
|
|
527
|
-
|
|
523
|
+
const leftTotal = iw - sparkW - 10; // reserve space for spark + gap
|
|
524
|
+
const lRow1 = `${Au}⟐${c.reset} ${D}BALANCE${c.reset} ${c.bold}${Au}⏣${c.reset} ${formatCoins(totalBalance)}`;
|
|
525
|
+
const lRow2 = `${G}▲${c.reset} ${D}EARNED${c.reset} ${c.bold}${G}${perHrSign}⏣${c.reset} ${formatCoins(totalCoins)}${newHighFlag}`;
|
|
526
|
+
const lRow3 = `${O}★${c.reset} ${D}PEAK${c.reset} ${c.bold}${O}⏣${c.reset} ${formatCoins(sessionPeakCoins)}`;
|
|
527
|
+
const lRow4 = `${B}◆${c.reset} ${D}CMDS${c.reset} ${c.bold}${B}${totalCommands}${c.reset} ${srColor}${successRate}%${c.reset} ${srBar} ${Cy}${cpmVal}${c.reset}${D}/min${c.reset}`;
|
|
528
|
+
const lRow5 = `${D}≡${c.reset} ${D}MEM${c.reset} ${rgb(memCol[0], memCol[1], memCol[2])}${c.bold}${memMB}MB${c.reset} ${memBar}`;
|
|
528
529
|
|
|
529
|
-
//
|
|
530
|
-
const
|
|
531
|
-
lines.push(bRow(` ${cmdsRow}`));
|
|
530
|
+
// Build right column label
|
|
531
|
+
const rRate = `${perHrColor}${perHrSign}⏣${c.reset} ${formatCoins(Math.abs(perHr))}/h`;
|
|
532
532
|
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
lines.push(bRow(` ${
|
|
533
|
+
lines.push(bRow(` ${padRow(lRow1, leftTotal)} ${sparkLabel} ${spark}`));
|
|
534
|
+
lines.push(bRow(` ${padRow(lRow2, leftTotal)} ${D}────────${c.reset} ${c.dim}earned${c.reset}`));
|
|
535
|
+
lines.push(bRow(` ${padRow(lRow3, leftTotal)} ${D} ${c.reset} ${rRate}`));
|
|
536
|
+
lines.push(bRow(` ${padRow(lRow4, leftTotal)} ${D} ${c.reset}`));
|
|
537
|
+
lines.push(bRow(` ${padRow(lRow5, leftTotal)} ${D} ${c.reset}`));
|
|
536
538
|
|
|
537
539
|
lines.push(bEmpty);
|
|
538
540
|
|
|
@@ -642,9 +644,9 @@ function renderDashboard() {
|
|
|
642
644
|
else if (ls != null) lsStr = `${G}♥${ls}${c.reset}`;
|
|
643
645
|
else lsStr = `${D}♥?${c.reset}`;
|
|
644
646
|
|
|
645
|
-
// ── Level indicator ──
|
|
647
|
+
// ── Level indicator (fixed width so value changes don't jitter) ──
|
|
646
648
|
const lvl = wk._level || 0;
|
|
647
|
-
const lvlStr = lvl > 0 ? `${Cy}L${lvl}${c.reset}` : `${D}L
|
|
649
|
+
const lvlStr = lvl > 0 ? `${Cy}L${String(lvl).padStart(3)}${c.reset}` : `${D}L???${c.reset}`;
|
|
648
650
|
|
|
649
651
|
// ── Earned (fixed visible width) ──
|
|
650
652
|
const earnNum = wk.stats.coins || 0;
|
|
@@ -1413,6 +1415,7 @@ class AccountWorker {
|
|
|
1413
1415
|
startupProgress = null,
|
|
1414
1416
|
requireComplete = false,
|
|
1415
1417
|
maxAttempts = 1,
|
|
1418
|
+
silent = false,
|
|
1416
1419
|
} = options;
|
|
1417
1420
|
if (this._invRunning) return { ok: false, skipped: 'busy' };
|
|
1418
1421
|
if (!force && this._lastInvCheck && Date.now() - this._lastInvCheck < 300_000) return { ok: false, skipped: 'recent' };
|
|
@@ -1426,7 +1429,7 @@ class AccountWorker {
|
|
|
1426
1429
|
const baseLabel = startupProgress ? `[inv] ${startupProgress.current}/${startupProgress.total}` : '[inv]';
|
|
1427
1430
|
const attemptLabel = tries > 1 ? ` [try ${attempt}/${tries}]` : '';
|
|
1428
1431
|
const progressLine = `${baseLabel}${c.bold} ${this.username}${c.reset}${attemptLabel}`;
|
|
1429
|
-
process.stdout.write(`\x1b[2K\r${progressLine}`);
|
|
1432
|
+
if (!silent) process.stdout.write(`\x1b[2K\r${progressLine}`);
|
|
1430
1433
|
|
|
1431
1434
|
try {
|
|
1432
1435
|
const result = await commands.runInventory({
|
|
@@ -1436,9 +1439,10 @@ class AccountWorker {
|
|
|
1436
1439
|
accountId: this.account.id,
|
|
1437
1440
|
redis,
|
|
1438
1441
|
onPageProgress: ({ page, total }) => {
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
+
if (!silent) {
|
|
1443
|
+
const erase = '\x1b[2K\r';
|
|
1444
|
+
process.stdout.write(`${erase}${baseLabel} ${c.bold}${this.username}${c.reset} · page ${page}/${total}${attemptLabel}`);
|
|
1445
|
+
}
|
|
1442
1446
|
},
|
|
1443
1447
|
});
|
|
1444
1448
|
|
|
@@ -1447,8 +1451,10 @@ class AccountWorker {
|
|
|
1447
1451
|
}
|
|
1448
1452
|
|
|
1449
1453
|
// Final result on same line
|
|
1450
|
-
|
|
1451
|
-
|
|
1454
|
+
if (!silent) {
|
|
1455
|
+
const resultLine = `${baseLabel} ${c.bold}${this.username}${c.reset}: ${c.green}${result.items?.length || 0} items${c.reset}, ⏣ ${c.green}${(result.totalValue || 0).toLocaleString()}${c.reset} net${attemptLabel}`;
|
|
1456
|
+
process.stdout.write(`\x1b[2K\r${resultLine}\n`);
|
|
1457
|
+
}
|
|
1452
1458
|
|
|
1453
1459
|
// Extract lifesaver count from inventory and cache in Redis
|
|
1454
1460
|
if (result.items && redis) {
|
|
@@ -1492,7 +1498,7 @@ class AccountWorker {
|
|
|
1492
1498
|
if (attempt < tries) {
|
|
1493
1499
|
const baseLabel = startupProgress ? `[inv] ${startupProgress.current}/${startupProgress.total}` : '[inv]';
|
|
1494
1500
|
const retryLine = `${baseLabel} ${c.bold}${this.username}${c.reset}: ${c.yellow}attempt ${attempt}/${tries} failed${c.reset} — retrying...`;
|
|
1495
|
-
process.stdout.write(`\x1b[2K\r${retryLine}\n`);
|
|
1501
|
+
if (!silent) process.stdout.write(`\x1b[2K\r${retryLine}\n`);
|
|
1496
1502
|
await new Promise((r) => setTimeout(r, 1500 + Math.floor(Math.random() * 1500)));
|
|
1497
1503
|
continue;
|
|
1498
1504
|
}
|
|
@@ -1503,7 +1509,7 @@ class AccountWorker {
|
|
|
1503
1509
|
} catch (e) {
|
|
1504
1510
|
const baseLabel = startupProgress ? `[inv] ${startupProgress.current}/${startupProgress.total}` : '[inv]';
|
|
1505
1511
|
const failLine = `${baseLabel} ${c.bold}${this.username}${c.reset}: ${c.red}failed${c.reset} — ${e.message}`;
|
|
1506
|
-
process.stdout.write(`\x1b[2K\r${failLine}\n`);
|
|
1512
|
+
if (!silent) process.stdout.write(`\x1b[2K\r${failLine}\n`);
|
|
1507
1513
|
return { ok: false, error: e.message };
|
|
1508
1514
|
} finally {
|
|
1509
1515
|
this._invRunning = false;
|
|
@@ -3014,102 +3020,90 @@ async function start(apiKey, apiUrl) {
|
|
|
3014
3020
|
console.log(` ${checks.join(' ')}`);
|
|
3015
3021
|
console.log('');
|
|
3016
3022
|
|
|
3017
|
-
// ──
|
|
3018
|
-
|
|
3023
|
+
// ── Phase 1: Login with per-account inline rendering ─────────────────────────
|
|
3024
|
+
const startupTw = process.stdout.columns || 90;
|
|
3025
|
+
const colNum = 4; // " #"
|
|
3026
|
+
const colSts = 3; // "ST"
|
|
3027
|
+
const colName = Math.min(24, Math.max(12, Math.floor(startupTw * 0.25)));
|
|
3028
|
+
const colGuild = Math.min(18, Math.max(8, Math.floor(startupTw * 0.2)));
|
|
3029
|
+
const colCmds = 8;
|
|
3030
|
+
const loginVis = colNum + colSts + colName + colGuild + colCmds + 10;
|
|
3031
|
+
|
|
3019
3032
|
const loginStates = accounts.map((acc, i) => ({
|
|
3020
3033
|
name: acc.label || acc.id || '?',
|
|
3021
3034
|
done: false,
|
|
3022
3035
|
failed: false,
|
|
3023
3036
|
worker: null,
|
|
3024
|
-
workerIdx: i,
|
|
3025
3037
|
}));
|
|
3026
3038
|
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
const colNum = 4; // " # "
|
|
3030
|
-
const colSts = 3; // "ST "
|
|
3031
|
-
const colName = Math.min(24, Math.max(12, Math.floor(terminalW * 0.25)));
|
|
3032
|
-
const colGuild = Math.min(20, Math.max(8, Math.floor(terminalW * 0.2)));
|
|
3033
|
-
const colCmds = 10; // " 20 cmds"
|
|
3034
|
-
const totalVis = colNum + colSts + colName + colGuild + colCmds + 8;
|
|
3035
|
-
|
|
3036
|
-
// Print header + all account lines (initial pending state)
|
|
3037
|
-
console.log(` ${'─'.repeat(totalVis)}`);
|
|
3039
|
+
let loginLines = [];
|
|
3040
|
+
loginLines.push(` ${'─'.repeat(loginVis)}`);
|
|
3038
3041
|
for (let i = 0; i < loginStates.length; i++) {
|
|
3039
3042
|
const s = loginStates[i];
|
|
3040
|
-
const num = `${c.dim}${(i + 1).toString().padStart(colNum - 1)}${c.reset}
|
|
3043
|
+
const num = `${c.dim}${(i + 1).toString().padStart(colNum - 1)}${c.reset}`;
|
|
3041
3044
|
const name = s.name.substring(0, colName).padEnd(colName);
|
|
3042
|
-
|
|
3045
|
+
const guild = c.dim + '···'.padEnd(colGuild) + c.reset;
|
|
3046
|
+
const cmds = c.dim + '···'.padEnd(colCmds) + c.reset;
|
|
3047
|
+
loginLines.push(` ${num} ${c.dim}··${c.reset} ${name} ${guild} ${cmds}`);
|
|
3043
3048
|
}
|
|
3044
|
-
|
|
3049
|
+
loginLines.push(` ${'─'.repeat(loginVis)}`);
|
|
3050
|
+
for (const l of loginLines) console.log(l);
|
|
3051
|
+
loginLines = null;
|
|
3045
3052
|
|
|
3046
|
-
|
|
3053
|
+
let loginPending = new Array(accounts.length).fill(true);
|
|
3047
3054
|
const drawLoginSpinners = () => {
|
|
3048
|
-
for (let i = 0; i <
|
|
3049
|
-
|
|
3050
|
-
if (s.done || s.failed) continue;
|
|
3055
|
+
for (let i = 0; i < loginPending.length; i++) {
|
|
3056
|
+
if (!loginPending[i]) continue;
|
|
3051
3057
|
const spin = BRAILLE_SPIN[Math.floor(Date.now() / 80) % BRAILLE_SPIN.length];
|
|
3052
|
-
const num = `${c.dim}${(i + 1).toString().padStart(colNum - 1)}${c.reset}
|
|
3053
|
-
const name =
|
|
3054
|
-
const guild = 'logging in...'.substring(0, colGuild).
|
|
3055
|
-
|
|
3058
|
+
const num = `${c.dim}${(i + 1).toString().padStart(colNum - 1)}${c.reset}`;
|
|
3059
|
+
const name = loginStates[i].name.substring(0, colName).padEnd(colName);
|
|
3060
|
+
const guild = c.dim + 'logging in...'.substring(0, colGuild) + c.reset;
|
|
3061
|
+
const cmds = c.dim + '···'.padEnd(colCmds) + c.reset;
|
|
3062
|
+
process.stdout.write(`\r\x1b[2K ${num} ${rgb(139, 92, 246)}${spin}${c.reset} ${name} ${guild} ${cmds}\x1b[K`);
|
|
3056
3063
|
}
|
|
3057
|
-
process.stdout.write(`\x1b[${loginStates.length + 3};0H`);
|
|
3058
3064
|
};
|
|
3059
3065
|
const loginSpinnerInterval = setInterval(drawLoginSpinners, 80);
|
|
3060
3066
|
|
|
3061
|
-
// Update a line when an account finishes
|
|
3062
3067
|
const finalizeLoginLine = (idx, worker) => {
|
|
3068
|
+
if (!loginPending[idx]) return;
|
|
3069
|
+
loginPending[idx] = false;
|
|
3063
3070
|
const s = loginStates[idx];
|
|
3064
|
-
if (s.done || s.failed) return;
|
|
3065
3071
|
s.done = true;
|
|
3066
3072
|
s.worker = worker;
|
|
3067
3073
|
|
|
3068
|
-
const num = `${c.dim}${(idx + 1).toString().padStart(colNum - 1)}${c.reset}
|
|
3074
|
+
const num = `${c.dim}${(idx + 1).toString().padStart(colNum - 1)}${c.reset}`;
|
|
3069
3075
|
const name = (worker.username || s.name || '?').substring(0, colName).padEnd(colName);
|
|
3070
|
-
|
|
3071
3076
|
let sts, guild, cmds;
|
|
3072
3077
|
if (worker._tokenInvalid) {
|
|
3073
3078
|
sts = `${rgb(239, 68, 68)}✗${c.reset}`;
|
|
3074
3079
|
guild = 'INVALID'.padEnd(colGuild);
|
|
3075
|
-
cmds = '···';
|
|
3080
|
+
cmds = '···'.padEnd(colCmds);
|
|
3076
3081
|
s.failed = true;
|
|
3077
3082
|
} else if (worker.channel) {
|
|
3078
3083
|
sts = `${rgb(52, 211, 153)}✓${c.reset}`;
|
|
3079
3084
|
const gn = (worker.channel.guild?.name || worker.channel.guild?.id || 'DM').substring(0, colGuild);
|
|
3080
3085
|
guild = gn.padEnd(colGuild);
|
|
3081
|
-
cmds = `${worker.stats?.commands || 0}
|
|
3086
|
+
cmds = `${worker.stats?.commands || 0}`.padEnd(colCmds);
|
|
3082
3087
|
} else {
|
|
3083
3088
|
sts = `${rgb(251, 146, 60)}⏳${c.reset}`;
|
|
3084
3089
|
guild = 'timeout'.padEnd(colGuild);
|
|
3085
|
-
cmds = '···';
|
|
3090
|
+
cmds = '···'.padEnd(colCmds);
|
|
3086
3091
|
}
|
|
3087
|
-
|
|
3088
|
-
const line = ` ${num}${sts} ${name} ${guild} ${cmds}`;
|
|
3089
|
-
process.stdout.write(`\x1b[${idx + 2};0H\x1b[2K${line}`);
|
|
3092
|
+
process.stdout.write(`\r\x1b[2K ${num} ${sts} ${name} ${c.dim}${guild}${c.reset} ${c.dim}${cmds}${c.reset}\x1b[K`);
|
|
3090
3093
|
};
|
|
3091
3094
|
|
|
3092
|
-
// Phase 1: Login in batches of 10
|
|
3093
3095
|
const parsedGapMin = Number.parseInt(String(process.env.LOGIN_GAP_MIN_MS || '50'), 10);
|
|
3094
3096
|
const parsedGapMax = Number.parseInt(String(process.env.LOGIN_GAP_MAX_MS || '150'), 10);
|
|
3095
3097
|
const LOGIN_GAP_MIN_MS = Number.isFinite(parsedGapMin) && parsedGapMin >= 0 ? parsedGapMin : 50;
|
|
3096
|
-
const LOGIN_GAP_MAX_MS = Number.isFinite(parsedGapMax) && parsedGapMax >= LOGIN_GAP_MIN_MS ? parsedGapMax : Math.max(
|
|
3097
|
-
|
|
3098
|
-
const randomLoginGap = () => {
|
|
3099
|
-
if (LOGIN_GAP_MAX_MS <= LOGIN_GAP_MIN_MS) return LOGIN_GAP_MIN_MS;
|
|
3100
|
-
return LOGIN_GAP_MIN_MS + Math.floor(Math.random() * (LOGIN_GAP_MAX_MS - LOGIN_GAP_MIN_MS + 1));
|
|
3101
|
-
};
|
|
3098
|
+
const LOGIN_GAP_MAX_MS = Number.isFinite(parsedGapMax) && parsedGapMax >= LOGIN_GAP_MIN_MS ? parsedGapMax : Math.max(parsedGapMin, 150);
|
|
3099
|
+
const randomLoginGap = () => LOGIN_GAP_MAX_MS <= LOGIN_GAP_MIN_MS ? LOGIN_GAP_MIN_MS : LOGIN_GAP_MIN_MS + Math.floor(Math.random() * (LOGIN_GAP_MAX_MS - LOGIN_GAP_MIN_MS + 1));
|
|
3102
3100
|
|
|
3103
3101
|
const BATCH_SIZE = 10;
|
|
3104
3102
|
for (let i = 0; i < accounts.length; i += BATCH_SIZE) {
|
|
3105
3103
|
if (shutdownCalled) break;
|
|
3106
3104
|
const batch = accounts.slice(i, Math.min(i + BATCH_SIZE, accounts.length));
|
|
3107
|
-
|
|
3108
3105
|
await Promise.all(batch.map(async (acc, idx) => {
|
|
3109
|
-
if (idx > 0)
|
|
3110
|
-
const jitter = 100 + Math.floor(Math.random() * 500);
|
|
3111
|
-
await new Promise(r => setTimeout(r, jitter));
|
|
3112
|
-
}
|
|
3106
|
+
if (idx > 0) await new Promise(r => setTimeout(r, 100 + Math.floor(Math.random() * 500)));
|
|
3113
3107
|
const worker = new AccountWorker(acc, i + idx);
|
|
3114
3108
|
workers.push(worker);
|
|
3115
3109
|
workerMap.set(acc.id, worker);
|
|
@@ -3117,216 +3111,143 @@ async function start(apiKey, apiUrl) {
|
|
|
3117
3111
|
await worker.start();
|
|
3118
3112
|
finalizeLoginLine(i + idx, worker);
|
|
3119
3113
|
}));
|
|
3120
|
-
|
|
3121
|
-
if (i + BATCH_SIZE < accounts.length) {
|
|
3122
|
-
await new Promise(r => setTimeout(r, randomLoginGap()));
|
|
3123
|
-
}
|
|
3124
|
-
|
|
3114
|
+
if (i + BATCH_SIZE < accounts.length) await new Promise(r => setTimeout(r, randomLoginGap()));
|
|
3125
3115
|
hintGC();
|
|
3126
3116
|
}
|
|
3127
3117
|
|
|
3128
3118
|
clearInterval(loginSpinnerInterval);
|
|
3129
|
-
process.stdout.write(`\x1b[${loginStates.length + 3};0H`);
|
|
3130
|
-
|
|
3131
|
-
// Final summary
|
|
3132
3119
|
const loginDone = workers.filter(w => !w._tokenInvalid && w.channel).length;
|
|
3133
3120
|
const invalidWorkers = workers.filter(w => w._tokenInvalid);
|
|
3134
3121
|
const timedOutWorkers = workers.filter(w => !w.channel && !w._tokenInvalid);
|
|
3135
|
-
console.log(
|
|
3122
|
+
console.log(`\r\x1b[2K ${rgb(52, 211, 153)}✓${c.reset} ${c.bold}Login complete${c.reset} ${rgb(52, 211, 153)}${loginDone}${c.reset}${c.dim}/${c.reset}${c.white}${accounts.length}${c.reset} ${c.dim}accounts connected${c.reset}`);
|
|
3136
3123
|
console.log('');
|
|
3137
|
-
|
|
3138
3124
|
if (invalidWorkers.length > 0) {
|
|
3139
3125
|
log('warn', `${rgb(239, 68, 68)}${invalidWorkers.length} account(s) have INVALID tokens:${c.reset}`);
|
|
3140
|
-
for (const w of invalidWorkers) {
|
|
3141
|
-
log('error', ` ✗ ${w.account.label || w.account.id} — token is invalid or expired`);
|
|
3142
|
-
}
|
|
3126
|
+
for (const w of invalidWorkers) log('error', ` ✗ ${w.account.label || w.account.id} — token is invalid or expired`);
|
|
3143
3127
|
console.log('');
|
|
3144
3128
|
}
|
|
3145
|
-
if (timedOutWorkers.length > 0) {
|
|
3146
|
-
log('warn', `${timedOutWorkers.length} account(s) timed out during login (will retry in background)`);
|
|
3147
|
-
}
|
|
3129
|
+
if (timedOutWorkers.length > 0) log('warn', `${timedOutWorkers.length} account(s) timed out during login (will retry in background)`);
|
|
3148
3130
|
|
|
3149
|
-
// Filter out workers with invalid tokens from grinding
|
|
3150
3131
|
const activeWorkers = workers.filter(w => !w._tokenInvalid);
|
|
3151
3132
|
|
|
3152
|
-
// ── Phase 2: Inventory check
|
|
3153
|
-
const invStates = activeWorkers.map((w, i) => ({
|
|
3154
|
-
name: w.username || w.account.label || '?',
|
|
3155
|
-
idx: i,
|
|
3156
|
-
done: false,
|
|
3157
|
-
failed: false,
|
|
3158
|
-
items: 0,
|
|
3159
|
-
value: 0,
|
|
3160
|
-
attempt: 0,
|
|
3161
|
-
worker: w,
|
|
3162
|
-
}));
|
|
3163
|
-
|
|
3164
|
-
const tw2 = process.stdout.columns || 90;
|
|
3133
|
+
// ── Phase 2: Inventory check — spinner for pending count, results inline ─────────
|
|
3165
3134
|
const iColNum = 4;
|
|
3166
|
-
const iColName = Math.min(22, Math.max(
|
|
3135
|
+
const iColName = Math.min(22, Math.max(12, Math.floor(startupTw * 0.22)));
|
|
3167
3136
|
const iColItems = 8;
|
|
3168
|
-
const iColVal =
|
|
3169
|
-
const
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
const
|
|
3175
|
-
const
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
}
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
const items = 'checking'.substring(0, iColItems).padEnd(iColItems);
|
|
3191
|
-
const val = ''.padEnd(iColVal);
|
|
3192
|
-
const tries = s.attempt > 0 ? `try ${s.attempt}/3` : '';
|
|
3193
|
-
process.stdout.write(`\x1b[${i + 2};0H\x1b[2K ${num}${rgb(34, 211, 238)}${spin}${c.reset} ${name} ${c.dim}${items}${c.reset} ${c.dim}${val}${c.reset} ${c.dim}${tries}${c.reset}`);
|
|
3194
|
-
}
|
|
3195
|
-
process.stdout.write(`\x1b[${invStates.length + 3};0H`);
|
|
3196
|
-
};
|
|
3197
|
-
const invSpinnerInterval = setInterval(drawInvSpinners, 80);
|
|
3198
|
-
|
|
3199
|
-
const finalizeInvLine = (idx, invRes) => {
|
|
3200
|
-
const s = invStates[idx];
|
|
3201
|
-
if (s.done || s.failed) return;
|
|
3202
|
-
if (invRes?.ok) {
|
|
3203
|
-
s.done = true;
|
|
3204
|
-
invDone++;
|
|
3205
|
-
} else {
|
|
3206
|
-
s.failed = true;
|
|
3207
|
-
invFailed++;
|
|
3208
|
-
}
|
|
3209
|
-
|
|
3210
|
-
const num = `${c.dim}${(idx + 1).toString().padStart(iColNum - 1)}${c.reset} `;
|
|
3211
|
-
const name = s.name.substring(0, iColName).padEnd(iColName);
|
|
3212
|
-
const items = `${invRes?.items?.length || 0}`.padEnd(iColItems);
|
|
3213
|
-
const val = invRes?.ok
|
|
3214
|
-
? `${c.green}⏣${(invRes.totalValue || 0).toLocaleString()}${c.reset}`.padEnd(iColVal + 3)
|
|
3215
|
-
: `${c.dim}··${c.reset}`.padEnd(iColVal);
|
|
3216
|
-
const sts = invRes?.ok ? `${rgb(52, 211, 153)}✓${c.reset}` : `${rgb(239, 68, 68)}✗${c.reset}`;
|
|
3217
|
-
|
|
3218
|
-
const line = ` ${num}${sts} ${name} ${items} ${val}`;
|
|
3219
|
-
process.stdout.write(`\x1b[${idx + 2};0H\x1b[2K${line}`);
|
|
3137
|
+
const iColVal = 16;
|
|
3138
|
+
const invVis = 7 + iColNum + iColName + iColItems + iColVal + 12;
|
|
3139
|
+
|
|
3140
|
+
console.log(` ${'─'.repeat(invVis)}`);
|
|
3141
|
+
for (let i = 0; i < activeWorkers.length; i++) {
|
|
3142
|
+
const w = activeWorkers[i];
|
|
3143
|
+
const num = `${c.dim}${(i + 1).toString().padStart(iColNum - 1)}${c.reset}`;
|
|
3144
|
+
const name = (w.username || w.account.label || '?').substring(0, iColName).padEnd(iColName);
|
|
3145
|
+
console.log(` ${num} ${c.dim}··${c.reset} ${name} ${c.dim}${'checking...'.padEnd(iColItems)}${c.reset} ${c.dim}${'···'.padEnd(iColVal)}${c.reset}`);
|
|
3146
|
+
}
|
|
3147
|
+
console.log(` ${'─'.repeat(invVis)}`);
|
|
3148
|
+
|
|
3149
|
+
let invDone = 0, invFailed = 0, invPending = activeWorkers.length;
|
|
3150
|
+
const drawInvProgress = () => {
|
|
3151
|
+
if (invPending === 0) return;
|
|
3152
|
+
const spin = BRAILLE_SPIN[Math.floor(Date.now() / 80) % BRAILLE_SPIN.length];
|
|
3153
|
+
const pct = activeWorkers.length > 0 ? ((activeWorkers.length - invPending) / activeWorkers.length) : 0;
|
|
3154
|
+
const barW = Math.min(20, startupTw - 40);
|
|
3155
|
+
const filled = Math.round(pct * barW);
|
|
3156
|
+
const bar = rgb(34, 211, 238) + '█'.repeat(filled) + rgb(50, 50, 70) + '░'.repeat(barW - filled) + c.reset;
|
|
3157
|
+
const pctStr = `${Math.round(pct * 100)}%`;
|
|
3158
|
+
process.stdout.write(`\r\x1b[2K ${rgb(34, 211, 238)}${spin}${c.reset} ${c.dim}Inventory...${c.reset} ${bar} ${c.bold}${rgb(52, 211, 153)}${activeWorkers.length - invPending}${c.reset}${c.dim}/${c.reset}${c.white}${activeWorkers.length}${c.reset} ${c.dim}${pctStr}${c.reset} `);
|
|
3220
3159
|
};
|
|
3160
|
+
const invSpinnerInterval = setInterval(drawInvProgress, 80);
|
|
3221
3161
|
|
|
3222
3162
|
await Promise.all(activeWorkers.map(async (w, i) => {
|
|
3223
|
-
|
|
3224
|
-
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
|
|
3163
|
+
const num = `${c.dim}${(i + 1).toString().padStart(iColNum - 1)}${c.reset}`;
|
|
3164
|
+
const name = (w.username || w.account.label || '?').substring(0, iColName).padEnd(iColName);
|
|
3165
|
+
let invRes;
|
|
3166
|
+
try { invRes = await w.checkInventory({ force: true, requireComplete: true, maxAttempts: 3, silent: true }); }
|
|
3167
|
+
catch { invRes = { ok: false }; }
|
|
3168
|
+
invPending--;
|
|
3169
|
+
const items = invRes?.ok ? (invRes.result?.items?.length || 0) : 0;
|
|
3170
|
+
const val = invRes?.ok ? (invRes.result?.totalValue || 0) : 0;
|
|
3171
|
+
const sts = invRes?.ok ? `${rgb(52, 211, 153)}✓${c.reset}` : `${rgb(239, 68, 68)}✗${c.reset}`;
|
|
3172
|
+
const itemStr = `${items}`.padEnd(iColItems);
|
|
3173
|
+
const valStr = invRes?.ok ? `${c.green}⏣${val.toLocaleString()}${c.reset}` : `${c.dim}···${c.reset}`;
|
|
3174
|
+
process.stdout.write(`\r\x1b[2K ${num} ${sts} ${name} ${itemStr} ${valStr.padEnd(iColVal + 5)}\x1b[K`);
|
|
3175
|
+
if (invRes?.ok) invDone++; else invFailed++;
|
|
3229
3176
|
}));
|
|
3230
3177
|
|
|
3231
3178
|
clearInterval(invSpinnerInterval);
|
|
3232
|
-
process.stdout.write(`\x1b[
|
|
3179
|
+
process.stdout.write(`\r\x1b[2K`);
|
|
3233
3180
|
|
|
3234
3181
|
if (invFailed > 0) {
|
|
3235
3182
|
console.log(` ${rgb(239, 68, 68)}✗${c.reset} ${c.bold}Inventory incomplete${c.reset} ${rgb(52, 211, 153)}${invDone}${c.reset}${c.dim}/${c.reset}${c.white}${activeWorkers.length}${c.reset} ${c.dim}done, ${rgb(239, 68, 68)}${invFailed} failed${c.reset}`);
|
|
3236
3183
|
log('error', `${c.red}Not starting grind loops — ${invFailed} accounts failed inventory.${c.reset}`);
|
|
3237
3184
|
return;
|
|
3238
3185
|
}
|
|
3239
|
-
|
|
3240
3186
|
console.log(` ${rgb(52, 211, 153)}✓${c.reset} ${c.bold}Inventory complete${c.reset} ${rgb(52, 211, 153)}${invDone}/${activeWorkers.length}${c.reset} ${c.dim}all clear${c.reset}`);
|
|
3241
3187
|
console.log('');
|
|
3242
3188
|
|
|
3243
|
-
// ── Phase 2.5: Balance check
|
|
3244
|
-
const balStates = activeWorkers.map((w, i) => ({
|
|
3245
|
-
name: w.username || w.account.label || '?',
|
|
3246
|
-
idx: i,
|
|
3247
|
-
done: false,
|
|
3248
|
-
wallet: 0,
|
|
3249
|
-
bank: 0,
|
|
3250
|
-
worker: w,
|
|
3251
|
-
}));
|
|
3252
|
-
|
|
3189
|
+
// ── Phase 2.5: Balance check — inline table, single spinner for progress ─────────
|
|
3253
3190
|
const bColNum = 4;
|
|
3254
|
-
const bColName = Math.min(22, Math.max(
|
|
3255
|
-
const bColWallet =
|
|
3256
|
-
const bColBank =
|
|
3191
|
+
const bColName = Math.min(22, Math.max(12, Math.floor(startupTw * 0.22)));
|
|
3192
|
+
const bColWallet = 12;
|
|
3193
|
+
const bColBank = 12;
|
|
3257
3194
|
const bColTotal = 14;
|
|
3258
3195
|
const bColLs = 4;
|
|
3259
|
-
const
|
|
3260
|
-
|
|
3261
|
-
console.log(` ${'─'.repeat(
|
|
3262
|
-
for (let i = 0; i <
|
|
3263
|
-
const
|
|
3264
|
-
const num = `${c.dim}${(i + 1).toString().padStart(bColNum - 1)}${c.reset}
|
|
3265
|
-
const name =
|
|
3266
|
-
console.log(
|
|
3267
|
-
}
|
|
3268
|
-
console.log(` ${'─'.repeat(
|
|
3269
|
-
|
|
3270
|
-
let balDone = 0;
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
process.stdout.write(`\x1b[${i + 2};0H\x1b[2K ${num}${rgb(251, 191, 36)}${spin}${c.reset} ${name} ${c.dim}${'checking'.padEnd(bColWallet)}${c.reset} ${c.dim}${'··'.padEnd(bColBank)}${c.reset} ${c.dim}${'··'.padEnd(bColTotal)}${c.reset} ${c.dim}♥?${c.reset}`);
|
|
3280
|
-
}
|
|
3281
|
-
process.stdout.write(`\x1b[${balStates.length + 3};0H`);
|
|
3196
|
+
const balVis = 7 + bColNum + bColName + bColWallet + bColBank + bColTotal + bColLs + 14;
|
|
3197
|
+
|
|
3198
|
+
console.log(` ${'─'.repeat(balVis)}`);
|
|
3199
|
+
for (let i = 0; i < activeWorkers.length; i++) {
|
|
3200
|
+
const w = activeWorkers[i];
|
|
3201
|
+
const num = `${c.dim}${(i + 1).toString().padStart(bColNum - 1)}${c.reset}`;
|
|
3202
|
+
const name = (w.username || w.account.label || '?').substring(0, bColName).padEnd(bColName);
|
|
3203
|
+
console.log(` ${num} ${c.dim}··${c.reset} ${name} ${c.dim}${'checking'.padEnd(bColWallet)}${c.reset} ${c.dim}${'···'.padEnd(bColBank)}${c.reset} ${c.dim}${'···'.padEnd(bColTotal)}${c.reset} ${c.dim}♥?${c.reset}`);
|
|
3204
|
+
}
|
|
3205
|
+
console.log(` ${'─'.repeat(balVis)}`);
|
|
3206
|
+
|
|
3207
|
+
let balDone = 0, balPending = activeWorkers.length;
|
|
3208
|
+
const drawBalProgress = () => {
|
|
3209
|
+
if (balPending === 0) return;
|
|
3210
|
+
const spin = BRAILLE_SPIN[Math.floor(Date.now() / 80) % BRAILLE_SPIN.length];
|
|
3211
|
+
const pct = activeWorkers.length > 0 ? ((activeWorkers.length - balPending) / activeWorkers.length) : 0;
|
|
3212
|
+
const barW = Math.min(20, startupTw - 40);
|
|
3213
|
+
const filled = Math.round(pct * barW);
|
|
3214
|
+
const bar = rgb(251, 191, 36) + '█'.repeat(filled) + rgb(50, 50, 70) + '░'.repeat(barW - filled) + c.reset;
|
|
3215
|
+
process.stdout.write(`\r\x1b[2K ${rgb(251, 191, 36)}${spin}${c.reset} ${c.dim}Balance...${c.reset} ${bar} ${c.bold}${rgb(52, 211, 153)}${activeWorkers.length - balPending}${c.reset}${c.dim}/${c.reset}${c.white}${activeWorkers.length}${c.reset} `);
|
|
3282
3216
|
};
|
|
3283
|
-
const balSpinnerInterval = setInterval(
|
|
3284
|
-
|
|
3285
|
-
const finalizeBalLine = (idx, w) => {
|
|
3286
|
-
const s = balStates[idx];
|
|
3287
|
-
if (s.done) return;
|
|
3288
|
-
s.done = true;
|
|
3289
|
-
s.wallet = w.stats?.balance || 0;
|
|
3290
|
-
s.bank = w.stats?.bankBalance || 0;
|
|
3291
|
-
balDone++;
|
|
3217
|
+
const balSpinnerInterval = setInterval(drawBalProgress, 80);
|
|
3292
3218
|
|
|
3293
|
-
|
|
3294
|
-
|
|
3219
|
+
await Promise.all(activeWorkers.map(async (w, i) => {
|
|
3220
|
+
try { await w.checkBalance(true); } catch {}
|
|
3221
|
+
balPending--;
|
|
3222
|
+
const num = `${c.dim}${(i + 1).toString().padStart(bColNum - 1)}${c.reset}`;
|
|
3223
|
+
const name = (w.username || w.account.label || '?').substring(0, bColName).padEnd(bColName);
|
|
3224
|
+
const wallet = w.stats?.balance || 0;
|
|
3225
|
+
const bank = w.stats?.bankBalance || 0;
|
|
3295
3226
|
const ls = w._lifesavers ?? '?';
|
|
3296
3227
|
const lsColor = ls === 0 ? rgb(239, 68, 68) : ls <= 2 ? rgb(251, 191, 36) : rgb(52, 211, 153);
|
|
3297
|
-
const
|
|
3298
|
-
const
|
|
3299
|
-
const
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
process.stdout.write(`\x1b[${idx + 2};0H\x1b[2K${line}`);
|
|
3303
|
-
};
|
|
3304
|
-
|
|
3305
|
-
await Promise.all(activeWorkers.map(async w => {
|
|
3306
|
-
try {
|
|
3307
|
-
await w.checkBalance(true); // silent: don't spam console during inline rendering
|
|
3308
|
-
} catch {}
|
|
3309
|
-
const idx = balStates.findIndex(s => s.worker === w);
|
|
3310
|
-
if (idx >= 0) finalizeBalLine(idx, w);
|
|
3228
|
+
const walletStr = `${c.green}⏣${wallet.toLocaleString()}${c.reset}`;
|
|
3229
|
+
const bankStr = `${c.cyan}⏣${bank.toLocaleString()}${c.reset}`;
|
|
3230
|
+
const totalStr = `${c.bold}⏣${(wallet + bank).toLocaleString()}${c.reset}`;
|
|
3231
|
+
process.stdout.write(`\r\x1b[2K ${num} ${rgb(52, 211, 153)}✓${c.reset} ${name} ${walletStr.padEnd(bColWallet + 4)} ${bankStr.padEnd(bColBank + 4)} ${totalStr.padEnd(bColTotal + 3)} ${lsColor}♥${ls}${c.reset}\x1b[K`);
|
|
3232
|
+
balDone++;
|
|
3311
3233
|
}));
|
|
3312
3234
|
|
|
3313
3235
|
clearInterval(balSpinnerInterval);
|
|
3314
|
-
process.stdout.write(`\x1b[
|
|
3236
|
+
process.stdout.write(`\r\x1b[2K`);
|
|
3315
3237
|
|
|
3316
|
-
// Balance summary
|
|
3317
3238
|
let totalWallet = 0, totalBank = 0, noLifesaverAccounts = [];
|
|
3318
|
-
for (const
|
|
3319
|
-
totalWallet +=
|
|
3320
|
-
totalBank +=
|
|
3321
|
-
if (
|
|
3239
|
+
for (const w of activeWorkers) {
|
|
3240
|
+
totalWallet += w.stats?.balance || 0;
|
|
3241
|
+
totalBank += w.stats?.bankBalance || 0;
|
|
3242
|
+
if (w._lifesavers === 0) noLifesaverAccounts.push(w.username || w.account.label);
|
|
3322
3243
|
}
|
|
3323
3244
|
console.log(` ${rgb(52, 211, 153)}✓${c.reset} ${c.bold}Balance${c.reset} Total: ${c.bold}${c.green}⏣ ${(totalWallet + totalBank).toLocaleString()}${c.reset} ${c.dim}(wallet: ⏣ ${totalWallet.toLocaleString()} + bank: ⏣ ${totalBank.toLocaleString()})${c.reset}`);
|
|
3324
|
-
|
|
3325
3245
|
if (noLifesaverAccounts.length > 0) {
|
|
3326
3246
|
console.log(` ${rgb(239, 68, 68)}⚠${c.reset} ${c.bold}${c.red}WARNING: ${noLifesaverAccounts.length} account(s) have 0 LIFESAVERS!${c.reset} Crime/Search disabled for: ${noLifesaverAccounts.join(', ')}`);
|
|
3327
3247
|
}
|
|
3328
3248
|
console.log('');
|
|
3329
3249
|
|
|
3250
|
+
|
|
3330
3251
|
// Phase 2.75: Check DM history for deaths/level-ups (sequential, fast)
|
|
3331
3252
|
console.log(` ${rgb(139, 92, 246)}${BRAILLE_SPIN[0]}${c.reset} ${c.dim}Checking DM history...${c.reset}`);
|
|
3332
3253
|
let dmDeaths = 0, dmLevelUps = 0, dmNoLs = [];
|