dankgrinder 4.5.1 → 4.8.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 +11 -46
- package/lib/commands/blackjack.js +70 -20
- package/lib/commands/dig.js +1 -1
- package/lib/commands/gamble.js +52 -43
- package/lib/commands/generic.js +5 -4
- package/lib/commands/highlow.js +53 -36
- package/lib/commands/hunt.js +1 -1
- package/lib/commands/index.js +2 -2
- package/lib/commands/postmemes.js +15 -4
- package/lib/commands/shop.js +13 -22
- package/lib/commands/utils.js +24 -1
- package/lib/grinder.js +147 -57
- package/package.json +1 -1
|
@@ -34,11 +34,10 @@
|
|
|
34
34
|
*/
|
|
35
35
|
|
|
36
36
|
const {
|
|
37
|
-
LOG, c, sleep, humanDelay, getFullText, parseCoins,
|
|
38
|
-
getAllButtons, getAllSelectMenus, findButton,
|
|
39
|
-
safeClickButton, isHoldTight, logMsg,
|
|
37
|
+
LOG, c, sleep, humanDelay, getFullText, parseCoins,
|
|
38
|
+
getAllButtons, getAllSelectMenus, findButton,
|
|
39
|
+
safeClickButton, isHoldTight, logMsg,
|
|
40
40
|
} = require('./utils');
|
|
41
|
-
const { buyItem } = require('./shop');
|
|
42
41
|
|
|
43
42
|
// ── Adventure type rotation (cycle through all types each run) ────
|
|
44
43
|
let lastAdventureIndex = -1;
|
|
@@ -63,8 +62,7 @@ async function clickAndRefetch(channel, msg, btn) {
|
|
|
63
62
|
LOG.error(`[adventure] Click error: ${e.message}`);
|
|
64
63
|
return null;
|
|
65
64
|
}
|
|
66
|
-
|
|
67
|
-
await sleep(500);
|
|
65
|
+
await sleep(250);
|
|
68
66
|
return await refetchMsg(channel, msg.id);
|
|
69
67
|
}
|
|
70
68
|
|
|
@@ -172,7 +170,7 @@ async function playAdventureRounds(channel, msg) {
|
|
|
172
170
|
const choice = pickSafeChoice(choices);
|
|
173
171
|
if (choice) {
|
|
174
172
|
LOG.info(`[adventure] → Choosing: "${choice.label}"`);
|
|
175
|
-
await sleep(
|
|
173
|
+
await sleep(100);
|
|
176
174
|
const afterChoice = await clickAndRefetch(channel, current, choice);
|
|
177
175
|
if (afterChoice) {
|
|
178
176
|
current = afterChoice;
|
|
@@ -194,7 +192,7 @@ async function playAdventureRounds(channel, msg) {
|
|
|
194
192
|
const nextBtnNow = getNextButton(current);
|
|
195
193
|
if (nextBtnNow && !nextBtnNow.disabled) {
|
|
196
194
|
LOG.debug(`[adventure] Clicking Next arrow...`);
|
|
197
|
-
await sleep(
|
|
195
|
+
await sleep(100);
|
|
198
196
|
const afterNext = await clickAndRefetch(channel, current, nextBtnNow);
|
|
199
197
|
if (afterNext) {
|
|
200
198
|
current = afterNext;
|
|
@@ -206,7 +204,7 @@ async function playAdventureRounds(channel, msg) {
|
|
|
206
204
|
} else if (nextBtnNow && nextBtnNow.disabled) {
|
|
207
205
|
// Next is disabled but no choices found — might be loading
|
|
208
206
|
LOG.debug(`[adventure] Next disabled, no choices — waiting...`);
|
|
209
|
-
await sleep(
|
|
207
|
+
await sleep(300);
|
|
210
208
|
const refreshed = await refetchMsg(channel, current.id);
|
|
211
209
|
if (refreshed) {
|
|
212
210
|
current = refreshed;
|
|
@@ -219,8 +217,7 @@ async function playAdventureRounds(channel, msg) {
|
|
|
219
217
|
// No next button at all
|
|
220
218
|
LOG.debug(`[adventure] No Next button — checking if done`);
|
|
221
219
|
if (isAdventureDone(current)) break;
|
|
222
|
-
|
|
223
|
-
await sleep(1500);
|
|
220
|
+
await sleep(500);
|
|
224
221
|
const refreshed = await refetchMsg(channel, current.id);
|
|
225
222
|
if (refreshed) {
|
|
226
223
|
current = refreshed;
|
|
@@ -314,42 +311,10 @@ async function runAdventure({ channel, waitForDankMemer, client }) {
|
|
|
314
311
|
return { result: 'cooldown', coins: 0, nextCooldownSec: cooldownSec + 3 };
|
|
315
312
|
}
|
|
316
313
|
|
|
317
|
-
// ── 4) If we need a ticket,
|
|
314
|
+
// ── 4) If we need a ticket, skip (too expensive to auto-buy) ──
|
|
318
315
|
if (needsTicket) {
|
|
319
|
-
LOG.warn(`[adventure]
|
|
320
|
-
|
|
321
|
-
// Ticket costs 250,000 coins — check balance first to avoid wasting time in shop
|
|
322
|
-
const TICKET_COST = 250000;
|
|
323
|
-
let currentBalance = 0;
|
|
324
|
-
await channel.send('pls bal');
|
|
325
|
-
const balMsg = await waitForDankMemer(8000);
|
|
326
|
-
if (balMsg) {
|
|
327
|
-
currentBalance = parseBalance(balMsg);
|
|
328
|
-
LOG.info(`[adventure] Balance: ${c.yellow}⏣ ${currentBalance.toLocaleString()}${c.reset} (ticket costs ⏣ ${TICKET_COST.toLocaleString()})`);
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
if (currentBalance < TICKET_COST) {
|
|
332
|
-
LOG.warn(`[adventure] Not enough coins for ticket (⏣ ${currentBalance.toLocaleString()} < ⏣ ${TICKET_COST.toLocaleString()}). Grind more first.`);
|
|
333
|
-
return { result: `need ticket (⏣ ${currentBalance.toLocaleString()}/${TICKET_COST.toLocaleString()})`, coins: 0, nextCooldownSec: 120 };
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
const bought = await buyItem({
|
|
337
|
-
channel, waitForDankMemer, client,
|
|
338
|
-
itemName: 'Adventure Ticket', quantity: 1,
|
|
339
|
-
});
|
|
340
|
-
|
|
341
|
-
if (!bought) {
|
|
342
|
-
LOG.error('[adventure] Could not buy adventure ticket from shop.');
|
|
343
|
-
return { result: 'need ticket (buy failed)', coins: 0, nextCooldownSec: 120 };
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
LOG.success('[adventure] Tickets purchased! Re-running adventure...');
|
|
347
|
-
await sleep(1500);
|
|
348
|
-
|
|
349
|
-
await channel.send('pls adventure');
|
|
350
|
-
response = await waitForDankMemer(12000);
|
|
351
|
-
if (!response) return { result: 'no response after ticket buy', coins: 0, nextCooldownSec: null };
|
|
352
|
-
logMsg(response, 'adventure-after-buy');
|
|
316
|
+
LOG.warn(`[adventure] No ticket — skipping (too expensive to auto-buy)`);
|
|
317
|
+
return { result: 'no ticket', coins: 0, nextCooldownSec: 3600, skipReason: 'no_ticket' };
|
|
353
318
|
}
|
|
354
319
|
|
|
355
320
|
// ── Check if we're already mid-adventure (no select menu) ──
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Blackjack command handler.
|
|
3
|
-
*
|
|
3
|
+
* Smart hit/stand: follows basic strategy based on player total and dealer upcard.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
const {
|
|
@@ -8,14 +8,50 @@ const {
|
|
|
8
8
|
logMsg, isHoldTight, getHoldTightReason, sleep, humanDelay,
|
|
9
9
|
} = require('./utils');
|
|
10
10
|
|
|
11
|
+
function parsePlayerTotal(text) {
|
|
12
|
+
// Match the player section: "username (Player)\n ♦ J ♦ 2 12"
|
|
13
|
+
// The number after the cards on the player line is the total
|
|
14
|
+
const playerSection = text.match(/\(Player\)[\s\S]*?(\d+)/i);
|
|
15
|
+
if (playerSection) return parseInt(playerSection[1]);
|
|
16
|
+
const totalMatch = text.match(/total[:\s]*\**(\d+)\**/i) || text.match(/value[:\s]*\**(\d+)\**/i);
|
|
17
|
+
return totalMatch ? parseInt(totalMatch[1]) : 0;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function parseDealerUpcard(text) {
|
|
21
|
+
// Match dealer section: "Dank Memer (Dealer)\n ♦ 10 ♣ 8 18"
|
|
22
|
+
const dealerSection = text.match(/\(Dealer\)[\s\S]*?(\d+)/i);
|
|
23
|
+
if (dealerSection) return parseInt(dealerSection[1]);
|
|
24
|
+
return 0;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function parseNetCoins(text) {
|
|
28
|
+
// Parse "Net: ⏣ -5,000" or "Net: ⏣ 5,000"
|
|
29
|
+
const netMatch = text.match(/Net:\s*[⏣o]\s*(-?[\d,]+)/i);
|
|
30
|
+
if (netMatch) {
|
|
31
|
+
return parseInt(netMatch[1].replace(/,/g, ''));
|
|
32
|
+
}
|
|
33
|
+
// Parse "Winnings: ⏣ 5,000"
|
|
34
|
+
const winMatch = text.match(/Winnings:\s*[⏣o]\s*([\d,]+)/i);
|
|
35
|
+
if (winMatch) return parseInt(winMatch[1].replace(/,/g, ''));
|
|
36
|
+
return 0;
|
|
37
|
+
}
|
|
38
|
+
|
|
11
39
|
/**
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* @returns {Promise<{result: string, coins: number}>}
|
|
40
|
+
* Basic strategy decision.
|
|
41
|
+
* - Always hit on ≤ 11
|
|
42
|
+
* - Stand on ≥ 17
|
|
43
|
+
* - 12-16: hit if dealer shows 7+ (strong upcard), else stand
|
|
17
44
|
*/
|
|
18
|
-
|
|
45
|
+
function shouldHit(playerTotal, dealerTotal) {
|
|
46
|
+
if (playerTotal <= 11) return true;
|
|
47
|
+
if (playerTotal >= 17) return false;
|
|
48
|
+
// 12-16: dealer has strong hand (7+), we need to risk hitting
|
|
49
|
+
if (dealerTotal >= 7) return true;
|
|
50
|
+
// Dealer has weak hand (2-6), likely to bust — stand and wait
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function runBlackjack({ channel, waitForDankMemer, betAmount = 5000 }) {
|
|
19
55
|
const cmd = `pls bj ${betAmount}`;
|
|
20
56
|
LOG.cmd(`${c.white}${c.bold}${cmd}${c.reset}`);
|
|
21
57
|
|
|
@@ -42,21 +78,22 @@ async function runBlackjack({ channel, waitForDankMemer, betAmount = 1000 }) {
|
|
|
42
78
|
const buttons = getAllButtons(current);
|
|
43
79
|
if (buttons.length === 0 || buttons.every(b => b.disabled)) break;
|
|
44
80
|
|
|
45
|
-
|
|
46
|
-
const
|
|
47
|
-
const playerTotal = totalMatch ? parseInt(totalMatch[1]) : 0;
|
|
81
|
+
const playerTotal = parsePlayerTotal(text);
|
|
82
|
+
const dealerTotal = parseDealerUpcard(text);
|
|
48
83
|
|
|
49
84
|
let targetBtn;
|
|
50
|
-
if (playerTotal
|
|
85
|
+
if (playerTotal === 0) {
|
|
51
86
|
targetBtn = buttons.find(b => (b.label || '').toLowerCase().includes('stand')) || buttons[1];
|
|
52
|
-
} else {
|
|
87
|
+
} else if (shouldHit(playerTotal, dealerTotal)) {
|
|
53
88
|
targetBtn = buttons.find(b => (b.label || '').toLowerCase().includes('hit')) || buttons[0];
|
|
89
|
+
} else {
|
|
90
|
+
targetBtn = buttons.find(b => (b.label || '').toLowerCase().includes('stand')) || buttons[1];
|
|
54
91
|
}
|
|
55
92
|
|
|
56
93
|
if (!targetBtn || targetBtn.disabled) break;
|
|
57
94
|
|
|
58
|
-
LOG.info(`[bj]
|
|
59
|
-
await humanDelay(
|
|
95
|
+
LOG.info(`[bj] You:${playerTotal} Dealer:${dealerTotal} → ${targetBtn.label}`);
|
|
96
|
+
await humanDelay(400, 900);
|
|
60
97
|
|
|
61
98
|
try {
|
|
62
99
|
const followUp = await safeClickButton(current, targetBtn);
|
|
@@ -70,15 +107,28 @@ async function runBlackjack({ channel, waitForDankMemer, betAmount = 1000 }) {
|
|
|
70
107
|
}
|
|
71
108
|
|
|
72
109
|
const finalText = getFullText(current);
|
|
73
|
-
const
|
|
110
|
+
const net = parseNetCoins(finalText);
|
|
74
111
|
const lower = finalText.toLowerCase();
|
|
75
112
|
|
|
76
|
-
if (
|
|
77
|
-
LOG.coin(`[bj] ${c.green}+⏣ ${
|
|
78
|
-
return { result: `bj → +⏣ ${
|
|
113
|
+
if (net > 0) {
|
|
114
|
+
LOG.coin(`[bj] ${c.green}+⏣ ${net.toLocaleString()}${c.reset}`);
|
|
115
|
+
return { result: `bj → +⏣ ${net.toLocaleString()}`, coins: net };
|
|
116
|
+
}
|
|
117
|
+
if (net < 0) {
|
|
118
|
+
LOG.warn(`[bj] ${c.red}-⏣ ${Math.abs(net).toLocaleString()}${c.reset}`);
|
|
119
|
+
return { result: `bj → -⏣ ${Math.abs(net).toLocaleString()}`, coins: 0, lost: Math.abs(net) };
|
|
79
120
|
}
|
|
80
|
-
if (lower.includes('won')
|
|
81
|
-
|
|
121
|
+
if (lower.includes('won') || lower.includes('beat')) {
|
|
122
|
+
const coins = parseCoins(finalText);
|
|
123
|
+
return { result: `bj → ${c.green}won${c.reset}`, coins };
|
|
124
|
+
}
|
|
125
|
+
if (lower.includes('lost') || lower.includes('bust') || lower.includes('lower score')) {
|
|
126
|
+
return { result: `bj → lost`, coins: 0, lost: betAmount };
|
|
127
|
+
}
|
|
128
|
+
if (lower.includes('tied') || lower.includes('push')) {
|
|
129
|
+
return { result: 'bj → push', coins: 0 };
|
|
130
|
+
}
|
|
131
|
+
|
|
82
132
|
return { result: 'bj done', coins: 0 };
|
|
83
133
|
}
|
|
84
134
|
|
package/lib/commands/dig.js
CHANGED
package/lib/commands/gamble.js
CHANGED
|
@@ -1,23 +1,36 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* Covers:
|
|
2
|
+
* Gambling command handlers.
|
|
3
|
+
* Covers: cointoss, roulette, slots, snakeeyes
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
const {
|
|
7
|
-
LOG, c, getFullText, parseCoins, getAllButtons, safeClickButton,
|
|
7
|
+
LOG, c, getFullText, parseCoins, parseNetCoins, getAllButtons, safeClickButton,
|
|
8
8
|
logMsg, isHoldTight, getHoldTightReason, sleep, humanDelay,
|
|
9
9
|
} = require('./utils');
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
11
|
+
function parseGambleResult(text, cmdName) {
|
|
12
|
+
const net = parseNetCoins(text);
|
|
13
|
+
const lower = text.toLowerCase();
|
|
14
|
+
|
|
15
|
+
if (net > 0) {
|
|
16
|
+
LOG.coin(`[${cmdName}] ${c.green}+⏣ ${net.toLocaleString()}${c.reset}`);
|
|
17
|
+
return { result: `${cmdName} → +⏣ ${net.toLocaleString()}`, coins: net };
|
|
18
|
+
}
|
|
19
|
+
if (net < 0) {
|
|
20
|
+
LOG.warn(`[${cmdName}] ${c.red}-⏣ ${Math.abs(net).toLocaleString()}${c.reset}`);
|
|
21
|
+
return { result: `${cmdName} → -⏣ ${Math.abs(net).toLocaleString()}`, coins: 0, lost: Math.abs(net) };
|
|
22
|
+
}
|
|
23
|
+
const coins = parseCoins(text);
|
|
24
|
+
if (coins > 0) {
|
|
25
|
+
LOG.coin(`[${cmdName}] ${c.green}+⏣ ${coins.toLocaleString()}${c.reset}`);
|
|
26
|
+
return { result: `${cmdName} → +⏣ ${coins.toLocaleString()}`, coins };
|
|
27
|
+
}
|
|
28
|
+
if (lower.includes('won') || lower.includes('beat')) return { result: `${cmdName} → won`, coins: 0 };
|
|
29
|
+
if (lower.includes('lost') || lower.includes('bust')) return { result: `${cmdName} → lost`, coins: 0, lost: coins };
|
|
30
|
+
|
|
31
|
+
return { result: `${cmdName} done`, coins: 0 };
|
|
32
|
+
}
|
|
33
|
+
|
|
21
34
|
async function runGamble({ channel, waitForDankMemer, cmdName, cmdString }) {
|
|
22
35
|
LOG.cmd(`${c.white}${c.bold}${cmdString}${c.reset}`);
|
|
23
36
|
|
|
@@ -36,52 +49,48 @@ async function runGamble({ channel, waitForDankMemer, cmdName, cmdString }) {
|
|
|
36
49
|
return { result: `hold tight (${reason || 'unknown'})`, coins: 0, holdTightReason: reason };
|
|
37
50
|
}
|
|
38
51
|
|
|
52
|
+
// Check for min bet error
|
|
53
|
+
const initText = getFullText(response);
|
|
54
|
+
const initLower = initText.toLowerCase();
|
|
55
|
+
if (initLower.includes("can't bet less than") || initLower.includes('cannot bet less than') || initLower.includes('minimum bet')) {
|
|
56
|
+
const betMatch = initText.match(/less than\s*\*?\*?[⏣o]?\s*([\d,]+)/i) || initText.match(/(\d[\d,]+)/);
|
|
57
|
+
if (betMatch) {
|
|
58
|
+
const minBet = parseInt(betMatch[1].replace(/,/g, ''));
|
|
59
|
+
if (minBet > 0) return { result: `min bet ${minBet}`, coins: 0, newMinBet: minBet };
|
|
60
|
+
}
|
|
61
|
+
return { result: 'min bet error', coins: 0 };
|
|
62
|
+
}
|
|
63
|
+
|
|
39
64
|
logMsg(response, cmdName);
|
|
40
65
|
const text = getFullText(response);
|
|
41
|
-
const coins = parseCoins(text);
|
|
42
66
|
|
|
43
|
-
//
|
|
67
|
+
// For cointoss/gambles with buttons: click a random non-disabled button
|
|
44
68
|
const buttons = getAllButtons(response);
|
|
45
69
|
if (buttons.length > 0) {
|
|
46
|
-
const
|
|
47
|
-
if (
|
|
48
|
-
|
|
49
|
-
|
|
70
|
+
const clickable = buttons.filter(b => !b.disabled);
|
|
71
|
+
if (clickable.length > 0) {
|
|
72
|
+
const btn = clickable[Math.floor(Math.random() * clickable.length)];
|
|
73
|
+
LOG.info(`[${cmdName}] Clicking "${btn.label || '?'}"`);
|
|
74
|
+
await humanDelay(50, 200);
|
|
50
75
|
try {
|
|
51
76
|
const followUp = await safeClickButton(response, btn);
|
|
52
77
|
if (followUp) {
|
|
53
78
|
logMsg(followUp, `${cmdName}-result`);
|
|
54
79
|
const fText = getFullText(followUp);
|
|
55
|
-
|
|
56
|
-
if (fCoins > 0) {
|
|
57
|
-
LOG.coin(`[${cmdName}] ${c.green}+⏣ ${fCoins.toLocaleString()}${c.reset}`);
|
|
58
|
-
return { result: `${cmdName} → +⏣ ${fCoins.toLocaleString()}`, coins: fCoins };
|
|
59
|
-
}
|
|
60
|
-
if (fText.toLowerCase().includes('won')) return { result: `${cmdName} → ${c.green}won${c.reset}`, coins: 0 };
|
|
61
|
-
if (fText.toLowerCase().includes('lost')) return { result: `${cmdName} → ${c.red}lost${c.reset}`, coins: 0 };
|
|
80
|
+
return parseGambleResult(fText, cmdName);
|
|
62
81
|
}
|
|
63
82
|
} catch (e) { LOG.error(`[${cmdName}] Click error: ${e.message}`); }
|
|
64
83
|
}
|
|
65
84
|
}
|
|
66
85
|
|
|
67
|
-
|
|
68
|
-
LOG.coin(`[${cmdName}] ${c.green}+⏣ ${coins.toLocaleString()}${c.reset}`);
|
|
69
|
-
return { result: `${cmdName} → +⏣ ${coins.toLocaleString()}`, coins };
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
const lower = text.toLowerCase();
|
|
73
|
-
if (lower.includes('won')) return { result: `${cmdName} → won`, coins };
|
|
74
|
-
if (lower.includes('lost')) {
|
|
75
|
-
const lostCoins = parseCoins(text);
|
|
76
|
-
return { result: `${cmdName} → lost`, coins: 0, lost: lostCoins };
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
return { result: `${cmdName} done`, coins: 0 };
|
|
86
|
+
return parseGambleResult(text, cmdName);
|
|
80
87
|
}
|
|
81
88
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
89
|
+
/**
|
|
90
|
+
* Cointoss: send "pls cointoss <bet>", then click Heads or Tails randomly.
|
|
91
|
+
*/
|
|
92
|
+
async function runCointoss({ channel, waitForDankMemer, betAmount = 10000 }) {
|
|
93
|
+
return runGamble({ channel, waitForDankMemer, cmdName: 'cointoss', cmdString: `pls cointoss ${betAmount}` });
|
|
85
94
|
}
|
|
86
95
|
|
|
87
96
|
async function runRoulette({ channel, waitForDankMemer, betAmount = 5000 }) {
|
|
@@ -96,4 +105,4 @@ async function runSnakeeyes({ channel, waitForDankMemer, betAmount = 5000 }) {
|
|
|
96
105
|
return runGamble({ channel, waitForDankMemer, cmdName: 'snakeeyes', cmdString: `pls snakeeyes ${betAmount}` });
|
|
97
106
|
}
|
|
98
107
|
|
|
99
|
-
module.exports = { runGamble,
|
|
108
|
+
module.exports = { runGamble, runCointoss, runRoulette, runSlots, runSnakeeyes };
|
package/lib/commands/generic.js
CHANGED
|
@@ -45,7 +45,7 @@ async function runGeneric({ channel, waitForDankMemer, cmdString, cmdName, clien
|
|
|
45
45
|
const missing = needsItem(text);
|
|
46
46
|
if (missing) {
|
|
47
47
|
LOG.warn(`[${cmdName}] Missing ${c.bold}${missing}${c.reset} — auto-buying...`);
|
|
48
|
-
const bought = await buyItem({ channel, waitForDankMemer, client, itemName: missing, quantity:
|
|
48
|
+
const bought = await buyItem({ channel, waitForDankMemer, client, itemName: missing, quantity: 1 });
|
|
49
49
|
if (bought) {
|
|
50
50
|
LOG.success(`[${cmdName}] Bought ${missing}, retrying command...`);
|
|
51
51
|
await sleep(3000);
|
|
@@ -65,11 +65,12 @@ async function runGeneric({ channel, waitForDankMemer, cmdString, cmdName, clien
|
|
|
65
65
|
return { result: `need ${missing} (buy failed)`, coins: 0 };
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
// Handle buttons if present
|
|
68
|
+
// Handle buttons if present — pick a random non-disabled button
|
|
69
69
|
const buttons = getAllButtons(response);
|
|
70
70
|
if (buttons.length > 0) {
|
|
71
|
-
const
|
|
72
|
-
|
|
71
|
+
const clickable = buttons.filter(b => !b.disabled);
|
|
72
|
+
const btn = clickable.length > 0 ? clickable[Math.floor(Math.random() * clickable.length)] : null;
|
|
73
|
+
if (btn) {
|
|
73
74
|
LOG.info(`[${cmdName}] Clicking "${btn.label || '?'}"`);
|
|
74
75
|
await humanDelay();
|
|
75
76
|
try {
|
package/lib/commands/highlow.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* HighLow command handler.
|
|
3
|
-
*
|
|
3
|
+
* Strategy: if hint > 50 → lower, if hint < 50 → higher, if exactly 50 → jackpot.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
const {
|
|
@@ -8,38 +8,63 @@ const {
|
|
|
8
8
|
logMsg, isHoldTight, getHoldTightReason, sleep, humanDelay,
|
|
9
9
|
} = require('./utils');
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
function parseHintNumber(text) {
|
|
12
|
+
// Dank Memer shows something like "your hint is **69**" or "The number is 42"
|
|
13
|
+
const hintMatch = text.match(/hint.*?\*\*(\d+)\*\*/i)
|
|
14
|
+
|| text.match(/number.*?\*\*(\d+)\*\*/i)
|
|
15
|
+
|| text.match(/hint.*?(\d+)/i)
|
|
16
|
+
|| text.match(/number.*?(\d+)/i);
|
|
17
|
+
if (hintMatch) return parseInt(hintMatch[1]);
|
|
18
|
+
// Fallback: find any standalone number in the text (1-100 range)
|
|
19
|
+
const nums = text.match(/\b(\d{1,3})\b/g);
|
|
20
|
+
if (nums) {
|
|
21
|
+
for (const n of nums) {
|
|
22
|
+
const v = parseInt(n);
|
|
23
|
+
if (v >= 1 && v <= 100) return v;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function parseNetCoins(text) {
|
|
30
|
+
const netMatch = text.match(/Net:\s*[⏣o]\s*(-?[\d,]+)/i);
|
|
31
|
+
if (netMatch) return parseInt(netMatch[1].replace(/,/g, ''));
|
|
32
|
+
const winMatch = text.match(/Winnings:\s*[⏣o]\s*([\d,]+)/i);
|
|
33
|
+
if (winMatch) return parseInt(winMatch[1].replace(/,/g, ''));
|
|
34
|
+
return 0;
|
|
35
|
+
}
|
|
36
|
+
|
|
14
37
|
async function playHighLow(response, depth = 0) {
|
|
15
38
|
if (!response || depth > 5) return { result: 'done', coins: 0 };
|
|
16
39
|
|
|
17
40
|
const text = getFullText(response);
|
|
18
41
|
const buttons = getAllButtons(response);
|
|
19
42
|
|
|
20
|
-
if (buttons.length === 0) {
|
|
21
|
-
const
|
|
22
|
-
|
|
43
|
+
if (buttons.length === 0 || buttons.every(b => b.disabled)) {
|
|
44
|
+
const net = parseNetCoins(text);
|
|
45
|
+
const coins = net > 0 ? net : parseCoins(text);
|
|
46
|
+
const lost = net < 0 ? Math.abs(net) : 0;
|
|
47
|
+
return { result: coins > 0 ? `+⏣ ${coins.toLocaleString()}` : 'done', coins, lost };
|
|
23
48
|
}
|
|
24
49
|
|
|
25
|
-
|
|
26
|
-
const match = text.match(/number.*?(\d+)/i) || text.match(/(\d+)/);
|
|
50
|
+
const hint = parseHintNumber(text);
|
|
27
51
|
let targetBtn;
|
|
28
52
|
|
|
29
|
-
if (
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
targetBtn = buttons.find(b => (b.label || '').toLowerCase().includes('higher')) || buttons[0];
|
|
53
|
+
if (hint !== null && buttons.length >= 2) {
|
|
54
|
+
if (hint > 50) {
|
|
55
|
+
targetBtn = buttons.find(b => (b.label || '').toLowerCase().includes('lower'));
|
|
56
|
+
} else if (hint < 50) {
|
|
57
|
+
targetBtn = buttons.find(b => (b.label || '').toLowerCase().includes('higher'));
|
|
35
58
|
} else {
|
|
36
|
-
|
|
37
|
-
targetBtn =
|
|
59
|
+
// Exactly 50 — try jackpot, fallback to higher (slightly better odds)
|
|
60
|
+
targetBtn = buttons.find(b => (b.label || '').toLowerCase().includes('jackpot'))
|
|
61
|
+
|| buttons.find(b => (b.label || '').toLowerCase().includes('higher'));
|
|
38
62
|
}
|
|
39
|
-
|
|
63
|
+
if (!targetBtn) targetBtn = buttons.find(b => !b.disabled);
|
|
64
|
+
LOG.info(`[hl] Hint: ${hint} → ${targetBtn?.label || '?'}`);
|
|
40
65
|
} else {
|
|
41
66
|
targetBtn = buttons.find(b => !b.disabled) || buttons[0];
|
|
42
|
-
LOG.info(`[hl] No hint
|
|
67
|
+
LOG.info(`[hl] No hint parsed → ${targetBtn?.label || '?'}`);
|
|
43
68
|
}
|
|
44
69
|
|
|
45
70
|
if (!targetBtn || targetBtn.disabled) {
|
|
@@ -47,26 +72,24 @@ async function playHighLow(response, depth = 0) {
|
|
|
47
72
|
return { result: 'buttons disabled', coins };
|
|
48
73
|
}
|
|
49
74
|
|
|
50
|
-
await humanDelay(
|
|
75
|
+
await humanDelay(400, 900);
|
|
51
76
|
|
|
52
77
|
try {
|
|
53
78
|
const followUp = await safeClickButton(response, targetBtn);
|
|
54
79
|
if (followUp) {
|
|
55
80
|
logMsg(followUp, `hl-round-${depth}`);
|
|
56
81
|
const fText = getFullText(followUp);
|
|
57
|
-
const fCoins = parseCoins(fText);
|
|
58
82
|
|
|
59
|
-
// Check for more rounds
|
|
60
83
|
const moreButtons = getAllButtons(followUp);
|
|
61
84
|
if (moreButtons.length >= 2 && !moreButtons.every(b => b.disabled)) {
|
|
62
|
-
|
|
63
|
-
return { result: deeper.result, coins: fCoins + deeper.coins };
|
|
85
|
+
return playHighLow(followUp, depth + 1);
|
|
64
86
|
}
|
|
65
87
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
88
|
+
// Final round — parse net earnings
|
|
89
|
+
const net = parseNetCoins(fText);
|
|
90
|
+
const coins = net > 0 ? net : parseCoins(fText);
|
|
91
|
+
const lost = net < 0 ? Math.abs(net) : 0;
|
|
92
|
+
return { result: `${targetBtn.label} → ${coins > 0 ? '+⏣ ' + coins.toLocaleString() : 'done'}`, coins, lost };
|
|
70
93
|
}
|
|
71
94
|
} catch (e) {
|
|
72
95
|
LOG.error(`[hl] Click error: ${e.message}`);
|
|
@@ -75,12 +98,6 @@ async function playHighLow(response, depth = 0) {
|
|
|
75
98
|
return { result: 'done', coins: 0 };
|
|
76
99
|
}
|
|
77
100
|
|
|
78
|
-
/**
|
|
79
|
-
* @param {object} opts
|
|
80
|
-
* @param {object} opts.channel
|
|
81
|
-
* @param {function} opts.waitForDankMemer
|
|
82
|
-
* @returns {Promise<{result: string, coins: number}>}
|
|
83
|
-
*/
|
|
84
101
|
async function runHighLow({ channel, waitForDankMemer }) {
|
|
85
102
|
LOG.cmd(`${c.white}${c.bold}pls hl${c.reset}`);
|
|
86
103
|
|
|
@@ -100,13 +117,13 @@ async function runHighLow({ channel, waitForDankMemer }) {
|
|
|
100
117
|
}
|
|
101
118
|
|
|
102
119
|
logMsg(response, 'hl');
|
|
103
|
-
const { result, coins } = await playHighLow(response);
|
|
120
|
+
const { result, coins, lost } = await playHighLow(response);
|
|
104
121
|
|
|
105
122
|
if (coins > 0) {
|
|
106
123
|
LOG.coin(`[hl] ${c.green}+⏣ ${coins.toLocaleString()}${c.reset}`);
|
|
107
124
|
}
|
|
108
125
|
|
|
109
|
-
return { result: `hl → ${result}`, coins };
|
|
126
|
+
return { result: `hl → ${result}`, coins, lost: lost || 0 };
|
|
110
127
|
}
|
|
111
128
|
|
|
112
129
|
module.exports = { runHighLow };
|
package/lib/commands/hunt.js
CHANGED
package/lib/commands/index.js
CHANGED
|
@@ -16,7 +16,7 @@ const { runScratch } = require('./scratch');
|
|
|
16
16
|
const { runBlackjack } = require('./blackjack');
|
|
17
17
|
const { runTrivia, triviaDB } = require('./trivia');
|
|
18
18
|
const { runWorkShift } = require('./work');
|
|
19
|
-
const {
|
|
19
|
+
const { runCointoss, runRoulette, runSlots, runSnakeeyes, runGamble } = require('./gamble');
|
|
20
20
|
const { runDeposit } = require('./deposit');
|
|
21
21
|
const { runGeneric, runAlert } = require('./generic');
|
|
22
22
|
const { runStream } = require('./stream');
|
|
@@ -39,7 +39,7 @@ module.exports = {
|
|
|
39
39
|
runBlackjack,
|
|
40
40
|
runTrivia,
|
|
41
41
|
runWorkShift,
|
|
42
|
-
|
|
42
|
+
runCointoss,
|
|
43
43
|
runRoulette,
|
|
44
44
|
runSlots,
|
|
45
45
|
runSnakeeyes,
|
|
@@ -54,15 +54,26 @@ async function runPostMemes({ channel, waitForDankMemer }) {
|
|
|
54
54
|
|
|
55
55
|
// Check for cooldown or direct text response (no select menus)
|
|
56
56
|
let selects = getAllSelectMenus(response);
|
|
57
|
+
const initText = getFullText(response);
|
|
58
|
+
const initLower = initText.toLowerCase();
|
|
59
|
+
|
|
60
|
+
// Detect "dead meme" / cooldown message → return with nextCooldownSec
|
|
61
|
+
if (initLower.includes('cannot post another meme') || initLower.includes('dead meme') ||
|
|
62
|
+
initLower.includes('another meme for another')) {
|
|
63
|
+
const minMatch = initText.match(/(\d+)\s*minute/i);
|
|
64
|
+
const cdSec = minMatch ? parseInt(minMatch[1]) * 60 : 120;
|
|
65
|
+
LOG.warn(`[pm] Cooldown: ${cdSec}s`);
|
|
66
|
+
return { result: `pm cooldown ${cdSec}s`, coins: 0, nextCooldownSec: cdSec };
|
|
67
|
+
}
|
|
68
|
+
|
|
57
69
|
if (selects.length === 0) {
|
|
58
|
-
const
|
|
59
|
-
const coins = parseCoins(text);
|
|
70
|
+
const coins = parseCoins(initText);
|
|
60
71
|
if (coins > 0) {
|
|
61
72
|
LOG.coin(`[pm] ${c.green}+⏣ ${coins.toLocaleString()}${c.reset}`);
|
|
62
73
|
return { result: `pm → +⏣ ${coins.toLocaleString()}`, coins };
|
|
63
74
|
}
|
|
64
|
-
LOG.info(`[pm] No menus: ${
|
|
65
|
-
return { result:
|
|
75
|
+
LOG.info(`[pm] No menus: ${initText.substring(0, 100).replace(/\n/g, ' ')}`);
|
|
76
|
+
return { result: initText.substring(0, 60), coins: 0 };
|
|
66
77
|
}
|
|
67
78
|
|
|
68
79
|
const msgId = response.id;
|
package/lib/commands/shop.js
CHANGED
|
@@ -32,8 +32,12 @@ const ITEM_COSTS = {
|
|
|
32
32
|
* @returns {Promise<boolean>} true if purchase succeeded
|
|
33
33
|
*/
|
|
34
34
|
async function buyItem({ channel, waitForDankMemer, itemName, quantity = 1, client }) {
|
|
35
|
-
const MAX_RETRIES =
|
|
36
|
-
const
|
|
35
|
+
const MAX_RETRIES = 1;
|
|
36
|
+
const searchNames = [
|
|
37
|
+
itemName.toLowerCase(),
|
|
38
|
+
itemName.toLowerCase().replace('hunting ', '').replace('fishing ', '').replace('adventure ', ''),
|
|
39
|
+
itemName.toLowerCase().split(' ').pop(),
|
|
40
|
+
];
|
|
37
41
|
|
|
38
42
|
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
|
|
39
43
|
LOG.buy(`Opening shop to buy ${c.bold}${quantity}x ${itemName}${c.reset} (attempt ${attempt}/${MAX_RETRIES})`);
|
|
@@ -120,30 +124,17 @@ async function buyItem({ channel, waitForDankMemer, itemName, quantity = 1, clie
|
|
|
120
124
|
logMsg(response, 'shop-after-nav');
|
|
121
125
|
|
|
122
126
|
// Step 3: Find the Buy button for our item
|
|
123
|
-
let buyBtn = getAllButtons(response).find(b =>
|
|
127
|
+
let buyBtn = getAllButtons(response).find(b => {
|
|
128
|
+
if (!b.label) return false;
|
|
129
|
+
const label = b.label.toLowerCase();
|
|
130
|
+
return searchNames.some(s => label.includes(s) || s.includes(label));
|
|
131
|
+
});
|
|
124
132
|
|
|
125
133
|
if (!buyBtn) {
|
|
126
|
-
LOG.warn(`No Buy button found for "${itemName}"
|
|
127
|
-
// Log all available buttons for debugging
|
|
134
|
+
LOG.warn(`No Buy button found for "${itemName}"`);
|
|
128
135
|
const allBtns = getAllButtons(response);
|
|
129
136
|
if (allBtns.length > 0) {
|
|
130
|
-
LOG.debug(`Available
|
|
131
|
-
}
|
|
132
|
-
// Maybe we need to scroll/paginate?
|
|
133
|
-
const nextBtn = findButton(response, 'next') || findButton(response, '▶') || findButton(response, '→');
|
|
134
|
-
if (nextBtn && !nextBtn.disabled) {
|
|
135
|
-
LOG.buy('Clicking next page to find item...');
|
|
136
|
-
try {
|
|
137
|
-
const nextPage = await safeClickButton(response, nextBtn);
|
|
138
|
-
if (nextPage) {
|
|
139
|
-
response = nextPage;
|
|
140
|
-
logMsg(response, 'shop-page2');
|
|
141
|
-
// Try finding button again
|
|
142
|
-
buyBtn = getAllButtons(response).find(b => b.label && b.label.toLowerCase().includes(searchName));
|
|
143
|
-
}
|
|
144
|
-
} catch (e) {
|
|
145
|
-
LOG.error(`Page nav failed: ${e.message}`);
|
|
146
|
-
}
|
|
137
|
+
LOG.debug(`Available: ${allBtns.map(b => `"${b.label}"`).join(', ')}`);
|
|
147
138
|
}
|
|
148
139
|
|
|
149
140
|
if (!buyBtn) {
|
package/lib/commands/utils.js
CHANGED
|
@@ -65,7 +65,22 @@ function getFullText(msg) {
|
|
|
65
65
|
|
|
66
66
|
function parseCoins(text) {
|
|
67
67
|
if (!text) return 0;
|
|
68
|
-
//
|
|
68
|
+
// Prefer "Net:" if present (accurate earned/lost from Dank Memer)
|
|
69
|
+
const netMatch = text.match(/Net:\s*[⏣o]\s*(-?[\d,]+)/i);
|
|
70
|
+
if (netMatch) {
|
|
71
|
+
const net = parseInt(netMatch[1].replace(/,/g, ''));
|
|
72
|
+
return net > 0 ? net : 0;
|
|
73
|
+
}
|
|
74
|
+
// Prefer "Winnings:" over random coin values
|
|
75
|
+
const winMatch = text.match(/Winnings:\s*[⏣o]\s*([\d,]+)/i);
|
|
76
|
+
if (winMatch) {
|
|
77
|
+
const w = parseInt(winMatch[1].replace(/,/g, ''));
|
|
78
|
+
if (w > 0) return w;
|
|
79
|
+
}
|
|
80
|
+
// Prefer "placed in your wallet" pattern (daily, beg, etc.)
|
|
81
|
+
const walletMatch = text.match(/⏣\s*([\d,]+)\s*was placed/i);
|
|
82
|
+
if (walletMatch) return parseInt(walletMatch[1].replace(/,/g, ''));
|
|
83
|
+
// Fallback: max ⏣ number
|
|
69
84
|
const matches = text.match(/⏣\s*([\d,]+)/g);
|
|
70
85
|
if (!matches) return 0;
|
|
71
86
|
let best = 0;
|
|
@@ -79,6 +94,13 @@ function parseCoins(text) {
|
|
|
79
94
|
return best;
|
|
80
95
|
}
|
|
81
96
|
|
|
97
|
+
function parseNetCoins(text) {
|
|
98
|
+
if (!text) return 0;
|
|
99
|
+
const netMatch = text.match(/Net:\s*[⏣o]\s*(-?[\d,]+)/i);
|
|
100
|
+
if (netMatch) return parseInt(netMatch[1].replace(/,/g, ''));
|
|
101
|
+
return 0;
|
|
102
|
+
}
|
|
103
|
+
|
|
82
104
|
/**
|
|
83
105
|
* Parse wallet balance from a balance response message.
|
|
84
106
|
* Handles both embed-based and CV2 TEXT_DISPLAY component formats.
|
|
@@ -288,6 +310,7 @@ module.exports = {
|
|
|
288
310
|
humanDelay,
|
|
289
311
|
getFullText,
|
|
290
312
|
parseCoins,
|
|
313
|
+
parseNetCoins,
|
|
291
314
|
parseBalance,
|
|
292
315
|
getAllButtons,
|
|
293
316
|
getAllSelectMenus,
|
package/lib/grinder.js
CHANGED
|
@@ -154,54 +154,75 @@ function renderDashboard() {
|
|
|
154
154
|
lastRenderTime = Date.now();
|
|
155
155
|
|
|
156
156
|
totalBalance = 0; totalCoins = 0; totalCommands = 0;
|
|
157
|
+
let totalErrors = 0;
|
|
157
158
|
for (const w of workers) {
|
|
158
159
|
totalBalance += w.stats.balance || 0;
|
|
159
160
|
totalCoins += w.stats.coins || 0;
|
|
160
161
|
totalCommands += w.stats.commands || 0;
|
|
162
|
+
totalErrors += w.stats.errors || 0;
|
|
161
163
|
}
|
|
164
|
+
const successRate = totalCommands > 0 ? Math.round(((totalCommands - totalErrors) / totalCommands) * 100) : 100;
|
|
162
165
|
|
|
163
166
|
const lines = [];
|
|
164
|
-
const tw = Math.min(process.stdout.columns || 80,
|
|
167
|
+
const tw = Math.min(process.stdout.columns || 80, 78);
|
|
165
168
|
const thinBar = c.dim + '─'.repeat(tw) + c.reset;
|
|
166
|
-
const
|
|
167
|
-
const
|
|
168
|
-
|
|
169
|
-
|
|
169
|
+
const topBar = rgb(139, 92, 246) + c.bold + '━'.repeat(tw) + c.reset;
|
|
170
|
+
const botBar = rgb(34, 211, 238) + c.bold + '━'.repeat(tw) + c.reset;
|
|
171
|
+
|
|
172
|
+
// Status bar
|
|
173
|
+
lines.push(topBar);
|
|
174
|
+
const liveIcon = rgb(52, 211, 153) + '◉' + c.reset;
|
|
175
|
+
const balStr = `${rgb(192, 132, 252)}${c.bold}⏣ ${formatCoins(totalBalance)}${c.reset}`;
|
|
176
|
+
const earnStr = `${rgb(52, 211, 153)}↑ ${formatCoins(totalCoins)}${c.reset}`;
|
|
177
|
+
const cmdStr = `${rgb(96, 165, 250)}${totalCommands}${c.reset}${c.dim} cmds${c.reset}`;
|
|
178
|
+
const rateStr = successRate >= 95
|
|
179
|
+
? `${rgb(52, 211, 153)}${successRate}%${c.reset}`
|
|
180
|
+
: successRate >= 80 ? `${rgb(251, 191, 36)}${successRate}%${c.reset}`
|
|
181
|
+
: `${rgb(239, 68, 68)}${successRate}%${c.reset}`;
|
|
182
|
+
const upStr = `${rgb(251, 191, 36)}⏱ ${formatUptime()}${c.reset}`;
|
|
170
183
|
lines.push(
|
|
171
|
-
` ${
|
|
172
|
-
` ${c.dim}│${c.reset}` +
|
|
173
|
-
` ${rgb(52, 211, 153)}+${formatCoins(totalCoins)}${c.reset}` +
|
|
174
|
-
` ${c.dim}│${c.reset}` +
|
|
175
|
-
` ${c.white}${totalCommands}${c.reset}${c.dim} cmds${c.reset}` +
|
|
176
|
-
` ${c.dim}│${c.reset}` +
|
|
177
|
-
` ${rgb(251, 191, 36)}${formatUptime()}${c.reset}` +
|
|
178
|
-
` ${c.dim}│${c.reset}` +
|
|
179
|
-
` ${rgb(52, 211, 153)}● LIVE${c.reset}`
|
|
184
|
+
` ${liveIcon} ${balStr} ${c.dim}│${c.reset} ${earnStr} ${c.dim}│${c.reset} ${cmdStr} ${c.dim}(${c.reset}${rateStr}${c.dim})${c.reset} ${c.dim}│${c.reset} ${upStr}`
|
|
180
185
|
);
|
|
181
186
|
lines.push(thinBar);
|
|
182
187
|
|
|
183
|
-
|
|
184
|
-
const
|
|
188
|
+
// Worker rows
|
|
189
|
+
const nameWidth = Math.min(16, tw > 65 ? 16 : 10);
|
|
190
|
+
const statusWidth = Math.max(16, tw - nameWidth - 34);
|
|
185
191
|
|
|
186
192
|
for (const wk of workers) {
|
|
187
193
|
const rawStatus = (wk.lastStatus || 'idle').replace(/\x1b\[[0-9;]*m/g, '');
|
|
188
194
|
const last = rawStatus.substring(0, statusWidth);
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
195
|
+
|
|
196
|
+
let dot, stateLabel;
|
|
197
|
+
if (!wk.running) {
|
|
198
|
+
dot = `${rgb(239, 68, 68)}○${c.reset}`;
|
|
199
|
+
stateLabel = `${c.dim}offline${c.reset}`;
|
|
200
|
+
} else if (wk.paused) {
|
|
201
|
+
dot = `${rgb(239, 68, 68)}⏸${c.reset}`;
|
|
202
|
+
stateLabel = `${rgb(239, 68, 68)}PAUSED${c.reset}`;
|
|
203
|
+
} else if (wk.dashboardPaused) {
|
|
204
|
+
dot = `${rgb(251, 191, 36)}⏸${c.reset}`;
|
|
205
|
+
stateLabel = `${rgb(251, 191, 36)}paused${c.reset}`;
|
|
206
|
+
} else if (wk.busy) {
|
|
207
|
+
dot = `${rgb(251, 191, 36)}◉${c.reset}`;
|
|
208
|
+
stateLabel = `${c.dim}${last}${c.reset}`;
|
|
209
|
+
} else {
|
|
210
|
+
dot = `${rgb(52, 211, 153)}●${c.reset}`;
|
|
211
|
+
stateLabel = `${c.dim}${last}${c.reset}`;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const name = `${wk.color}${c.bold}${(wk.username || '?').substring(0, nameWidth)}${c.reset}`;
|
|
196
215
|
const bal = wk.stats.balance > 0
|
|
197
|
-
? `${rgb(251, 191, 36)}⏣${c.reset}${c.white}${formatCoins(wk.stats.balance).padStart(
|
|
198
|
-
: `${c.dim}⏣
|
|
216
|
+
? `${rgb(251, 191, 36)}⏣${c.reset} ${c.white}${formatCoins(wk.stats.balance).padStart(7)}${c.reset}`
|
|
217
|
+
: `${c.dim}⏣ -${c.reset}`;
|
|
199
218
|
const earned = wk.stats.coins > 0
|
|
200
|
-
? `${rgb(52, 211, 153)}+${formatCoins(wk.stats.coins)}${c.reset}`
|
|
201
|
-
: `${c.dim}+0${c.reset}`;
|
|
202
|
-
|
|
219
|
+
? `${rgb(52, 211, 153)}+${formatCoins(wk.stats.coins).padStart(6)}${c.reset}`
|
|
220
|
+
: `${c.dim} +0${c.reset}`;
|
|
221
|
+
|
|
222
|
+
lines.push(` ${dot} ${name.padEnd(nameWidth + wk.color.length + c.bold.length + c.reset.length)} ${bal} ${earned} ${stateLabel}`);
|
|
203
223
|
}
|
|
204
224
|
|
|
225
|
+
// Log section
|
|
205
226
|
if (recentLogs.length > 0) {
|
|
206
227
|
lines.push(thinBar);
|
|
207
228
|
for (const entry of recentLogs) {
|
|
@@ -209,7 +230,7 @@ function renderDashboard() {
|
|
|
209
230
|
}
|
|
210
231
|
}
|
|
211
232
|
|
|
212
|
-
lines.push(
|
|
233
|
+
lines.push(botBar);
|
|
213
234
|
|
|
214
235
|
if (dashboardLines > 0) {
|
|
215
236
|
process.stdout.write(c.cursorUp(dashboardLines));
|
|
@@ -291,19 +312,21 @@ function safeParseJSON(str, fallback = []) {
|
|
|
291
312
|
try { return JSON.parse(str || '[]'); } catch { return fallback; }
|
|
292
313
|
}
|
|
293
314
|
|
|
294
|
-
// ── Coin Parser
|
|
315
|
+
// ── Coin Parser — prefers Net:/Winnings: fields, falls back to max ⏣ ──
|
|
295
316
|
function parseCoins(text) {
|
|
296
317
|
if (!text) return 0;
|
|
297
|
-
|
|
318
|
+
const netMatch = text.match(/Net:\s*[⏣o]\s*(-?[\d,]+)/i);
|
|
319
|
+
if (netMatch) { const n = parseInt(netMatch[1].replace(/,/g, '')); return n > 0 ? n : 0; }
|
|
320
|
+
const winMatch = text.match(/Winnings:\s*[⏣o]\s*([\d,]+)/i);
|
|
321
|
+
if (winMatch) { const w = parseInt(winMatch[1].replace(/,/g, '')); if (w > 0) return w; }
|
|
322
|
+
const walletMatch = text.match(/⏣\s*([\d,]+)\s*was placed/i);
|
|
323
|
+
if (walletMatch) return parseInt(walletMatch[1].replace(/,/g, ''));
|
|
298
324
|
const matches = text.match(/⏣\s*([\d,]+)/g);
|
|
299
325
|
if (!matches) return 0;
|
|
300
326
|
let best = 0;
|
|
301
327
|
for (const m of matches) {
|
|
302
328
|
const numStr = m.replace(/[^\d]/g, '');
|
|
303
|
-
if (numStr) {
|
|
304
|
-
const val = parseInt(numStr);
|
|
305
|
-
if (val > 0 && val < 10000000) best = Math.max(best, val); // Cap at 10M sanity
|
|
306
|
-
}
|
|
329
|
+
if (numStr) { const val = parseInt(numStr); if (val > 0 && val < 10000000) best = Math.max(best, val); }
|
|
307
330
|
}
|
|
308
331
|
return best;
|
|
309
332
|
}
|
|
@@ -486,6 +509,7 @@ class AccountWorker {
|
|
|
486
509
|
this.globalCooldownUntil = 0;
|
|
487
510
|
this.commandQueue = null;
|
|
488
511
|
this.lastHealthCheck = Date.now();
|
|
512
|
+
this.doneToday = new Map(); // in-memory dedup: cmd → expiry timestamp
|
|
489
513
|
}
|
|
490
514
|
|
|
491
515
|
get tag() { return `${this.color}${c.bold}${this.username}${c.reset}`; }
|
|
@@ -593,8 +617,8 @@ class AccountWorker {
|
|
|
593
617
|
return null;
|
|
594
618
|
}
|
|
595
619
|
|
|
596
|
-
async buyItem(itemName, quantity =
|
|
597
|
-
const MAX_RETRIES =
|
|
620
|
+
async buyItem(itemName, quantity = 1) {
|
|
621
|
+
const MAX_RETRIES = 1;
|
|
598
622
|
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
|
|
599
623
|
this.log('buy', `Opening shop to buy ${c.bold}${quantity}x ${itemName}${c.reset}... (attempt ${attempt}/${MAX_RETRIES})`);
|
|
600
624
|
if (this.account.use_slash) {
|
|
@@ -648,12 +672,18 @@ class AccountWorker {
|
|
|
648
672
|
}
|
|
649
673
|
await humanDelay(1000, 2000);
|
|
650
674
|
|
|
651
|
-
// Find Buy button
|
|
675
|
+
// Find Buy button — match by full name or partial name
|
|
652
676
|
let buyBtn = null;
|
|
653
|
-
const
|
|
677
|
+
const searchNames = [
|
|
678
|
+
itemName.toLowerCase(),
|
|
679
|
+
itemName.toLowerCase().replace('hunting ', '').replace('fishing ', ''),
|
|
680
|
+
itemName.toLowerCase().split(' ')[0],
|
|
681
|
+
];
|
|
654
682
|
for (const row of response.components || []) {
|
|
655
683
|
for (const comp of row.components || []) {
|
|
656
|
-
if (comp.type
|
|
684
|
+
if (comp.type !== 2 || !comp.label) continue;
|
|
685
|
+
const label = comp.label.toLowerCase();
|
|
686
|
+
if (searchNames.some(s => label.includes(s) || s.includes(label))) {
|
|
657
687
|
buyBtn = comp; break;
|
|
658
688
|
}
|
|
659
689
|
}
|
|
@@ -765,7 +795,7 @@ class AccountWorker {
|
|
|
765
795
|
case 'dep max': cmdString = `${prefix} dep max`; break;
|
|
766
796
|
case 'with max': cmdString = `${prefix} with max`; break;
|
|
767
797
|
case 'blackjack': cmdString = `${prefix} bj ${betAmount}`; break;
|
|
768
|
-
case '
|
|
798
|
+
case 'cointoss': cmdString = `${prefix} cointoss ${betAmount}`; break;
|
|
769
799
|
case 'roulette': cmdString = `${prefix} roulette ${betAmount} red`; break;
|
|
770
800
|
case 'slots': cmdString = `${prefix} slots ${betAmount}`; break;
|
|
771
801
|
case 'snakeeyes': cmdString = `${prefix} snakeeyes ${betAmount}`; break;
|
|
@@ -803,7 +833,7 @@ class AccountWorker {
|
|
|
803
833
|
case 'blackjack': cmdResult = await commands.runBlackjack(cmdOpts); break;
|
|
804
834
|
case 'trivia': cmdResult = await commands.runTrivia(cmdOpts); break;
|
|
805
835
|
case 'work shift': cmdResult = await commands.runWorkShift(cmdOpts); break;
|
|
806
|
-
case '
|
|
836
|
+
case 'cointoss': cmdResult = await commands.runCointoss(cmdOpts); break;
|
|
807
837
|
case 'roulette': cmdResult = await commands.runRoulette(cmdOpts); break;
|
|
808
838
|
case 'slots': cmdResult = await commands.runSlots(cmdOpts); break;
|
|
809
839
|
case 'snakeeyes': cmdResult = await commands.runSnakeeyes(cmdOpts); break;
|
|
@@ -825,6 +855,15 @@ class AccountWorker {
|
|
|
825
855
|
return;
|
|
826
856
|
}
|
|
827
857
|
|
|
858
|
+
// PostMemes / command-specific cooldown from response
|
|
859
|
+
if (resultLower.includes('cannot post another meme') || resultLower.includes('dead meme')) {
|
|
860
|
+
const minMatch = result.match(/(\d+)\s*minute/i);
|
|
861
|
+
const cdSec = minMatch ? parseInt(minMatch[1]) * 60 : 120;
|
|
862
|
+
this.log('warn', `${cmdName} on cooldown: ${cdSec}s`);
|
|
863
|
+
await this.setCooldown(cmdName, cdSec);
|
|
864
|
+
return;
|
|
865
|
+
}
|
|
866
|
+
|
|
828
867
|
// Captcha/verification detection — deactivate account and stop
|
|
829
868
|
if (resultLower.includes('captcha') || resultLower.includes('verification required') ||
|
|
830
869
|
resultLower.includes('verify your account') || resultLower.includes('pass verification') ||
|
|
@@ -846,18 +885,24 @@ class AccountWorker {
|
|
|
846
885
|
return;
|
|
847
886
|
}
|
|
848
887
|
|
|
849
|
-
// Min bet detection —
|
|
850
|
-
if (resultLower.includes('cannot bet less than') || resultLower.includes('minimum bet')) {
|
|
851
|
-
const betMatch = result.match(
|
|
888
|
+
// Min bet detection — "You can't bet less than 10,000" or "cannot bet less than ⏣ 5,000"
|
|
889
|
+
if (resultLower.includes("can't bet less than") || resultLower.includes('cannot bet less than') || resultLower.includes('minimum bet')) {
|
|
890
|
+
const betMatch = result.match(/less than\s*\*?\*?\s*[⏣o]?\s*([\d,]+)/i) || result.match(/(\d[\d,]+)/);
|
|
852
891
|
if (betMatch) {
|
|
853
892
|
const minBet = parseInt(betMatch[1].replace(/,/g, ''));
|
|
854
893
|
if (minBet > 0) {
|
|
855
894
|
this.account.bet_amount = minBet;
|
|
856
|
-
this.log('info', `${cmdName} min bet
|
|
895
|
+
this.log('info', `${cmdName} min bet raised → ⏣ ${minBet.toLocaleString()}`);
|
|
857
896
|
}
|
|
858
897
|
}
|
|
859
898
|
return;
|
|
860
899
|
}
|
|
900
|
+
// Also handle newMinBet from gamble handler
|
|
901
|
+
if (cmdResult.newMinBet && cmdResult.newMinBet > (this.account.bet_amount || 0)) {
|
|
902
|
+
this.account.bet_amount = cmdResult.newMinBet;
|
|
903
|
+
this.log('info', `${cmdName} min bet raised → ⏣ ${cmdResult.newMinBet.toLocaleString()}`);
|
|
904
|
+
return;
|
|
905
|
+
}
|
|
861
906
|
|
|
862
907
|
// Premium-only command detection — disable for 24h
|
|
863
908
|
if (resultLower.includes('only available on premium') || resultLower.includes('premium') ||
|
|
@@ -867,32 +912,52 @@ class AccountWorker {
|
|
|
867
912
|
return;
|
|
868
913
|
}
|
|
869
914
|
|
|
870
|
-
// Already claimed today (daily/weekly) — set long cooldown
|
|
915
|
+
// Already claimed today (daily/weekly) — set long cooldown + mark done
|
|
871
916
|
if (resultLower.includes('already got your daily') || resultLower.includes('try again <t:')) {
|
|
872
917
|
this.log('info', `${cmdName} already claimed — waiting`);
|
|
873
918
|
const timeMatch = result.match(/<t:(\d+):R>/);
|
|
919
|
+
let waitSec;
|
|
874
920
|
if (timeMatch) {
|
|
875
921
|
const nextAvail = parseInt(timeMatch[1]) * 1000;
|
|
876
|
-
|
|
877
|
-
await this.setCooldown(cmdName, waitSec);
|
|
922
|
+
waitSec = Math.max(60, Math.ceil((nextAvail - Date.now()) / 1000));
|
|
878
923
|
} else {
|
|
879
|
-
|
|
924
|
+
waitSec = cmdName === 'daily' ? 86400 : 604800;
|
|
880
925
|
}
|
|
926
|
+
await this.setCooldown(cmdName, waitSec);
|
|
927
|
+
this.doneToday.set(cmdName, Date.now() + waitSec * 1000);
|
|
928
|
+
if (redis) try { await redis.set(`dkg:done:${this.account.id}:${cmdName}`, '1', 'EX', waitSec); } catch {}
|
|
881
929
|
return;
|
|
882
930
|
}
|
|
883
931
|
|
|
884
|
-
//
|
|
932
|
+
// Skip reasons: level too low, no ticket, etc.
|
|
885
933
|
if (cmdResult.skipReason === 'level') {
|
|
886
934
|
this.log('warn', `${cmdName} level too low — retry in 24h`);
|
|
887
935
|
await this.setCooldown(cmdName, 86400);
|
|
888
936
|
return;
|
|
889
937
|
}
|
|
938
|
+
if (cmdResult.skipReason === 'no_ticket') {
|
|
939
|
+
this.log('warn', `${cmdName} no ticket — retry in 1h`);
|
|
940
|
+
await this.setCooldown(cmdName, 3600);
|
|
941
|
+
return;
|
|
942
|
+
}
|
|
890
943
|
|
|
891
944
|
const earned = Math.max(0, cmdResult.coins || 0);
|
|
892
945
|
const spent = Math.max(0, cmdResult.lost || 0);
|
|
893
946
|
if (earned > 0) this.stats.coins += earned;
|
|
894
947
|
if (cmdResult.nextCooldownSec) await this.setCooldown(cmdName, cmdResult.nextCooldownSec);
|
|
895
948
|
|
|
949
|
+
// Mark daily/drops as done so we don't re-run this session
|
|
950
|
+
if (cmdName === 'daily' && earned > 0) {
|
|
951
|
+
const expiry = Date.now() + 86400 * 1000;
|
|
952
|
+
this.doneToday.set('daily', expiry);
|
|
953
|
+
if (redis) try { await redis.set(`dkg:done:${this.account.id}:daily`, '1', 'EX', 86400); } catch {}
|
|
954
|
+
}
|
|
955
|
+
if (cmdName === 'drops') {
|
|
956
|
+
const expiry = Date.now() + 86400 * 1000;
|
|
957
|
+
this.doneToday.set('drops', expiry);
|
|
958
|
+
if (redis) try { await redis.set(`dkg:done:${this.account.id}:drops`, '1', 'EX', 86400); } catch {}
|
|
959
|
+
}
|
|
960
|
+
|
|
896
961
|
if (cmdResult.holdTightReason) {
|
|
897
962
|
const reason = cmdResult.holdTightReason;
|
|
898
963
|
this.log('warn', `Hold Tight: /${reason} — 35s cooldown`);
|
|
@@ -948,7 +1013,7 @@ class AccountWorker {
|
|
|
948
1013
|
{ key: 'cmd_trivia', cmd: 'trivia', cdKey: 'cd_trivia', defaultCd: 10, priority: 2 },
|
|
949
1014
|
// Gambling (fast cycle)
|
|
950
1015
|
{ key: 'cmd_blackjack', cmd: 'blackjack', cdKey: 'cd_blackjack', defaultCd: 3, priority: 3 },
|
|
951
|
-
{ key: 'cmd_cointoss', cmd: '
|
|
1016
|
+
{ key: 'cmd_cointoss', cmd: 'cointoss', cdKey: 'cd_cointoss', defaultCd: 2, priority: 3 },
|
|
952
1017
|
{ key: 'cmd_roulette', cmd: 'roulette', cdKey: 'cd_roulette', defaultCd: 3, priority: 3 },
|
|
953
1018
|
{ key: 'cmd_slots', cmd: 'slots', cdKey: 'cd_slots', defaultCd: 3, priority: 3 },
|
|
954
1019
|
{ key: 'cmd_snakeeyes', cmd: 'snakeeyes', cdKey: 'cd_snakeeyes', defaultCd: 3, priority: 3 },
|
|
@@ -1104,6 +1169,30 @@ class AccountWorker {
|
|
|
1104
1169
|
return;
|
|
1105
1170
|
}
|
|
1106
1171
|
|
|
1172
|
+
// Skip daily/drops if already done today (in-memory + Redis)
|
|
1173
|
+
if (item.cmd === 'daily' || item.cmd === 'drops') {
|
|
1174
|
+
const memExpiry = this.doneToday.get(item.cmd);
|
|
1175
|
+
if (memExpiry && Date.now() < memExpiry) {
|
|
1176
|
+
item.nextRunAt = memExpiry;
|
|
1177
|
+
if (this.commandQueue) this.commandQueue.push(item);
|
|
1178
|
+
this.tickTimeout = setTimeout(() => this.tick(), 100);
|
|
1179
|
+
return;
|
|
1180
|
+
}
|
|
1181
|
+
if (redis) {
|
|
1182
|
+
try {
|
|
1183
|
+
const done = await redis.get(`dkg:done:${this.account.id}:${item.cmd}`);
|
|
1184
|
+
if (done) {
|
|
1185
|
+
const expiry = now + 86400 * 1000;
|
|
1186
|
+
this.doneToday.set(item.cmd, expiry);
|
|
1187
|
+
item.nextRunAt = expiry;
|
|
1188
|
+
if (this.commandQueue) this.commandQueue.push(item);
|
|
1189
|
+
this.tickTimeout = setTimeout(() => this.tick(), 100);
|
|
1190
|
+
return;
|
|
1191
|
+
}
|
|
1192
|
+
} catch {}
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1107
1196
|
this.busy = true;
|
|
1108
1197
|
const cd = (this.account[item.info.cdKey] || item.info.defaultCd);
|
|
1109
1198
|
const jitter = 1 + Math.random() * 3;
|
|
@@ -1111,10 +1200,11 @@ class AccountWorker {
|
|
|
1111
1200
|
|
|
1112
1201
|
await this.setCooldown(item.cmd, totalWait);
|
|
1113
1202
|
|
|
1203
|
+
// Inter-command delay: 1-3s random (human-like spacing)
|
|
1114
1204
|
const timeSinceLastCmd = now - (this.lastCommandRun || 0);
|
|
1115
|
-
const
|
|
1116
|
-
if (timeSinceLastCmd <
|
|
1117
|
-
await new Promise(r => setTimeout(r,
|
|
1205
|
+
const minGap = 1000 + Math.random() * 2000; // 1-3s
|
|
1206
|
+
if (timeSinceLastCmd < minGap) {
|
|
1207
|
+
await new Promise(r => setTimeout(r, minGap - timeSinceLastCmd));
|
|
1118
1208
|
}
|
|
1119
1209
|
|
|
1120
1210
|
const prefix = this.account.use_slash ? '/' : 'pls';
|
|
@@ -1270,7 +1360,7 @@ class AccountWorker {
|
|
|
1270
1360
|
{ key: 'cmd_stream', l: 'stream' }, { key: 'cmd_scratch', l: 'scratch' },
|
|
1271
1361
|
{ key: 'cmd_adventure', l: 'adv' }, { key: 'cmd_farm', l: 'farm' },
|
|
1272
1362
|
{ key: 'cmd_tidy', l: 'tidy' }, { key: 'cmd_blackjack', l: 'bj' },
|
|
1273
|
-
{ key: 'cmd_cointoss', l: '
|
|
1363
|
+
{ key: 'cmd_cointoss', l: 'toss' }, { key: 'cmd_roulette', l: 'roul' },
|
|
1274
1364
|
{ key: 'cmd_slots', l: 'slots' }, { key: 'cmd_snakeeyes', l: 'snake' },
|
|
1275
1365
|
{ key: 'cmd_trivia', l: 'trivia' }, { key: 'cmd_use', l: 'use' },
|
|
1276
1366
|
{ key: 'cmd_deposit', l: 'dep' }, { key: 'cmd_drops', l: 'drops' },
|
|
@@ -1321,7 +1411,7 @@ async function start(apiKey, apiUrl) {
|
|
|
1321
1411
|
|
|
1322
1412
|
console.log(colorBanner());
|
|
1323
1413
|
console.log(
|
|
1324
|
-
` ${rgb(139, 92, 246)}v4.
|
|
1414
|
+
` ${rgb(139, 92, 246)}v4.8${c.reset}` +
|
|
1325
1415
|
` ${c.dim}·${c.reset} ${c.white}30 Commands${c.reset}` +
|
|
1326
1416
|
` ${c.dim}·${c.reset} ${rgb(34, 211, 238)}Priority Queue${c.reset}` +
|
|
1327
1417
|
` ${c.dim}·${c.reset} ${rgb(52, 211, 153)}Redis Cooldowns${c.reset}` +
|