dankgrinder 6.17.0 → 6.21.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/adventure.js +5 -5
- package/lib/commands/crime.js +9 -1
- package/lib/commands/postmemes.js +4 -4
- package/lib/commands/search.js +5 -2
- package/lib/commands/stream.js +7 -7
- package/lib/commands/utils.js +52 -35
- package/lib/grinder.js +420 -297
- package/lib/rawLogger.js +46 -8
- package/package.json +1 -1
package/lib/grinder.js
CHANGED
|
@@ -487,7 +487,7 @@ function renderDashboard() {
|
|
|
487
487
|
lines.push(bEmpty);
|
|
488
488
|
|
|
489
489
|
// ═══════════════════════════════════════════════════════════════
|
|
490
|
-
// STATS PANEL
|
|
490
|
+
// STATS PANEL (split: left = metrics, right = big trend)
|
|
491
491
|
// ═══════════════════════════════════════════════════════════════
|
|
492
492
|
lines.push(bSep);
|
|
493
493
|
lines.push(bEmpty);
|
|
@@ -495,32 +495,44 @@ function renderDashboard() {
|
|
|
495
495
|
// Earnings sparkline data
|
|
496
496
|
const now = Date.now();
|
|
497
497
|
if (now - lastEarningsSample > 8000) { earningsHistory.push(totalCoins); lastEarningsSample = now; }
|
|
498
|
-
const sparkW = Math.min(30, Math.floor(iw * 0.3));
|
|
499
|
-
const spark = drawSparkline(earningsHistory.toArray(), sparkW);
|
|
500
|
-
|
|
501
|
-
// Row 1: Balance + Earned
|
|
502
498
|
const elapsedHrs = (Date.now() - startTime) / 3_600_000;
|
|
503
499
|
const perHr = elapsedHrs > 0.01 ? Math.round(totalCoins / elapsedHrs) : 0;
|
|
504
500
|
const peakFlag = isNewHigh ? ` ${R}${c.bold}* NEW HIGH *${c.reset}` : '';
|
|
505
501
|
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
// Row 2: Peak + Trend sparkline
|
|
509
|
-
lines.push(bRow(` ${O}★${c.reset} ${D}PEAK${c.reset} ${c.bold}${O}⏣ ${formatCoins(sessionPeakCoins)}${c.reset} ${A}~${c.reset} ${D}TREND${c.reset} ${spark}`));
|
|
510
|
-
|
|
511
|
-
// Row 3: Commands + Success + Rate + Uptime
|
|
502
|
+
// Left column: fixed metrics (left-aligned)
|
|
512
503
|
const cpmVal = globalCmdRate.getRate().toFixed(1);
|
|
513
504
|
const srColor = successRate >= 95 ? G : successRate >= 80 ? Y : R;
|
|
514
|
-
const srBarW = Math.min(15, Math.floor(iw * 0.
|
|
505
|
+
const srBarW = Math.min(15, Math.floor(iw * 0.1));
|
|
515
506
|
const srBar = progressBar(successRate, 100, srBarW, successRate >= 95 ? [52, 211, 153] : successRate >= 80 ? [251, 191, 36] : [239, 68, 68]);
|
|
516
|
-
lines.push(bRow(` ${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} ${Y}◷${c.reset} ${D}UP${c.reset} ${c.bold}${Y}${formatUptime()}${c.reset}`));
|
|
517
|
-
|
|
518
|
-
// Row 4: Memory
|
|
519
507
|
const memMB = Math.round((process.memoryUsage?.rss?.() ?? process.memoryUsage().rss) / 1048576);
|
|
520
508
|
const memCol = memMB > 900 ? [239, 68, 68] : memMB > 600 ? [251, 191, 36] : [52, 211, 153];
|
|
521
|
-
const memBarW = Math.min(
|
|
509
|
+
const memBarW = Math.min(15, Math.floor(iw * 0.1));
|
|
522
510
|
const memBar = progressBar(memMB, 1024, memBarW, memCol, [40, 40, 55]);
|
|
523
|
-
|
|
511
|
+
|
|
512
|
+
// Right column: big trend sparkline
|
|
513
|
+
const sparkW = Math.floor(iw * 0.42);
|
|
514
|
+
const spark = drawSparkline(earningsHistory.toArray(), sparkW);
|
|
515
|
+
|
|
516
|
+
// Build split rows
|
|
517
|
+
const leftHalf = Math.floor(iw * 0.52);
|
|
518
|
+
|
|
519
|
+
// Row 1: Balance + EARNED | TREND (big sparkline)
|
|
520
|
+
const leftRow1 = `${Au}⟐${c.reset} ${D}BALANCE${c.reset} ${c.bold}${Au}⏣ ${formatCoins(totalBalance)}${c.reset} ${G}▲${c.reset} ${D}EARNED${c.reset} ${c.bold}${G}+⏣ ${formatCoins(totalCoins)}${c.reset}${peakFlag}`;
|
|
521
|
+
const rightRow1 = `${A}~${c.reset} ${D}TREND${c.reset} ${spark}`;
|
|
522
|
+
const combined1 = `${leftRow1}${' '.repeat(Math.max(2, leftHalf - leftRow1.replace(RE, '').length))}${rightRow1}`;
|
|
523
|
+
lines.push(bRow(` ${combined1}`));
|
|
524
|
+
|
|
525
|
+
// Row 2: Peak (left)
|
|
526
|
+
const peakRow = `${O}★${c.reset} ${D}PEAK${c.reset} ${c.bold}${O}⏣ ${formatCoins(sessionPeakCoins)}${c.reset}`;
|
|
527
|
+
lines.push(bRow(` ${peakRow}${' '.repeat(Math.max(2, leftHalf - peakRow.replace(RE, '').length))}${Y}◷${c.reset} ${D}UP${c.reset} ${c.bold}${Y}${formatUptime()}${c.reset}`));
|
|
528
|
+
|
|
529
|
+
// Row 3: CMDS (left) + success bar + rate
|
|
530
|
+
const cmdsRow = `${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}`;
|
|
531
|
+
lines.push(bRow(` ${cmdsRow}`));
|
|
532
|
+
|
|
533
|
+
// Row 4: MEM (left)
|
|
534
|
+
const memRow = `${D}≡${c.reset} ${D}MEM${c.reset} ${rgb(memCol[0], memCol[1], memCol[2])}${c.bold}${memMB}MB${c.reset} ${memBar}`;
|
|
535
|
+
lines.push(bRow(` ${memRow}`));
|
|
524
536
|
|
|
525
537
|
lines.push(bEmpty);
|
|
526
538
|
|
|
@@ -1499,7 +1511,7 @@ class AccountWorker {
|
|
|
1499
1511
|
}
|
|
1500
1512
|
}
|
|
1501
1513
|
|
|
1502
|
-
async checkBalance() {
|
|
1514
|
+
async checkBalance(silent = false) {
|
|
1503
1515
|
const prefix = this.account.use_slash ? '/' : 'pls';
|
|
1504
1516
|
const sentAt = Date.now();
|
|
1505
1517
|
|
|
@@ -1507,219 +1519,129 @@ class AccountWorker {
|
|
|
1507
1519
|
if (!t) return false;
|
|
1508
1520
|
const lower = t.toLowerCase();
|
|
1509
1521
|
return lower.includes('balance') || lower.includes('balances') || lower.includes('global rank')
|
|
1510
|
-
|| lower.includes('wallet') || /<a?:coin:\d
|
|
1511
|
-
};
|
|
1512
|
-
|
|
1513
|
-
const readBalanceText = async (msg, forceCV2 = false) => {
|
|
1514
|
-
if (!msg) return '';
|
|
1515
|
-
const needsCv2 = forceCV2
|
|
1516
|
-
|| isCV2(msg)
|
|
1517
|
-
|| (Array.isArray(msg.components) && msg.components.length > 0
|
|
1518
|
-
&& (!msg.content || msg.content.length === 0)
|
|
1519
|
-
&& (!msg.embeds || msg.embeds.length === 0));
|
|
1520
|
-
if (needsCv2) await ensureCV2(msg, forceCV2);
|
|
1521
|
-
return stripAnsi(getFullText(msg)).replace(/\s+/g, ' ').trim();
|
|
1522
|
-
};
|
|
1523
|
-
|
|
1524
|
-
const findRecentBalanceMessage = async () => {
|
|
1525
|
-
if (!this.channel?.messages?.fetch) return null;
|
|
1526
|
-
for (let attempt = 0; attempt < 6; attempt++) {
|
|
1527
|
-
try {
|
|
1528
|
-
const recent = await this.channel.messages.fetch({ limit: 12 });
|
|
1529
|
-
const candidates = [...recent.values()].filter((m) =>
|
|
1530
|
-
m?.author?.id === DANK_MEMER_ID && (m.createdTimestamp || 0) >= sentAt - 10000
|
|
1531
|
-
);
|
|
1532
|
-
for (const m of candidates) {
|
|
1533
|
-
const t = await readBalanceText(m, true);
|
|
1534
|
-
if (looksLikeBalance(t)) return m;
|
|
1535
|
-
}
|
|
1536
|
-
} catch {}
|
|
1537
|
-
await new Promise((r) => setTimeout(r, 700));
|
|
1538
|
-
}
|
|
1539
|
-
return null;
|
|
1522
|
+
|| lower.includes('wallet') || /<a?:coin:\d+>/i.test(t) || /<a?:bank:\d+>/i.test(t);
|
|
1540
1523
|
};
|
|
1541
1524
|
|
|
1525
|
+
// Fast path: send command, wait 3s, read from rawLogger
|
|
1542
1526
|
if (this.account.use_slash && this.channel?.sendSlash) {
|
|
1543
1527
|
await this.channel.sendSlash(DANK_MEMER_ID, 'balance').catch(() => this.channel.send('/balance'));
|
|
1544
1528
|
} else {
|
|
1545
1529
|
await this.channel.send(`${prefix} bal`);
|
|
1546
1530
|
}
|
|
1547
|
-
|
|
1531
|
+
await new Promise(r => setTimeout(r, 3000));
|
|
1548
1532
|
|
|
1549
|
-
//
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1533
|
+
// Try rawLogger first — it captures CV2 text from gateway instantly
|
|
1534
|
+
let text = '';
|
|
1535
|
+
const rawData = rawLogger.getLastRaw(this.channel?.id);
|
|
1536
|
+
if (rawData && rawData.cv2Text && looksLikeBalance(rawData.cv2Text)) {
|
|
1537
|
+
text = rawData.cv2Text;
|
|
1538
|
+
} else if (rawData && rawData.allText && looksLikeBalance(rawData.allText)) {
|
|
1539
|
+
text = rawData.allText;
|
|
1553
1540
|
}
|
|
1554
1541
|
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
if (edited) {
|
|
1562
|
-
text = await readBalanceText(edited, true);
|
|
1563
|
-
response = edited;
|
|
1564
|
-
}
|
|
1542
|
+
// If rawLogger didn't capture it, fall back to waitForDankMemer
|
|
1543
|
+
if (!text || !looksLikeBalance(text)) {
|
|
1544
|
+
let response = await this.waitForDankMemer(8000);
|
|
1545
|
+
if (!response && this.account.use_slash) {
|
|
1546
|
+
await this.channel.send('pls bal');
|
|
1547
|
+
response = await this.waitForDankMemer(8000);
|
|
1565
1548
|
}
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
const fetched = await Promise.resolve(this.channel.messages.fetch(response.id)).catch(() => null);
|
|
1570
|
-
if (fetched) {
|
|
1571
|
-
const fetchedText = await readBalanceText(fetched, true);
|
|
1572
|
-
if (fetchedText) {
|
|
1573
|
-
text = fetchedText;
|
|
1574
|
-
response = fetched;
|
|
1575
|
-
}
|
|
1576
|
-
}
|
|
1549
|
+
if (response) {
|
|
1550
|
+
if (isCV2(response)) await ensureCV2(response);
|
|
1551
|
+
text = stripAnsi(getFullText(response)).replace(/\s+/g, ' ').trim();
|
|
1577
1552
|
}
|
|
1578
|
-
|
|
1579
|
-
// Fallback: scan latest Dank messages right after command send.
|
|
1553
|
+
// One more rawLogger check after the wait
|
|
1580
1554
|
if (!text || !looksLikeBalance(text)) {
|
|
1581
|
-
const
|
|
1582
|
-
if (
|
|
1583
|
-
|
|
1584
|
-
response = recentBalance;
|
|
1585
|
-
}
|
|
1586
|
-
}
|
|
1587
|
-
|
|
1588
|
-
// Last resort: wait for CV2 content propagation then re-fetch
|
|
1589
|
-
if ((!text || !looksLikeBalance(text)) && response.id && this.channel?.messages?.fetch) {
|
|
1590
|
-
await new Promise(r => setTimeout(r, 3000));
|
|
1591
|
-
try {
|
|
1592
|
-
const fresh = await this.channel.messages.fetch(response.id);
|
|
1593
|
-
if (fresh) {
|
|
1594
|
-
const freshText = await readBalanceText(fresh, true);
|
|
1595
|
-
if (freshText && looksLikeBalance(freshText)) {
|
|
1596
|
-
text = freshText;
|
|
1597
|
-
response = fresh;
|
|
1598
|
-
}
|
|
1599
|
-
}
|
|
1600
|
-
} catch {}
|
|
1601
|
-
}
|
|
1602
|
-
|
|
1603
|
-
// Absolute last: re-scan channel messages after the extra wait
|
|
1604
|
-
if (!text || !looksLikeBalance(text)) {
|
|
1605
|
-
const recentBalance2 = await findRecentBalanceMessage();
|
|
1606
|
-
if (recentBalance2) {
|
|
1607
|
-
text = await readBalanceText(recentBalance2, true);
|
|
1608
|
-
response = recentBalance2;
|
|
1609
|
-
}
|
|
1610
|
-
}
|
|
1611
|
-
|
|
1612
|
-
// Raw logger fallback — CV2 text is captured directly from gateway
|
|
1613
|
-
if (!text || !looksLikeBalance(text)) {
|
|
1614
|
-
const rawData = rawLogger.getLastRaw(this.channel?.id);
|
|
1615
|
-
if (rawData && rawData.cv2Text) {
|
|
1616
|
-
const rawText = rawData.cv2Text;
|
|
1617
|
-
if (looksLikeBalance(rawText)) {
|
|
1618
|
-
text = rawText;
|
|
1619
|
-
this.log('debug', 'Balance: using rawLogger CV2 text fallback');
|
|
1620
|
-
}
|
|
1621
|
-
}
|
|
1622
|
-
// Also try from Redis raw message
|
|
1623
|
-
if ((!text || !looksLikeBalance(text)) && response?.id) {
|
|
1624
|
-
try {
|
|
1625
|
-
const rawMsg = await rawLogger.getMsg(response.id);
|
|
1626
|
-
if (rawMsg?.allText && looksLikeBalance(rawMsg.allText)) {
|
|
1627
|
-
text = rawMsg.allText;
|
|
1628
|
-
this.log('debug', 'Balance: using rawLogger Redis fallback');
|
|
1629
|
-
}
|
|
1630
|
-
} catch {}
|
|
1631
|
-
}
|
|
1632
|
-
}
|
|
1633
|
-
|
|
1634
|
-
if (!text) {
|
|
1635
|
-
this.log('warn', 'Balance response was empty after waiting for update');
|
|
1636
|
-
return;
|
|
1637
|
-
}
|
|
1638
|
-
|
|
1639
|
-
if (!looksLikeBalance(text)) {
|
|
1640
|
-
this.log('warn', `Balance response did not look like balance card: "${text.substring(0, 140)}"`);
|
|
1641
|
-
return;
|
|
1555
|
+
const raw2 = rawLogger.getLastRaw(this.channel?.id);
|
|
1556
|
+
if (raw2?.cv2Text && looksLikeBalance(raw2.cv2Text)) text = raw2.cv2Text;
|
|
1557
|
+
else if (raw2?.allText && looksLikeBalance(raw2.allText)) text = raw2.allText;
|
|
1642
1558
|
}
|
|
1559
|
+
}
|
|
1643
1560
|
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1561
|
+
if (!text || !looksLikeBalance(text)) {
|
|
1562
|
+
this.log('warn', 'Balance: no data after all attempts');
|
|
1563
|
+
return;
|
|
1564
|
+
}
|
|
1647
1565
|
|
|
1648
|
-
//
|
|
1566
|
+
// Parse wallet and bank
|
|
1567
|
+
let wallet = 0, bank = 0, matched = '';
|
|
1649
1568
|
const coinMatch = text.match(/<a?:Coin:\d+>\s*([\d,]+)/i);
|
|
1650
1569
|
const bankEmojiMatch = text.match(/<a?:Bank:\d+>\s*([\d,]+)/i);
|
|
1651
1570
|
const bankSlashMatch = text.match(/(?:<a?:Bank:\d+>\s*)?([\d,]+)\s*\/\s*[\d,]+/i);
|
|
1652
1571
|
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1572
|
+
// Legacy embed format: Wallet: ⏣ 1,234,567
|
|
1573
|
+
const walletMatch = text.match(/wallet[:\s]*\*{0,2}\s*[⏣💰]?\s*\*{0,2}\s*([\d,]+)/i);
|
|
1574
|
+
const bankTextMatch = text.match(/bank[:\s]*\*{0,2}\s*[⏣💰]?\s*\*{0,2}\s*([\d,]+)/i);
|
|
1656
1575
|
|
|
1657
|
-
|
|
1658
|
-
|
|
1576
|
+
// Fallback: any numbers near ⏣ or just plain numbers in CV2 text
|
|
1577
|
+
const allNums = [...text.matchAll(/(?:⏣\s*)?(\d[\d,]*\d)/g)].map(m => parseInt(m[1].replace(/,/g, ''), 10)).filter(n => n > 0);
|
|
1659
1578
|
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1579
|
+
if (coinMatch) {
|
|
1580
|
+
wallet = parseInt(coinMatch[1].replace(/,/g, ''), 10);
|
|
1581
|
+
matched = 'cv2-emoji';
|
|
1582
|
+
} else if (walletMatch) {
|
|
1583
|
+
wallet = parseInt(walletMatch[1].replace(/,/g, ''), 10);
|
|
1584
|
+
matched = 'legacy-wallet';
|
|
1585
|
+
} else if (allNums.length > 0) {
|
|
1586
|
+
wallet = Math.max(...allNums);
|
|
1587
|
+
matched = 'fallback-nums';
|
|
1588
|
+
}
|
|
1670
1589
|
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1590
|
+
if (bankEmojiMatch) {
|
|
1591
|
+
bank = parseInt(bankEmojiMatch[1].replace(/,/g, ''), 10);
|
|
1592
|
+
matched += matched ? '+bank-emoji' : 'bank-emoji';
|
|
1593
|
+
} else if (bankTextMatch) {
|
|
1594
|
+
bank = parseInt(bankTextMatch[1].replace(/,/g, ''), 10);
|
|
1595
|
+
matched += matched ? '+bank-text' : 'bank-text';
|
|
1596
|
+
} else if (bankSlashMatch) {
|
|
1597
|
+
bank = parseInt(bankSlashMatch[1].replace(/,/g, ''), 10);
|
|
1598
|
+
matched += matched ? '+bank-slash' : 'bank-slash';
|
|
1599
|
+
}
|
|
1681
1600
|
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1601
|
+
if (wallet === 0 && bank === 0) {
|
|
1602
|
+
this.log('warn', `Balance parse returned 0 — raw text: "${text.substring(0, 200)}"`);
|
|
1603
|
+
// Don't overwrite a known-good balance with 0
|
|
1604
|
+
if (this.stats.balance > 0 || this.stats.bankBalance > 0) return;
|
|
1605
|
+
}
|
|
1687
1606
|
|
|
1688
1607
|
this.stats.balance = wallet;
|
|
1689
1608
|
this.stats.bankBalance = bank;
|
|
1690
|
-
this.log('bal', `Wallet: ${c.bold}${c.green}⏣ ${wallet.toLocaleString()}${c.reset} Bank: ${c.bold}${c.cyan}⏣ ${bank.toLocaleString()}${c.reset} Total: ${c.bold}⏣ ${(wallet + bank).toLocaleString()}${c.reset}
|
|
1609
|
+
if (!silent) this.log('bal', `Wallet: ${c.bold}${c.green}⏣ ${wallet.toLocaleString()}${c.reset} Bank: ${c.bold}${c.cyan}⏣ ${bank.toLocaleString()}${c.reset} Total: ${c.bold}⏣ ${(wallet + bank).toLocaleString()}${c.reset}`);
|
|
1691
1610
|
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
try {
|
|
1695
|
-
await redis.set(`dkg:bal:${this.account.id}`, JSON.stringify({ wallet, bank, ts: Date.now() }));
|
|
1696
|
-
} catch {}
|
|
1697
|
-
}
|
|
1698
|
-
|
|
1699
|
-
// Always report to dashboard API
|
|
1611
|
+
// Store in Redis for persistence
|
|
1612
|
+
if (redis) {
|
|
1700
1613
|
try {
|
|
1701
|
-
await
|
|
1702
|
-
|
|
1703
|
-
headers: { Authorization: `Bearer ${API_KEY}`, 'Content-Type': 'application/json' },
|
|
1704
|
-
body: JSON.stringify({
|
|
1705
|
-
account_id: this.account.id,
|
|
1706
|
-
balance: wallet,
|
|
1707
|
-
bank_balance: bank,
|
|
1708
|
-
total_balance: wallet + bank,
|
|
1709
|
-
lifesavers: this._lifesavers ?? null,
|
|
1710
|
-
}),
|
|
1711
|
-
});
|
|
1712
|
-
} catch { /* silent */ }
|
|
1614
|
+
await redis.set(`dkg:bal:${this.account.id}`, JSON.stringify({ wallet, bank, ts: Date.now() }));
|
|
1615
|
+
} catch {}
|
|
1713
1616
|
}
|
|
1617
|
+
|
|
1618
|
+
// Always report to dashboard API
|
|
1619
|
+
try {
|
|
1620
|
+
await fetch(`${API_URL}/api/grinder/status`, {
|
|
1621
|
+
method: 'POST',
|
|
1622
|
+
headers: { Authorization: `Bearer ${API_KEY}`, 'Content-Type': 'application/json' },
|
|
1623
|
+
body: JSON.stringify({
|
|
1624
|
+
account_id: this.account.id,
|
|
1625
|
+
balance: wallet,
|
|
1626
|
+
bank_balance: bank,
|
|
1627
|
+
total_balance: wallet + bank,
|
|
1628
|
+
lifesavers: this._lifesavers ?? null,
|
|
1629
|
+
}),
|
|
1630
|
+
});
|
|
1631
|
+
} catch { /* silent */ }
|
|
1714
1632
|
}
|
|
1715
1633
|
|
|
1716
|
-
// ── Check DM History for deaths/level-ups
|
|
1634
|
+
// ── Check DM History for deaths/level-ups (with retry) ─────
|
|
1717
1635
|
async checkDmHistory() {
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1636
|
+
const maxRetries = 3;
|
|
1637
|
+
const delays = [1000, 2000, 4000];
|
|
1638
|
+
let lastError;
|
|
1639
|
+
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
1640
|
+
try {
|
|
1641
|
+
const dankUser = await this.client.users.fetch(DANK_MEMER_ID);
|
|
1642
|
+
const dm = await dankUser.createDM();
|
|
1643
|
+
this._dmChannelId = dm.id;
|
|
1644
|
+
const recent = await dm.messages.fetch({ limit: 20 });
|
|
1723
1645
|
|
|
1724
1646
|
let deaths = 0, levelUps = 0, currentLevel = 0, lastLifesaverCount = -1;
|
|
1725
1647
|
for (const [, msg] of recent) {
|
|
@@ -1773,11 +1695,16 @@ class AccountWorker {
|
|
|
1773
1695
|
}
|
|
1774
1696
|
}
|
|
1775
1697
|
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1698
|
+
return { deaths, levelUps, currentLevel, lifesavers: lastLifesaverCount, dmChannelId: dm.id };
|
|
1699
|
+
} catch (e) {
|
|
1700
|
+
lastError = e;
|
|
1701
|
+
if (attempt < maxRetries - 1) {
|
|
1702
|
+
await new Promise(r => setTimeout(r, delays[attempt]));
|
|
1703
|
+
}
|
|
1704
|
+
}
|
|
1780
1705
|
}
|
|
1706
|
+
this.log('debug', `DM check failed after ${maxRetries} attempts: ${lastError.message}`);
|
|
1707
|
+
return { deaths: 0, levelUps: 0, currentLevel: 0, lifesavers: -1 };
|
|
1781
1708
|
}
|
|
1782
1709
|
|
|
1783
1710
|
// ── Run Single Command ──────────────────────────────────────
|
|
@@ -1805,20 +1732,68 @@ class AccountWorker {
|
|
|
1805
1732
|
if (shutdownCalled || !this.running) return;
|
|
1806
1733
|
this.stats.commands++;
|
|
1807
1734
|
|
|
1735
|
+
// ── Monthly: only run if balance ≥ 18M (advancements requirement) ──
|
|
1736
|
+
if (cmdName === 'monthly') {
|
|
1737
|
+
const totalBal = (this.stats.balance || 0) + (this.stats.bankBalance || 0);
|
|
1738
|
+
if (totalBal < 18_000_000) {
|
|
1739
|
+
this.log('warn', `[monthly] SKIPPED — balance ${(totalBal / 1e6).toFixed(1)}M < 18M`);
|
|
1740
|
+
await this.setCooldown(cmdName, 86400);
|
|
1741
|
+
return;
|
|
1742
|
+
}
|
|
1743
|
+
// Check if disabled (not premium / no advancement)
|
|
1744
|
+
if (redis) {
|
|
1745
|
+
try {
|
|
1746
|
+
const disabled = await redis.get(`dkg:disabled:${this.account.id}:monthly`);
|
|
1747
|
+
if (disabled) {
|
|
1748
|
+
this.log('warn', `[monthly] SKIPPED — disabled (needs advancement purchase)`);
|
|
1749
|
+
return;
|
|
1750
|
+
}
|
|
1751
|
+
} catch {}
|
|
1752
|
+
}
|
|
1753
|
+
}
|
|
1754
|
+
|
|
1755
|
+
// ── Daily/Monthly: check if already claimed today (avoid wasting a command) ──
|
|
1756
|
+
if (cmdName === 'daily' || cmdName === 'monthly') {
|
|
1757
|
+
if (redis) {
|
|
1758
|
+
try {
|
|
1759
|
+
const done = await redis.get(`dkg:done:${this.account.id}:${cmdName}`);
|
|
1760
|
+
if (done) {
|
|
1761
|
+
const ttl = await redis.ttl(`dkg:done:${this.account.id}:${cmdName}`);
|
|
1762
|
+
this.log('info', `[${cmdName}] already claimed — ${Math.ceil(ttl / 60)}min left`);
|
|
1763
|
+
await this.setCooldown(cmdName, Math.max(60, ttl));
|
|
1764
|
+
return;
|
|
1765
|
+
}
|
|
1766
|
+
} catch {}
|
|
1767
|
+
}
|
|
1768
|
+
}
|
|
1769
|
+
|
|
1770
|
+
// ── Deposit: max once per hour, only when enabled ──
|
|
1771
|
+
if (cmdName === 'dep max') {
|
|
1772
|
+
if (this._lastDepositAt && Date.now() - this._lastDepositAt < 3600_000) {
|
|
1773
|
+
return; // silently skip — too soon
|
|
1774
|
+
}
|
|
1775
|
+
}
|
|
1776
|
+
|
|
1808
1777
|
// ── Lifesaver protection: skip crime/search if 0 lifesavers ──
|
|
1809
1778
|
if (cmdName === 'crime' || cmdName === 'search') {
|
|
1779
|
+
// Fast path: check in-memory lifesaver count (set from inv + DM check)
|
|
1780
|
+
if (this._lifesavers === 0) {
|
|
1781
|
+
this.log('warn', `[${cmdName}] SKIPPED — 0 lifesavers (in-memory)`);
|
|
1782
|
+
await this.setCooldown(cmdName, 3600);
|
|
1783
|
+
return;
|
|
1784
|
+
}
|
|
1810
1785
|
const noLifesaver = await rawLogger.hasNoLifesaverAlert(this.channel?.id);
|
|
1811
1786
|
if (noLifesaver) {
|
|
1812
1787
|
this.log('warn', `[${cmdName}] SKIPPED — no lifesavers! (death detected in DMs)`);
|
|
1813
|
-
await this.setCooldown(cmdName, 3600);
|
|
1788
|
+
await this.setCooldown(cmdName, 3600);
|
|
1814
1789
|
return;
|
|
1815
1790
|
}
|
|
1816
|
-
// Also check Redis key for lifesaver count
|
|
1817
1791
|
if (redis) {
|
|
1818
1792
|
try {
|
|
1819
1793
|
const lsCount = await redis.get(`dkg:lifesavers:${this.account.id}`);
|
|
1820
1794
|
if (lsCount === '0') {
|
|
1821
|
-
this.
|
|
1795
|
+
this._lifesavers = 0;
|
|
1796
|
+
this.log('warn', `[${cmdName}] SKIPPED — 0 lifesavers (Redis)`);
|
|
1822
1797
|
await this.setCooldown(cmdName, 3600);
|
|
1823
1798
|
return;
|
|
1824
1799
|
}
|
|
@@ -1885,7 +1860,7 @@ class AccountWorker {
|
|
|
1885
1860
|
// PostMemes / command-specific cooldown from response
|
|
1886
1861
|
if (resultLower.includes('cannot post another meme') || resultLower.includes('dead meme')) {
|
|
1887
1862
|
const minMatch = result.match(/(\d+)\s*minute/i);
|
|
1888
|
-
const cdSec = minMatch ? parseInt(minMatch[1]) * 60 :
|
|
1863
|
+
const cdSec = minMatch ? parseInt(minMatch[1]) * 60 + 30 : 150; // dead meme = N min + 30s buffer
|
|
1889
1864
|
this.log('warn', `${cmdName} on cooldown: ${cdSec}s`);
|
|
1890
1865
|
await this.setCooldown(cmdName, cdSec);
|
|
1891
1866
|
return;
|
|
@@ -1965,11 +1940,13 @@ class AccountWorker {
|
|
|
1965
1940
|
return;
|
|
1966
1941
|
}
|
|
1967
1942
|
|
|
1968
|
-
// Premium-only command detection — disable
|
|
1943
|
+
// Premium-only command detection — disable permanently
|
|
1969
1944
|
if (resultLower.includes('only available on premium') || resultLower.includes('premium') ||
|
|
1970
|
-
resultLower.includes('buy the ability to use this command')
|
|
1971
|
-
|
|
1972
|
-
|
|
1945
|
+
resultLower.includes('buy the ability to use this command') ||
|
|
1946
|
+
resultLower.includes('advancements upgrades')) {
|
|
1947
|
+
this.log('warn', `${cmdName} requires premium/advancement — DISABLED`);
|
|
1948
|
+
await this.setCooldown(cmdName, 2592000); // 30 days
|
|
1949
|
+
if (redis) try { await redis.set(`dkg:disabled:${this.account.id}:${cmdName}`, '1', 'EX', 2592000); } catch {}
|
|
1973
1950
|
return;
|
|
1974
1951
|
}
|
|
1975
1952
|
|
|
@@ -2076,9 +2053,12 @@ class AccountWorker {
|
|
|
2076
2053
|
}
|
|
2077
2054
|
}
|
|
2078
2055
|
|
|
2079
|
-
// Smart auto-deposit:
|
|
2080
|
-
|
|
2056
|
+
// Smart auto-deposit: max once per hour, only if deposit is enabled
|
|
2057
|
+
const depositEnabled = this.account.cmd_deposit !== false;
|
|
2058
|
+
const depositCooldownOk = !this._lastDepositAt || Date.now() - this._lastDepositAt >= 3600_000;
|
|
2059
|
+
if (depositEnabled && depositCooldownOk && earned > 0 && this.stats.balance > this._autoDepositThreshold) {
|
|
2081
2060
|
this.log('info', `Wallet ⏣ ${this.stats.balance.toLocaleString()} exceeds threshold — auto-depositing`);
|
|
2061
|
+
this._lastDepositAt = Date.now();
|
|
2082
2062
|
try {
|
|
2083
2063
|
await this.channel.send('pls dep max');
|
|
2084
2064
|
await this.waitForDankMemer(6000);
|
|
@@ -2172,10 +2152,10 @@ class AccountWorker {
|
|
|
2172
2152
|
{ key: 'cmd_work', cmd: 'work shift', cdKey: 'cd_work', defaultCd: 1800, priority: 3 },
|
|
2173
2153
|
// Time-gated (run ASAP when available)
|
|
2174
2154
|
{ key: 'cmd_daily', cmd: 'daily', cdKey: 'cd_daily', defaultCd: 86400, priority: 10 },
|
|
2175
|
-
|
|
2155
|
+
// weekly removed — premium only, not available for free users
|
|
2176
2156
|
{ key: 'cmd_monthly', cmd: 'monthly', cdKey: 'cd_monthly', defaultCd: 2592000,priority: 10 },
|
|
2177
2157
|
// Financial safety
|
|
2178
|
-
{ key: 'cmd_deposit', cmd: 'dep max', cdKey: 'cd_deposit', defaultCd:
|
|
2158
|
+
{ key: 'cmd_deposit', cmd: 'dep max', cdKey: 'cd_deposit', defaultCd: 3600, priority: 8 },
|
|
2179
2159
|
{ key: 'cmd_drops', cmd: 'drops', cdKey: 'cd_drops', defaultCd: 86400, priority: 2 },
|
|
2180
2160
|
// Alert is NOT scheduled — it's reactive (listener-based, see grindLoop)
|
|
2181
2161
|
].map(Object.freeze);
|
|
@@ -3034,25 +3014,82 @@ async function start(apiKey, apiUrl) {
|
|
|
3034
3014
|
console.log(` ${checks.join(' ')}`);
|
|
3035
3015
|
console.log('');
|
|
3036
3016
|
|
|
3037
|
-
// ──
|
|
3038
|
-
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
|
|
3017
|
+
// ── Per-account inline login UI ──────────────────────────────
|
|
3018
|
+
// Track login state per account for inline rendering
|
|
3019
|
+
const loginStates = accounts.map((acc, i) => ({
|
|
3020
|
+
name: acc.label || acc.id || '?',
|
|
3021
|
+
done: false,
|
|
3022
|
+
failed: false,
|
|
3023
|
+
worker: null,
|
|
3024
|
+
workerIdx: i,
|
|
3025
|
+
}));
|
|
3026
|
+
|
|
3027
|
+
// Column widths (visible chars)
|
|
3028
|
+
const terminalW = process.stdout.columns || 90;
|
|
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)}`);
|
|
3038
|
+
for (let i = 0; i < loginStates.length; i++) {
|
|
3039
|
+
const s = loginStates[i];
|
|
3040
|
+
const num = `${c.dim}${(i + 1).toString().padStart(colNum - 1)}${c.reset} `;
|
|
3041
|
+
const name = s.name.substring(0, colName).padEnd(colName);
|
|
3042
|
+
console.log(`${num}${c.dim}PN${c.reset} ${name} ${c.dim}${'···'.padEnd(colGuild)}${c.reset} ${c.dim}···${c.reset}`);
|
|
3043
|
+
}
|
|
3044
|
+
console.log(` ${'─'.repeat(totalVis)}`);
|
|
3045
|
+
|
|
3046
|
+
// Draw pending spinners
|
|
3047
|
+
const drawLoginSpinners = () => {
|
|
3048
|
+
for (let i = 0; i < loginStates.length; i++) {
|
|
3049
|
+
const s = loginStates[i];
|
|
3050
|
+
if (s.done || s.failed) continue;
|
|
3051
|
+
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 = s.name.substring(0, colName).padEnd(colName);
|
|
3054
|
+
const guild = 'logging in...'.substring(0, colGuild).padEnd(colGuild);
|
|
3055
|
+
process.stdout.write(`\x1b[${i + 2};0H\x1b[2K ${num}${rgb(139, 92, 246)}${spin}${c.reset} ${name} ${c.dim}${guild}${c.reset} ${c.dim}···${c.reset}`);
|
|
3056
|
+
}
|
|
3057
|
+
process.stdout.write(`\x1b[${loginStates.length + 3};0H`);
|
|
3048
3058
|
};
|
|
3059
|
+
const loginSpinnerInterval = setInterval(drawLoginSpinners, 80);
|
|
3060
|
+
|
|
3061
|
+
// Update a line when an account finishes
|
|
3062
|
+
const finalizeLoginLine = (idx, worker) => {
|
|
3063
|
+
const s = loginStates[idx];
|
|
3064
|
+
if (s.done || s.failed) return;
|
|
3065
|
+
s.done = true;
|
|
3066
|
+
s.worker = worker;
|
|
3067
|
+
|
|
3068
|
+
const num = `${c.dim}${(idx + 1).toString().padStart(colNum - 1)}${c.reset} `;
|
|
3069
|
+
const name = (worker.username || s.name || '?').substring(0, colName).padEnd(colName);
|
|
3070
|
+
|
|
3071
|
+
let sts, guild, cmds;
|
|
3072
|
+
if (worker._tokenInvalid) {
|
|
3073
|
+
sts = `${rgb(239, 68, 68)}✗${c.reset}`;
|
|
3074
|
+
guild = 'INVALID'.padEnd(colGuild);
|
|
3075
|
+
cmds = '···';
|
|
3076
|
+
s.failed = true;
|
|
3077
|
+
} else if (worker.channel) {
|
|
3078
|
+
sts = `${rgb(52, 211, 153)}✓${c.reset}`;
|
|
3079
|
+
const gn = (worker.channel.guild?.name || worker.channel.guild?.id || 'DM').substring(0, colGuild);
|
|
3080
|
+
guild = gn.padEnd(colGuild);
|
|
3081
|
+
cmds = `${worker.stats?.commands || 0}cmds`;
|
|
3082
|
+
} else {
|
|
3083
|
+
sts = `${rgb(251, 146, 60)}⏳${c.reset}`;
|
|
3084
|
+
guild = 'timeout'.padEnd(colGuild);
|
|
3085
|
+
cmds = '···';
|
|
3086
|
+
}
|
|
3049
3087
|
|
|
3050
|
-
|
|
3051
|
-
|
|
3088
|
+
const line = ` ${num}${sts} ${name} ${guild} ${cmds}`;
|
|
3089
|
+
process.stdout.write(`\x1b[${idx + 2};0H\x1b[2K${line}`);
|
|
3090
|
+
};
|
|
3052
3091
|
|
|
3053
|
-
// Phase 1: Login
|
|
3054
|
-
const LOGIN_PROGRESS_EVERY = 10;
|
|
3055
|
-
// Reduced delays: 50-150ms between logins (faster startup for 1k+ accounts)
|
|
3092
|
+
// Phase 1: Login in batches of 10
|
|
3056
3093
|
const parsedGapMin = Number.parseInt(String(process.env.LOGIN_GAP_MIN_MS || '50'), 10);
|
|
3057
3094
|
const parsedGapMax = Number.parseInt(String(process.env.LOGIN_GAP_MAX_MS || '150'), 10);
|
|
3058
3095
|
const LOGIN_GAP_MIN_MS = Number.isFinite(parsedGapMin) && parsedGapMin >= 0 ? parsedGapMin : 50;
|
|
@@ -3063,16 +3100,12 @@ async function start(apiKey, apiUrl) {
|
|
|
3063
3100
|
return LOGIN_GAP_MIN_MS + Math.floor(Math.random() * (LOGIN_GAP_MAX_MS - LOGIN_GAP_MIN_MS + 1));
|
|
3064
3101
|
};
|
|
3065
3102
|
|
|
3066
|
-
// Parallel login in batches of 10 to avoid rate limits while being fast
|
|
3067
|
-
// Within each batch, stagger logins by 100-600ms to avoid gateway flood
|
|
3068
3103
|
const BATCH_SIZE = 10;
|
|
3069
3104
|
for (let i = 0; i < accounts.length; i += BATCH_SIZE) {
|
|
3070
3105
|
if (shutdownCalled) break;
|
|
3071
3106
|
const batch = accounts.slice(i, Math.min(i + BATCH_SIZE, accounts.length));
|
|
3072
3107
|
|
|
3073
|
-
// Staggered parallel login: fire each login with a small jitter delay
|
|
3074
3108
|
await Promise.all(batch.map(async (acc, idx) => {
|
|
3075
|
-
// Stagger within batch: 0ms for first, 100-600ms for subsequent
|
|
3076
3109
|
if (idx > 0) {
|
|
3077
3110
|
const jitter = 100 + Math.floor(Math.random() * 500);
|
|
3078
3111
|
await new Promise(r => setTimeout(r, jitter));
|
|
@@ -3080,30 +3113,29 @@ async function start(apiKey, apiUrl) {
|
|
|
3080
3113
|
const worker = new AccountWorker(acc, i + idx);
|
|
3081
3114
|
workers.push(worker);
|
|
3082
3115
|
workerMap.set(acc.id, worker);
|
|
3116
|
+
loginStates[i + idx].worker = worker;
|
|
3083
3117
|
await worker.start();
|
|
3084
|
-
|
|
3118
|
+
finalizeLoginLine(i + idx, worker);
|
|
3085
3119
|
}));
|
|
3086
3120
|
|
|
3087
|
-
// Small gap between batches
|
|
3088
3121
|
if (i + BATCH_SIZE < accounts.length) {
|
|
3089
|
-
|
|
3090
|
-
await new Promise(r => setTimeout(r, gapMs));
|
|
3122
|
+
await new Promise(r => setTimeout(r, randomLoginGap()));
|
|
3091
3123
|
}
|
|
3092
3124
|
|
|
3093
3125
|
hintGC();
|
|
3094
3126
|
}
|
|
3095
3127
|
|
|
3096
|
-
clearInterval(
|
|
3097
|
-
|
|
3098
|
-
process.stdout.write(`\r${c.clearLine}`);
|
|
3099
|
-
console.log(` ${rgb(52, 211, 153)}✓${c.reset} ${c.bold}Login complete${c.reset} ${rgb(52, 211, 153)}${loginDone}/${accounts.length}${c.reset} ${c.dim}accounts connected${c.reset}`);
|
|
3100
|
-
console.log('');
|
|
3128
|
+
clearInterval(loginSpinnerInterval);
|
|
3129
|
+
process.stdout.write(`\x1b[${loginStates.length + 3};0H`);
|
|
3101
3130
|
|
|
3102
|
-
//
|
|
3131
|
+
// Final summary
|
|
3132
|
+
const loginDone = workers.filter(w => !w._tokenInvalid && w.channel).length;
|
|
3103
3133
|
const invalidWorkers = workers.filter(w => w._tokenInvalid);
|
|
3104
3134
|
const timedOutWorkers = workers.filter(w => !w.channel && !w._tokenInvalid);
|
|
3135
|
+
console.log(` ${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
|
+
console.log('');
|
|
3137
|
+
|
|
3105
3138
|
if (invalidWorkers.length > 0) {
|
|
3106
|
-
console.log('');
|
|
3107
3139
|
log('warn', `${rgb(239, 68, 68)}${invalidWorkers.length} account(s) have INVALID tokens:${c.reset}`);
|
|
3108
3140
|
for (const w of invalidWorkers) {
|
|
3109
3141
|
log('error', ` ✗ ${w.account.label || w.account.id} — token is invalid or expired`);
|
|
@@ -3117,85 +3149,176 @@ async function start(apiKey, apiUrl) {
|
|
|
3117
3149
|
// Filter out workers with invalid tokens from grinding
|
|
3118
3150
|
const activeWorkers = workers.filter(w => !w._tokenInvalid);
|
|
3119
3151
|
|
|
3120
|
-
// Phase 2:
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
|
|
3152
|
+
// ── Phase 2: Inventory check with per-account inline rendering ─────────────────
|
|
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;
|
|
3165
|
+
const iColNum = 4;
|
|
3166
|
+
const iColName = Math.min(22, Math.max(10, Math.floor(tw2 * 0.22)));
|
|
3167
|
+
const iColItems = 8;
|
|
3168
|
+
const iColVal = 14;
|
|
3169
|
+
const iColTries = 10;
|
|
3170
|
+
const iTotalVis = iColNum + iColName + iColItems + iColVal + iColTries + 10;
|
|
3171
|
+
|
|
3172
|
+
console.log(` ${'─'.repeat(iTotalVis)}`);
|
|
3173
|
+
for (let i = 0; i < invStates.length; i++) {
|
|
3174
|
+
const s = invStates[i];
|
|
3175
|
+
const num = `${c.dim}${(i + 1).toString().padStart(iColNum - 1)}${c.reset} `;
|
|
3176
|
+
const name = s.name.substring(0, iColName).padEnd(iColName);
|
|
3177
|
+
console.log(`${num}${c.dim}··${c.reset} ${name} ${c.dim}${'checking...'.padEnd(iColItems)}${c.reset} ${c.dim}${'··'.padEnd(iColVal)}${c.reset} ${c.dim}${''.padEnd(iColTries)}${c.reset}`);
|
|
3178
|
+
}
|
|
3179
|
+
console.log(` ${'─'.repeat(iTotalVis)}`);
|
|
3180
|
+
|
|
3181
|
+
let invDone = 0, invFailed = 0;
|
|
3182
|
+
|
|
3183
|
+
const drawInvSpinners = () => {
|
|
3184
|
+
for (let i = 0; i < invStates.length; i++) {
|
|
3185
|
+
const s = invStates[i];
|
|
3186
|
+
if (s.done || s.failed) continue;
|
|
3187
|
+
const spin = BRAILLE_SPIN[Math.floor(Date.now() / 80) % BRAILLE_SPIN.length];
|
|
3188
|
+
const num = `${c.dim}${(i + 1).toString().padStart(iColNum - 1)}${c.reset} `;
|
|
3189
|
+
const name = s.name.substring(0, iColName).padEnd(iColName);
|
|
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`);
|
|
3136
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
|
+
}
|
|
3137
3209
|
|
|
3138
|
-
|
|
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}`);
|
|
3220
|
+
};
|
|
3139
3221
|
|
|
3140
3222
|
await Promise.all(activeWorkers.map(async (w, i) => {
|
|
3141
3223
|
try {
|
|
3142
|
-
const invRes = await w.checkInventory({
|
|
3143
|
-
|
|
3144
|
-
startupProgress: { current: i + 1, total },
|
|
3145
|
-
requireComplete: true,
|
|
3146
|
-
maxAttempts: 3,
|
|
3147
|
-
});
|
|
3148
|
-
if (invRes?.ok) invDone++;
|
|
3149
|
-
else invFailed++;
|
|
3224
|
+
const invRes = await w.checkInventory({ force: true, requireComplete: true, maxAttempts: 3 });
|
|
3225
|
+
finalizeInvLine(i, invRes);
|
|
3150
3226
|
} catch {
|
|
3151
|
-
|
|
3227
|
+
finalizeInvLine(i, { ok: false });
|
|
3152
3228
|
}
|
|
3153
3229
|
}));
|
|
3154
3230
|
|
|
3155
|
-
clearInterval(
|
|
3156
|
-
process.stdout.write(`\
|
|
3231
|
+
clearInterval(invSpinnerInterval);
|
|
3232
|
+
process.stdout.write(`\x1b[${invStates.length + 3};0H`);
|
|
3157
3233
|
|
|
3158
|
-
// Final summary
|
|
3159
3234
|
if (invFailed > 0) {
|
|
3160
|
-
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}${
|
|
3235
|
+
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}`);
|
|
3161
3236
|
log('error', `${c.red}Not starting grind loops — ${invFailed} accounts failed inventory.${c.reset}`);
|
|
3162
3237
|
return;
|
|
3163
3238
|
}
|
|
3164
3239
|
|
|
3165
|
-
console.log(` ${rgb(52, 211, 153)}✓${c.reset} ${c.bold}Inventory complete${c.reset} ${rgb(52, 211, 153)}${invDone}/${
|
|
3240
|
+
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}`);
|
|
3166
3241
|
console.log('');
|
|
3167
3242
|
|
|
3168
|
-
// Phase 2.5:
|
|
3169
|
-
|
|
3243
|
+
// ── Phase 2.5: Balance check with inline per-account rendering ─────────────────
|
|
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
|
+
|
|
3253
|
+
const bColNum = 4;
|
|
3254
|
+
const bColName = Math.min(22, Math.max(10, Math.floor(tw2 * 0.22)));
|
|
3255
|
+
const bColWallet = 14;
|
|
3256
|
+
const bColBank = 14;
|
|
3257
|
+
const bColTotal = 14;
|
|
3258
|
+
const bColLs = 4;
|
|
3259
|
+
const bTotalVis = bColNum + bColName + bColWallet + bColBank + bColTotal + bColLs + 12;
|
|
3260
|
+
|
|
3261
|
+
console.log(` ${'─'.repeat(bTotalVis)}`);
|
|
3262
|
+
for (let i = 0; i < balStates.length; i++) {
|
|
3263
|
+
const s = balStates[i];
|
|
3264
|
+
const num = `${c.dim}${(i + 1).toString().padStart(bColNum - 1)}${c.reset} `;
|
|
3265
|
+
const name = s.name.substring(0, bColName).padEnd(bColName);
|
|
3266
|
+
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}`);
|
|
3267
|
+
}
|
|
3268
|
+
console.log(` ${'─'.repeat(bTotalVis)}`);
|
|
3170
3269
|
|
|
3171
3270
|
let balDone = 0;
|
|
3172
|
-
const balProgressInterval = setInterval(() => {
|
|
3173
|
-
const spin = BRAILLE_SPIN[Math.floor(Date.now() / 80) % BRAILLE_SPIN.length];
|
|
3174
|
-
process.stdout.write(`\r ${rgb(34, 211, 238)}${spin}${c.reset} ${c.dim}Balance...${c.reset} ${c.bold}${rgb(52, 211, 153)}${balDone}${c.reset}${c.dim}/${c.reset}${c.white}${activeWorkers.length}${c.reset} `);
|
|
3175
|
-
}, 80);
|
|
3176
3271
|
|
|
3177
|
-
|
|
3272
|
+
const drawBalSpinners = () => {
|
|
3273
|
+
for (let i = 0; i < balStates.length; i++) {
|
|
3274
|
+
const s = balStates[i];
|
|
3275
|
+
if (s.done) continue;
|
|
3276
|
+
const spin = BRAILLE_SPIN[Math.floor(Date.now() / 80) % BRAILLE_SPIN.length];
|
|
3277
|
+
const num = `${c.dim}${(i + 1).toString().padStart(bColNum - 1)}${c.reset} `;
|
|
3278
|
+
const name = s.name.substring(0, bColName).padEnd(bColName);
|
|
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`);
|
|
3282
|
+
};
|
|
3283
|
+
const balSpinnerInterval = setInterval(drawBalSpinners, 80);
|
|
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++;
|
|
3292
|
+
|
|
3293
|
+
const num = `${c.dim}${(idx + 1).toString().padStart(bColNum - 1)}${c.reset} `;
|
|
3294
|
+
const name = (w.username || s.name || '?').substring(0, bColName).padEnd(bColName);
|
|
3295
|
+
const ls = w._lifesavers ?? '?';
|
|
3296
|
+
const lsColor = ls === 0 ? rgb(239, 68, 68) : ls <= 2 ? rgb(251, 191, 36) : rgb(52, 211, 153);
|
|
3297
|
+
const wallet = `${c.green}⏣${(s.wallet).toLocaleString()}${c.reset}`.padEnd(bColWallet + 3);
|
|
3298
|
+
const bank = `${c.cyan}⏣${(s.bank).toLocaleString()}${c.reset}`.padEnd(bColBank + 3);
|
|
3299
|
+
const total = `${c.bold}⏣${(s.wallet + s.bank).toLocaleString()}${c.reset}`.padEnd(bColTotal + 3);
|
|
3300
|
+
|
|
3301
|
+
const line = ` ${num}${rgb(52, 211, 153)}✓${c.reset} ${name} ${wallet} ${bank} ${total} ${lsColor}♥${ls}${c.reset}`;
|
|
3302
|
+
process.stdout.write(`\x1b[${idx + 2};0H\x1b[2K${line}`);
|
|
3303
|
+
};
|
|
3304
|
+
|
|
3178
3305
|
await Promise.all(activeWorkers.map(async w => {
|
|
3179
3306
|
try {
|
|
3180
|
-
await w.checkBalance();
|
|
3181
|
-
balDone++;
|
|
3307
|
+
await w.checkBalance(true); // silent: don't spam console during inline rendering
|
|
3182
3308
|
} catch {}
|
|
3309
|
+
const idx = balStates.findIndex(s => s.worker === w);
|
|
3310
|
+
if (idx >= 0) finalizeBalLine(idx, w);
|
|
3183
3311
|
}));
|
|
3184
3312
|
|
|
3185
|
-
clearInterval(
|
|
3186
|
-
process.stdout.write(`\
|
|
3313
|
+
clearInterval(balSpinnerInterval);
|
|
3314
|
+
process.stdout.write(`\x1b[${balStates.length + 3};0H`);
|
|
3187
3315
|
|
|
3188
|
-
//
|
|
3316
|
+
// Balance summary
|
|
3189
3317
|
let totalWallet = 0, totalBank = 0, noLifesaverAccounts = [];
|
|
3190
|
-
for (const
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
totalWallet += wallet;
|
|
3195
|
-
totalBank += bank;
|
|
3196
|
-
const lsColor = ls === 0 ? rgb(239, 68, 68) : ls <= 2 ? rgb(251, 191, 36) : rgb(52, 211, 153);
|
|
3197
|
-
console.log(` ${c.dim}├${c.reset} ${c.bold}${w.username}${c.reset} Wallet: ${c.green}⏣ ${wallet.toLocaleString()}${c.reset} Bank: ${c.cyan}⏣ ${bank.toLocaleString()}${c.reset} Total: ${c.bold}⏣ ${(wallet + bank).toLocaleString()}${c.reset} LS: ${lsColor}${ls}${c.reset}`);
|
|
3198
|
-
if (ls === 0) noLifesaverAccounts.push(w.username);
|
|
3318
|
+
for (const s of balStates) {
|
|
3319
|
+
totalWallet += s.wallet;
|
|
3320
|
+
totalBank += s.bank;
|
|
3321
|
+
if (s.worker._lifesavers === 0) noLifesaverAccounts.push(s.name);
|
|
3199
3322
|
}
|
|
3200
3323
|
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}`);
|
|
3201
3324
|
|