dankgrinder 5.25.0 → 5.260.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 +98 -29
- package/package.json +1 -1
package/lib/grinder.js
CHANGED
|
@@ -605,6 +605,51 @@ function safeParseJSON(str, fallback = []) {
|
|
|
605
605
|
try { return JSON.parse(str || '[]'); } catch { return fallback; }
|
|
606
606
|
}
|
|
607
607
|
|
|
608
|
+
// ── Command Result Formatter ──────────────────────────────────
|
|
609
|
+
// Clean command names and format results with color codes
|
|
610
|
+
const CMD_NAMES_CLEAN = {
|
|
611
|
+
bj: 'Blackjack', blackjack: 'Blackjack', hl: 'High Low', pm: 'Post Memes', postmemes: 'Post Memes',
|
|
612
|
+
ct: 'Coin Toss', cointoss: 'Coin Toss', se: 'Snake Eyes', snakeeyes: 'Snake Eyes',
|
|
613
|
+
hunt: 'Hunt', dig: 'Dig', fish: 'Fish', beg: 'Beg', search: 'Search', crime: 'Crime',
|
|
614
|
+
tidy: 'Tidy', farm: 'Farm', daily: 'Daily', weekly: 'Weekly', monthly: 'Monthly',
|
|
615
|
+
scratch: 'Scratch', adventure: 'Adventure', trivia: 'Trivia', stream: 'Stream',
|
|
616
|
+
drops: 'Drops', use: 'Use Item', dep: 'Deposit', deposit: 'Deposit', inv: 'Inventory',
|
|
617
|
+
work: 'Work', stream: 'Stream', roulette: 'Roulette', slots: 'Slots',
|
|
618
|
+
};
|
|
619
|
+
|
|
620
|
+
function formatCommandName(cmd) {
|
|
621
|
+
if (!cmd) return '?';
|
|
622
|
+
const clean = cmd.replace(/^pls\s+/, '').replace(/\s+\d+.*$/, '').trim().toLowerCase();
|
|
623
|
+
return CMD_NAMES_CLEAN[clean] || clean.charAt(0).toUpperCase() + clean.slice(1);
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
function formatCommandResult(cmdName, result, earned, spent) {
|
|
627
|
+
const name = formatCommandName(cmdName);
|
|
628
|
+
const net = (earned || 0) - (spent || 0);
|
|
629
|
+
|
|
630
|
+
// Extract clean result text
|
|
631
|
+
let cleanResult = stripAnsi(result || '').replace(/\n/g, ' ').substring(0, 40);
|
|
632
|
+
|
|
633
|
+
// Check for common failure/hold patterns
|
|
634
|
+
if (cleanResult.toLowerCase().includes('hold tight')) return `${name}: ${c.yellow}Hold Tight${c.reset}`;
|
|
635
|
+
if (cleanResult.toLowerCase().includes('cooldown')) return `${name}: ${c.dim}On Cooldown${c.reset}`;
|
|
636
|
+
if (cleanResult.toLowerCase().includes('no response')) return `${name}: ${c.red}No Response${c.reset}`;
|
|
637
|
+
|
|
638
|
+
// Format with earnings/losses
|
|
639
|
+
if (net > 0) {
|
|
640
|
+
return `${name}: ${c.green}+⏣ ${net.toLocaleString()}${c.reset}`;
|
|
641
|
+
} else if (net < 0) {
|
|
642
|
+
return `${name}: ${c.red}-⏣ ${Math.abs(net).toLocaleString()}${c.reset}`;
|
|
643
|
+
} else if (earned === 0 && spent === 0) {
|
|
644
|
+
// No coins changed - show result context
|
|
645
|
+
if (cleanResult.includes('completed') || cleanResult.includes('done')) {
|
|
646
|
+
return `${name}: ${c.dim}Done${c.reset}`;
|
|
647
|
+
}
|
|
648
|
+
return `${name}: ${c.dim}${cleanResult || 'Complete'}${c.reset}`;
|
|
649
|
+
}
|
|
650
|
+
return `${name}: ${c.dim}${cleanResult || 'Complete'}${c.reset}`;
|
|
651
|
+
}
|
|
652
|
+
|
|
608
653
|
// ── Coin Parser — prefers Net:/Winnings: fields, falls back to max ⏣ ──
|
|
609
654
|
function parseCoins(text) {
|
|
610
655
|
if (!text) return 0;
|
|
@@ -1139,7 +1184,10 @@ class AccountWorker {
|
|
|
1139
1184
|
accountId: this.account.id,
|
|
1140
1185
|
redis,
|
|
1141
1186
|
onPageProgress: ({ page, total }) => {
|
|
1142
|
-
|
|
1187
|
+
// Update same line instead of spamming new lines - uses cursor control
|
|
1188
|
+
const progress = `[inv] ${this.username}: ${page}/${total} pages`;
|
|
1189
|
+
const erase = '\x1b[2K\r'; // Clear line + carriage return
|
|
1190
|
+
process.stdout.write(`${erase}${this.color}${progress}${c.reset}`);
|
|
1143
1191
|
},
|
|
1144
1192
|
});
|
|
1145
1193
|
|
|
@@ -1147,6 +1195,8 @@ class AccountWorker {
|
|
|
1147
1195
|
throw new Error(`incomplete pages (${result.pagesVisited || 0}/${result.pagesTotal || 0})`);
|
|
1148
1196
|
}
|
|
1149
1197
|
|
|
1198
|
+
// Add newline after inventory progress
|
|
1199
|
+
process.stdout.write('\n');
|
|
1150
1200
|
this.log('success', `Inventory: ${result.items?.length || 0} items, ⏣ ${(result.totalValue || 0).toLocaleString()} net`);
|
|
1151
1201
|
try {
|
|
1152
1202
|
await fetch(`${API_URL}/api/grinder/inventory`, {
|
|
@@ -1549,7 +1599,8 @@ class AccountWorker {
|
|
|
1549
1599
|
|
|
1550
1600
|
const earned = Math.max(0, cmdResult.coins || 0);
|
|
1551
1601
|
const spent = Math.max(0, cmdResult.lost || 0);
|
|
1552
|
-
|
|
1602
|
+
// Track net earnings (add wins, subtract losses)
|
|
1603
|
+
this.stats.coins += (earned - spent);
|
|
1553
1604
|
if (cmdResult.nextCooldownSec) {
|
|
1554
1605
|
await this.setCooldown(cmdName, cmdResult.nextCooldownSec);
|
|
1555
1606
|
this._lastCooldownOverride = cmdResult.nextCooldownSec;
|
|
@@ -1596,8 +1647,9 @@ class AccountWorker {
|
|
|
1596
1647
|
}
|
|
1597
1648
|
|
|
1598
1649
|
this.stats.successes++;
|
|
1599
|
-
|
|
1600
|
-
|
|
1650
|
+
// Format result with clean command name and colored earnings
|
|
1651
|
+
const formattedResult = formatCommandResult(cmdName, result, earned, spent);
|
|
1652
|
+
this.setStatus(formattedResult);
|
|
1601
1653
|
await sendLog(this.username, cmdName, result, 'success');
|
|
1602
1654
|
reportEarnings(this.account.id, this.username, earned, spent, cmdName);
|
|
1603
1655
|
|
|
@@ -2346,8 +2398,8 @@ class AccountWorker {
|
|
|
2346
2398
|
} catch {}
|
|
2347
2399
|
}
|
|
2348
2400
|
|
|
2349
|
-
// Let Discord gateway settle
|
|
2350
|
-
await new Promise(r => setTimeout(r,
|
|
2401
|
+
// Let Discord gateway settle (reduced for faster startup)
|
|
2402
|
+
await new Promise(r => setTimeout(r, 500));
|
|
2351
2403
|
resolve();
|
|
2352
2404
|
});
|
|
2353
2405
|
|
|
@@ -2472,43 +2524,58 @@ async function start(apiKey, apiUrl) {
|
|
|
2472
2524
|
console.log(` ${checks.join(' ')}`);
|
|
2473
2525
|
console.log('');
|
|
2474
2526
|
|
|
2475
|
-
// Phase 1: Login all accounts (
|
|
2476
|
-
const LOGIN_PROGRESS_EVERY =
|
|
2477
|
-
|
|
2478
|
-
const
|
|
2479
|
-
const
|
|
2480
|
-
const
|
|
2527
|
+
// Phase 1: Login all accounts (optimized for speed)
|
|
2528
|
+
const LOGIN_PROGRESS_EVERY = 10;
|
|
2529
|
+
// Reduced delays: 50-150ms between logins (faster startup for 1k+ accounts)
|
|
2530
|
+
const parsedGapMin = Number.parseInt(String(process.env.LOGIN_GAP_MIN_MS || '50'), 10);
|
|
2531
|
+
const parsedGapMax = Number.parseInt(String(process.env.LOGIN_GAP_MAX_MS || '150'), 10);
|
|
2532
|
+
const LOGIN_GAP_MIN_MS = Number.isFinite(parsedGapMin) && parsedGapMin >= 0 ? parsedGapMin : 50;
|
|
2533
|
+
const LOGIN_GAP_MAX_MS = Number.isFinite(parsedGapMax) && parsedGapMax >= LOGIN_GAP_MIN_MS ? parsedGapMax : Math.max(LOGIN_GAP_MIN_MS, 150);
|
|
2481
2534
|
|
|
2482
2535
|
const randomLoginGap = () => {
|
|
2483
2536
|
if (LOGIN_GAP_MAX_MS <= LOGIN_GAP_MIN_MS) return LOGIN_GAP_MIN_MS;
|
|
2484
2537
|
return LOGIN_GAP_MIN_MS + Math.floor(Math.random() * (LOGIN_GAP_MAX_MS - LOGIN_GAP_MIN_MS + 1));
|
|
2485
2538
|
};
|
|
2486
2539
|
|
|
2487
|
-
|
|
2540
|
+
// Parallel login in batches of 10 to avoid rate limits while being fast
|
|
2541
|
+
const BATCH_SIZE = 10;
|
|
2542
|
+
for (let i = 0; i < accounts.length; i += BATCH_SIZE) {
|
|
2488
2543
|
if (shutdownCalled) break;
|
|
2489
|
-
const
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
await
|
|
2493
|
-
|
|
2544
|
+
const batch = accounts.slice(i, Math.min(i + BATCH_SIZE, accounts.length));
|
|
2545
|
+
|
|
2546
|
+
// Login batch in parallel
|
|
2547
|
+
await Promise.all(batch.map(async (acc, idx) => {
|
|
2548
|
+
const worker = new AccountWorker(acc, i + idx);
|
|
2549
|
+
workers.push(worker);
|
|
2550
|
+
workerMap.set(acc.id, worker);
|
|
2551
|
+
await worker.start();
|
|
2552
|
+
}));
|
|
2553
|
+
|
|
2554
|
+
// Small gap between batches
|
|
2555
|
+
if (i + BATCH_SIZE < accounts.length) {
|
|
2494
2556
|
const gapMs = randomLoginGap();
|
|
2495
|
-
|
|
2496
|
-
log('info', `${c.dim}Logged in ${i + 1}/${accounts.length}, next account in ${gapMs}ms...${c.reset}`);
|
|
2497
|
-
}
|
|
2557
|
+
log('info', `${c.dim}Logged in ${Math.min(i + BATCH_SIZE, accounts.length)}/${accounts.length}...${c.reset}`);
|
|
2498
2558
|
await new Promise(r => setTimeout(r, gapMs));
|
|
2499
2559
|
}
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
}
|
|
2560
|
+
|
|
2561
|
+
hintGC();
|
|
2503
2562
|
}
|
|
2504
2563
|
|
|
2505
2564
|
// Phase 2: Run inventory on ALL accounts (must complete before any grinding)
|
|
2506
2565
|
log('info', `${c.dim}Checking inventory for all ${workers.length} accounts...${c.reset}`);
|
|
2507
2566
|
let invDone = 0;
|
|
2508
2567
|
let invFailed = 0;
|
|
2509
|
-
|
|
2568
|
+
|
|
2569
|
+
// Use sequential inventory checks with single-line progress update
|
|
2570
|
+
const totalAccounts = workers.length;
|
|
2571
|
+
for (let i = 0; i < workers.length; i++) {
|
|
2572
|
+
const w = workers[i];
|
|
2510
2573
|
const label = w?.username || w?.account?.label || w?.account?.id || `account-${i + 1}`;
|
|
2511
|
-
|
|
2574
|
+
|
|
2575
|
+
// Update single line instead of spamming new lines
|
|
2576
|
+
const progressLine = `\x1b[2K\r${c.dim}[inv] ${i + 1}/${totalAccounts}: ${label}${c.reset}`;
|
|
2577
|
+
process.stdout.write(progressLine);
|
|
2578
|
+
|
|
2512
2579
|
try {
|
|
2513
2580
|
const invRes = await w.checkInventory({
|
|
2514
2581
|
force: true,
|
|
@@ -2521,9 +2588,11 @@ async function start(apiKey, apiUrl) {
|
|
|
2521
2588
|
} catch {
|
|
2522
2589
|
invFailed++;
|
|
2523
2590
|
}
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2591
|
+
}
|
|
2592
|
+
|
|
2593
|
+
// Newline after progress
|
|
2594
|
+
process.stdout.write('\n');
|
|
2595
|
+
log('success', `${c.dim}Inventory: ${invDone}/${workers.length} complete${invFailed > 0 ? `, ${c.yellow}${invFailed} failed${c.reset}${c.dim}` : ''}${c.reset}`);
|
|
2527
2596
|
|
|
2528
2597
|
if (invFailed > 0) {
|
|
2529
2598
|
log('error', `${c.red}Inventory phase incomplete: ${invDone}/${workers.length} complete, ${invFailed} failed/incomplete. Not starting grind loops.${c.reset}`);
|