dankgrinder 4.9.2 → 4.9.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/commands/adventure.js +9 -4
- package/lib/commands/crime.js +11 -4
- package/lib/commands/generic.js +56 -13
- package/lib/commands/search.js +11 -4
- package/lib/commands/utils.js +81 -23
- package/lib/grinder.js +48 -11
- package/package.json +1 -1
|
@@ -349,10 +349,15 @@ async function runAdventure({ channel, waitForDankMemer, client }) {
|
|
|
349
349
|
|
|
350
350
|
const menu = response.components[menuRowIdx]?.components[0];
|
|
351
351
|
const options = menu?.options || [];
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
352
|
+
const PREFERRED = ['space', 'out west'];
|
|
353
|
+
const preferred = options.filter(o =>
|
|
354
|
+
PREFERRED.some(kw => (o.label || '').toLowerCase().includes(kw) || (o.value || '').toLowerCase().includes(kw))
|
|
355
|
+
);
|
|
356
|
+
const pool = preferred.length > 0 ? preferred : options;
|
|
357
|
+
|
|
358
|
+
if (pool.length > 0 && menuRowIdx >= 0) {
|
|
359
|
+
const opt = pool[Math.floor(Math.random() * pool.length)];
|
|
360
|
+
LOG.info(`[adventure] Selecting: "${opt.label}" (from ${pool.length} options)`);
|
|
356
361
|
try {
|
|
357
362
|
const selectResult = await response.selectMenu(menuRowIdx, [opt.value]);
|
|
358
363
|
if (selectResult) {
|
package/lib/commands/crime.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
const {
|
|
7
7
|
LOG, c, getFullText, parseCoins, getAllButtons, safeClickButton,
|
|
8
8
|
logMsg, isHoldTight, getHoldTightReason, sleep, humanDelay,
|
|
9
|
+
isCV2, ensureCV2,
|
|
9
10
|
} = require('./utils');
|
|
10
11
|
|
|
11
12
|
const SAFE_CRIME_OPTIONS = [
|
|
@@ -71,8 +72,9 @@ async function runCrime({ channel, waitForDankMemer, safeAnswers }) {
|
|
|
71
72
|
return { result: `hold tight (${reason || 'unknown'})`, coins: 0, holdTightReason: reason };
|
|
72
73
|
}
|
|
73
74
|
|
|
75
|
+
if (isCV2(response)) await ensureCV2(response);
|
|
74
76
|
logMsg(response, 'crime');
|
|
75
|
-
|
|
77
|
+
let buttons = getAllButtons(response);
|
|
76
78
|
|
|
77
79
|
if (buttons.length === 0) {
|
|
78
80
|
const text = getFullText(response);
|
|
@@ -80,10 +82,15 @@ async function runCrime({ channel, waitForDankMemer, safeAnswers }) {
|
|
|
80
82
|
return { result: text.substring(0, 60), coins: 0 };
|
|
81
83
|
}
|
|
82
84
|
|
|
83
|
-
|
|
85
|
+
let btn = pickSafeButton(buttons, safeAnswers);
|
|
84
86
|
if (!btn) {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
+
const clickable = buttons.filter(b => !b.disabled);
|
|
88
|
+
btn = clickable.length > 0 ? clickable[Math.floor(Math.random() * clickable.length)] : null;
|
|
89
|
+
if (!btn) {
|
|
90
|
+
LOG.warn('[crime] No clickable button found');
|
|
91
|
+
return { result: 'no clickable option', coins: 0 };
|
|
92
|
+
}
|
|
93
|
+
LOG.info(`[crime] No safe match — picking random: "${btn.label}"`);
|
|
87
94
|
}
|
|
88
95
|
|
|
89
96
|
LOG.info(`[crime] Picking: "${btn.label}"`);
|
package/lib/commands/generic.js
CHANGED
|
@@ -7,10 +7,26 @@
|
|
|
7
7
|
const {
|
|
8
8
|
LOG, c, getFullText, parseCoins, getAllButtons, getAllSelectMenus,
|
|
9
9
|
safeClickButton, logMsg, isHoldTight, getHoldTightReason, sleep, humanDelay, needsItem,
|
|
10
|
-
isCV2, ensureCV2,
|
|
10
|
+
isCV2, ensureCV2, stripAnsi,
|
|
11
11
|
} = require('./utils');
|
|
12
12
|
const { buyItem } = require('./shop');
|
|
13
13
|
|
|
14
|
+
async function waitForEditedMessage(channel, messageId, baselineText, timeoutMs = 12000) {
|
|
15
|
+
const start = Date.now();
|
|
16
|
+
while (Date.now() - start < timeoutMs) {
|
|
17
|
+
await sleep(700);
|
|
18
|
+
try {
|
|
19
|
+
if (!channel?.messages?.fetch) continue;
|
|
20
|
+
const fresh = await channel.messages.fetch(messageId);
|
|
21
|
+
if (!fresh) continue;
|
|
22
|
+
if (isCV2(fresh)) await ensureCV2(fresh, true);
|
|
23
|
+
const next = stripAnsi(getFullText(fresh)).replace(/\s+/g, ' ').trim();
|
|
24
|
+
if (next && next !== baselineText) return fresh;
|
|
25
|
+
} catch {}
|
|
26
|
+
}
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
|
|
14
30
|
/**
|
|
15
31
|
* @param {object} opts
|
|
16
32
|
* @param {object} opts.channel
|
|
@@ -41,10 +57,11 @@ async function runGeneric({ channel, waitForDankMemer, cmdString, cmdName, clien
|
|
|
41
57
|
if (isCV2(response)) await ensureCV2(response);
|
|
42
58
|
logMsg(response, cmdName);
|
|
43
59
|
const text = getFullText(response);
|
|
44
|
-
const
|
|
60
|
+
const cleanText = stripAnsi(text).replace(/\s+/g, ' ').trim();
|
|
61
|
+
const coins = parseCoins(cleanText);
|
|
45
62
|
|
|
46
63
|
// Check if we need an item
|
|
47
|
-
const missing = needsItem(
|
|
64
|
+
const missing = needsItem(cleanText);
|
|
48
65
|
if (missing) {
|
|
49
66
|
LOG.warn(`[${cmdName}] Missing ${c.bold}${missing}${c.reset} — auto-buying...`);
|
|
50
67
|
const bought = await buyItem({ channel, waitForDankMemer, client, itemName: missing, quantity: 1 });
|
|
@@ -55,7 +72,7 @@ async function runGeneric({ channel, waitForDankMemer, cmdString, cmdName, clien
|
|
|
55
72
|
const r2 = await waitForDankMemer(10000);
|
|
56
73
|
if (r2) {
|
|
57
74
|
logMsg(r2, `${cmdName}-retry`);
|
|
58
|
-
const t2 = getFullText(r2);
|
|
75
|
+
const t2 = stripAnsi(getFullText(r2)).replace(/\s+/g, ' ').trim();
|
|
59
76
|
const c2 = parseCoins(t2);
|
|
60
77
|
if (c2 > 0) {
|
|
61
78
|
LOG.coin(`[${cmdName}] ${c.green}+⏣ ${c2.toLocaleString()}${c.reset}`);
|
|
@@ -77,25 +94,43 @@ async function runGeneric({ channel, waitForDankMemer, cmdString, cmdName, clien
|
|
|
77
94
|
await humanDelay();
|
|
78
95
|
try {
|
|
79
96
|
const followUp = await safeClickButton(response, btn);
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
97
|
+
|
|
98
|
+
let postClickMsg = followUp || null;
|
|
99
|
+
if (!postClickMsg && response.id) {
|
|
100
|
+
postClickMsg = await waitForEditedMessage(channel, response.id, cleanText, 12000);
|
|
101
|
+
}
|
|
102
|
+
if (!postClickMsg) {
|
|
103
|
+
postClickMsg = await waitForDankMemer(9000);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (postClickMsg) {
|
|
107
|
+
if (isCV2(postClickMsg)) await ensureCV2(postClickMsg);
|
|
108
|
+
logMsg(postClickMsg, `${cmdName}-followup`);
|
|
109
|
+
const fText = stripAnsi(getFullText(postClickMsg)).replace(/\s+/g, ' ').trim();
|
|
83
110
|
const fCoins = parseCoins(fText);
|
|
111
|
+
|
|
84
112
|
if (fCoins > 0) {
|
|
85
113
|
LOG.coin(`[${cmdName}] ${c.green}+⏣ ${fCoins.toLocaleString()}${c.reset}`);
|
|
86
114
|
return { result: `+⏣ ${fCoins.toLocaleString()}`, coins: fCoins };
|
|
87
115
|
}
|
|
116
|
+
|
|
88
117
|
// Multi-step: click next button too
|
|
89
|
-
const nextButtons = getAllButtons(
|
|
118
|
+
const nextButtons = getAllButtons(postClickMsg);
|
|
90
119
|
if (nextButtons.length > 0) {
|
|
91
120
|
const nextBtn = nextButtons.find(b => !b.disabled);
|
|
92
121
|
if (nextBtn) {
|
|
93
122
|
await humanDelay();
|
|
94
|
-
try { await safeClickButton(
|
|
123
|
+
try { await safeClickButton(postClickMsg, nextBtn); } catch {}
|
|
95
124
|
}
|
|
96
125
|
}
|
|
126
|
+
|
|
127
|
+
// For XP-only outputs (like tidy) return concise title text.
|
|
128
|
+
const firstLine = fText.split(/\s*-#\s*/)[0].trim() || fText;
|
|
129
|
+
return { result: firstLine.substring(0, 90), coins: 0 };
|
|
97
130
|
}
|
|
98
|
-
} catch (e) {
|
|
131
|
+
} catch (e) {
|
|
132
|
+
LOG.error(`[${cmdName}] Click error: ${e.message}`);
|
|
133
|
+
}
|
|
99
134
|
}
|
|
100
135
|
}
|
|
101
136
|
|
|
@@ -108,6 +143,7 @@ async function runGeneric({ channel, waitForDankMemer, cmdString, cmdName, clien
|
|
|
108
143
|
if (freshMsg) response = freshMsg;
|
|
109
144
|
}
|
|
110
145
|
} catch { /* proceed with original */ }
|
|
146
|
+
|
|
111
147
|
// Find row index of first select menu
|
|
112
148
|
let menuRowIdx = -1;
|
|
113
149
|
for (let i = 0; i < (response.components || []).length; i++) {
|
|
@@ -118,6 +154,7 @@ async function runGeneric({ channel, waitForDankMemer, cmdString, cmdName, clien
|
|
|
118
154
|
}
|
|
119
155
|
if (menuRowIdx >= 0) break;
|
|
120
156
|
}
|
|
157
|
+
|
|
121
158
|
const menu = menuRowIdx >= 0 ? response.components?.[menuRowIdx]?.components?.[0] : null;
|
|
122
159
|
const options = menu?.options || [];
|
|
123
160
|
if (options.length > 0 && menuRowIdx >= 0) {
|
|
@@ -127,14 +164,16 @@ async function runGeneric({ channel, waitForDankMemer, cmdString, cmdName, clien
|
|
|
127
164
|
await response.selectMenu(menuRowIdx, [opt.value]);
|
|
128
165
|
const followUp = await waitForDankMemer(8000);
|
|
129
166
|
if (followUp) {
|
|
130
|
-
const fText = getFullText(followUp);
|
|
167
|
+
const fText = stripAnsi(getFullText(followUp)).replace(/\s+/g, ' ').trim();
|
|
131
168
|
const fCoins = parseCoins(fText);
|
|
132
169
|
if (fCoins > 0) {
|
|
133
170
|
LOG.coin(`[${cmdName}] ${opt.label} → ${c.green}+⏣ ${fCoins.toLocaleString()}${c.reset}`);
|
|
134
171
|
return { result: `${opt.label} → +⏣ ${fCoins.toLocaleString()}`, coins: fCoins };
|
|
135
172
|
}
|
|
136
173
|
}
|
|
137
|
-
} catch (e) {
|
|
174
|
+
} catch (e) {
|
|
175
|
+
LOG.error(`[${cmdName}] Select error: ${e.message}`);
|
|
176
|
+
}
|
|
138
177
|
}
|
|
139
178
|
}
|
|
140
179
|
|
|
@@ -143,7 +182,11 @@ async function runGeneric({ channel, waitForDankMemer, cmdString, cmdName, clien
|
|
|
143
182
|
return { result: `+⏣ ${coins.toLocaleString()}`, coins };
|
|
144
183
|
}
|
|
145
184
|
|
|
146
|
-
|
|
185
|
+
if (cmdName === 'tidy') {
|
|
186
|
+
return { result: 'tidy → no coins (xp only)', coins: 0 };
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return { result: cleanText.substring(0, 90) || 'done', coins: 0 };
|
|
147
190
|
}
|
|
148
191
|
|
|
149
192
|
/**
|
package/lib/commands/search.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
const {
|
|
7
7
|
LOG, c, getFullText, parseCoins, getAllButtons, safeClickButton,
|
|
8
8
|
logMsg, isHoldTight, getHoldTightReason, sleep, humanDelay,
|
|
9
|
+
isCV2, ensureCV2,
|
|
9
10
|
} = require('./utils');
|
|
10
11
|
|
|
11
12
|
const SAFE_SEARCH_LOCATIONS = [
|
|
@@ -74,8 +75,9 @@ async function runSearch({ channel, waitForDankMemer, safeAnswers }) {
|
|
|
74
75
|
return { result: `hold tight (${reason || 'unknown'})`, coins: 0, holdTightReason: reason };
|
|
75
76
|
}
|
|
76
77
|
|
|
78
|
+
if (isCV2(response)) await ensureCV2(response);
|
|
77
79
|
logMsg(response, 'search');
|
|
78
|
-
|
|
80
|
+
let buttons = getAllButtons(response);
|
|
79
81
|
|
|
80
82
|
if (buttons.length === 0) {
|
|
81
83
|
const text = getFullText(response);
|
|
@@ -83,10 +85,15 @@ async function runSearch({ channel, waitForDankMemer, safeAnswers }) {
|
|
|
83
85
|
return { result: text.substring(0, 60), coins: 0 };
|
|
84
86
|
}
|
|
85
87
|
|
|
86
|
-
|
|
88
|
+
let btn = pickSafeButton(buttons, safeAnswers);
|
|
87
89
|
if (!btn) {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
+
const clickable = buttons.filter(b => !b.disabled);
|
|
91
|
+
btn = clickable.length > 0 ? clickable[Math.floor(Math.random() * clickable.length)] : null;
|
|
92
|
+
if (!btn) {
|
|
93
|
+
LOG.warn('[search] No clickable button found');
|
|
94
|
+
return { result: 'no clickable option', coins: 0 };
|
|
95
|
+
}
|
|
96
|
+
LOG.info(`[search] No safe match — picking random: "${btn.label}"`);
|
|
90
97
|
}
|
|
91
98
|
|
|
92
99
|
LOG.info(`[search] Picking: "${btn.label}"`);
|
package/lib/commands/utils.js
CHANGED
|
@@ -68,24 +68,49 @@ function getFullText(msg) {
|
|
|
68
68
|
return text;
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
+
function stripAnsi(text) {
|
|
72
|
+
if (!text) return '';
|
|
73
|
+
return String(text).replace(/\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g, '');
|
|
74
|
+
}
|
|
75
|
+
|
|
71
76
|
function parseCoins(text) {
|
|
72
77
|
if (!text) return 0;
|
|
78
|
+
const cleanText = stripAnsi(text);
|
|
73
79
|
// Prefer "Net:" if present (accurate earned/lost from Dank Memer)
|
|
74
|
-
const netMatch =
|
|
80
|
+
const netMatch = cleanText.match(/Net:\s*\*{0,2}\s*[⏣o]\s*\*{0,2}([+-]?[\d,]+)/i);
|
|
75
81
|
if (netMatch) {
|
|
76
82
|
const net = parseInt(netMatch[1].replace(/,/g, ''));
|
|
77
83
|
return net > 0 ? net : 0;
|
|
78
84
|
}
|
|
79
|
-
const winMatch =
|
|
85
|
+
const winMatch = cleanText.match(/Winnings:\s*\*{0,2}\s*[⏣o]\s*\*{0,2}([\d,]+)/i);
|
|
80
86
|
if (winMatch) {
|
|
81
87
|
const w = parseInt(winMatch[1].replace(/,/g, ''));
|
|
82
88
|
if (w > 0) return w;
|
|
83
89
|
}
|
|
90
|
+
|
|
91
|
+
// Tidy/CV2 reward blocks often use "You received:" with non-⏣ symbols.
|
|
92
|
+
const receivedMatch = cleanText.match(/you\s+received\s*:\s*[\s\S]{0,80}?([\d,]+)/i);
|
|
93
|
+
if (receivedMatch) {
|
|
94
|
+
const r = parseInt(receivedMatch[1].replace(/,/g, ''), 10);
|
|
95
|
+
if (r > 0) return r;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// CV2 pattern: <:Coin:ID> NUMBER
|
|
99
|
+
const coinEmojiMatches = [...cleanText.matchAll(/<a?:Coin:\d+>\s*([\d,]+)/gi)];
|
|
100
|
+
if (coinEmojiMatches.length > 0) {
|
|
101
|
+
let best = 0;
|
|
102
|
+
for (const m of coinEmojiMatches) {
|
|
103
|
+
const v = parseInt((m[1] || '0').replace(/,/g, ''), 10) || 0;
|
|
104
|
+
if (v > best) best = v;
|
|
105
|
+
}
|
|
106
|
+
if (best > 0) return best;
|
|
107
|
+
}
|
|
108
|
+
|
|
84
109
|
// Prefer "placed in your wallet" pattern (daily, beg, etc.)
|
|
85
|
-
const walletMatch =
|
|
110
|
+
const walletMatch = cleanText.match(/⏣\s*([\d,]+)\s*was placed/i);
|
|
86
111
|
if (walletMatch) return parseInt(walletMatch[1].replace(/,/g, ''));
|
|
87
112
|
// Fallback: max ⏣ number
|
|
88
|
-
const matches =
|
|
113
|
+
const matches = cleanText.match(/⏣\s*([\d,]+)/g);
|
|
89
114
|
if (!matches) return 0;
|
|
90
115
|
let best = 0;
|
|
91
116
|
for (const m of matches) {
|
|
@@ -119,16 +144,19 @@ function parseNetCoins(text) {
|
|
|
119
144
|
*/
|
|
120
145
|
function parseBalance(msg) {
|
|
121
146
|
if (!msg) return 0;
|
|
122
|
-
const text = getFullText(msg);
|
|
147
|
+
const text = stripAnsi(getFullText(msg));
|
|
123
148
|
|
|
124
149
|
// Try standard embed wallet pattern
|
|
125
150
|
const walletMatch = text.match(/wallet[:\s]*[⏣💰]?\s*([\d,]+)/i);
|
|
126
151
|
if (walletMatch) return parseInt(walletMatch[1].replace(/,/g, ''));
|
|
127
152
|
|
|
128
153
|
// CV2 pattern: <:Coin:ID> NUMBER (first number after Coin emoji = wallet)
|
|
129
|
-
const coinEmojiMatch = text.match(
|
|
154
|
+
const coinEmojiMatch = text.match(/<a?:Coin:\d+>\s*([\d,]+)/i);
|
|
130
155
|
if (coinEmojiMatch) return parseInt(coinEmojiMatch[1].replace(/,/g, ''));
|
|
131
156
|
|
|
157
|
+
const bankEmojiMatch = text.match(/<a?:Bank:\d+>\s*([\d,]+)/i);
|
|
158
|
+
if (bankEmojiMatch) return parseInt(bankEmojiMatch[1].replace(/,/g, ''));
|
|
159
|
+
|
|
132
160
|
// Fallback: ⏣ prefixed
|
|
133
161
|
const coins = parseCoins(text);
|
|
134
162
|
if (coins > 0) return coins;
|
|
@@ -206,8 +234,12 @@ async function safeClickButton(msg, button) {
|
|
|
206
234
|
return button.click();
|
|
207
235
|
}
|
|
208
236
|
const id = button.customId || button.custom_id;
|
|
209
|
-
if (id && typeof msg.clickButton === 'function'
|
|
210
|
-
|
|
237
|
+
if (id && typeof msg.clickButton === 'function') {
|
|
238
|
+
try {
|
|
239
|
+
return await msg.clickButton(id);
|
|
240
|
+
} catch {
|
|
241
|
+
// Fall through to CV2 raw interaction fallback.
|
|
242
|
+
}
|
|
211
243
|
}
|
|
212
244
|
// CV2 fallback: send interaction via raw HTTP
|
|
213
245
|
if (id) {
|
|
@@ -366,19 +398,39 @@ function isCV2(msg) {
|
|
|
366
398
|
return msg.components?.length > 0 && msg.components.every(c => !c);
|
|
367
399
|
}
|
|
368
400
|
|
|
369
|
-
async function ensureCV2(msg) {
|
|
370
|
-
if (!msg
|
|
401
|
+
async function ensureCV2(msg, force = false) {
|
|
402
|
+
if (!msg) return msg;
|
|
403
|
+
if (!force && msg._cv2) return msg;
|
|
371
404
|
if (!isCV2(msg)) return msg;
|
|
372
405
|
try {
|
|
406
|
+
if (force) {
|
|
407
|
+
delete msg._cv2;
|
|
408
|
+
delete msg._cv2text;
|
|
409
|
+
delete msg._cv2buttons;
|
|
410
|
+
}
|
|
373
411
|
const token = msg.client?.token;
|
|
374
412
|
const chId = msg.channelId || msg.channel?.id;
|
|
375
413
|
if (!token || !chId) return msg;
|
|
376
|
-
//
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
414
|
+
// Try single-message endpoint first (usually freshest for edited CV2 messages).
|
|
415
|
+
let raw = null;
|
|
416
|
+
try {
|
|
417
|
+
raw = await _httpGet(
|
|
418
|
+
`https://discord.com/api/v9/channels/${chId}/messages/${msg.id}`,
|
|
419
|
+
{ Authorization: token }
|
|
420
|
+
);
|
|
421
|
+
} catch {
|
|
422
|
+
raw = null;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// Fallback to listing endpoint when single fetch is unavailable.
|
|
426
|
+
if (!raw || !raw.components) {
|
|
427
|
+
const msgs = await _httpGet(
|
|
428
|
+
`https://discord.com/api/v9/channels/${chId}/messages?limit=5&around=${msg.id}`,
|
|
429
|
+
{ Authorization: token }
|
|
430
|
+
);
|
|
431
|
+
raw = Array.isArray(msgs) ? msgs.find(m => m.id === msg.id) : null;
|
|
432
|
+
}
|
|
433
|
+
|
|
382
434
|
if (raw?.components) {
|
|
383
435
|
msg._cv2 = raw.components;
|
|
384
436
|
msg._cv2text = _extractCV2Text(raw.components).trim();
|
|
@@ -393,15 +445,20 @@ async function clickCV2Button(msg, customId) {
|
|
|
393
445
|
const chId = msg.channelId || msg.channel?.id;
|
|
394
446
|
const gId = msg.guildId || msg.guild?.id;
|
|
395
447
|
if (!token) throw new Error('No token for CV2 click');
|
|
396
|
-
const sessionId = msg.client
|
|
448
|
+
const sessionId = msg.client?.ws?.shards?.first?.()?.sessionId;
|
|
397
449
|
const nonce = `${BigInt(Date.now() - 1420070400000) << 22n}`;
|
|
398
|
-
const
|
|
399
|
-
type: 3,
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
450
|
+
const payloadObj = {
|
|
451
|
+
type: 3,
|
|
452
|
+
application_id: String(msg.applicationId || DANK_MEMER_ID),
|
|
453
|
+
nonce,
|
|
454
|
+
channel_id: String(chId),
|
|
455
|
+
message_id: String(msg.id),
|
|
403
456
|
data: { component_type: 2, custom_id: customId },
|
|
404
|
-
}
|
|
457
|
+
};
|
|
458
|
+
if (gId) payloadObj.guild_id = String(gId);
|
|
459
|
+
if (sessionId) payloadObj.session_id = sessionId;
|
|
460
|
+
|
|
461
|
+
const payload = JSON.stringify(payloadObj);
|
|
405
462
|
const resp = await _httpPost('https://discord.com/api/v9/interactions', {
|
|
406
463
|
Authorization: token, 'Content-Type': 'application/json',
|
|
407
464
|
}, payload);
|
|
@@ -441,6 +498,7 @@ module.exports = {
|
|
|
441
498
|
sleep,
|
|
442
499
|
humanDelay,
|
|
443
500
|
getFullText,
|
|
501
|
+
stripAnsi,
|
|
444
502
|
parseCoins,
|
|
445
503
|
parseNetCoins,
|
|
446
504
|
parseBalance,
|
package/lib/grinder.js
CHANGED
|
@@ -828,42 +828,79 @@ class AccountWorker {
|
|
|
828
828
|
async checkBalance() {
|
|
829
829
|
const prefix = this.account.use_slash ? '/' : 'pls';
|
|
830
830
|
await this.channel.send(`${prefix} bal`);
|
|
831
|
-
|
|
831
|
+
let response = await this.waitForDankMemer(10000);
|
|
832
|
+
|
|
833
|
+
// Fallback for slash setup: sometimes "/ bal" misses and only "/balance" works.
|
|
834
|
+
if (!response && this.account.use_slash) {
|
|
835
|
+
await this.channel.send('/balance');
|
|
836
|
+
response = await this.waitForDankMemer(10000);
|
|
837
|
+
}
|
|
838
|
+
|
|
832
839
|
if (response) {
|
|
833
840
|
if (isCV2(response)) await ensureCV2(response);
|
|
834
|
-
|
|
841
|
+
let text = stripAnsi(getFullText(response)).replace(/\s+/g, ' ').trim();
|
|
842
|
+
|
|
843
|
+
// Dank Memer sometimes sends empty first payload then edits in the full card.
|
|
844
|
+
if (!text && response.id) {
|
|
845
|
+
const edited = await this.waitForMessageUpdate(response.id, 5000);
|
|
846
|
+
if (edited) {
|
|
847
|
+
if (isCV2(edited)) await ensureCV2(edited);
|
|
848
|
+
text = stripAnsi(getFullText(edited)).replace(/\s+/g, ' ').trim();
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
if (!text) {
|
|
853
|
+
this.log('warn', 'Balance response was empty after waiting for update');
|
|
854
|
+
return;
|
|
855
|
+
}
|
|
835
856
|
|
|
836
857
|
let wallet = 0;
|
|
837
858
|
let bank = 0;
|
|
859
|
+
let matched = '';
|
|
838
860
|
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
861
|
+
// CV2 format: <:Coin:ID> 3,272,896 and <:Bank:ID> 275,000 / 275,000
|
|
862
|
+
const coinMatch = text.match(/<a?:Coin:\d+>\s*([\d,]+)/i);
|
|
863
|
+
const bankEmojiMatch = text.match(/<a?:Bank:\d+>\s*([\d,]+)/i);
|
|
864
|
+
const bankSlashMatch = text.match(/(?:<a?:Bank:\d+>\s*)?([\d,]+)\s*\/\s*[\d,]+/i);
|
|
842
865
|
|
|
843
866
|
// Legacy embed format: Wallet: ⏣ 1,234,567
|
|
844
867
|
const walletMatch = text.match(/wallet[:\s]*\*{0,2}\s*[⏣💰]?\s*\*{0,2}\s*([\d,]+)/i);
|
|
845
868
|
const bankTextMatch = text.match(/bank[:\s]*\*{0,2}\s*[⏣💰]?\s*\*{0,2}\s*([\d,]+)/i);
|
|
846
869
|
|
|
847
|
-
// Fallback:
|
|
848
|
-
const
|
|
870
|
+
// Fallback: any numbers near ⏣ or just plain numbers in CV2 text
|
|
871
|
+
const allNums = [...text.matchAll(/(?:⏣\s*)?(\d[\d,]*\d)/g)].map(m => parseInt(m[1].replace(/,/g, ''), 10)).filter(n => n > 0);
|
|
849
872
|
|
|
850
873
|
if (coinMatch) {
|
|
851
874
|
wallet = parseInt(coinMatch[1].replace(/,/g, ''), 10);
|
|
875
|
+
matched = 'cv2-emoji';
|
|
852
876
|
} else if (walletMatch) {
|
|
853
877
|
wallet = parseInt(walletMatch[1].replace(/,/g, ''), 10);
|
|
854
|
-
|
|
855
|
-
|
|
878
|
+
matched = 'legacy-wallet';
|
|
879
|
+
} else if (allNums.length > 0) {
|
|
880
|
+
wallet = Math.max(...allNums);
|
|
881
|
+
matched = 'fallback-nums';
|
|
856
882
|
}
|
|
857
883
|
|
|
858
884
|
if (bankEmojiMatch) {
|
|
859
885
|
bank = parseInt(bankEmojiMatch[1].replace(/,/g, ''), 10);
|
|
886
|
+
matched += matched ? '+bank-emoji' : 'bank-emoji';
|
|
860
887
|
} else if (bankTextMatch) {
|
|
861
888
|
bank = parseInt(bankTextMatch[1].replace(/,/g, ''), 10);
|
|
889
|
+
matched += matched ? '+bank-text' : 'bank-text';
|
|
890
|
+
} else if (bankSlashMatch) {
|
|
891
|
+
bank = parseInt(bankSlashMatch[1].replace(/,/g, ''), 10);
|
|
892
|
+
matched += matched ? '+bank-slash' : 'bank-slash';
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
if (wallet === 0 && bank === 0) {
|
|
896
|
+
this.log('warn', `Balance parse returned 0 — raw text: "${text.substring(0, 200)}"`);
|
|
897
|
+
// Don't overwrite a known-good balance with 0
|
|
898
|
+
if (this.stats.balance > 0 || this.stats.bankBalance > 0) return;
|
|
862
899
|
}
|
|
863
900
|
|
|
864
901
|
this.stats.balance = wallet;
|
|
865
902
|
this.stats.bankBalance = bank;
|
|
866
|
-
|
|
903
|
+
this.log('bal', `Wallet: ${c.bold}${c.green}⏣ ${wallet.toLocaleString()}${c.reset} Bank: ${c.bold}${c.cyan}⏣ ${bank.toLocaleString()}${c.reset} Total: ${c.bold}⏣ ${(wallet + bank).toLocaleString()}${c.reset} ${c.dim}(${matched || 'none'})${c.reset}`);
|
|
867
904
|
|
|
868
905
|
// Store in Redis for persistence
|
|
869
906
|
if (redis) {
|
|
@@ -901,7 +938,7 @@ class AccountWorker {
|
|
|
901
938
|
case 'with max': cmdString = `${prefix} with max`; break;
|
|
902
939
|
case 'blackjack': cmdString = `${prefix} bj ${bjBet}`; break;
|
|
903
940
|
case 'cointoss': cmdString = `${prefix} cointoss ${gambBet}`; break;
|
|
904
|
-
case 'roulette': cmdString = `${prefix} roulette ${gambBet} red`; break;
|
|
941
|
+
case 'roulette': cmdString = `${prefix} roulette ${gambBet} ${Math.random() < 0.5 ? 'red' : 'black'}`; break;
|
|
905
942
|
case 'slots': cmdString = `${prefix} slots ${gambBet}`; break;
|
|
906
943
|
case 'snakeeyes': cmdString = `${prefix} snakeeyes ${gambBet}`; break;
|
|
907
944
|
case 'work shift': cmdString = `${prefix} work shift`; break;
|