dankgrinder 4.5.0 → 4.6.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 +70 -20
- package/lib/commands/gamble.js +22 -28
- package/lib/commands/highlow.js +53 -36
- package/lib/commands/postmemes.js +15 -4
- package/lib/commands/utils.js +24 -1
- package/lib/grinder.js +95 -39
- package/package.json +1 -1
|
@@ -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/gamble.js
CHANGED
|
@@ -4,20 +4,10 @@
|
|
|
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
|
-
* Generic gamble handler — works for coinflip, roulette, slots, snakeeyes.
|
|
13
|
-
*
|
|
14
|
-
* @param {object} opts
|
|
15
|
-
* @param {object} opts.channel
|
|
16
|
-
* @param {function} opts.waitForDankMemer
|
|
17
|
-
* @param {string} opts.cmdName - e.g. 'coinflip', 'roulette', 'slots', 'snakeeyes'
|
|
18
|
-
* @param {string} opts.cmdString - Full command string e.g. 'pls coinflip 1000 heads'
|
|
19
|
-
* @returns {Promise<{result: string, coins: number}>}
|
|
20
|
-
*/
|
|
21
11
|
async function runGamble({ channel, waitForDankMemer, cmdName, cmdString }) {
|
|
22
12
|
LOG.cmd(`${c.white}${c.bold}${cmdString}${c.reset}`);
|
|
23
13
|
|
|
@@ -38,9 +28,8 @@ async function runGamble({ channel, waitForDankMemer, cmdName, cmdString }) {
|
|
|
38
28
|
|
|
39
29
|
logMsg(response, cmdName);
|
|
40
30
|
const text = getFullText(response);
|
|
41
|
-
const coins = parseCoins(text);
|
|
42
31
|
|
|
43
|
-
// Some gambles have buttons (
|
|
32
|
+
// Some gambles have buttons (coinflip: pick heads/tails)
|
|
44
33
|
const buttons = getAllButtons(response);
|
|
45
34
|
if (buttons.length > 0) {
|
|
46
35
|
const btn = buttons.find(b => !b.disabled);
|
|
@@ -52,34 +41,39 @@ async function runGamble({ channel, waitForDankMemer, cmdName, cmdString }) {
|
|
|
52
41
|
if (followUp) {
|
|
53
42
|
logMsg(followUp, `${cmdName}-result`);
|
|
54
43
|
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 };
|
|
44
|
+
return parseGambleResult(fText, cmdName);
|
|
62
45
|
}
|
|
63
46
|
} catch (e) { LOG.error(`[${cmdName}] Click error: ${e.message}`); }
|
|
64
47
|
}
|
|
65
48
|
}
|
|
66
49
|
|
|
50
|
+
return parseGambleResult(text, cmdName);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function parseGambleResult(text, cmdName) {
|
|
54
|
+
const net = parseNetCoins(text);
|
|
55
|
+
const lower = text.toLowerCase();
|
|
56
|
+
|
|
57
|
+
if (net > 0) {
|
|
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
67
|
if (coins > 0) {
|
|
68
68
|
LOG.coin(`[${cmdName}] ${c.green}+⏣ ${coins.toLocaleString()}${c.reset}`);
|
|
69
69
|
return { result: `${cmdName} → +⏣ ${coins.toLocaleString()}`, coins };
|
|
70
70
|
}
|
|
71
|
-
|
|
72
|
-
|
|
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
|
-
}
|
|
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 };
|
|
78
73
|
|
|
79
74
|
return { result: `${cmdName} done`, coins: 0 };
|
|
80
75
|
}
|
|
81
76
|
|
|
82
|
-
// Convenience wrappers
|
|
83
77
|
async function runCoinflip({ channel, waitForDankMemer, betAmount = 5000 }) {
|
|
84
78
|
return runGamble({ channel, waitForDankMemer, cmdName: 'coinflip', cmdString: `pls coinflip ${betAmount} heads` });
|
|
85
79
|
}
|
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 };
|
|
@@ -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/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
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const { Client } = require('discord.js-selfbot-v13');
|
|
2
2
|
const Redis = require('ioredis');
|
|
3
3
|
const commands = require('./commands');
|
|
4
|
-
const { setDashboardActive
|
|
4
|
+
const { setDashboardActive } = require('./commands/utils');
|
|
5
5
|
|
|
6
6
|
// ── Terminal Colors & ANSI ───────────────────────────────────
|
|
7
7
|
const c = {
|
|
@@ -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
|
}
|
|
@@ -825,6 +848,15 @@ class AccountWorker {
|
|
|
825
848
|
return;
|
|
826
849
|
}
|
|
827
850
|
|
|
851
|
+
// PostMemes / command-specific cooldown from response
|
|
852
|
+
if (resultLower.includes('cannot post another meme') || resultLower.includes('dead meme')) {
|
|
853
|
+
const minMatch = result.match(/(\d+)\s*minute/i);
|
|
854
|
+
const cdSec = minMatch ? parseInt(minMatch[1]) * 60 : 120;
|
|
855
|
+
this.log('warn', `${cmdName} on cooldown: ${cdSec}s`);
|
|
856
|
+
await this.setCooldown(cmdName, cdSec);
|
|
857
|
+
return;
|
|
858
|
+
}
|
|
859
|
+
|
|
828
860
|
// Captcha/verification detection — deactivate account and stop
|
|
829
861
|
if (resultLower.includes('captcha') || resultLower.includes('verification required') ||
|
|
830
862
|
resultLower.includes('verify your account') || resultLower.includes('pass verification') ||
|
|
@@ -867,7 +899,7 @@ class AccountWorker {
|
|
|
867
899
|
return;
|
|
868
900
|
}
|
|
869
901
|
|
|
870
|
-
// Already claimed today (daily/weekly) — set long cooldown
|
|
902
|
+
// Already claimed today (daily/weekly) — set long cooldown + mark in Redis
|
|
871
903
|
if (resultLower.includes('already got your daily') || resultLower.includes('try again <t:')) {
|
|
872
904
|
this.log('info', `${cmdName} already claimed — waiting`);
|
|
873
905
|
const timeMatch = result.match(/<t:(\d+):R>/);
|
|
@@ -875,8 +907,11 @@ class AccountWorker {
|
|
|
875
907
|
const nextAvail = parseInt(timeMatch[1]) * 1000;
|
|
876
908
|
const waitSec = Math.max(60, Math.ceil((nextAvail - Date.now()) / 1000));
|
|
877
909
|
await this.setCooldown(cmdName, waitSec);
|
|
910
|
+
if (redis) try { await redis.set(`dkg:done:${this.account.id}:${cmdName}`, '1', 'EX', waitSec); } catch {}
|
|
878
911
|
} else {
|
|
879
|
-
|
|
912
|
+
const fallbackSec = cmdName === 'daily' ? 86400 : 604800;
|
|
913
|
+
await this.setCooldown(cmdName, fallbackSec);
|
|
914
|
+
if (redis) try { await redis.set(`dkg:done:${this.account.id}:${cmdName}`, '1', 'EX', fallbackSec); } catch {}
|
|
880
915
|
}
|
|
881
916
|
return;
|
|
882
917
|
}
|
|
@@ -893,6 +928,14 @@ class AccountWorker {
|
|
|
893
928
|
if (earned > 0) this.stats.coins += earned;
|
|
894
929
|
if (cmdResult.nextCooldownSec) await this.setCooldown(cmdName, cmdResult.nextCooldownSec);
|
|
895
930
|
|
|
931
|
+
// Mark daily/drops as done in Redis so we don't re-run this session
|
|
932
|
+
if (redis && earned > 0 && cmdName === 'daily') {
|
|
933
|
+
try { await redis.set(`dkg:done:${this.account.id}:daily`, '1', 'EX', 86400); } catch {}
|
|
934
|
+
}
|
|
935
|
+
if (redis && cmdName === 'drops') {
|
|
936
|
+
try { await redis.set(`dkg:done:${this.account.id}:drops`, '1', 'EX', 86400); } catch {}
|
|
937
|
+
}
|
|
938
|
+
|
|
896
939
|
if (cmdResult.holdTightReason) {
|
|
897
940
|
const reason = cmdResult.holdTightReason;
|
|
898
941
|
this.log('warn', `Hold Tight: /${reason} — 35s cooldown`);
|
|
@@ -1104,6 +1147,19 @@ class AccountWorker {
|
|
|
1104
1147
|
return;
|
|
1105
1148
|
}
|
|
1106
1149
|
|
|
1150
|
+
// Skip daily/drops if already done today (Redis marker)
|
|
1151
|
+
if (redis && (item.cmd === 'daily' || item.cmd === 'drops')) {
|
|
1152
|
+
try {
|
|
1153
|
+
const done = await redis.get(`dkg:done:${this.account.id}:${item.cmd}`);
|
|
1154
|
+
if (done) {
|
|
1155
|
+
item.nextRunAt = now + 86400 * 1000;
|
|
1156
|
+
if (this.commandQueue) this.commandQueue.push(item);
|
|
1157
|
+
this.tickTimeout = setTimeout(() => this.tick(), 100);
|
|
1158
|
+
return;
|
|
1159
|
+
}
|
|
1160
|
+
} catch {}
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1107
1163
|
this.busy = true;
|
|
1108
1164
|
const cd = (this.account[item.info.cdKey] || item.info.defaultCd);
|
|
1109
1165
|
const jitter = 1 + Math.random() * 3;
|
|
@@ -1321,7 +1377,7 @@ async function start(apiKey, apiUrl) {
|
|
|
1321
1377
|
|
|
1322
1378
|
console.log(colorBanner());
|
|
1323
1379
|
console.log(
|
|
1324
|
-
` ${rgb(139, 92, 246)}v4.
|
|
1380
|
+
` ${rgb(139, 92, 246)}v4.6${c.reset}` +
|
|
1325
1381
|
` ${c.dim}·${c.reset} ${c.white}30 Commands${c.reset}` +
|
|
1326
1382
|
` ${c.dim}·${c.reset} ${rgb(34, 211, 238)}Priority Queue${c.reset}` +
|
|
1327
1383
|
` ${c.dim}·${c.reset} ${rgb(52, 211, 153)}Redis Cooldowns${c.reset}` +
|