dankgrinder 5.16.0 → 5.19.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/farm.js +1540 -0
- package/lib/commands/farmVision.js +437 -0
- package/lib/commands/index.js +2 -0
- package/lib/commands/shop.js +166 -63
- package/lib/commands/utils.js +70 -2
- package/lib/grinder.js +1 -0
- package/package.json +1 -1
package/lib/commands/shop.js
CHANGED
|
@@ -29,6 +29,13 @@ const ITEM_COSTS = Object.freeze({
|
|
|
29
29
|
'adventure ticket': 250000,
|
|
30
30
|
'keyboard': 10000,
|
|
31
31
|
'mouse': 10000,
|
|
32
|
+
'hoe': 25000,
|
|
33
|
+
'watering can': 25000,
|
|
34
|
+
'water can': 25000,
|
|
35
|
+
'water bucket': 100000,
|
|
36
|
+
'seeds': 5000,
|
|
37
|
+
'seed': 5000,
|
|
38
|
+
'corn seeds': 5000,
|
|
32
39
|
});
|
|
33
40
|
|
|
34
41
|
const ITEM_SEARCH_TERM = Object.freeze({
|
|
@@ -39,6 +46,13 @@ const ITEM_SEARCH_TERM = Object.freeze({
|
|
|
39
46
|
'adventure ticket': 'ticket',
|
|
40
47
|
'keyboard': 'keyboard',
|
|
41
48
|
'mouse': 'mouse',
|
|
49
|
+
'hoe': 'hoe',
|
|
50
|
+
'watering can': 'water can',
|
|
51
|
+
'water can': 'water can',
|
|
52
|
+
'water bucket': 'water bucket',
|
|
53
|
+
'seeds': 'seeds',
|
|
54
|
+
'seed': 'seed',
|
|
55
|
+
'corn seeds': 'corn seeds',
|
|
42
56
|
});
|
|
43
57
|
|
|
44
58
|
const ITEM_SHOP_TAB = Object.freeze({
|
|
@@ -49,6 +63,13 @@ const ITEM_SHOP_TAB = Object.freeze({
|
|
|
49
63
|
'adventure ticket': 'Coin Shop',
|
|
50
64
|
'keyboard': 'Coin Shop',
|
|
51
65
|
'mouse': 'Coin Shop',
|
|
66
|
+
'hoe': 'Coin Shop',
|
|
67
|
+
'watering can': 'Coin Shop',
|
|
68
|
+
'water can': 'Coin Shop',
|
|
69
|
+
'water bucket': 'Coin Shop',
|
|
70
|
+
'seeds': 'Coin Shop',
|
|
71
|
+
'seed': 'Coin Shop',
|
|
72
|
+
'corn seeds': 'Coin Shop',
|
|
52
73
|
});
|
|
53
74
|
|
|
54
75
|
// Trie for item button label matching — O(k) vs O(n) linear scan
|
|
@@ -102,50 +123,34 @@ function findNextShopPageButton(msg) {
|
|
|
102
123
|
}) || null;
|
|
103
124
|
}
|
|
104
125
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
*/
|
|
116
|
-
async function buyItem({ channel, waitForDankMemer, itemName, quantity = 1, client }) {
|
|
117
|
-
const key = itemName.toLowerCase();
|
|
118
|
-
const searchTerm = ITEM_SEARCH_TERM[key] || key.split(' ').pop();
|
|
119
|
-
const targetTab = ITEM_SHOP_TAB[key] || 'Coin Shop';
|
|
120
|
-
|
|
121
|
-
// LRU dedup: skip if we bought this item in the last 10 seconds
|
|
122
|
-
const lastBuy = recentPurchases.get(key);
|
|
123
|
-
if (lastBuy && Date.now() - lastBuy < 10000) {
|
|
124
|
-
LOG.info(`[shop] Skipping ${itemName} — just purchased ${Math.round((Date.now() - lastBuy) / 1000)}s ago`);
|
|
125
|
-
return true;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
LOG.buy(`Opening shop to buy ${c.bold}${quantity}x ${itemName}${c.reset}`);
|
|
126
|
+
function normalizeShopRequest(itemName, quantity = 1) {
|
|
127
|
+
const key = String(itemName || '').toLowerCase();
|
|
128
|
+
return {
|
|
129
|
+
itemName,
|
|
130
|
+
quantity,
|
|
131
|
+
key,
|
|
132
|
+
searchTerm: ITEM_SEARCH_TERM[key] || key.split(' ').pop(),
|
|
133
|
+
targetTab: ITEM_SHOP_TAB[key] || 'Coin Shop',
|
|
134
|
+
};
|
|
135
|
+
}
|
|
129
136
|
|
|
130
|
-
|
|
137
|
+
async function openShopView({ channel, waitForDankMemer }) {
|
|
131
138
|
await channel.send('pls shop view');
|
|
132
139
|
let response = await waitForDankMemer(10000);
|
|
133
140
|
|
|
134
|
-
if (!response) {
|
|
135
|
-
LOG.warn('[shop] No response to pls shop view');
|
|
136
|
-
return false;
|
|
137
|
-
}
|
|
141
|
+
if (!response) return { ok: false, reason: 'no-response', response: null };
|
|
138
142
|
if (isHoldTight(response)) {
|
|
139
143
|
LOG.warn('[shop] Hold tight — waiting 30s');
|
|
140
144
|
await sleep(30000);
|
|
141
|
-
return false;
|
|
145
|
+
return { ok: false, reason: 'hold-tight', response: null };
|
|
142
146
|
}
|
|
143
147
|
|
|
144
148
|
if (isCV2(response)) await ensureCV2(response);
|
|
145
|
-
|
|
146
149
|
logMsg(response, 'shop');
|
|
150
|
+
return { ok: true, reason: 'ok', response };
|
|
151
|
+
}
|
|
147
152
|
|
|
148
|
-
|
|
153
|
+
async function ensureShopTab({ response, waitForDankMemer, targetTab }) {
|
|
149
154
|
const selectInfo = findSelectMenuOption(response, targetTab);
|
|
150
155
|
if (selectInfo) {
|
|
151
156
|
const isAlreadySelected = (selectInfo.component.options || [])
|
|
@@ -176,12 +181,9 @@ async function buyItem({ channel, waitForDankMemer, itemName, quantity = 1, clie
|
|
|
176
181
|
await sleep(300);
|
|
177
182
|
} catch (e) {
|
|
178
183
|
LOG.error(`[shop] Tab switch failed: ${e.message}`);
|
|
179
|
-
return false;
|
|
180
184
|
}
|
|
181
185
|
}
|
|
182
186
|
} else {
|
|
183
|
-
// If select menu exists but option matching failed due label variations,
|
|
184
|
-
// try selecting by value from any menu option containing coin/fishing/etc.
|
|
185
187
|
const menus = getAllSelectMenus(response);
|
|
186
188
|
const menu = menus.find(m => (m.options || []).length > 0);
|
|
187
189
|
if (menu) {
|
|
@@ -199,8 +201,10 @@ async function buyItem({ channel, waitForDankMemer, itemName, quantity = 1, clie
|
|
|
199
201
|
}
|
|
200
202
|
}
|
|
201
203
|
}
|
|
204
|
+
return response;
|
|
205
|
+
}
|
|
202
206
|
|
|
203
|
-
|
|
207
|
+
async function findBuyButtonPaged({ response, waitForDankMemer, searchTerm, key }) {
|
|
204
208
|
let buyBtn = null;
|
|
205
209
|
for (let page = 0; page < 10; page++) {
|
|
206
210
|
buyBtn = findBuyButtonInMessage(response, searchTerm, key);
|
|
@@ -217,22 +221,16 @@ async function buyItem({ channel, waitForDankMemer, itemName, quantity = 1, clie
|
|
|
217
221
|
break;
|
|
218
222
|
}
|
|
219
223
|
}
|
|
224
|
+
return { response, buyBtn };
|
|
225
|
+
}
|
|
220
226
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
const named = getAllButtons(response).filter(b => b.label);
|
|
224
|
-
if (named.length > 0) {
|
|
225
|
-
LOG.debug(`[shop] Available: ${named.map(b => `"${b.label}"(${b.disabled ? 'DIS' : 'EN'})`).join(', ')}`);
|
|
226
|
-
}
|
|
227
|
-
return false;
|
|
228
|
-
}
|
|
229
|
-
|
|
227
|
+
async function performBuyFromButton({ response, buyBtn, waitForDankMemer, itemName, quantity, key }) {
|
|
228
|
+
if (!buyBtn) return { ok: false, reason: 'not-found', response };
|
|
230
229
|
if (buyBtn.disabled) {
|
|
231
230
|
LOG.warn(`[shop] "${buyBtn.label}" is DISABLED — not enough coins for ${itemName}`);
|
|
232
|
-
return false;
|
|
231
|
+
return { ok: false, reason: 'disabled', response };
|
|
233
232
|
}
|
|
234
233
|
|
|
235
|
-
// Step 4: Click the Buy button → Modal with quantity input
|
|
236
234
|
LOG.buy(`Clicking "${buyBtn.label}"...`);
|
|
237
235
|
const btnId = buyBtn.customId || buyBtn.custom_id;
|
|
238
236
|
let clickResult;
|
|
@@ -240,11 +238,9 @@ async function buyItem({ channel, waitForDankMemer, itemName, quantity = 1, clie
|
|
|
240
238
|
clickResult = await response.clickButton(btnId);
|
|
241
239
|
} catch (e) {
|
|
242
240
|
LOG.error(`[shop] Button click failed: ${e.message}`);
|
|
243
|
-
return false;
|
|
241
|
+
return { ok: false, reason: 'click-failed', response };
|
|
244
242
|
}
|
|
245
243
|
|
|
246
|
-
// Some shop buttons buy immediately (no quantity modal).
|
|
247
|
-
// If we received a message-like response, parse it directly.
|
|
248
244
|
if (clickResult && !clickResult.components?.[0]?.components?.[0]?.setValue) {
|
|
249
245
|
if (isCV2(clickResult)) await ensureCV2(clickResult);
|
|
250
246
|
const directText = getFullText(clickResult);
|
|
@@ -252,14 +248,13 @@ async function buyItem({ channel, waitForDankMemer, itemName, quantity = 1, clie
|
|
|
252
248
|
if (isBuySuccessText(directText)) {
|
|
253
249
|
LOG.success(`${c.green}${c.bold}Purchased ${quantity}x ${itemName}!${c.reset}`);
|
|
254
250
|
recentPurchases.set(key, Date.now());
|
|
255
|
-
return true;
|
|
251
|
+
return { ok: true, reason: 'direct-success', response: clickResult };
|
|
256
252
|
}
|
|
257
253
|
if (isBuyFailText(directText)) {
|
|
258
254
|
LOG.warn(`[shop] Direct buy failed for ${itemName}: ${directText.substring(0, 120)}`);
|
|
259
|
-
return false;
|
|
255
|
+
return { ok: false, reason: 'direct-failed', response: clickResult };
|
|
260
256
|
}
|
|
261
257
|
|
|
262
|
-
// Fallback: wait one follow-up message for purchase outcome text.
|
|
263
258
|
const follow = await waitForDankMemer(6000);
|
|
264
259
|
if (follow) {
|
|
265
260
|
if (isCV2(follow)) await ensureCV2(follow);
|
|
@@ -268,27 +263,26 @@ async function buyItem({ channel, waitForDankMemer, itemName, quantity = 1, clie
|
|
|
268
263
|
if (isBuySuccessText(fText)) {
|
|
269
264
|
LOG.success(`${c.green}${c.bold}Purchased ${quantity}x ${itemName}!${c.reset}`);
|
|
270
265
|
recentPurchases.set(key, Date.now());
|
|
271
|
-
return true;
|
|
266
|
+
return { ok: true, reason: 'followup-success', response: follow };
|
|
272
267
|
}
|
|
273
268
|
if (isBuyFailText(fText)) {
|
|
274
269
|
LOG.warn(`[shop] Buy failed for ${itemName}: ${fText.substring(0, 120)}`);
|
|
275
|
-
return false;
|
|
270
|
+
return { ok: false, reason: 'followup-failed', response: follow };
|
|
276
271
|
}
|
|
272
|
+
return { ok: true, reason: 'followup-unknown', response: follow };
|
|
277
273
|
}
|
|
278
274
|
|
|
279
275
|
LOG.success(`Buy click sent for ${itemName} (no modal path)`);
|
|
280
276
|
recentPurchases.set(key, Date.now());
|
|
281
|
-
return true;
|
|
277
|
+
return { ok: true, reason: 'direct-unknown', response: clickResult };
|
|
282
278
|
}
|
|
283
279
|
|
|
284
280
|
const modal = clickResult;
|
|
285
|
-
|
|
286
281
|
if (!modal || !modal.components) {
|
|
287
282
|
LOG.warn('[shop] No modal returned from button click');
|
|
288
|
-
return false;
|
|
283
|
+
return { ok: false, reason: 'no-modal', response };
|
|
289
284
|
}
|
|
290
285
|
|
|
291
|
-
// Step 5: Set quantity and submit the modal
|
|
292
286
|
LOG.buy(`Modal "${modal.title}" — qty=${quantity}`);
|
|
293
287
|
try {
|
|
294
288
|
modal.components[0].components[0].setValue(String(quantity));
|
|
@@ -301,20 +295,129 @@ async function buyItem({ channel, waitForDankMemer, itemName, quantity = 1, clie
|
|
|
301
295
|
if (isBuySuccessText(text)) {
|
|
302
296
|
LOG.success(`${c.green}${c.bold}Purchased ${quantity}x ${itemName}!${c.reset}`);
|
|
303
297
|
recentPurchases.set(key, Date.now());
|
|
304
|
-
return true;
|
|
298
|
+
return { ok: true, reason: 'modal-success', response: result };
|
|
305
299
|
}
|
|
306
300
|
if (isBuyFailText(text)) {
|
|
307
301
|
LOG.warn(`Not enough coins to buy ${itemName}`);
|
|
308
|
-
return false;
|
|
302
|
+
return { ok: false, reason: 'modal-failed', response: result };
|
|
309
303
|
}
|
|
304
|
+
return { ok: true, reason: 'modal-unknown', response: result };
|
|
310
305
|
}
|
|
311
306
|
|
|
312
307
|
LOG.success(`Modal submitted for ${quantity}x ${itemName} (assuming success)`);
|
|
313
|
-
return true;
|
|
308
|
+
return { ok: true, reason: 'modal-submitted', response };
|
|
314
309
|
} catch (e) {
|
|
315
310
|
LOG.error(`[shop] Modal submit failed: ${e.message}`);
|
|
311
|
+
return { ok: false, reason: 'modal-submit-failed', response };
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Buy an item from the Dank Memer shop.
|
|
317
|
+
*
|
|
318
|
+
* @param {object} opts
|
|
319
|
+
* @param {object} opts.channel
|
|
320
|
+
* @param {function} opts.waitForDankMemer
|
|
321
|
+
* @param {string} opts.itemName - e.g. 'Hunting Rifle', 'Shovel'
|
|
322
|
+
* @param {number} [opts.quantity=1]
|
|
323
|
+
* @param {object} [opts.client] - Discord client
|
|
324
|
+
* @returns {Promise<boolean>} true if purchase succeeded
|
|
325
|
+
*/
|
|
326
|
+
async function buyItem({ channel, waitForDankMemer, itemName, quantity = 1, client }) {
|
|
327
|
+
const { key, searchTerm, targetTab } = normalizeShopRequest(itemName, quantity);
|
|
328
|
+
|
|
329
|
+
// LRU dedup: skip if we bought this item in the last 10 seconds
|
|
330
|
+
const lastBuy = recentPurchases.get(key);
|
|
331
|
+
if (lastBuy && Date.now() - lastBuy < 10000) {
|
|
332
|
+
LOG.info(`[shop] Skipping ${itemName} — just purchased ${Math.round((Date.now() - lastBuy) / 1000)}s ago`);
|
|
333
|
+
return true;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
LOG.buy(`Opening shop to buy ${c.bold}${quantity}x ${itemName}${c.reset}`);
|
|
337
|
+
|
|
338
|
+
// Step 1: Open shop
|
|
339
|
+
const opened = await openShopView({ channel, waitForDankMemer });
|
|
340
|
+
if (!opened.ok) {
|
|
341
|
+
LOG.warn('[shop] No response to pls shop view');
|
|
316
342
|
return false;
|
|
317
343
|
}
|
|
344
|
+
let response = opened.response;
|
|
345
|
+
|
|
346
|
+
// Step 2: Switch shop tab if needed
|
|
347
|
+
response = await ensureShopTab({ response, waitForDankMemer, targetTab });
|
|
348
|
+
|
|
349
|
+
// Step 3: Find the Buy button for our item (scan pages in current tab)
|
|
350
|
+
const located = await findBuyButtonPaged({ response, waitForDankMemer, searchTerm, key });
|
|
351
|
+
response = located.response;
|
|
352
|
+
const buyBtn = located.buyBtn;
|
|
353
|
+
|
|
354
|
+
if (!buyBtn) {
|
|
355
|
+
LOG.warn(`[shop] No button for "${itemName}" (search: "${searchTerm}")`);
|
|
356
|
+
const named = getAllButtons(response).filter(b => b.label);
|
|
357
|
+
if (named.length > 0) {
|
|
358
|
+
LOG.debug(`[shop] Available: ${named.map(b => `"${b.label}"(${b.disabled ? 'DIS' : 'EN'})`).join(', ')}`);
|
|
359
|
+
}
|
|
360
|
+
return false;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
if (buyBtn.disabled) {
|
|
364
|
+
LOG.warn(`[shop] "${buyBtn.label}" is DISABLED — not enough coins for ${itemName}`);
|
|
365
|
+
return false;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// Step 4/5: purchase
|
|
369
|
+
const bought = await performBuyFromButton({ response, buyBtn, waitForDankMemer, itemName, quantity, key });
|
|
370
|
+
return !!bought.ok;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
async function buyItemsBatch({ channel, waitForDankMemer, client, items = [] }) {
|
|
374
|
+
const requests = items
|
|
375
|
+
.map(i => normalizeShopRequest(i.itemName || i.item || '', i.quantity || i.qty || 1))
|
|
376
|
+
.filter(r => r.itemName && r.quantity > 0);
|
|
377
|
+
|
|
378
|
+
if (requests.length === 0) return { ok: true, results: [] };
|
|
379
|
+
|
|
380
|
+
const opened = await openShopView({ channel, waitForDankMemer });
|
|
381
|
+
if (!opened.ok) return { ok: false, results: requests.map(r => ({ itemName: r.itemName, success: false, reason: opened.reason })) };
|
|
382
|
+
|
|
383
|
+
let response = opened.response;
|
|
384
|
+
const results = [];
|
|
385
|
+
|
|
386
|
+
for (const req of requests) {
|
|
387
|
+
const lastBuy = recentPurchases.get(req.key);
|
|
388
|
+
if (lastBuy && Date.now() - lastBuy < 10000) {
|
|
389
|
+
results.push({ itemName: req.itemName, success: true, reason: 'recent' });
|
|
390
|
+
continue;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
response = await ensureShopTab({ response, waitForDankMemer, targetTab: req.targetTab });
|
|
394
|
+
const located = await findBuyButtonPaged({ response, waitForDankMemer, searchTerm: req.searchTerm, key: req.key });
|
|
395
|
+
response = located.response;
|
|
396
|
+
|
|
397
|
+
if (!located.buyBtn) {
|
|
398
|
+
const named = getAllButtons(response).filter(b => b.label);
|
|
399
|
+
if (named.length > 0) {
|
|
400
|
+
LOG.debug(`[shop] Available: ${named.map(b => `"${b.label}"(${b.disabled ? 'DIS' : 'EN'})`).join(', ')}`);
|
|
401
|
+
}
|
|
402
|
+
results.push({ itemName: req.itemName, success: false, reason: 'not-found' });
|
|
403
|
+
continue;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
const bought = await performBuyFromButton({
|
|
407
|
+
response,
|
|
408
|
+
buyBtn: located.buyBtn,
|
|
409
|
+
waitForDankMemer,
|
|
410
|
+
itemName: req.itemName,
|
|
411
|
+
quantity: req.quantity,
|
|
412
|
+
key: req.key,
|
|
413
|
+
});
|
|
414
|
+
response = bought.response || response;
|
|
415
|
+
results.push({ itemName: req.itemName, success: !!bought.ok, reason: bought.reason });
|
|
416
|
+
|
|
417
|
+
await sleep(300);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
return { ok: results.every(r => r.success), results };
|
|
318
421
|
}
|
|
319
422
|
|
|
320
|
-
module.exports = { buyItem, ITEM_COSTS };
|
|
423
|
+
module.exports = { buyItem, buyItemsBatch, ITEM_COSTS };
|
package/lib/commands/utils.js
CHANGED
|
@@ -311,7 +311,8 @@ async function safeClickButton(msg, button) {
|
|
|
311
311
|
// CV2 fallback: send interaction via raw HTTP, then wait for the message
|
|
312
312
|
// to update so we can return the updated message to the caller.
|
|
313
313
|
if (id) {
|
|
314
|
-
await clickCV2Button(msg, id);
|
|
314
|
+
const interactionAck = await clickCV2Button(msg, id);
|
|
315
|
+
if (interactionAck) msg._lastInteractionAck = interactionAck;
|
|
315
316
|
// Wait for Dank Memer to process the interaction and update the message
|
|
316
317
|
const updatedMsg = await new Promise((resolve) => {
|
|
317
318
|
const timeout = setTimeout(() => {
|
|
@@ -523,7 +524,73 @@ async function clickCV2Button(msg, customId) {
|
|
|
523
524
|
Authorization: token, 'Content-Type': 'application/json',
|
|
524
525
|
}, payload);
|
|
525
526
|
if (resp.status >= 400) throw new Error(`CV2 click ${resp.status}: ${resp.body.substring(0, 200)}`);
|
|
526
|
-
|
|
527
|
+
|
|
528
|
+
let parsed = null;
|
|
529
|
+
try {
|
|
530
|
+
parsed = resp.body ? JSON.parse(resp.body) : null;
|
|
531
|
+
} catch {
|
|
532
|
+
parsed = null;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
const data = parsed?.data || null;
|
|
536
|
+
if (!data && !parsed) return null;
|
|
537
|
+
|
|
538
|
+
return {
|
|
539
|
+
interactionStatus: resp.status,
|
|
540
|
+
interactionType: parsed?.type ?? null,
|
|
541
|
+
flags: data?.flags ?? parsed?.flags ?? 0,
|
|
542
|
+
content: data?.content ?? parsed?.content ?? '',
|
|
543
|
+
embeds: data?.embeds ?? parsed?.embeds ?? [],
|
|
544
|
+
components: data?.components ?? parsed?.components ?? [],
|
|
545
|
+
raw: parsed || resp.body,
|
|
546
|
+
};
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
async function clickCV2SelectMenu(msg, customId, values = []) {
|
|
550
|
+
const token = msg.client?.token;
|
|
551
|
+
const chId = msg.channelId || msg.channel?.id;
|
|
552
|
+
const gId = msg.guildId || msg.guild?.id;
|
|
553
|
+
if (!token) throw new Error('No token for CV2 select');
|
|
554
|
+
const sessionId = msg.client?.ws?.shards?.first?.()?.sessionId;
|
|
555
|
+
const nonce = `${BigInt(Date.now() - 1420070400000) << 22n}`;
|
|
556
|
+
const payloadObj = {
|
|
557
|
+
type: 3,
|
|
558
|
+
application_id: String(msg.applicationId || DANK_MEMER_ID),
|
|
559
|
+
nonce,
|
|
560
|
+
channel_id: String(chId),
|
|
561
|
+
message_id: String(msg.id),
|
|
562
|
+
data: {
|
|
563
|
+
component_type: 3,
|
|
564
|
+
custom_id: customId,
|
|
565
|
+
values: Array.isArray(values) ? values.map(v => String(v)) : [],
|
|
566
|
+
},
|
|
567
|
+
};
|
|
568
|
+
if (gId) payloadObj.guild_id = String(gId);
|
|
569
|
+
if (sessionId) payloadObj.session_id = sessionId;
|
|
570
|
+
|
|
571
|
+
const resp = await _httpPost('https://discord.com/api/v9/interactions', {
|
|
572
|
+
Authorization: token, 'Content-Type': 'application/json',
|
|
573
|
+
}, JSON.stringify(payloadObj));
|
|
574
|
+
if (resp.status >= 400) throw new Error(`CV2 select ${resp.status}: ${resp.body.substring(0, 200)}`);
|
|
575
|
+
|
|
576
|
+
let parsed = null;
|
|
577
|
+
try {
|
|
578
|
+
parsed = resp.body ? JSON.parse(resp.body) : null;
|
|
579
|
+
} catch {
|
|
580
|
+
parsed = null;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
const data = parsed?.data || null;
|
|
584
|
+
if (!data && !parsed) return null;
|
|
585
|
+
return {
|
|
586
|
+
interactionStatus: resp.status,
|
|
587
|
+
interactionType: parsed?.type ?? null,
|
|
588
|
+
flags: data?.flags ?? parsed?.flags ?? 0,
|
|
589
|
+
content: data?.content ?? parsed?.content ?? '',
|
|
590
|
+
embeds: data?.embeds ?? parsed?.embeds ?? [],
|
|
591
|
+
components: data?.components ?? parsed?.components ?? [],
|
|
592
|
+
raw: parsed || resp.body,
|
|
593
|
+
};
|
|
527
594
|
}
|
|
528
595
|
|
|
529
596
|
// ── Item Detection (Aho-Corasick Automaton) ──────────────────
|
|
@@ -596,6 +663,7 @@ module.exports = {
|
|
|
596
663
|
isCV2,
|
|
597
664
|
ensureCV2,
|
|
598
665
|
clickCV2Button,
|
|
666
|
+
clickCV2SelectMenu,
|
|
599
667
|
// Shared structures and optimized constants
|
|
600
668
|
strings,
|
|
601
669
|
cv2Cache,
|
package/lib/grinder.js
CHANGED
|
@@ -1353,6 +1353,7 @@ class AccountWorker {
|
|
|
1353
1353
|
case 'search': cmdResult = await commands.runSearch(cmdOpts); break;
|
|
1354
1354
|
case 'crime': cmdResult = await commands.runCrime(cmdOpts); break;
|
|
1355
1355
|
case 'hl': cmdResult = await commands.runHighLow(cmdOpts); break;
|
|
1356
|
+
case 'farm': cmdResult = await commands.runFarm(cmdOpts); break;
|
|
1356
1357
|
case 'pm': cmdResult = await commands.runPostMemes(cmdOpts); break;
|
|
1357
1358
|
case 'hunt': cmdResult = await commands.runHunt(cmdOpts); break;
|
|
1358
1359
|
case 'dig': cmdResult = await commands.runDig(cmdOpts); break;
|