dankgrinder 6.8.2 → 6.16.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/blackjack.js +110 -51
- package/lib/commands/crime.js +8 -0
- package/lib/commands/fish.js +7 -0
- package/lib/commands/gamble.js +70 -54
- package/lib/commands/highlow.js +48 -28
- package/lib/commands/hunt.js +153 -32
- package/lib/commands/profile.js +5 -4
- package/lib/commands/search.js +8 -0
- package/lib/commands/utils.js +24 -8
- package/lib/commands/work.js +92 -4
- package/lib/grinder.js +302 -5
- package/lib/rawLogger.js +550 -0
- package/package.json +1 -1
package/lib/grinder.js
CHANGED
|
@@ -2,6 +2,7 @@ const { Client, Options } = require('discord.js-selfbot-v13');
|
|
|
2
2
|
const Redis = require('ioredis');
|
|
3
3
|
const commands = require('./commands');
|
|
4
4
|
const { setDashboardActive, isCV2, ensureCV2, stripAnsi } = require('./commands/utils');
|
|
5
|
+
const rawLogger = require('./rawLogger');
|
|
5
6
|
const {
|
|
6
7
|
BloomFilter, RingBuffer, TokenBucket, EMA, SlidingWindowCounter,
|
|
7
8
|
AhoCorasick, LRUCache, StringPool, AsyncBatchQueue, JitterBackoff,
|
|
@@ -430,10 +431,34 @@ function renderDashboard() {
|
|
|
430
431
|
lines.push(bTop);
|
|
431
432
|
lines.push(bEmpty);
|
|
432
433
|
|
|
433
|
-
// Title with animated spinner
|
|
434
434
|
const spin = getSpinner('braille');
|
|
435
|
-
|
|
436
|
-
|
|
435
|
+
|
|
436
|
+
// Title — big gradient banner
|
|
437
|
+
const titleLines = [
|
|
438
|
+
'██████╗ █████╗ ███╗ ██╗██╗ ██╗ ██████╗ ██████╗ ██╗███╗ ██╗██████╗ ███████╗██████╗',
|
|
439
|
+
'██╔══██╗██╔══██╗████╗ ██║██║ ██╔╝ ██╔════╝ ██╔══██╗██║████╗ ██║██╔══██╗██╔════╝██╔══██╗',
|
|
440
|
+
'██║ ██║███████║██╔██╗ ██║█████╔╝ ██║ ███╗██████╔╝██║██╔██╗ ██║██║ ██║█████╗ ██████╔╝',
|
|
441
|
+
'██║ ██║██╔══██║██║╚██╗██║██╔═██╗ ██║ ██║██╔══██╗██║██║╚██╗██║██║ ██║██╔══╝ ██╔══██╗',
|
|
442
|
+
'██████╔╝██║ ██║██║ ╚████║██║ ██╗ ╚██████╔╝██║ ██║██║██║ ╚████║██████╔╝███████╗██║ ██║',
|
|
443
|
+
'╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═══╝╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝╚═╝ ╚═══╝╚═════╝ ╚══════╝╚═╝ ╚═╝',
|
|
444
|
+
];
|
|
445
|
+
// Check terminal width — fall back to compact title if too narrow
|
|
446
|
+
const termW = (process.stdout.columns || 120) - 6; // account for box borders
|
|
447
|
+
const useBigTitle = termW >= 92;
|
|
448
|
+
if (useBigTitle) {
|
|
449
|
+
for (let i = 0; i < titleLines.length; i++) {
|
|
450
|
+
const t = i / (titleLines.length - 1);
|
|
451
|
+
const from = t < 0.5
|
|
452
|
+
? [lerp(192, 139, t * 2), lerp(132, 92, t * 2), lerp(252, 246, t * 2)]
|
|
453
|
+
: [lerp(139, 34, (t - 0.5) * 2), lerp(92, 211, (t - 0.5) * 2), lerp(246, 238, (t - 0.5) * 2)];
|
|
454
|
+
lines.push(bRow(` ${c.bold}${gradientLine(titleLines[i], from, [52, 211, 153])}${c.reset}`));
|
|
455
|
+
}
|
|
456
|
+
} else {
|
|
457
|
+
const titleGrad = gradientText(' D A N K G R I N D E R ', [192, 132, 252], [52, 211, 153]);
|
|
458
|
+
lines.push(bRow(` ${c.bold}${titleGrad}${c.reset} ${D}v${PKG_VERSION}${c.reset} ${G}${spin}${c.reset}`));
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
lines.push(bRow(` ${D}v${PKG_VERSION}${c.reset} ${G}${spin}${c.reset}`));
|
|
437
462
|
|
|
438
463
|
// Subtitle info
|
|
439
464
|
const activeCount = workers.filter(w => w.running && !w.paused && !w.dashboardPaused).length;
|
|
@@ -597,6 +622,18 @@ function renderDashboard() {
|
|
|
597
622
|
balStr = `${D}⏣${' '.repeat(colBal - 3)}-${c.reset}`;
|
|
598
623
|
}
|
|
599
624
|
|
|
625
|
+
// ── Lifesaver indicator ──
|
|
626
|
+
const ls = wk._lifesavers;
|
|
627
|
+
let lsStr;
|
|
628
|
+
if (ls === 0) lsStr = `${R}♥0${c.reset}`;
|
|
629
|
+
else if (ls != null && ls <= 2) lsStr = `${Y}♥${ls}${c.reset}`;
|
|
630
|
+
else if (ls != null) lsStr = `${G}♥${ls}${c.reset}`;
|
|
631
|
+
else lsStr = `${D}♥?${c.reset}`;
|
|
632
|
+
|
|
633
|
+
// ── Level indicator ──
|
|
634
|
+
const lvl = wk._level || 0;
|
|
635
|
+
const lvlStr = lvl > 0 ? `${Cy}L${lvl}${c.reset}` : `${D}L?${c.reset}`;
|
|
636
|
+
|
|
600
637
|
// ── Earned (fixed visible width) ──
|
|
601
638
|
const earnNum = wk.stats.coins || 0;
|
|
602
639
|
let earnStr;
|
|
@@ -612,7 +649,7 @@ function renderDashboard() {
|
|
|
612
649
|
const earnBarFill = earnNum > 0 ? Math.min(colBar, Math.max(1, Math.floor(Math.log10(earnNum + 1)))) : 0;
|
|
613
650
|
const earnBar = progressBar(earnBarFill, colBar, colBar, [52, 211, 153], [40, 40, 55]);
|
|
614
651
|
|
|
615
|
-
lines.push(bRow(` ${D}${origNum}${c.reset} ${stsIcon} ${medalStr}${nameStr} ${balStr} ${earnStr} ${earnBar} ${actLabel}`));
|
|
652
|
+
lines.push(bRow(` ${D}${origNum}${c.reset} ${stsIcon} ${medalStr}${nameStr} ${balStr} ${lvlStr} ${lsStr} ${earnStr} ${earnBar} ${actLabel}`));
|
|
616
653
|
}
|
|
617
654
|
|
|
618
655
|
// Overflow summary
|
|
@@ -1400,6 +1437,24 @@ class AccountWorker {
|
|
|
1400
1437
|
// Final result on same line
|
|
1401
1438
|
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}`;
|
|
1402
1439
|
process.stdout.write(`\x1b[2K\r${resultLine}\n`);
|
|
1440
|
+
|
|
1441
|
+
// Extract lifesaver count from inventory and cache in Redis
|
|
1442
|
+
if (result.items && redis) {
|
|
1443
|
+
const lsItem = result.items.find(i =>
|
|
1444
|
+
/life\s*saver/i.test(i.name) || /lifesaver/i.test(i.name)
|
|
1445
|
+
);
|
|
1446
|
+
const lsCount = lsItem ? lsItem.qty : 0;
|
|
1447
|
+
this._lifesavers = lsCount;
|
|
1448
|
+
try {
|
|
1449
|
+
await redis.set(`dkg:lifesavers:${this.account.id}`, String(lsCount), 'EX', 86400);
|
|
1450
|
+
if (lsCount === 0) {
|
|
1451
|
+
await redis.set(`raw:alert:no-lifesaver:${this.channel?.id}`, '1', 'EX', 86400);
|
|
1452
|
+
} else {
|
|
1453
|
+
await redis.del(`raw:alert:no-lifesaver:${this.channel?.id}`);
|
|
1454
|
+
}
|
|
1455
|
+
} catch {}
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1403
1458
|
try {
|
|
1404
1459
|
await fetch(`${API_URL}/api/grinder/inventory`, {
|
|
1405
1460
|
method: 'POST',
|
|
@@ -1554,6 +1609,28 @@ class AccountWorker {
|
|
|
1554
1609
|
}
|
|
1555
1610
|
}
|
|
1556
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
|
+
|
|
1557
1634
|
if (!text) {
|
|
1558
1635
|
this.log('warn', 'Balance response was empty after waiting for update');
|
|
1559
1636
|
return;
|
|
@@ -1629,12 +1706,80 @@ class AccountWorker {
|
|
|
1629
1706
|
balance: wallet,
|
|
1630
1707
|
bank_balance: bank,
|
|
1631
1708
|
total_balance: wallet + bank,
|
|
1709
|
+
lifesavers: this._lifesavers ?? null,
|
|
1632
1710
|
}),
|
|
1633
1711
|
});
|
|
1634
1712
|
} catch { /* silent */ }
|
|
1635
1713
|
}
|
|
1636
1714
|
}
|
|
1637
1715
|
|
|
1716
|
+
// ── Check DM History for deaths/level-ups ──────────────────
|
|
1717
|
+
async checkDmHistory() {
|
|
1718
|
+
try {
|
|
1719
|
+
const dankUser = await this.client.users.fetch(DANK_MEMER_ID);
|
|
1720
|
+
const dm = await dankUser.createDM();
|
|
1721
|
+
this._dmChannelId = dm.id;
|
|
1722
|
+
const recent = await dm.messages.fetch({ limit: 20 });
|
|
1723
|
+
|
|
1724
|
+
let deaths = 0, levelUps = 0, currentLevel = 0, lastLifesaverCount = -1;
|
|
1725
|
+
for (const [, msg] of recent) {
|
|
1726
|
+
const text = stripAnsi(getFullText(msg)).toLowerCase();
|
|
1727
|
+
|
|
1728
|
+
// Death detection
|
|
1729
|
+
if (text.includes('you died') || text.includes('lifesaver protected')) {
|
|
1730
|
+
deaths++;
|
|
1731
|
+
// Button label: "You have 0 Life Saver left" or "You have 3 Life Saver left"
|
|
1732
|
+
for (const row of (msg.components || [])) {
|
|
1733
|
+
for (const comp of (row.components || [row])) {
|
|
1734
|
+
const label = (comp.label || '').toLowerCase();
|
|
1735
|
+
const lsMatch = label.match(/you have (\d+) life\s*saver/i);
|
|
1736
|
+
if (lsMatch && lastLifesaverCount === -1) {
|
|
1737
|
+
lastLifesaverCount = parseInt(lsMatch[1]);
|
|
1738
|
+
}
|
|
1739
|
+
}
|
|
1740
|
+
}
|
|
1741
|
+
// Fallback: check text
|
|
1742
|
+
if (lastLifesaverCount === -1) {
|
|
1743
|
+
const lsMatch = text.match(/(\d+)\s*life\s*saver/i);
|
|
1744
|
+
if (lsMatch) lastLifesaverCount = parseInt(lsMatch[1]);
|
|
1745
|
+
}
|
|
1746
|
+
}
|
|
1747
|
+
|
|
1748
|
+
// Level up detection
|
|
1749
|
+
if (text.includes('leveled up') || text.includes('level up')) {
|
|
1750
|
+
levelUps++;
|
|
1751
|
+
const m = text.match(/level\s+\*{0,2}(\d+)\*{0,2}\s+to\s+\*{0,2}(\d+)\*{0,2}/i);
|
|
1752
|
+
if (m && parseInt(m[2]) > currentLevel) {
|
|
1753
|
+
currentLevel = parseInt(m[2]);
|
|
1754
|
+
}
|
|
1755
|
+
}
|
|
1756
|
+
}
|
|
1757
|
+
|
|
1758
|
+
// Update Redis with findings
|
|
1759
|
+
if (redis) {
|
|
1760
|
+
if (currentLevel > 0) {
|
|
1761
|
+
await redis.set(`dkg:level:${this.account.id}`, String(currentLevel), 'EX', 2592000);
|
|
1762
|
+
this._level = currentLevel;
|
|
1763
|
+
this.log('info', `DM level: ${c.bold}${currentLevel}${c.reset}`);
|
|
1764
|
+
}
|
|
1765
|
+
if (lastLifesaverCount >= 0) {
|
|
1766
|
+
await redis.set(`dkg:lifesavers:${this.account.id}`, String(lastLifesaverCount), 'EX', 86400);
|
|
1767
|
+
this._lifesavers = lastLifesaverCount;
|
|
1768
|
+
if (lastLifesaverCount === 0) {
|
|
1769
|
+
await redis.set(`raw:alert:no-lifesaver:${dm.id}`, '1', 'EX', 86400);
|
|
1770
|
+
await redis.set(`raw:alert:no-lifesaver:${this.channel?.id}`, '1', 'EX', 86400);
|
|
1771
|
+
this.log('error', `${c.red}0 LIFESAVERS! Crime/Search will be disabled.${c.reset}`);
|
|
1772
|
+
}
|
|
1773
|
+
}
|
|
1774
|
+
}
|
|
1775
|
+
|
|
1776
|
+
return { deaths, levelUps, currentLevel, lifesavers: lastLifesaverCount, dmChannelId: dm.id };
|
|
1777
|
+
} catch (e) {
|
|
1778
|
+
this.log('debug', `DM check failed: ${e.message}`);
|
|
1779
|
+
return { deaths: 0, levelUps: 0, currentLevel: 0, lifesavers: -1 };
|
|
1780
|
+
}
|
|
1781
|
+
}
|
|
1782
|
+
|
|
1638
1783
|
// ── Run Single Command ──────────────────────────────────────
|
|
1639
1784
|
// Each modular command handler sends the command, waits for response,
|
|
1640
1785
|
// handles Hold Tight / cooldowns / item-buying internally.
|
|
@@ -1660,6 +1805,27 @@ class AccountWorker {
|
|
|
1660
1805
|
if (shutdownCalled || !this.running) return;
|
|
1661
1806
|
this.stats.commands++;
|
|
1662
1807
|
|
|
1808
|
+
// ── Lifesaver protection: skip crime/search if 0 lifesavers ──
|
|
1809
|
+
if (cmdName === 'crime' || cmdName === 'search') {
|
|
1810
|
+
const noLifesaver = await rawLogger.hasNoLifesaverAlert(this.channel?.id);
|
|
1811
|
+
if (noLifesaver) {
|
|
1812
|
+
this.log('warn', `[${cmdName}] SKIPPED — no lifesavers! (death detected in DMs)`);
|
|
1813
|
+
await this.setCooldown(cmdName, 3600); // block for 1 hour
|
|
1814
|
+
return;
|
|
1815
|
+
}
|
|
1816
|
+
// Also check Redis key for lifesaver count
|
|
1817
|
+
if (redis) {
|
|
1818
|
+
try {
|
|
1819
|
+
const lsCount = await redis.get(`dkg:lifesavers:${this.account.id}`);
|
|
1820
|
+
if (lsCount === '0') {
|
|
1821
|
+
this.log('warn', `[${cmdName}] SKIPPED — 0 lifesavers cached`);
|
|
1822
|
+
await this.setCooldown(cmdName, 3600);
|
|
1823
|
+
return;
|
|
1824
|
+
}
|
|
1825
|
+
} catch {}
|
|
1826
|
+
}
|
|
1827
|
+
}
|
|
1828
|
+
|
|
1663
1829
|
const cmdOpts = {
|
|
1664
1830
|
channel: this.channel,
|
|
1665
1831
|
waitForDankMemer: (timeout) => this.waitForDankMemer(timeout),
|
|
@@ -1763,6 +1929,42 @@ class AccountWorker {
|
|
|
1763
1929
|
return;
|
|
1764
1930
|
}
|
|
1765
1931
|
|
|
1932
|
+
// ── Death / lifesaver detection in command responses ──
|
|
1933
|
+
if (resultLower.includes('you died') || resultLower.includes('lifesaver protected')) {
|
|
1934
|
+
this.log('error', `DEATH DETECTED during ${cmdName}!`);
|
|
1935
|
+
// Check for lifesaver count in the response
|
|
1936
|
+
const lsMatch = result.match(/(\d+)\s*life\s*saver/i);
|
|
1937
|
+
const lsCount = lsMatch ? parseInt(lsMatch[1]) : -1;
|
|
1938
|
+
if (redis) {
|
|
1939
|
+
if (lsCount === 0) {
|
|
1940
|
+
// 0 lifesavers — DISABLE crime/search immediately
|
|
1941
|
+
await redis.set(`dkg:lifesavers:${this.account.id}`, '0', 'EX', 86400);
|
|
1942
|
+
await redis.set(`raw:alert:no-lifesaver:${this.channel?.id}`, '1', 'EX', 86400);
|
|
1943
|
+
this.log('error', `0 LIFESAVERS! Disabling crime/search for this account!`);
|
|
1944
|
+
await this.setCooldown('crime', 86400); // 24h
|
|
1945
|
+
await this.setCooldown('search', 86400);
|
|
1946
|
+
sendWebhook('DEATH ALERT', `**${this.username}** died during \`${cmdName}\`!\n**0 lifesavers remaining!**\nCrime/search disabled for 24h.`, 0xef4444);
|
|
1947
|
+
} else if (lsCount > 0) {
|
|
1948
|
+
await redis.set(`dkg:lifesavers:${this.account.id}`, String(lsCount), 'EX', 86400);
|
|
1949
|
+
this.log('warn', `Lifesaver used! ${lsCount} remaining.`);
|
|
1950
|
+
if (lsCount <= 2) {
|
|
1951
|
+
sendWebhook('LOW LIFESAVERS', `**${this.username}** has only **${lsCount}** lifesaver(s) left!`, 0xfbbf24);
|
|
1952
|
+
}
|
|
1953
|
+
}
|
|
1954
|
+
}
|
|
1955
|
+
return;
|
|
1956
|
+
}
|
|
1957
|
+
|
|
1958
|
+
// Died flag from crime/search handler (death detected in the command response)
|
|
1959
|
+
if (cmdResult.died) {
|
|
1960
|
+
this.log('error', `${cmdName} → DIED! Checking lifesaver count...`);
|
|
1961
|
+
// The DM will come separately with the actual death details
|
|
1962
|
+
// For now, be cautious — set a short cooldown and let DM listener handle the rest
|
|
1963
|
+
await this.setCooldown('crime', 300); // 5 min cooldown to check DMs
|
|
1964
|
+
await this.setCooldown('search', 300);
|
|
1965
|
+
return;
|
|
1966
|
+
}
|
|
1967
|
+
|
|
1766
1968
|
// Premium-only command detection — disable for 24h
|
|
1767
1969
|
if (resultLower.includes('only available on premium') || resultLower.includes('premium') ||
|
|
1768
1970
|
resultLower.includes('buy the ability to use this command')) {
|
|
@@ -1966,7 +2168,7 @@ class AccountWorker {
|
|
|
1966
2168
|
// Interactive — response-driven CD (handler sets nextCooldownSec)
|
|
1967
2169
|
{ key: 'cmd_adventure', cmd: 'adventure', cdKey: 'cd_adventure', defaultCd: 300, priority: 3 },
|
|
1968
2170
|
{ key: 'cmd_stream', cmd: 'stream', cdKey: 'cd_stream', defaultCd: 600, priority: 3 },
|
|
1969
|
-
|
|
2171
|
+
// scratch removed — requires voting which can't be automated
|
|
1970
2172
|
{ key: 'cmd_work', cmd: 'work shift', cdKey: 'cd_work', defaultCd: 1800, priority: 3 },
|
|
1971
2173
|
// Time-gated (run ASAP when available)
|
|
1972
2174
|
{ key: 'cmd_daily', cmd: 'daily', cdKey: 'cd_daily', defaultCd: 86400, priority: 10 },
|
|
@@ -2101,6 +2303,8 @@ class AccountWorker {
|
|
|
2101
2303
|
|
|
2102
2304
|
// Set up error/disconnect handlers for auto-recovery
|
|
2103
2305
|
this._attachRecoveryListeners();
|
|
2306
|
+
rawLogger.attachRawLogger(this.client, { channelId: this.account.channel_id });
|
|
2307
|
+
rawLogger.attachDmLogger(this.client);
|
|
2104
2308
|
|
|
2105
2309
|
await this.client.login(this.account.discord_token);
|
|
2106
2310
|
this.channel = await this.client.channels.fetch(this.account.channel_id).catch(() => null);
|
|
@@ -2554,6 +2758,11 @@ class AccountWorker {
|
|
|
2554
2758
|
clearTimeout(timeoutId);
|
|
2555
2759
|
this.username = this.client.user.tag || this.username;
|
|
2556
2760
|
this.avatarUrl = this.client.user.displayAvatarURL?.({ format: 'png', dynamic: true, size: 128 }) || null;
|
|
2761
|
+
|
|
2762
|
+
// Attach raw gateway logger for CV2 component capture
|
|
2763
|
+
rawLogger.attachRawLogger(this.client, { channelId: this.account.channel_id });
|
|
2764
|
+
rawLogger.attachDmLogger(this.client);
|
|
2765
|
+
|
|
2557
2766
|
// Report status non-blocking
|
|
2558
2767
|
fetch(`${API_URL}/api/grinder/status`, {
|
|
2559
2768
|
method: 'POST',
|
|
@@ -2795,6 +3004,27 @@ async function start(apiKey, apiUrl) {
|
|
|
2795
3004
|
const checks = [];
|
|
2796
3005
|
checks.push(`${rgb(52, 211, 153)}✓${c.reset} ${c.white}API${c.reset}`);
|
|
2797
3006
|
if (REDIS_URL) checks.push(redis ? `${rgb(52, 211, 153)}✓${c.reset} ${c.white}Redis${c.reset}` : `${rgb(251, 191, 36)}○${c.reset} ${c.dim}Redis (connecting...)${c.reset}`);
|
|
3007
|
+
|
|
3008
|
+
// Init rawLogger Redis (uses same URL — logs all raw gateway data)
|
|
3009
|
+
if (REDIS_URL) {
|
|
3010
|
+
rawLogger.init(REDIS_URL).catch(() => {});
|
|
3011
|
+
// Listen for DM death events across all accounts
|
|
3012
|
+
rawLogger.onDmEvent((event, raw) => {
|
|
3013
|
+
if (event.type === 'death' && event.lifesaversLeft === 0) {
|
|
3014
|
+
const channelId = raw.channel_id;
|
|
3015
|
+
// Find which worker uses this DM channel and disable their crime/search
|
|
3016
|
+
for (const w of workers) {
|
|
3017
|
+
if (w.client?.user?.dmChannel?.id === channelId || w.channel?.id) {
|
|
3018
|
+
w.log?.('error', `DEATH in DMs! 0 lifesavers — disabling crime/search`);
|
|
3019
|
+
w.setCooldown?.('crime', 86400);
|
|
3020
|
+
w.setCooldown?.('search', 86400);
|
|
3021
|
+
}
|
|
3022
|
+
}
|
|
3023
|
+
sendWebhook?.('DEATH ALERT (DM)', `Account died! **0 lifesavers!**\nCrime/search auto-disabled.`, 0xef4444);
|
|
3024
|
+
}
|
|
3025
|
+
});
|
|
3026
|
+
checks.push(`${rgb(52, 211, 153)}✓${c.reset} ${c.white}RawLog${c.reset}`);
|
|
3027
|
+
}
|
|
2798
3028
|
if (hasZlib) checks.push(`${rgb(52, 211, 153)}✓${c.reset} ${c.white}zlib${c.reset}`);
|
|
2799
3029
|
if (WEBHOOK_URL) checks.push(`${rgb(52, 211, 153)}✓${c.reset} ${c.white}Webhook${c.reset}`);
|
|
2800
3030
|
if (CLUSTER_ENABLED) {
|
|
@@ -2934,6 +3164,73 @@ async function start(apiKey, apiUrl) {
|
|
|
2934
3164
|
|
|
2935
3165
|
console.log(` ${rgb(52, 211, 153)}✓${c.reset} ${c.bold}Inventory complete${c.reset} ${rgb(52, 211, 153)}${invDone}/${total}${c.reset} ${c.dim}all clear${c.reset}`);
|
|
2936
3166
|
console.log('');
|
|
3167
|
+
|
|
3168
|
+
// Phase 2.5: Check balance for ALL accounts sequentially (CV2 needs raw logger timing)
|
|
3169
|
+
console.log(` ${rgb(139, 92, 246)}${BRAILLE_SPIN[0]}${c.reset} ${c.dim}Checking balance for ${c.reset}${c.bold}${activeWorkers.length}${c.reset}${c.dim} accounts...${c.reset}`);
|
|
3170
|
+
|
|
3171
|
+
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}${total}${c.reset} `);
|
|
3175
|
+
}, 80);
|
|
3176
|
+
|
|
3177
|
+
// Run sequentially — parallel causes CV2 text to be empty (raw logger timing)
|
|
3178
|
+
for (const w of activeWorkers) {
|
|
3179
|
+
try {
|
|
3180
|
+
await w.checkBalance();
|
|
3181
|
+
} catch {}
|
|
3182
|
+
balDone++;
|
|
3183
|
+
}
|
|
3184
|
+
|
|
3185
|
+
clearInterval(balProgressInterval);
|
|
3186
|
+
process.stdout.write(`\r${c.clearLine}`);
|
|
3187
|
+
|
|
3188
|
+
// Show balance + lifesaver summary for each account
|
|
3189
|
+
let totalWallet = 0, totalBank = 0, noLifesaverAccounts = [];
|
|
3190
|
+
for (const w of activeWorkers) {
|
|
3191
|
+
const wallet = w.stats?.balance || 0;
|
|
3192
|
+
const bank = w.stats?.bankBalance || 0;
|
|
3193
|
+
const ls = w._lifesavers ?? '?';
|
|
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);
|
|
3199
|
+
}
|
|
3200
|
+
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
|
+
|
|
3202
|
+
if (noLifesaverAccounts.length > 0) {
|
|
3203
|
+
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(', ')}`);
|
|
3204
|
+
}
|
|
3205
|
+
console.log('');
|
|
3206
|
+
|
|
3207
|
+
// Phase 2.75: Check DM history for deaths/level-ups (sequential, fast)
|
|
3208
|
+
console.log(` ${rgb(139, 92, 246)}${BRAILLE_SPIN[0]}${c.reset} ${c.dim}Checking DM history...${c.reset}`);
|
|
3209
|
+
let dmDeaths = 0, dmLevelUps = 0, dmNoLs = [];
|
|
3210
|
+
for (const w of activeWorkers) {
|
|
3211
|
+
try {
|
|
3212
|
+
const dm = await w.checkDmHistory();
|
|
3213
|
+
if (dm.deaths > 0) dmDeaths += dm.deaths;
|
|
3214
|
+
if (dm.levelUps > 0) dmLevelUps += dm.levelUps;
|
|
3215
|
+
if (dm.lifesavers === 0) dmNoLs.push(w.username);
|
|
3216
|
+
const parts = [];
|
|
3217
|
+
if (dm.currentLevel > 0) parts.push(`Lv${dm.currentLevel}`);
|
|
3218
|
+
if (dm.deaths > 0) parts.push(`${rgb(239, 68, 68)}${dm.deaths} deaths${c.reset}`);
|
|
3219
|
+
if (dm.lifesavers >= 0) {
|
|
3220
|
+
const lc = dm.lifesavers === 0 ? rgb(239, 68, 68) : dm.lifesavers <= 2 ? rgb(251, 191, 36) : rgb(52, 211, 153);
|
|
3221
|
+
parts.push(`${lc}♥${dm.lifesavers}${c.reset}`);
|
|
3222
|
+
}
|
|
3223
|
+
if (parts.length > 0) {
|
|
3224
|
+
console.log(` ${c.dim}├${c.reset} ${c.bold}${w.username}${c.reset} ${parts.join(' ')}`);
|
|
3225
|
+
}
|
|
3226
|
+
} catch {}
|
|
3227
|
+
}
|
|
3228
|
+
if (dmNoLs.length > 0) {
|
|
3229
|
+
console.log(` ${rgb(239, 68, 68)}⚠${c.reset} ${c.bold}${c.red}DM confirms 0 lifesavers:${c.reset} ${dmNoLs.join(', ')}`);
|
|
3230
|
+
}
|
|
3231
|
+
console.log(` ${rgb(52, 211, 153)}✓${c.reset} ${c.bold}DM check${c.reset} ${c.dim}${dmDeaths} deaths, ${dmLevelUps} level-ups found${c.reset}`);
|
|
3232
|
+
console.log('');
|
|
3233
|
+
|
|
2937
3234
|
console.log(` ${rgb(139, 92, 246)}${c.bold}>>>${c.reset} ${gradientText('Starting grind loops...', [139, 92, 246], [52, 211, 153])}`);
|
|
2938
3235
|
console.log('');
|
|
2939
3236
|
|