dankgrinder 6.14.0 → 6.17.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 +166 -9
- package/lib/rawLogger.js +19 -6
- package/package.json +1 -1
package/lib/grinder.js
CHANGED
|
@@ -431,10 +431,34 @@ function renderDashboard() {
|
|
|
431
431
|
lines.push(bTop);
|
|
432
432
|
lines.push(bEmpty);
|
|
433
433
|
|
|
434
|
-
// Title with animated spinner
|
|
435
434
|
const spin = getSpinner('braille');
|
|
436
|
-
|
|
437
|
-
|
|
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}`));
|
|
438
462
|
|
|
439
463
|
// Subtitle info
|
|
440
464
|
const activeCount = workers.filter(w => w.running && !w.paused && !w.dashboardPaused).length;
|
|
@@ -606,6 +630,10 @@ function renderDashboard() {
|
|
|
606
630
|
else if (ls != null) lsStr = `${G}♥${ls}${c.reset}`;
|
|
607
631
|
else lsStr = `${D}♥?${c.reset}`;
|
|
608
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
|
+
|
|
609
637
|
// ── Earned (fixed visible width) ──
|
|
610
638
|
const earnNum = wk.stats.coins || 0;
|
|
611
639
|
let earnStr;
|
|
@@ -621,7 +649,7 @@ function renderDashboard() {
|
|
|
621
649
|
const earnBarFill = earnNum > 0 ? Math.min(colBar, Math.max(1, Math.floor(Math.log10(earnNum + 1)))) : 0;
|
|
622
650
|
const earnBar = progressBar(earnBarFill, colBar, colBar, [52, 211, 153], [40, 40, 55]);
|
|
623
651
|
|
|
624
|
-
lines.push(bRow(` ${D}${origNum}${c.reset} ${stsIcon} ${medalStr}${nameStr} ${balStr} ${lsStr} ${earnStr} ${earnBar} ${actLabel}`));
|
|
652
|
+
lines.push(bRow(` ${D}${origNum}${c.reset} ${stsIcon} ${medalStr}${nameStr} ${balStr} ${lvlStr} ${lsStr} ${earnStr} ${earnBar} ${actLabel}`));
|
|
625
653
|
}
|
|
626
654
|
|
|
627
655
|
// Overflow summary
|
|
@@ -1581,6 +1609,28 @@ class AccountWorker {
|
|
|
1581
1609
|
}
|
|
1582
1610
|
}
|
|
1583
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
|
+
|
|
1584
1634
|
if (!text) {
|
|
1585
1635
|
this.log('warn', 'Balance response was empty after waiting for update');
|
|
1586
1636
|
return;
|
|
@@ -1663,6 +1713,73 @@ class AccountWorker {
|
|
|
1663
1713
|
}
|
|
1664
1714
|
}
|
|
1665
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
|
+
|
|
1666
1783
|
// ── Run Single Command ──────────────────────────────────────
|
|
1667
1784
|
// Each modular command handler sends the command, waits for response,
|
|
1668
1785
|
// handles Hold Tight / cooldowns / item-buying internally.
|
|
@@ -2051,7 +2168,7 @@ class AccountWorker {
|
|
|
2051
2168
|
// Interactive — response-driven CD (handler sets nextCooldownSec)
|
|
2052
2169
|
{ key: 'cmd_adventure', cmd: 'adventure', cdKey: 'cd_adventure', defaultCd: 300, priority: 3 },
|
|
2053
2170
|
{ key: 'cmd_stream', cmd: 'stream', cdKey: 'cd_stream', defaultCd: 600, priority: 3 },
|
|
2054
|
-
|
|
2171
|
+
// scratch removed — requires voting which can't be automated
|
|
2055
2172
|
{ key: 'cmd_work', cmd: 'work shift', cdKey: 'cd_work', defaultCd: 1800, priority: 3 },
|
|
2056
2173
|
// Time-gated (run ASAP when available)
|
|
2057
2174
|
{ key: 'cmd_daily', cmd: 'daily', cdKey: 'cd_daily', defaultCd: 86400, priority: 10 },
|
|
@@ -3048,20 +3165,21 @@ async function start(apiKey, apiUrl) {
|
|
|
3048
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}`);
|
|
3049
3166
|
console.log('');
|
|
3050
3167
|
|
|
3051
|
-
// Phase 2.5: Check balance for ALL accounts
|
|
3168
|
+
// Phase 2.5: Check balance for ALL accounts sequentially (CV2 needs raw logger timing)
|
|
3052
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}`);
|
|
3053
3170
|
|
|
3054
3171
|
let balDone = 0;
|
|
3055
3172
|
const balProgressInterval = setInterval(() => {
|
|
3056
3173
|
const spin = BRAILLE_SPIN[Math.floor(Date.now() / 80) % BRAILLE_SPIN.length];
|
|
3057
|
-
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}${
|
|
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} `);
|
|
3058
3175
|
}, 80);
|
|
3059
3176
|
|
|
3060
|
-
|
|
3177
|
+
// Run in parallel
|
|
3178
|
+
await Promise.all(activeWorkers.map(async w => {
|
|
3061
3179
|
try {
|
|
3062
3180
|
await w.checkBalance();
|
|
3181
|
+
balDone++;
|
|
3063
3182
|
} catch {}
|
|
3064
|
-
balDone++;
|
|
3065
3183
|
}));
|
|
3066
3184
|
|
|
3067
3185
|
clearInterval(balProgressInterval);
|
|
@@ -3086,6 +3204,45 @@ async function start(apiKey, apiUrl) {
|
|
|
3086
3204
|
}
|
|
3087
3205
|
console.log('');
|
|
3088
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
|
+
// Store level and lifesaver for dashboard
|
|
3217
|
+
if (dm.currentLevel > 0) w._level = dm.currentLevel;
|
|
3218
|
+
if (dm.lifesavers >= 0) w._lifesavers = dm.lifesavers;
|
|
3219
|
+
const parts = [];
|
|
3220
|
+
if (dm.currentLevel > 0) parts.push(`Lv${dm.currentLevel}`);
|
|
3221
|
+
if (dm.deaths > 0) parts.push(`${rgb(239, 68, 68)}${dm.deaths} deaths${c.reset}`);
|
|
3222
|
+
if (dm.lifesavers >= 0) {
|
|
3223
|
+
const lc = dm.lifesavers === 0 ? rgb(239, 68, 68) : dm.lifesavers <= 2 ? rgb(251, 191, 36) : rgb(52, 211, 153);
|
|
3224
|
+
parts.push(`${lc}♥${dm.lifesavers}${c.reset}`);
|
|
3225
|
+
}
|
|
3226
|
+
if (parts.length > 0) {
|
|
3227
|
+
console.log(` ${c.dim}├${c.reset} ${c.bold}${w.username}${c.reset} ${parts.join(' ')}`);
|
|
3228
|
+
}
|
|
3229
|
+
} catch {}
|
|
3230
|
+
}
|
|
3231
|
+
if (dmNoLs.length > 0) {
|
|
3232
|
+
console.log(` ${rgb(239, 68, 68)}⚠${c.reset} ${c.bold}${c.red}DM confirms 0 lifesavers:${c.reset} ${dmNoLs.join(', ')}`);
|
|
3233
|
+
// Set Redis keys to block crime/search
|
|
3234
|
+
for (const w of activeWorkers) {
|
|
3235
|
+
if (dmNoLs.includes(w.username) && redis) {
|
|
3236
|
+
try {
|
|
3237
|
+
await redis.set(`dkg:lifesavers:${w.account.id}`, '0', 'EX', 86400);
|
|
3238
|
+
await redis.set(`raw:alert:no-lifesaver:${w.channel?.id}`, '1', 'EX', 86400);
|
|
3239
|
+
} catch {}
|
|
3240
|
+
}
|
|
3241
|
+
}
|
|
3242
|
+
}
|
|
3243
|
+
console.log(` ${rgb(52, 211, 153)}✓${c.reset} ${c.bold}DM check${c.reset} ${c.dim}${dmDeaths} deaths, ${dmLevelUps} level-ups found${c.reset}`);
|
|
3244
|
+
console.log('');
|
|
3245
|
+
|
|
3089
3246
|
console.log(` ${rgb(139, 92, 246)}${c.bold}>>>${c.reset} ${gradientText('Starting grind loops...', [139, 92, 246], [52, 211, 153])}`);
|
|
3090
3247
|
console.log('');
|
|
3091
3248
|
|
package/lib/rawLogger.js
CHANGED
|
@@ -152,6 +152,9 @@ function detectCommand(d) {
|
|
|
152
152
|
if (cv2Text.includes('fishing') || cv2Text.includes('fisherfolk')) return 'fish';
|
|
153
153
|
if (cv2Text.includes('deposit') || cv2Text.includes('bank account')) return 'deposit';
|
|
154
154
|
if (cv2Text.includes('begging') || cv2Text.includes('imagine begging')) return 'beg';
|
|
155
|
+
if (cv2Text.includes('hunting') || cv2Text.includes('went hunting') || cv2Text.includes('hunting rifle')) return 'hunt';
|
|
156
|
+
if (cv2Text.includes('digging') || cv2Text.includes('found nothing while') || cv2Text.includes('you dig')) return 'dig';
|
|
157
|
+
if (cv2Text.includes('great work') || cv2Text.includes('for your shift') || cv2Text.includes('working as') || cv2Text.includes('work shift') || cv2Text.includes('what color was')) return 'work';
|
|
155
158
|
if (cv2Text.includes('weekly')) return 'weekly';
|
|
156
159
|
if (cv2Text.includes('daily')) return 'daily';
|
|
157
160
|
if (cv2Text.includes('inventory')) return 'inventory';
|
|
@@ -174,11 +177,12 @@ function detectCommand(d) {
|
|
|
174
177
|
if (embedText.includes('you searched') || embedText.includes('searched the')) return 'search';
|
|
175
178
|
if (embedText.includes('you committed') || embedText.includes('went outside')) return 'crime';
|
|
176
179
|
// Hunt / dig
|
|
177
|
-
if (embedText.includes('hunting') || embedText.includes('came back with') || embedText.includes('hunting rifle') || embedText.includes('dragon\'s fireball') || embedText.includes('dodge the')) return 'hunt';
|
|
178
|
-
if (embedText.includes('you dig') || embedText.includes('found a') && embedText.includes('
|
|
179
|
-
// Work
|
|
180
|
-
if (embedText.includes('work') && (embedText.includes('shift') || embedText.includes('mini-game') || embedText.includes('color') || embedText.includes('what color'))) return 'work';
|
|
180
|
+
if (embedText.includes('hunting') || embedText.includes('came back with') || embedText.includes('hunting rifle') || embedText.includes('dragon\'s fireball') || embedText.includes('dodge the') || embedText.includes('went hunting') || embedText.includes('hunt') && (embedText.includes('caught') || embedText.includes('brought back') || embedText.includes('attacked') || embedText.includes('nothing') || embedText.includes('laughed') || embedText.includes('escaped') || embedText.includes('fell asleep'))) return 'hunt';
|
|
181
|
+
if (embedText.includes('digging') || embedText.includes('you dig') || embedText.includes('found a') && embedText.includes('dig') || embedText.includes('shovel') || embedText.includes('found nothing while') || embedText.includes('you found') && !embedText.includes('search')) return 'dig';
|
|
182
|
+
// Work — match both minigame prompt AND completion
|
|
183
|
+
if (embedText.includes('work') && (embedText.includes('shift') || embedText.includes('mini-game') || embedText.includes('color') || embedText.includes('what color') || embedText.includes('babysitter') || embedText.includes('great work') || embedText.includes('for your shift'))) return 'work';
|
|
181
184
|
if (embedText.includes('you were given') && embedText.includes('shift')) return 'work';
|
|
185
|
+
if (embedText.includes('working as') || embedText.includes('for your shift')) return 'work';
|
|
182
186
|
// Postmemes
|
|
183
187
|
if (embedText.includes('pick a meme') || embedText.includes('meme posting')) return 'postmemes';
|
|
184
188
|
// Stream
|
|
@@ -355,8 +359,17 @@ function attachDmLogger(client, opts = {}) {
|
|
|
355
359
|
const m = allText.match(/level\s+(\d+)\s+to\s+(\d+)/i);
|
|
356
360
|
dmEvent = { type: 'levelup', from: m ? parseInt(m[1]) : 0, to: m ? parseInt(m[2]) : 0 };
|
|
357
361
|
} else if (allText.includes('lifesaver protected') || allText.includes('you died')) {
|
|
358
|
-
|
|
359
|
-
|
|
362
|
+
// Parse lifesaver count from button labels: "You have 0 Life Saver left"
|
|
363
|
+
let lsLeft = -1;
|
|
364
|
+
const btnText = extractButtons(d.components).map(b => (b.label || '').toLowerCase()).join(' ');
|
|
365
|
+
const btnMatch = btnText.match(/you have (\d+) life\s*saver/i);
|
|
366
|
+
if (btnMatch) lsLeft = parseInt(btnMatch[1]);
|
|
367
|
+
// Fallback to embed text
|
|
368
|
+
if (lsLeft === -1) {
|
|
369
|
+
const ls = allText.match(/(\d+)\s*life\s*saver/i);
|
|
370
|
+
if (ls) lsLeft = parseInt(ls[1]);
|
|
371
|
+
}
|
|
372
|
+
dmEvent = { type: 'death', lifesaversLeft: lsLeft };
|
|
360
373
|
} else if (allText.includes('you were robbed') || allText.includes('just robbed you')) {
|
|
361
374
|
const coins = allText.match(/[⏣]\s*([\d,]+)/);
|
|
362
375
|
dmEvent = { type: 'robbed', amount: coins ? parseInt(coins[1].replace(/,/g, '')) : 0 };
|