dankgrinder 6.8.2 → 6.14.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/blackjack.js +110 -51
- package/lib/commands/crime.js +8 -0
- package/lib/commands/fish.js +7 -0
- package/lib/commands/gamble.js +70 -54
- package/lib/commands/highlow.js +48 -28
- package/lib/commands/hunt.js +153 -32
- package/lib/commands/profile.js +5 -4
- package/lib/commands/search.js +8 -0
- package/lib/commands/utils.js +24 -8
- package/lib/commands/work.js +92 -4
- package/lib/grinder.js +153 -1
- package/lib/rawLogger.js +541 -0
- package/package.json +1 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Blackjack command handler.
|
|
3
|
-
*
|
|
3
|
+
* Full basic strategy: hit, stand, double down, surrender.
|
|
4
|
+
* Fast button clicks with retry on failure.
|
|
4
5
|
*/
|
|
5
6
|
|
|
6
7
|
const {
|
|
@@ -12,19 +13,16 @@ const RE_BACKTICK_SCORE = /`\s*(\d+)\s*`/;
|
|
|
12
13
|
const RE_BJ_FACE_GLOBAL = /bjFace(\w+?):/g;
|
|
13
14
|
const RE_BJ_FACE_SUFFIX = /[RB]$/;
|
|
14
15
|
const RE_NET_LINE = /Net:\s*\*{0,2}\s*[⏣]\s*\*{0,2}([+-]?[\d,]+)/i;
|
|
15
|
-
const RE_WINNINGS_LINE = /Winnings:\s*\*{0,2}\s*[⏣]\s*\*{0,2}([\d,]+)/i;
|
|
16
|
+
const RE_WINNINGS_LINE = /Winnings:\s*\*{0,2}\s*[⏣]\s*\*{0,2}([+-]?[\d,]+)/i;
|
|
16
17
|
const RE_COMMA = /,/g;
|
|
17
18
|
const COURT_RANKS = Object.freeze(['K', 'Q', 'J']);
|
|
18
19
|
|
|
19
20
|
function parsePlayerTotal(msg) {
|
|
20
|
-
// Score is in embed field: ` 18 ` (backtick-wrapped number in Player field)
|
|
21
21
|
const embeds = msg?.embeds || [];
|
|
22
|
-
for (
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
if (field.name?.toLowerCase().includes('player')) {
|
|
27
|
-
const m = field.value.match(RE_BACKTICK_SCORE);
|
|
22
|
+
for (const e of embeds) {
|
|
23
|
+
for (const f of (e.fields || [])) {
|
|
24
|
+
if (f.name?.toLowerCase().includes('player')) {
|
|
25
|
+
const m = f.value.match(RE_BACKTICK_SCORE);
|
|
28
26
|
if (m) return parseInt(m[1]);
|
|
29
27
|
}
|
|
30
28
|
}
|
|
@@ -35,21 +33,16 @@ function parsePlayerTotal(msg) {
|
|
|
35
33
|
}
|
|
36
34
|
|
|
37
35
|
function parseDealerUpcard(msg) {
|
|
38
|
-
// Dealer field: revealed total ` 18 ` or hidden ` ? `
|
|
39
|
-
// Upcard from emoji: bjFaceKB=K(10), bjFaceAR=A(11), bjFace7R=7
|
|
40
36
|
const embeds = msg?.embeds || [];
|
|
41
|
-
for (
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
if (field.name?.toLowerCase().includes('dealer')) {
|
|
46
|
-
const totalMatch = field.value.match(RE_BACKTICK_SCORE);
|
|
37
|
+
for (const e of embeds) {
|
|
38
|
+
for (const f of (e.fields || [])) {
|
|
39
|
+
if (f.name?.toLowerCase().includes('dealer')) {
|
|
40
|
+
const totalMatch = f.value.match(RE_BACKTICK_SCORE);
|
|
47
41
|
if (totalMatch) return parseInt(totalMatch[1]);
|
|
48
|
-
const faces = [...
|
|
49
|
-
for (
|
|
50
|
-
const
|
|
51
|
-
if (
|
|
52
|
-
const v = face.replace(RE_BJ_FACE_SUFFIX, '');
|
|
42
|
+
const faces = [...f.value.matchAll(RE_BJ_FACE_GLOBAL)];
|
|
43
|
+
for (const face of faces) {
|
|
44
|
+
const v = face[1].replace(RE_BJ_FACE_SUFFIX, '');
|
|
45
|
+
if (v === 'Unknown') continue;
|
|
53
46
|
if (v === 'A') return 11;
|
|
54
47
|
if (COURT_RANKS.includes(v)) return 10;
|
|
55
48
|
const n = parseInt(v);
|
|
@@ -70,18 +63,74 @@ function parseNetCoins(text) {
|
|
|
70
63
|
}
|
|
71
64
|
|
|
72
65
|
/**
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
66
|
+
* Full basic strategy decision.
|
|
67
|
+
* Returns: 'hit' | 'stand' | 'double' | 'surrender'
|
|
68
|
+
*
|
|
69
|
+
* Double Down (2x bet, get exactly 1 more card):
|
|
70
|
+
* - Always double on 11
|
|
71
|
+
* - Double on 10 if dealer shows 2-9
|
|
72
|
+
* - Double on 9 if dealer shows 3-6
|
|
73
|
+
*
|
|
74
|
+
* Surrender:
|
|
75
|
+
* - 16 vs dealer 9, 10, A
|
|
76
|
+
* - 15 vs dealer 10
|
|
77
|
+
*
|
|
78
|
+
* Standard:
|
|
79
|
+
* - Always hit ≤ 8
|
|
80
|
+
* - Always stand ≥ 17
|
|
81
|
+
* - 12: hit vs dealer 2-3 or 7+, stand vs 4-6
|
|
82
|
+
* - 13-16: hit vs dealer 7+, stand vs 2-6
|
|
77
83
|
*/
|
|
78
|
-
function
|
|
79
|
-
|
|
80
|
-
if (
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
84
|
+
function decide(playerTotal, dealerUpcard, canDouble, canSurrender) {
|
|
85
|
+
// Double down opportunities (best +EV plays in blackjack)
|
|
86
|
+
if (canDouble) {
|
|
87
|
+
if (playerTotal === 11) return 'double';
|
|
88
|
+
if (playerTotal === 10 && dealerUpcard >= 2 && dealerUpcard <= 9) return 'double';
|
|
89
|
+
if (playerTotal === 9 && dealerUpcard >= 3 && dealerUpcard <= 6) return 'double';
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Surrender (cut losses on worst hands)
|
|
93
|
+
if (canSurrender) {
|
|
94
|
+
if (playerTotal === 16 && (dealerUpcard >= 9 || dealerUpcard === 11)) return 'surrender';
|
|
95
|
+
if (playerTotal === 15 && dealerUpcard === 10) return 'surrender';
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Always hit soft hands (≤ 8 can't bust)
|
|
99
|
+
if (playerTotal <= 8) return 'hit';
|
|
100
|
+
|
|
101
|
+
// Always stand on 17+
|
|
102
|
+
if (playerTotal >= 17) return 'stand';
|
|
103
|
+
|
|
104
|
+
// 12: tricky — hit vs 2, 3, 7+ ; stand vs 4-6
|
|
105
|
+
if (playerTotal === 12) {
|
|
106
|
+
return (dealerUpcard <= 3 || dealerUpcard >= 7) ? 'hit' : 'stand';
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// 13-16: hit vs dealer 7+, stand vs 2-6
|
|
110
|
+
if (playerTotal >= 13 && playerTotal <= 16) {
|
|
111
|
+
return dealerUpcard >= 7 ? 'hit' : 'stand';
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// 9-11 without double: hit
|
|
115
|
+
return 'hit';
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Fast button click with retry
|
|
119
|
+
async function clickFast(msg, btn, retries = 2) {
|
|
120
|
+
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
121
|
+
try {
|
|
122
|
+
await sleep(50 + Math.random() * 100); // 50-150ms (fast)
|
|
123
|
+
const result = await safeClickButton(msg, btn);
|
|
124
|
+
if (result) return result;
|
|
125
|
+
} catch (e) {
|
|
126
|
+
if (attempt === retries) {
|
|
127
|
+
LOG.error(`[bj] Click failed after ${retries + 1} attempts: ${e.message}`);
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
await sleep(200);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return null;
|
|
85
134
|
}
|
|
86
135
|
|
|
87
136
|
async function runBlackjack({ channel, waitForDankMemer, betAmount = 5000 }) {
|
|
@@ -89,7 +138,7 @@ async function runBlackjack({ channel, waitForDankMemer, betAmount = 5000 }) {
|
|
|
89
138
|
LOG.cmd(`${c.white}${c.bold}${cmd}${c.reset}`);
|
|
90
139
|
|
|
91
140
|
await channel.send(cmd);
|
|
92
|
-
let current = await waitForDankMemer(
|
|
141
|
+
let current = await waitForDankMemer(12000);
|
|
93
142
|
|
|
94
143
|
if (!current) {
|
|
95
144
|
LOG.warn('[bj] No response');
|
|
@@ -100,7 +149,7 @@ async function runBlackjack({ channel, waitForDankMemer, betAmount = 5000 }) {
|
|
|
100
149
|
|
|
101
150
|
if (isHoldTight(current)) {
|
|
102
151
|
const reason = getHoldTightReason(current);
|
|
103
|
-
LOG.warn(`[bj] Hold Tight${reason ? ` (
|
|
152
|
+
LOG.warn(`[bj] Hold Tight${reason ? ` (/${reason})` : ''} — waiting 30s`);
|
|
104
153
|
await sleep(30000);
|
|
105
154
|
return { result: `hold tight (${reason || 'unknown'})`, coins: 0, holdTightReason: reason };
|
|
106
155
|
}
|
|
@@ -110,34 +159,44 @@ async function runBlackjack({ channel, waitForDankMemer, betAmount = 5000 }) {
|
|
|
110
159
|
const MAX_ROUNDS = 10;
|
|
111
160
|
for (let i = 0; i < MAX_ROUNDS; i++) {
|
|
112
161
|
const buttons = getAllButtons(current).filter(b => b.label && b.label !== 'null');
|
|
162
|
+
|
|
113
163
|
const hitBtn = buttons.find(b => !b.disabled && (b.label || '').toLowerCase().includes('hit'));
|
|
114
164
|
const standBtn = buttons.find(b => !b.disabled && (b.label || '').toLowerCase().includes('stand'));
|
|
165
|
+
const doubleBtn = buttons.find(b => !b.disabled && (b.label || '').toLowerCase().includes('double'));
|
|
166
|
+
const surrenderBtn = buttons.find(b => !b.disabled && (b.label || '').toLowerCase().includes('surrender'));
|
|
167
|
+
|
|
168
|
+
// No action buttons = game over
|
|
115
169
|
if (!hitBtn && !standBtn) break;
|
|
116
170
|
|
|
117
171
|
const playerTotal = parsePlayerTotal(current);
|
|
118
172
|
const dealerUpcard = parseDealerUpcard(current);
|
|
173
|
+
const canDouble = !!doubleBtn;
|
|
174
|
+
const canSurrender = !!surrenderBtn;
|
|
175
|
+
|
|
176
|
+
const action = playerTotal === 0
|
|
177
|
+
? 'stand' // can't parse score, play safe
|
|
178
|
+
: decide(playerTotal, dealerUpcard, canDouble, canSurrender);
|
|
119
179
|
|
|
120
180
|
let targetBtn;
|
|
121
|
-
|
|
122
|
-
targetBtn =
|
|
123
|
-
|
|
124
|
-
targetBtn = hitBtn || standBtn;
|
|
125
|
-
|
|
126
|
-
targetBtn = standBtn || hitBtn;
|
|
181
|
+
switch (action) {
|
|
182
|
+
case 'double': targetBtn = doubleBtn || hitBtn; break;
|
|
183
|
+
case 'surrender': targetBtn = surrenderBtn || standBtn; break;
|
|
184
|
+
case 'hit': targetBtn = hitBtn || standBtn; break;
|
|
185
|
+
case 'stand': targetBtn = standBtn || hitBtn; break;
|
|
186
|
+
default: targetBtn = standBtn || hitBtn;
|
|
127
187
|
}
|
|
128
188
|
|
|
129
189
|
if (!targetBtn) break;
|
|
130
190
|
|
|
131
|
-
LOG.info(`[bj] You:${playerTotal} Dealer:${dealerUpcard} → ${targetBtn.label}`);
|
|
132
|
-
await humanDelay(150, 400);
|
|
191
|
+
LOG.info(`[bj] You:${playerTotal} Dealer:${dealerUpcard} → ${action.toUpperCase()} (${targetBtn.label})`);
|
|
133
192
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
}
|
|
193
|
+
const followUp = await clickFast(current, targetBtn);
|
|
194
|
+
if (followUp) {
|
|
195
|
+
current = followUp;
|
|
196
|
+
logMsg(current, `bj-round-${i}`);
|
|
197
|
+
} else {
|
|
198
|
+
break;
|
|
199
|
+
}
|
|
141
200
|
}
|
|
142
201
|
|
|
143
202
|
const finalText = getFullText(current);
|
|
@@ -154,7 +213,7 @@ async function runBlackjack({ channel, waitForDankMemer, betAmount = 5000 }) {
|
|
|
154
213
|
}
|
|
155
214
|
if (lower.includes('won') || lower.includes('beat')) {
|
|
156
215
|
const coins = parseCoins(finalText);
|
|
157
|
-
return { result: `bj →
|
|
216
|
+
return { result: `bj → won`, coins };
|
|
158
217
|
}
|
|
159
218
|
if (lower.includes('lost') || lower.includes('bust') || lower.includes('lower score')) {
|
|
160
219
|
return { result: `bj → lost`, coins: 0, lost: betAmount };
|
package/lib/commands/crime.js
CHANGED
|
@@ -126,6 +126,14 @@ async function runCrime({ channel, waitForDankMemer, safeAnswers }) {
|
|
|
126
126
|
const crimeKey = (btn.label || '').toLowerCase();
|
|
127
127
|
const prev = crimeEarnings.get(crimeKey) || 0;
|
|
128
128
|
crimeEarnings.set(crimeKey, prev + coins);
|
|
129
|
+
|
|
130
|
+
// Check for death
|
|
131
|
+
const textLower = text.toLowerCase();
|
|
132
|
+
if (textLower.includes('you died') || textLower.includes('lifesaver protected')) {
|
|
133
|
+
LOG.error(`[crime] DEATH DETECTED! Lifesaver may have been used.`);
|
|
134
|
+
return { result: `you died during crime`, coins: 0, died: true };
|
|
135
|
+
}
|
|
136
|
+
|
|
129
137
|
if (coins > 0) {
|
|
130
138
|
LOG.coin(`[crime] ${btn.label} → ${c.green}+⏣ ${coins.toLocaleString()}${c.reset}`);
|
|
131
139
|
return { result: `${btn.label} → +⏣ ${coins.toLocaleString()}`, coins };
|
package/lib/commands/fish.js
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
const {
|
|
18
18
|
LOG, c, getFullText, parseCoins, getAllButtons, safeClickButton,
|
|
19
19
|
logMsg, isHoldTight, getHoldTightReason, sleep, humanDelay,
|
|
20
|
+
isCV2, ensureCV2,
|
|
20
21
|
} = require('./utils');
|
|
21
22
|
const { downloadImage, extractImageUrl, findSafeCells } = require('./fishVision');
|
|
22
23
|
|
|
@@ -328,6 +329,9 @@ async function runFish({ channel, waitForDankMemer }) {
|
|
|
328
329
|
return { result: `fish cooldown (${cd}s)`, coins: 0, nextCooldownSec: cd };
|
|
329
330
|
}
|
|
330
331
|
|
|
332
|
+
// Ensure CV2 buttons are hydrated from raw gateway
|
|
333
|
+
if (isCV2(response)) await ensureCV2(response);
|
|
334
|
+
|
|
331
335
|
// Re-fetch for hydrated CV2 components
|
|
332
336
|
const msgId = response.id;
|
|
333
337
|
const fresh = await refetchMsg(channel, msgId);
|
|
@@ -396,6 +400,9 @@ async function runFishLoop({ channel, waitForDankMemer, maxRounds = 50, onRound,
|
|
|
396
400
|
return { totalRounds: 0, totalCaught: 0, totalMines: 0, results: [] };
|
|
397
401
|
}
|
|
398
402
|
|
|
403
|
+
// Ensure CV2 buttons are hydrated
|
|
404
|
+
if (isCV2(response)) await ensureCV2(response);
|
|
405
|
+
|
|
399
406
|
// Refetch for hydrated components
|
|
400
407
|
let msgId = response.id;
|
|
401
408
|
const fresh = await refetchMsg(channel, msgId);
|
package/lib/commands/gamble.js
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
* Gambling command handlers.
|
|
3
3
|
* Covers: cointoss (CV2), roulette, slots, snakeeyes
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
5
|
+
* All commands use:
|
|
6
|
+
* - Fast button clicks (50-150ms) with retry (2 attempts)
|
|
7
|
+
* - Kelly Criterion bet sizing
|
|
8
|
+
* - EMA win-rate tracking
|
|
9
|
+
* - Raw gateway data via rawLogger for CV2 buttons
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
const {
|
|
@@ -15,8 +15,7 @@ const {
|
|
|
15
15
|
} = require('./utils');
|
|
16
16
|
const { EMA, AhoCorasick, SlidingWindowCounter } = require('../structures');
|
|
17
17
|
|
|
18
|
-
// ── Per-game EMA win-rate tracker
|
|
19
|
-
// α=0.2 gives recent results ~5x more weight than old ones
|
|
18
|
+
// ── Per-game EMA win-rate tracker ──
|
|
20
19
|
const winRateEMA = {
|
|
21
20
|
cointoss: new EMA(0.2),
|
|
22
21
|
roulette: new EMA(0.2),
|
|
@@ -24,7 +23,6 @@ const winRateEMA = {
|
|
|
24
23
|
snakeeyes: new EMA(0.2),
|
|
25
24
|
};
|
|
26
25
|
|
|
27
|
-
// ── Sliding window: wins in last 5 minutes per game ──────────
|
|
28
26
|
const winWindow = {
|
|
29
27
|
cointoss: new SlidingWindowCounter(300000),
|
|
30
28
|
roulette: new SlidingWindowCounter(300000),
|
|
@@ -38,12 +36,6 @@ const totalWindow = {
|
|
|
38
36
|
snakeeyes: new SlidingWindowCounter(300000),
|
|
39
37
|
};
|
|
40
38
|
|
|
41
|
-
// ── Kelly Criterion ──────────────────────────────────────────
|
|
42
|
-
// f* = (bp - q) / b where:
|
|
43
|
-
// b = net odds (payout ratio, e.g., 1.0 for even money)
|
|
44
|
-
// p = probability of winning (from EMA)
|
|
45
|
-
// q = 1 - p
|
|
46
|
-
// Returns fraction of bankroll to bet (0 = don't bet, capped at 0.25)
|
|
47
39
|
function kellyFraction(winProb, odds = 1.0) {
|
|
48
40
|
if (winProb <= 0 || winProb >= 1) return 0.1;
|
|
49
41
|
const q = 1 - winProb;
|
|
@@ -51,8 +43,7 @@ function kellyFraction(winProb, odds = 1.0) {
|
|
|
51
43
|
return Math.max(0, Math.min(f, 0.25));
|
|
52
44
|
}
|
|
53
45
|
|
|
54
|
-
// ──
|
|
55
|
-
// Single O(n) pass instead of 3 separate .includes() calls
|
|
46
|
+
// ── Min-bet detection ──
|
|
56
47
|
const minBetDetector = new AhoCorasick();
|
|
57
48
|
minBetDetector.addPattern("can't bet less than", 'minbet');
|
|
58
49
|
minBetDetector.addPattern('cannot bet less than', 'minbet');
|
|
@@ -126,14 +117,32 @@ async function commonChecks(response, cmdName) {
|
|
|
126
117
|
return { skip: false };
|
|
127
118
|
}
|
|
128
119
|
|
|
120
|
+
// ── Fast click with retry ──
|
|
121
|
+
async function clickFast(msg, btn, retries = 2) {
|
|
122
|
+
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
123
|
+
try {
|
|
124
|
+
await sleep(50 + Math.random() * 100); // 50-150ms
|
|
125
|
+
const result = await safeClickButton(msg, btn);
|
|
126
|
+
if (result) return result;
|
|
127
|
+
} catch (e) {
|
|
128
|
+
if (attempt === retries) {
|
|
129
|
+
LOG.error(`Click failed after ${retries + 1} tries: ${e.message}`);
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
await sleep(200);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
|
|
129
138
|
/**
|
|
130
|
-
* Cointoss — CV2 format.
|
|
139
|
+
* Cointoss — CV2 format. Pick Heads/Tails randomly.
|
|
131
140
|
*/
|
|
132
141
|
async function runCointoss({ channel, waitForDankMemer, betAmount = 10000 }) {
|
|
133
142
|
const cmd = `pls cointoss ${betAmount}`;
|
|
134
143
|
LOG.cmd(`${c.white}${c.bold}${cmd}${c.reset}`);
|
|
135
144
|
await channel.send(cmd);
|
|
136
|
-
let response = await waitForDankMemer(
|
|
145
|
+
let response = await waitForDankMemer(12000);
|
|
137
146
|
|
|
138
147
|
await ensureCV2(response);
|
|
139
148
|
const chk = await commonChecks(response, 'cointoss');
|
|
@@ -148,28 +157,27 @@ async function runCointoss({ channel, waitForDankMemer, betAmount = 10000 }) {
|
|
|
148
157
|
const choice = Math.random() < 0.5 ? 'head' : 'tail';
|
|
149
158
|
const btn = btns.find(b => (b.label || '').toLowerCase().includes(choice)) || btns[0];
|
|
150
159
|
LOG.info(`[cointoss] Picking "${btn.label}"`);
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
} catch (e) { LOG.error(`[cointoss] Click error: ${e.message}`); }
|
|
160
|
+
|
|
161
|
+
const followUp = await clickFast(response, btn);
|
|
162
|
+
if (followUp) {
|
|
163
|
+
await ensureCV2(followUp);
|
|
164
|
+
return parseResult(getFullText(followUp), 'cointoss');
|
|
165
|
+
}
|
|
166
|
+
// Retry: re-fetch CV2 data from raw gateway
|
|
167
|
+
await sleep(1500);
|
|
168
|
+
response._cv2 = null;
|
|
169
|
+
response._cv2text = null;
|
|
170
|
+
response._cv2buttons = null;
|
|
171
|
+
await ensureCV2(response, true);
|
|
172
|
+
return parseResult(getFullText(response), 'cointoss');
|
|
165
173
|
}
|
|
166
174
|
|
|
167
175
|
return parseResult(getFullText(response), 'cointoss');
|
|
168
176
|
}
|
|
169
177
|
|
|
170
178
|
/**
|
|
171
|
-
* Roulette —
|
|
172
|
-
*
|
|
179
|
+
* Roulette — pick color, click button, wait for result.
|
|
180
|
+
* Strategy: ~47.5% red, ~47.5% black, ~5% green.
|
|
173
181
|
*/
|
|
174
182
|
async function runRoulette({ channel, waitForDankMemer, betAmount = 10000 }) {
|
|
175
183
|
const roll = Math.random();
|
|
@@ -177,32 +185,31 @@ async function runRoulette({ channel, waitForDankMemer, betAmount = 10000 }) {
|
|
|
177
185
|
const cmd = `pls roulette ${betAmount} ${color}`;
|
|
178
186
|
LOG.cmd(`${c.white}${c.bold}${cmd}${c.reset}`);
|
|
179
187
|
await channel.send(cmd);
|
|
180
|
-
let response = await waitForDankMemer(
|
|
188
|
+
let response = await waitForDankMemer(12000);
|
|
181
189
|
|
|
182
190
|
const chk = await commonChecks(response, 'roulette');
|
|
183
191
|
if (chk.skip) return chk.ret;
|
|
184
192
|
|
|
185
193
|
logMsg(response, 'roulette');
|
|
186
194
|
|
|
195
|
+
// Click the color button
|
|
187
196
|
const btns = gameButtons(response).filter(b =>
|
|
188
197
|
!(b.customId || b.custom_id || '').includes('bet')
|
|
189
198
|
);
|
|
190
199
|
const colorBtn = btns.find(b => (b.label || '').toLowerCase() === color);
|
|
191
200
|
if (colorBtn) {
|
|
192
201
|
LOG.info(`[roulette] Picking "${colorBtn.label}"`);
|
|
193
|
-
await
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
logMsg(response, 'roulette-after-pick');
|
|
199
|
-
}
|
|
200
|
-
} catch (e) { LOG.error(`[roulette] Click error: ${e.message}`); }
|
|
202
|
+
const followUp = await clickFast(response, colorBtn);
|
|
203
|
+
if (followUp) {
|
|
204
|
+
response = followUp;
|
|
205
|
+
logMsg(response, 'roulette-after-pick');
|
|
206
|
+
}
|
|
201
207
|
}
|
|
202
208
|
|
|
209
|
+
// Wait for result (roulette animates)
|
|
203
210
|
let text = getFullText(response);
|
|
204
211
|
if (text.toLowerCase().includes('spinning') || text.toLowerCase().includes('rolling') || text.toLowerCase().includes('pick a color')) {
|
|
205
|
-
await sleep(
|
|
212
|
+
await sleep(2500);
|
|
206
213
|
try {
|
|
207
214
|
const final = await channel.messages.fetch(response.id);
|
|
208
215
|
if (final) text = getFullText(final);
|
|
@@ -213,25 +220,30 @@ async function runRoulette({ channel, waitForDankMemer, betAmount = 10000 }) {
|
|
|
213
220
|
}
|
|
214
221
|
|
|
215
222
|
/**
|
|
216
|
-
* Slots —
|
|
223
|
+
* Slots — auto-spin, wait for animation to finish, parse result.
|
|
217
224
|
*/
|
|
218
225
|
async function runSlots({ channel, waitForDankMemer, betAmount = 10000 }) {
|
|
219
226
|
const cmd = `pls slots ${betAmount}`;
|
|
220
227
|
LOG.cmd(`${c.white}${c.bold}${cmd}${c.reset}`);
|
|
221
228
|
await channel.send(cmd);
|
|
222
|
-
let response = await waitForDankMemer(
|
|
229
|
+
let response = await waitForDankMemer(12000);
|
|
223
230
|
|
|
224
231
|
const chk = await commonChecks(response, 'slots');
|
|
225
232
|
if (chk.skip) return chk.ret;
|
|
226
233
|
|
|
227
234
|
logMsg(response, 'slots');
|
|
228
|
-
|
|
235
|
+
let text = getFullText(response);
|
|
229
236
|
|
|
237
|
+
// Slots animate — wait for the UPDATE with final result
|
|
230
238
|
if (text.toLowerCase().includes('spinning')) {
|
|
231
|
-
|
|
239
|
+
// Wait for message update event (animation takes ~2s)
|
|
240
|
+
await sleep(2500);
|
|
232
241
|
try {
|
|
233
242
|
const final = await channel.messages.fetch(response.id);
|
|
234
|
-
if (final)
|
|
243
|
+
if (final) {
|
|
244
|
+
text = getFullText(final);
|
|
245
|
+
logMsg(final, 'slots-final');
|
|
246
|
+
}
|
|
235
247
|
} catch {}
|
|
236
248
|
}
|
|
237
249
|
|
|
@@ -239,25 +251,29 @@ async function runSlots({ channel, waitForDankMemer, betAmount = 10000 }) {
|
|
|
239
251
|
}
|
|
240
252
|
|
|
241
253
|
/**
|
|
242
|
-
* Snakeeyes —
|
|
254
|
+
* Snakeeyes — auto-roll, wait for animation to finish, parse result.
|
|
243
255
|
*/
|
|
244
256
|
async function runSnakeeyes({ channel, waitForDankMemer, betAmount = 10000 }) {
|
|
245
257
|
const cmd = `pls snakeeyes ${betAmount}`;
|
|
246
258
|
LOG.cmd(`${c.white}${c.bold}${cmd}${c.reset}`);
|
|
247
259
|
await channel.send(cmd);
|
|
248
|
-
let response = await waitForDankMemer(
|
|
260
|
+
let response = await waitForDankMemer(12000);
|
|
249
261
|
|
|
250
262
|
const chk = await commonChecks(response, 'snakeeyes');
|
|
251
263
|
if (chk.skip) return chk.ret;
|
|
252
264
|
|
|
253
265
|
logMsg(response, 'snakeeyes');
|
|
254
|
-
|
|
266
|
+
let text = getFullText(response);
|
|
255
267
|
|
|
268
|
+
// Snakeeyes animate — wait for final
|
|
256
269
|
if (text.toLowerCase().includes('rolling')) {
|
|
257
|
-
await sleep(
|
|
270
|
+
await sleep(2500);
|
|
258
271
|
try {
|
|
259
272
|
const final = await channel.messages.fetch(response.id);
|
|
260
|
-
if (final)
|
|
273
|
+
if (final) {
|
|
274
|
+
text = getFullText(final);
|
|
275
|
+
logMsg(final, 'snakeeyes-final');
|
|
276
|
+
}
|
|
261
277
|
} catch {}
|
|
262
278
|
}
|
|
263
279
|
|