dankgrinder 5.14.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.
@@ -249,6 +249,17 @@ function flattenComponents(components) {
249
249
  _accessoryOf: item,
250
250
  });
251
251
  }
252
+ if (acc.type === 3 || acc.type === 'SELECT_MENU' || acc.type === 'STRING_SELECT') {
253
+ flat.push({
254
+ type: 'STRING_SELECT',
255
+ customId: acc.custom_id || acc.customId,
256
+ options: acc.options || [],
257
+ disabled: acc.disabled || false,
258
+ placeholder: acc.placeholder,
259
+ data: acc,
260
+ _accessoryOf: item,
261
+ });
262
+ }
252
263
  }
253
264
  }
254
265
  return flat;
@@ -273,7 +284,7 @@ function findSelectMenuOption(msg, label) {
273
284
  const menus = getAllSelectMenus(msg);
274
285
  for (const comp of menus) {
275
286
  const opt = (comp.options || []).find(o => o.label?.toLowerCase().includes(label.toLowerCase()));
276
- if (opt) return { menuCustomId: comp.customId, option: opt, component: comp };
287
+ if (opt) return { menuCustomId: comp.customId || comp.custom_id, option: opt, component: comp };
277
288
  }
278
289
  return null;
279
290
  }
@@ -300,7 +311,8 @@ async function safeClickButton(msg, button) {
300
311
  // CV2 fallback: send interaction via raw HTTP, then wait for the message
301
312
  // to update so we can return the updated message to the caller.
302
313
  if (id) {
303
- await clickCV2Button(msg, id);
314
+ const interactionAck = await clickCV2Button(msg, id);
315
+ if (interactionAck) msg._lastInteractionAck = interactionAck;
304
316
  // Wait for Dank Memer to process the interaction and update the message
305
317
  const updatedMsg = await new Promise((resolve) => {
306
318
  const timeout = setTimeout(() => {
@@ -429,13 +441,15 @@ function isCV2(msg) {
429
441
 
430
442
  async function ensureCV2(msg, force = false) {
431
443
  if (!msg) return msg;
432
- if (!force && msg._cv2) return msg;
444
+ const msgEditedTs = msg.editedTimestamp || msg.editedAt?.getTime?.() || null;
445
+ if (!force && msg._cv2 && msg._cv2EditedTs === msgEditedTs) return msg;
433
446
  if (!isCV2(msg)) return msg;
434
447
  try {
435
448
  if (force) {
436
449
  delete msg._cv2;
437
450
  delete msg._cv2text;
438
451
  delete msg._cv2buttons;
452
+ delete msg._cv2EditedTs;
439
453
  cv2Cache.delete(msg.id);
440
454
  }
441
455
  const token = msg.client?.token;
@@ -445,10 +459,17 @@ async function ensureCV2(msg, force = false) {
445
459
  // LRU cache hit — O(1) lookup avoids redundant HTTP fetches
446
460
  const cached = cv2Cache.get(msg.id);
447
461
  if (cached && !force) {
448
- msg._cv2 = cached;
449
- msg._cv2text = _extractCV2Text(cached).trim();
450
- msg._cv2buttons = _extractCV2Buttons(cached);
451
- return msg;
462
+ const cachedComponents = Array.isArray(cached) ? cached : cached.components;
463
+ const cachedEditedTs = Array.isArray(cached) ? null : (cached.editedTimestamp || null);
464
+
465
+ // If message has been edited since cache snapshot, ignore stale cache.
466
+ if (!msgEditedTs || cachedEditedTs === msgEditedTs) {
467
+ msg._cv2 = cachedComponents;
468
+ msg._cv2text = _extractCV2Text(cachedComponents).trim();
469
+ msg._cv2buttons = _extractCV2Buttons(cachedComponents);
470
+ msg._cv2EditedTs = cachedEditedTs;
471
+ return msg;
472
+ }
452
473
  }
453
474
 
454
475
  let raw = null;
@@ -470,10 +491,11 @@ async function ensureCV2(msg, force = false) {
470
491
  }
471
492
 
472
493
  if (raw?.components) {
473
- cv2Cache.set(msg.id, raw.components);
494
+ cv2Cache.set(msg.id, { components: raw.components, editedTimestamp: msgEditedTs });
474
495
  msg._cv2 = raw.components;
475
496
  msg._cv2text = _extractCV2Text(raw.components).trim();
476
497
  msg._cv2buttons = _extractCV2Buttons(raw.components);
498
+ msg._cv2EditedTs = msgEditedTs;
477
499
  }
478
500
  } catch (e) { LOG.debug(`[cv2] fetch error: ${e.message}`); }
479
501
  return msg;
@@ -502,7 +524,73 @@ async function clickCV2Button(msg, customId) {
502
524
  Authorization: token, 'Content-Type': 'application/json',
503
525
  }, payload);
504
526
  if (resp.status >= 400) throw new Error(`CV2 click ${resp.status}: ${resp.body.substring(0, 200)}`);
505
- return null;
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
+ };
506
594
  }
507
595
 
508
596
  // ── Item Detection (Aho-Corasick Automaton) ──────────────────
@@ -575,6 +663,7 @@ module.exports = {
575
663
  isCV2,
576
664
  ensureCV2,
577
665
  clickCV2Button,
666
+ clickCV2SelectMenu,
578
667
  // Shared structures and optimized constants
579
668
  strings,
580
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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dankgrinder",
3
- "version": "5.14.0",
3
+ "version": "5.19.0",
4
4
  "description": "Dank Memer automation engine — grind coins while you sleep",
5
5
  "bin": {
6
6
  "dankgrinder": "bin/dankgrinder.js"