dankgrinder 4.6.0 → 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/dig.js +1 -1
- package/lib/commands/gamble.js +49 -34
- package/lib/commands/generic.js +5 -4
- package/lib/commands/hunt.js +1 -1
- package/lib/commands/index.js +2 -2
- package/lib/commands/shop.js +13 -22
- package/lib/grinder.js +75 -41
- 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) ──
|
package/lib/commands/dig.js
CHANGED
package/lib/commands/gamble.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* Covers:
|
|
2
|
+
* Gambling command handlers.
|
|
3
|
+
* Covers: cointoss, roulette, slots, snakeeyes
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
const {
|
|
@@ -8,6 +8,29 @@ const {
|
|
|
8
8
|
logMsg, isHoldTight, getHoldTightReason, sleep, humanDelay,
|
|
9
9
|
} = require('./utils');
|
|
10
10
|
|
|
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
|
+
|
|
11
34
|
async function runGamble({ channel, waitForDankMemer, cmdName, cmdString }) {
|
|
12
35
|
LOG.cmd(`${c.white}${c.bold}${cmdString}${c.reset}`);
|
|
13
36
|
|
|
@@ -26,16 +49,29 @@ async function runGamble({ channel, waitForDankMemer, cmdName, cmdString }) {
|
|
|
26
49
|
return { result: `hold tight (${reason || 'unknown'})`, coins: 0, holdTightReason: reason };
|
|
27
50
|
}
|
|
28
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
|
+
|
|
29
64
|
logMsg(response, cmdName);
|
|
30
65
|
const text = getFullText(response);
|
|
31
66
|
|
|
32
|
-
//
|
|
67
|
+
// For cointoss/gambles with buttons: click a random non-disabled button
|
|
33
68
|
const buttons = getAllButtons(response);
|
|
34
69
|
if (buttons.length > 0) {
|
|
35
|
-
const
|
|
36
|
-
if (
|
|
37
|
-
|
|
38
|
-
|
|
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);
|
|
39
75
|
try {
|
|
40
76
|
const followUp = await safeClickButton(response, btn);
|
|
41
77
|
if (followUp) {
|
|
@@ -50,32 +86,11 @@ async function runGamble({ channel, waitForDankMemer, cmdName, cmdString }) {
|
|
|
50
86
|
return parseGambleResult(text, cmdName);
|
|
51
87
|
}
|
|
52
88
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
LOG.coin(`[${cmdName}] ${c.green}+⏣ ${net.toLocaleString()}${c.reset}`);
|
|
59
|
-
return { result: `${cmdName} → +⏣ ${net.toLocaleString()}`, coins: net };
|
|
60
|
-
}
|
|
61
|
-
if (net < 0) {
|
|
62
|
-
LOG.warn(`[${cmdName}] ${c.red}-⏣ ${Math.abs(net).toLocaleString()}${c.reset}`);
|
|
63
|
-
return { result: `${cmdName} → -⏣ ${Math.abs(net).toLocaleString()}`, coins: 0, lost: Math.abs(net) };
|
|
64
|
-
}
|
|
65
|
-
// Fallback to parseCoins
|
|
66
|
-
const coins = parseCoins(text);
|
|
67
|
-
if (coins > 0) {
|
|
68
|
-
LOG.coin(`[${cmdName}] ${c.green}+⏣ ${coins.toLocaleString()}${c.reset}`);
|
|
69
|
-
return { result: `${cmdName} → +⏣ ${coins.toLocaleString()}`, coins };
|
|
70
|
-
}
|
|
71
|
-
if (lower.includes('won') || lower.includes('beat')) return { result: `${cmdName} → won`, coins: 0 };
|
|
72
|
-
if (lower.includes('lost') || lower.includes('bust')) return { result: `${cmdName} → lost`, coins: 0, lost: coins };
|
|
73
|
-
|
|
74
|
-
return { result: `${cmdName} done`, coins: 0 };
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
async function runCoinflip({ channel, waitForDankMemer, betAmount = 5000 }) {
|
|
78
|
-
return runGamble({ channel, waitForDankMemer, cmdName: 'coinflip', cmdString: `pls coinflip ${betAmount} heads` });
|
|
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}` });
|
|
79
94
|
}
|
|
80
95
|
|
|
81
96
|
async function runRoulette({ channel, waitForDankMemer, betAmount = 5000 }) {
|
|
@@ -90,4 +105,4 @@ async function runSnakeeyes({ channel, waitForDankMemer, betAmount = 5000 }) {
|
|
|
90
105
|
return runGamble({ channel, waitForDankMemer, cmdName: 'snakeeyes', cmdString: `pls snakeeyes ${betAmount}` });
|
|
91
106
|
}
|
|
92
107
|
|
|
93
|
-
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/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,
|
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/grinder.js
CHANGED
|
@@ -509,6 +509,7 @@ class AccountWorker {
|
|
|
509
509
|
this.globalCooldownUntil = 0;
|
|
510
510
|
this.commandQueue = null;
|
|
511
511
|
this.lastHealthCheck = Date.now();
|
|
512
|
+
this.doneToday = new Map(); // in-memory dedup: cmd → expiry timestamp
|
|
512
513
|
}
|
|
513
514
|
|
|
514
515
|
get tag() { return `${this.color}${c.bold}${this.username}${c.reset}`; }
|
|
@@ -616,8 +617,8 @@ class AccountWorker {
|
|
|
616
617
|
return null;
|
|
617
618
|
}
|
|
618
619
|
|
|
619
|
-
async buyItem(itemName, quantity =
|
|
620
|
-
const MAX_RETRIES =
|
|
620
|
+
async buyItem(itemName, quantity = 1) {
|
|
621
|
+
const MAX_RETRIES = 1;
|
|
621
622
|
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
|
|
622
623
|
this.log('buy', `Opening shop to buy ${c.bold}${quantity}x ${itemName}${c.reset}... (attempt ${attempt}/${MAX_RETRIES})`);
|
|
623
624
|
if (this.account.use_slash) {
|
|
@@ -671,12 +672,18 @@ class AccountWorker {
|
|
|
671
672
|
}
|
|
672
673
|
await humanDelay(1000, 2000);
|
|
673
674
|
|
|
674
|
-
// Find Buy button
|
|
675
|
+
// Find Buy button — match by full name or partial name
|
|
675
676
|
let buyBtn = null;
|
|
676
|
-
const
|
|
677
|
+
const searchNames = [
|
|
678
|
+
itemName.toLowerCase(),
|
|
679
|
+
itemName.toLowerCase().replace('hunting ', '').replace('fishing ', ''),
|
|
680
|
+
itemName.toLowerCase().split(' ')[0],
|
|
681
|
+
];
|
|
677
682
|
for (const row of response.components || []) {
|
|
678
683
|
for (const comp of row.components || []) {
|
|
679
|
-
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))) {
|
|
680
687
|
buyBtn = comp; break;
|
|
681
688
|
}
|
|
682
689
|
}
|
|
@@ -788,7 +795,7 @@ class AccountWorker {
|
|
|
788
795
|
case 'dep max': cmdString = `${prefix} dep max`; break;
|
|
789
796
|
case 'with max': cmdString = `${prefix} with max`; break;
|
|
790
797
|
case 'blackjack': cmdString = `${prefix} bj ${betAmount}`; break;
|
|
791
|
-
case '
|
|
798
|
+
case 'cointoss': cmdString = `${prefix} cointoss ${betAmount}`; break;
|
|
792
799
|
case 'roulette': cmdString = `${prefix} roulette ${betAmount} red`; break;
|
|
793
800
|
case 'slots': cmdString = `${prefix} slots ${betAmount}`; break;
|
|
794
801
|
case 'snakeeyes': cmdString = `${prefix} snakeeyes ${betAmount}`; break;
|
|
@@ -826,7 +833,7 @@ class AccountWorker {
|
|
|
826
833
|
case 'blackjack': cmdResult = await commands.runBlackjack(cmdOpts); break;
|
|
827
834
|
case 'trivia': cmdResult = await commands.runTrivia(cmdOpts); break;
|
|
828
835
|
case 'work shift': cmdResult = await commands.runWorkShift(cmdOpts); break;
|
|
829
|
-
case '
|
|
836
|
+
case 'cointoss': cmdResult = await commands.runCointoss(cmdOpts); break;
|
|
830
837
|
case 'roulette': cmdResult = await commands.runRoulette(cmdOpts); break;
|
|
831
838
|
case 'slots': cmdResult = await commands.runSlots(cmdOpts); break;
|
|
832
839
|
case 'snakeeyes': cmdResult = await commands.runSnakeeyes(cmdOpts); break;
|
|
@@ -878,18 +885,24 @@ class AccountWorker {
|
|
|
878
885
|
return;
|
|
879
886
|
}
|
|
880
887
|
|
|
881
|
-
// Min bet detection —
|
|
882
|
-
if (resultLower.includes('cannot bet less than') || resultLower.includes('minimum bet')) {
|
|
883
|
-
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,]+)/);
|
|
884
891
|
if (betMatch) {
|
|
885
892
|
const minBet = parseInt(betMatch[1].replace(/,/g, ''));
|
|
886
893
|
if (minBet > 0) {
|
|
887
894
|
this.account.bet_amount = minBet;
|
|
888
|
-
this.log('info', `${cmdName} min bet
|
|
895
|
+
this.log('info', `${cmdName} min bet raised → ⏣ ${minBet.toLocaleString()}`);
|
|
889
896
|
}
|
|
890
897
|
}
|
|
891
898
|
return;
|
|
892
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
|
+
}
|
|
893
906
|
|
|
894
907
|
// Premium-only command detection — disable for 24h
|
|
895
908
|
if (resultLower.includes('only available on premium') || resultLower.includes('premium') ||
|
|
@@ -899,41 +912,50 @@ class AccountWorker {
|
|
|
899
912
|
return;
|
|
900
913
|
}
|
|
901
914
|
|
|
902
|
-
// Already claimed today (daily/weekly) — set long cooldown + mark
|
|
915
|
+
// Already claimed today (daily/weekly) — set long cooldown + mark done
|
|
903
916
|
if (resultLower.includes('already got your daily') || resultLower.includes('try again <t:')) {
|
|
904
917
|
this.log('info', `${cmdName} already claimed — waiting`);
|
|
905
918
|
const timeMatch = result.match(/<t:(\d+):R>/);
|
|
919
|
+
let waitSec;
|
|
906
920
|
if (timeMatch) {
|
|
907
921
|
const nextAvail = parseInt(timeMatch[1]) * 1000;
|
|
908
|
-
|
|
909
|
-
await this.setCooldown(cmdName, waitSec);
|
|
910
|
-
if (redis) try { await redis.set(`dkg:done:${this.account.id}:${cmdName}`, '1', 'EX', waitSec); } catch {}
|
|
922
|
+
waitSec = Math.max(60, Math.ceil((nextAvail - Date.now()) / 1000));
|
|
911
923
|
} else {
|
|
912
|
-
|
|
913
|
-
await this.setCooldown(cmdName, fallbackSec);
|
|
914
|
-
if (redis) try { await redis.set(`dkg:done:${this.account.id}:${cmdName}`, '1', 'EX', fallbackSec); } catch {}
|
|
924
|
+
waitSec = cmdName === 'daily' ? 86400 : 604800;
|
|
915
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 {}
|
|
916
929
|
return;
|
|
917
930
|
}
|
|
918
931
|
|
|
919
|
-
//
|
|
932
|
+
// Skip reasons: level too low, no ticket, etc.
|
|
920
933
|
if (cmdResult.skipReason === 'level') {
|
|
921
934
|
this.log('warn', `${cmdName} level too low — retry in 24h`);
|
|
922
935
|
await this.setCooldown(cmdName, 86400);
|
|
923
936
|
return;
|
|
924
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
|
+
}
|
|
925
943
|
|
|
926
944
|
const earned = Math.max(0, cmdResult.coins || 0);
|
|
927
945
|
const spent = Math.max(0, cmdResult.lost || 0);
|
|
928
946
|
if (earned > 0) this.stats.coins += earned;
|
|
929
947
|
if (cmdResult.nextCooldownSec) await this.setCooldown(cmdName, cmdResult.nextCooldownSec);
|
|
930
948
|
|
|
931
|
-
// Mark daily/drops as done
|
|
932
|
-
if (
|
|
933
|
-
|
|
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 {}
|
|
934
954
|
}
|
|
935
|
-
if (
|
|
936
|
-
|
|
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 {}
|
|
937
959
|
}
|
|
938
960
|
|
|
939
961
|
if (cmdResult.holdTightReason) {
|
|
@@ -991,7 +1013,7 @@ class AccountWorker {
|
|
|
991
1013
|
{ key: 'cmd_trivia', cmd: 'trivia', cdKey: 'cd_trivia', defaultCd: 10, priority: 2 },
|
|
992
1014
|
// Gambling (fast cycle)
|
|
993
1015
|
{ key: 'cmd_blackjack', cmd: 'blackjack', cdKey: 'cd_blackjack', defaultCd: 3, priority: 3 },
|
|
994
|
-
{ key: 'cmd_cointoss', cmd: '
|
|
1016
|
+
{ key: 'cmd_cointoss', cmd: 'cointoss', cdKey: 'cd_cointoss', defaultCd: 2, priority: 3 },
|
|
995
1017
|
{ key: 'cmd_roulette', cmd: 'roulette', cdKey: 'cd_roulette', defaultCd: 3, priority: 3 },
|
|
996
1018
|
{ key: 'cmd_slots', cmd: 'slots', cdKey: 'cd_slots', defaultCd: 3, priority: 3 },
|
|
997
1019
|
{ key: 'cmd_snakeeyes', cmd: 'snakeeyes', cdKey: 'cd_snakeeyes', defaultCd: 3, priority: 3 },
|
|
@@ -1147,17 +1169,28 @@ class AccountWorker {
|
|
|
1147
1169
|
return;
|
|
1148
1170
|
}
|
|
1149
1171
|
|
|
1150
|
-
// Skip daily/drops if already done today (Redis
|
|
1151
|
-
if (
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
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
|
+
}
|
|
1161
1194
|
}
|
|
1162
1195
|
|
|
1163
1196
|
this.busy = true;
|
|
@@ -1167,10 +1200,11 @@ class AccountWorker {
|
|
|
1167
1200
|
|
|
1168
1201
|
await this.setCooldown(item.cmd, totalWait);
|
|
1169
1202
|
|
|
1203
|
+
// Inter-command delay: 1-3s random (human-like spacing)
|
|
1170
1204
|
const timeSinceLastCmd = now - (this.lastCommandRun || 0);
|
|
1171
|
-
const
|
|
1172
|
-
if (timeSinceLastCmd <
|
|
1173
|
-
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));
|
|
1174
1208
|
}
|
|
1175
1209
|
|
|
1176
1210
|
const prefix = this.account.use_slash ? '/' : 'pls';
|
|
@@ -1326,7 +1360,7 @@ class AccountWorker {
|
|
|
1326
1360
|
{ key: 'cmd_stream', l: 'stream' }, { key: 'cmd_scratch', l: 'scratch' },
|
|
1327
1361
|
{ key: 'cmd_adventure', l: 'adv' }, { key: 'cmd_farm', l: 'farm' },
|
|
1328
1362
|
{ key: 'cmd_tidy', l: 'tidy' }, { key: 'cmd_blackjack', l: 'bj' },
|
|
1329
|
-
{ key: 'cmd_cointoss', l: '
|
|
1363
|
+
{ key: 'cmd_cointoss', l: 'toss' }, { key: 'cmd_roulette', l: 'roul' },
|
|
1330
1364
|
{ key: 'cmd_slots', l: 'slots' }, { key: 'cmd_snakeeyes', l: 'snake' },
|
|
1331
1365
|
{ key: 'cmd_trivia', l: 'trivia' }, { key: 'cmd_use', l: 'use' },
|
|
1332
1366
|
{ key: 'cmd_deposit', l: 'dep' }, { key: 'cmd_drops', l: 'drops' },
|
|
@@ -1377,7 +1411,7 @@ async function start(apiKey, apiUrl) {
|
|
|
1377
1411
|
|
|
1378
1412
|
console.log(colorBanner());
|
|
1379
1413
|
console.log(
|
|
1380
|
-
` ${rgb(139, 92, 246)}v4.
|
|
1414
|
+
` ${rgb(139, 92, 246)}v4.8${c.reset}` +
|
|
1381
1415
|
` ${c.dim}·${c.reset} ${c.white}30 Commands${c.reset}` +
|
|
1382
1416
|
` ${c.dim}·${c.reset} ${rgb(34, 211, 238)}Priority Queue${c.reset}` +
|
|
1383
1417
|
` ${c.dim}·${c.reset} ${rgb(52, 211, 153)}Redis Cooldowns${c.reset}` +
|