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.
- package/lib/commands/adventure.js +9 -4
- package/lib/commands/crime.js +11 -4
- package/lib/commands/search.js +11 -4
- package/lib/grinder.js +83 -36
- package/package.json +1 -1
|
@@ -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
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
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) {
|
package/lib/commands/crime.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
85
|
+
let btn = pickSafeButton(buttons, safeAnswers);
|
|
84
86
|
if (!btn) {
|
|
85
|
-
|
|
86
|
-
|
|
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}"`);
|
package/lib/commands/search.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
88
|
+
let btn = pickSafeButton(buttons, safeAnswers);
|
|
87
89
|
if (!btn) {
|
|
88
|
-
|
|
89
|
-
|
|
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
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
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:
|
|
849
|
-
const
|
|
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
|
-
|
|
856
|
-
|
|
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
|
|
866
|
-
this.
|
|
867
|
-
|
|
868
|
-
this.
|
|
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
|
|
871
|
-
|
|
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();
|