dankgrinder 4.9.1 → 4.9.3

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.
@@ -349,10 +349,15 @@ async function runAdventure({ channel, waitForDankMemer, client }) {
349
349
 
350
350
  const menu = response.components[menuRowIdx]?.components[0];
351
351
  const options = menu?.options || [];
352
- if (options.length > 0 && menuRowIdx >= 0) {
353
- lastAdventureIndex = (lastAdventureIndex + 1) % options.length;
354
- const opt = options[lastAdventureIndex];
355
- LOG.info(`[adventure] Selecting [${lastAdventureIndex + 1}/${options.length}]: "${opt.label}"`);
352
+ const PREFERRED = ['space', 'out west'];
353
+ const preferred = options.filter(o =>
354
+ PREFERRED.some(kw => (o.label || '').toLowerCase().includes(kw) || (o.value || '').toLowerCase().includes(kw))
355
+ );
356
+ const pool = preferred.length > 0 ? preferred : options;
357
+
358
+ if (pool.length > 0 && menuRowIdx >= 0) {
359
+ const opt = pool[Math.floor(Math.random() * pool.length)];
360
+ LOG.info(`[adventure] Selecting: "${opt.label}" (from ${pool.length} options)`);
356
361
  try {
357
362
  const selectResult = await response.selectMenu(menuRowIdx, [opt.value]);
358
363
  if (selectResult) {
@@ -6,6 +6,7 @@
6
6
  const {
7
7
  LOG, c, getFullText, parseCoins, getAllButtons, safeClickButton,
8
8
  logMsg, isHoldTight, getHoldTightReason, sleep, humanDelay,
9
+ isCV2, ensureCV2,
9
10
  } = require('./utils');
10
11
 
11
12
  const SAFE_CRIME_OPTIONS = [
@@ -71,8 +72,9 @@ async function runCrime({ channel, waitForDankMemer, safeAnswers }) {
71
72
  return { result: `hold tight (${reason || 'unknown'})`, coins: 0, holdTightReason: reason };
72
73
  }
73
74
 
75
+ if (isCV2(response)) await ensureCV2(response);
74
76
  logMsg(response, 'crime');
75
- const buttons = getAllButtons(response);
77
+ let buttons = getAllButtons(response);
76
78
 
77
79
  if (buttons.length === 0) {
78
80
  const text = getFullText(response);
@@ -80,10 +82,15 @@ async function runCrime({ channel, waitForDankMemer, safeAnswers }) {
80
82
  return { result: text.substring(0, 60), coins: 0 };
81
83
  }
82
84
 
83
- const btn = pickSafeButton(buttons, safeAnswers);
85
+ let btn = pickSafeButton(buttons, safeAnswers);
84
86
  if (!btn) {
85
- LOG.warn('[crime] No safe button found');
86
- return { result: 'no safe option', coins: 0 };
87
+ const clickable = buttons.filter(b => !b.disabled);
88
+ btn = clickable.length > 0 ? clickable[Math.floor(Math.random() * clickable.length)] : null;
89
+ if (!btn) {
90
+ LOG.warn('[crime] No clickable button found');
91
+ return { result: 'no clickable option', coins: 0 };
92
+ }
93
+ LOG.info(`[crime] No safe match — picking random: "${btn.label}"`);
87
94
  }
88
95
 
89
96
  LOG.info(`[crime] Picking: "${btn.label}"`);
@@ -6,6 +6,7 @@
6
6
  const {
7
7
  LOG, c, getFullText, parseCoins, getAllButtons, safeClickButton,
8
8
  logMsg, isHoldTight, getHoldTightReason, sleep, humanDelay,
9
+ isCV2, ensureCV2,
9
10
  } = require('./utils');
10
11
 
11
12
  const SAFE_SEARCH_LOCATIONS = [
@@ -74,8 +75,9 @@ async function runSearch({ channel, waitForDankMemer, safeAnswers }) {
74
75
  return { result: `hold tight (${reason || 'unknown'})`, coins: 0, holdTightReason: reason };
75
76
  }
76
77
 
78
+ if (isCV2(response)) await ensureCV2(response);
77
79
  logMsg(response, 'search');
78
- const buttons = getAllButtons(response);
80
+ let buttons = getAllButtons(response);
79
81
 
80
82
  if (buttons.length === 0) {
81
83
  const text = getFullText(response);
@@ -83,10 +85,15 @@ async function runSearch({ channel, waitForDankMemer, safeAnswers }) {
83
85
  return { result: text.substring(0, 60), coins: 0 };
84
86
  }
85
87
 
86
- const btn = pickSafeButton(buttons, safeAnswers);
88
+ let btn = pickSafeButton(buttons, safeAnswers);
87
89
  if (!btn) {
88
- LOG.warn('[search] No safe button found');
89
- return { result: 'no safe option', coins: 0 };
90
+ const clickable = buttons.filter(b => !b.disabled);
91
+ btn = clickable.length > 0 ? clickable[Math.floor(Math.random() * clickable.length)] : null;
92
+ if (!btn) {
93
+ LOG.warn('[search] No clickable button found');
94
+ return { result: 'no clickable option', coins: 0 };
95
+ }
96
+ LOG.info(`[search] No safe match — picking random: "${btn.label}"`);
90
97
  }
91
98
 
92
99
  LOG.info(`[search] Picking: "${btn.label}"`);
package/lib/grinder.js CHANGED
@@ -795,6 +795,7 @@ class AccountWorker {
795
795
  if (this._lastInvCheck && Date.now() - this._lastInvCheck < 300_000) return;
796
796
  this._invRunning = true;
797
797
  this._lastInvCheck = Date.now();
798
+ this.busy = true;
798
799
  try {
799
800
  this.log('info', 'Checking inventory...');
800
801
  const result = await commands.runInventory({
@@ -804,25 +805,23 @@ class AccountWorker {
804
805
  accountId: this.account.id,
805
806
  redis,
806
807
  });
807
- if (result.items?.length > 0) {
808
- this.log('success', `Inventory: ${result.items.length} items, ⏣ ${(result.totalValue || 0).toLocaleString()} net`);
809
- // Report inventory to API
810
- try {
811
- await fetch(`${API_URL}/api/grinder/inventory`, {
812
- method: 'POST',
813
- headers: { Authorization: `Bearer ${API_KEY}`, 'Content-Type': 'application/json' },
814
- body: JSON.stringify({
815
- account_id: this.account.id,
816
- items: result.items,
817
- totalValue: result.totalValue,
818
- }),
819
- });
820
- } catch {}
821
- }
808
+ this.log('success', `Inventory: ${result.items?.length || 0} items, ⏣ ${(result.totalValue || 0).toLocaleString()} net`);
809
+ try {
810
+ await fetch(`${API_URL}/api/grinder/inventory`, {
811
+ method: 'POST',
812
+ headers: { Authorization: `Bearer ${API_KEY}`, 'Content-Type': 'application/json' },
813
+ body: JSON.stringify({
814
+ account_id: this.account.id,
815
+ items: result.items || [],
816
+ totalValue: result.totalValue || 0,
817
+ }),
818
+ });
819
+ } catch {}
822
820
  } catch (e) {
823
821
  this.log('error', `Inventory check failed: ${e.message}`);
824
822
  } finally {
825
823
  this._invRunning = false;
824
+ this.busy = false;
826
825
  }
827
826
  }
828
827
 
@@ -836,6 +835,7 @@ class AccountWorker {
836
835
 
837
836
  let wallet = 0;
838
837
  let bank = 0;
838
+ let matched = '';
839
839
 
840
840
  // CV2 format: <:Coin:ID> 3,272,896 and <:Bank:ID> 275,000 / 275,000
841
841
  const coinMatch = text.match(/<[a:]?:Coin:\d+>\s*([\d,]+)/i);
@@ -845,15 +845,18 @@ class AccountWorker {
845
845
  const walletMatch = text.match(/wallet[:\s]*\*{0,2}\s*[⏣💰]?\s*\*{0,2}\s*([\d,]+)/i);
846
846
  const bankTextMatch = text.match(/bank[:\s]*\*{0,2}\s*[⏣💰]?\s*\*{0,2}\s*([\d,]+)/i);
847
847
 
848
- // Fallback: justfollowed by number (pick largest)
849
- const coinSymMatch = text.match(/[⏣]\s*([\d,]+)/);
848
+ // Fallback: any numbers near or just plain numbers in CV2 text
849
+ const allNums = [...text.matchAll(/(?:⏣\s*)?(\d[\d,]*\d)/g)].map(m => parseInt(m[1].replace(/,/g, ''), 10)).filter(n => n > 0);
850
850
 
851
851
  if (coinMatch) {
852
852
  wallet = parseInt(coinMatch[1].replace(/,/g, ''), 10);
853
+ matched = 'cv2-emoji';
853
854
  } else if (walletMatch) {
854
855
  wallet = parseInt(walletMatch[1].replace(/,/g, ''), 10);
855
- } else if (coinSymMatch) {
856
- wallet = parseInt(coinSymMatch[1].replace(/,/g, ''), 10);
856
+ matched = 'legacy-wallet';
857
+ } else if (allNums.length > 0) {
858
+ wallet = Math.max(...allNums);
859
+ matched = 'fallback-nums';
857
860
  }
858
861
 
859
862
  if (bankEmojiMatch) {
@@ -862,23 +865,36 @@ class AccountWorker {
862
865
  bank = parseInt(bankTextMatch[1].replace(/,/g, ''), 10);
863
866
  }
864
867
 
865
- if (wallet > 0 || bank > 0) {
866
- this.stats.balance = wallet;
867
- this.stats.bankBalance = bank;
868
- this.log('bal', `Wallet: ${c.bold}${c.green}⏣ ${wallet.toLocaleString()}${c.reset} Bank: ${c.bold}${c.cyan}⏣ ${bank.toLocaleString()}${c.reset}`);
868
+ if (wallet === 0 && bank === 0) {
869
+ this.log('warn', `Balance parse returned 0 — raw text: "${text.substring(0, 200)}"`);
870
+ // Don't overwrite a known-good balance with 0
871
+ if (this.stats.balance > 0 || this.stats.bankBalance > 0) return;
872
+ }
873
+
874
+ this.stats.balance = wallet;
875
+ this.stats.bankBalance = bank;
876
+ this.log('bal', `Wallet: ${c.bold}${c.green}⏣ ${wallet.toLocaleString()}${c.reset} Bank: ${c.bold}${c.cyan}⏣ ${bank.toLocaleString()}${c.reset} ${c.dim}(${matched || 'none'})${c.reset}`);
877
+
878
+ // Store in Redis for persistence
879
+ if (redis) {
869
880
  try {
870
- await fetch(`${API_URL}/api/grinder/status`, {
871
- method: 'POST',
872
- headers: { Authorization: `Bearer ${API_KEY}`, 'Content-Type': 'application/json' },
873
- body: JSON.stringify({
874
- account_id: this.account.id,
875
- balance: wallet,
876
- bank_balance: bank,
877
- total_balance: wallet + bank,
878
- }),
879
- });
880
- } catch { /* silent */ }
881
+ await redis.set(`dkg:bal:${this.account.id}`, JSON.stringify({ wallet, bank, ts: Date.now() }));
882
+ } catch {}
881
883
  }
884
+
885
+ // Always report to dashboard API
886
+ try {
887
+ await fetch(`${API_URL}/api/grinder/status`, {
888
+ method: 'POST',
889
+ headers: { Authorization: `Bearer ${API_KEY}`, 'Content-Type': 'application/json' },
890
+ body: JSON.stringify({
891
+ account_id: this.account.id,
892
+ balance: wallet,
893
+ bank_balance: bank,
894
+ total_balance: wallet + bank,
895
+ }),
896
+ });
897
+ } catch { /* silent */ }
882
898
  }
883
899
  }
884
900
 
@@ -895,7 +911,7 @@ class AccountWorker {
895
911
  case 'with max': cmdString = `${prefix} with max`; break;
896
912
  case 'blackjack': cmdString = `${prefix} bj ${bjBet}`; break;
897
913
  case 'cointoss': cmdString = `${prefix} cointoss ${gambBet}`; break;
898
- case 'roulette': cmdString = `${prefix} roulette ${gambBet} red`; break;
914
+ case 'roulette': cmdString = `${prefix} roulette ${gambBet} ${Math.random() < 0.5 ? 'red' : 'black'}`; break;
899
915
  case 'slots': cmdString = `${prefix} slots ${gambBet}`; break;
900
916
  case 'snakeeyes': cmdString = `${prefix} snakeeyes ${gambBet}`; break;
901
917
  case 'work shift': cmdString = `${prefix} work shift`; break;
@@ -1534,8 +1550,39 @@ class AccountWorker {
1534
1550
  this.log('success', `#${chName} · ${enabledCmds.length} cmds`);
1535
1551
  this.setStatus('starting...');
1536
1552
 
1553
+ // Load daily/weekly/monthly done state from Redis
1554
+ if (redis) {
1555
+ for (const cmd of ['daily', 'weekly', 'monthly', 'drops']) {
1556
+ try {
1557
+ const val = await redis.get(`dkg:done:${this.account.id}:${cmd}`);
1558
+ if (val) {
1559
+ const ttlSec = await redis.ttl(`dkg:done:${this.account.id}:${cmd}`);
1560
+ if (ttlSec > 0) {
1561
+ this.doneToday.set(cmd, Date.now() + ttlSec * 1000);
1562
+ this.log('info', `${cmd} already claimed (${Math.round(ttlSec / 3600)}h remaining)`);
1563
+ }
1564
+ }
1565
+ } catch {}
1566
+ }
1567
+ // Load cached balance from Redis
1568
+ try {
1569
+ const balData = await redis.get(`dkg:bal:${this.account.id}`);
1570
+ if (balData) {
1571
+ const { wallet, bank } = JSON.parse(balData);
1572
+ if (wallet > 0 || bank > 0) {
1573
+ this.stats.balance = wallet;
1574
+ this.stats.bankBalance = bank;
1575
+ await fetch(`${API_URL}/api/grinder/status`, {
1576
+ method: 'POST',
1577
+ headers: { Authorization: `Bearer ${API_KEY}`, 'Content-Type': 'application/json' },
1578
+ body: JSON.stringify({ account_id: this.account.id, balance: wallet, bank_balance: bank, total_balance: wallet + bank }),
1579
+ }).catch(() => {});
1580
+ }
1581
+ }
1582
+ } catch {}
1583
+ }
1584
+
1537
1585
  await this.checkBalance();
1538
- // Check inventory on startup
1539
1586
  this.checkInventory().catch(() => {});
1540
1587
  this.grindLoop();
1541
1588
  resolve();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dankgrinder",
3
- "version": "4.9.1",
3
+ "version": "4.9.3",
4
4
  "description": "Dank Memer automation engine — grind coins while you sleep",
5
5
  "bin": {
6
6
  "dankgrinder": "bin/dankgrinder.js"