dankgrinder 5.12.0 → 5.13.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.
@@ -16,7 +16,7 @@
16
16
  const {
17
17
  LOG, c, sleep, humanDelay, getFullText,
18
18
  getAllButtons, findSelectMenuOption,
19
- logMsg, isHoldTight, isCV2, ensureCV2,
19
+ logMsg, isHoldTight, isCV2, ensureCV2, stripAnsi,
20
20
  } = require('./utils');
21
21
  const { LRUCache, Trie } = require('../structures');
22
22
 
@@ -61,6 +61,25 @@ for (const [item, searchTerm] of Object.entries(ITEM_SEARCH_TERM)) {
61
61
  // commands both detect "need shovel" within seconds of each other)
62
62
  const recentPurchases = new LRUCache(16);
63
63
 
64
+ function isBuySuccessText(text) {
65
+ const lower = String(stripAnsi(text || '')).toLowerCase();
66
+ return lower.includes('success')
67
+ || lower.includes('successful purchase')
68
+ || lower.includes('bought')
69
+ || lower.includes('purchased')
70
+ || (lower.includes('you bought') && lower.includes('for'));
71
+ }
72
+
73
+ function isBuyFailText(text) {
74
+ const lower = String(stripAnsi(text || '')).toLowerCase();
75
+ return lower.includes('not enough')
76
+ || lower.includes("can't afford")
77
+ || lower.includes('insufficient')
78
+ || lower.includes('you need')
79
+ || lower.includes('missing item')
80
+ || lower.includes('missing items');
81
+ }
82
+
64
83
  /**
65
84
  * Buy an item from the Dank Memer shop.
66
85
  *
@@ -171,14 +190,54 @@ async function buyItem({ channel, waitForDankMemer, itemName, quantity = 1, clie
171
190
  // Step 4: Click the Buy button → Modal with quantity input
172
191
  LOG.buy(`Clicking "${buyBtn.label}"...`);
173
192
  const btnId = buyBtn.customId || buyBtn.custom_id;
174
- let modal;
193
+ let clickResult;
175
194
  try {
176
- modal = await response.clickButton(btnId);
195
+ clickResult = await response.clickButton(btnId);
177
196
  } catch (e) {
178
197
  LOG.error(`[shop] Button click failed: ${e.message}`);
179
198
  return false;
180
199
  }
181
200
 
201
+ // Some shop buttons buy immediately (no quantity modal).
202
+ // If we received a message-like response, parse it directly.
203
+ if (clickResult && !clickResult.components?.[0]?.components?.[0]?.setValue) {
204
+ if (isCV2(clickResult)) await ensureCV2(clickResult);
205
+ const directText = getFullText(clickResult);
206
+ logMsg(clickResult, 'buy-direct-result');
207
+ if (isBuySuccessText(directText)) {
208
+ LOG.success(`${c.green}${c.bold}Purchased ${quantity}x ${itemName}!${c.reset}`);
209
+ recentPurchases.set(key, Date.now());
210
+ return true;
211
+ }
212
+ if (isBuyFailText(directText)) {
213
+ LOG.warn(`[shop] Direct buy failed for ${itemName}: ${directText.substring(0, 120)}`);
214
+ return false;
215
+ }
216
+
217
+ // Fallback: wait one follow-up message for purchase outcome text.
218
+ const follow = await waitForDankMemer(6000);
219
+ if (follow) {
220
+ if (isCV2(follow)) await ensureCV2(follow);
221
+ const fText = getFullText(follow);
222
+ logMsg(follow, 'buy-direct-followup');
223
+ if (isBuySuccessText(fText)) {
224
+ LOG.success(`${c.green}${c.bold}Purchased ${quantity}x ${itemName}!${c.reset}`);
225
+ recentPurchases.set(key, Date.now());
226
+ return true;
227
+ }
228
+ if (isBuyFailText(fText)) {
229
+ LOG.warn(`[shop] Buy failed for ${itemName}: ${fText.substring(0, 120)}`);
230
+ return false;
231
+ }
232
+ }
233
+
234
+ LOG.success(`Buy click sent for ${itemName} (no modal path)`);
235
+ recentPurchases.set(key, Date.now());
236
+ return true;
237
+ }
238
+
239
+ const modal = clickResult;
240
+
182
241
  if (!modal || !modal.components) {
183
242
  LOG.warn('[shop] No modal returned from button click');
184
243
  return false;
@@ -194,12 +253,12 @@ async function buyItem({ channel, waitForDankMemer, itemName, quantity = 1, clie
194
253
  const text = getFullText(result);
195
254
  logMsg(result, 'buy-result');
196
255
 
197
- if (text.toLowerCase().includes('success') || text.toLowerCase().includes('bought') || text.toLowerCase().includes('purchased')) {
256
+ if (isBuySuccessText(text)) {
198
257
  LOG.success(`${c.green}${c.bold}Purchased ${quantity}x ${itemName}!${c.reset}`);
199
258
  recentPurchases.set(key, Date.now());
200
259
  return true;
201
260
  }
202
- if (text.toLowerCase().includes('not enough') || text.toLowerCase().includes("can't afford") || text.toLowerCase().includes('insufficient')) {
261
+ if (isBuyFailText(text)) {
203
262
  LOG.warn(`Not enough coins to buy ${itemName}`);
204
263
  return false;
205
264
  }
@@ -53,6 +53,14 @@ function isStreamManagerScreen(lowerText) {
53
53
  || lowerText.includes('go live');
54
54
  }
55
55
 
56
+ function isActionResultText(lowerText) {
57
+ return lowerText.includes('you ran an ad')
58
+ || lowerText.includes('read chat')
59
+ || lowerText.includes('collect')
60
+ || lowerText.includes('donation')
61
+ || lowerText.includes('received');
62
+ }
63
+
56
64
  async function selectRandomStreamOption(msg) {
57
65
  const menus = getAllSelectMenus(msg);
58
66
  if (menus.length === 0) return false;
@@ -195,13 +203,34 @@ async function runStream({ channel, waitForDankMemer, client }) {
195
203
  await humanDelay(120, 320);
196
204
  try {
197
205
  const clicked = await safeClickButton(response, action);
198
- const updated = clicked || (await waitForDankMemer(6000)) || (await refetchMsg(channel, response.id));
206
+ let updated = clicked || (await waitForDankMemer(6000)) || (await refetchMsg(channel, response.id));
199
207
  if (updated) {
200
208
  response = updated;
201
209
  await hydrate(response);
202
210
  logMsg(response, 'stream-action');
203
211
  text = getFullText(response);
204
212
  lower = normalizeLower(stripAnsi(text));
213
+
214
+ // Action payouts may appear as a second follow-up message.
215
+ // Keep checking briefly so earnings are not missed.
216
+ let bestText = text;
217
+ let bestCoins = parseCoins(bestText);
218
+ for (let i = 0; i < 2; i++) {
219
+ const nextMsg = await waitForDankMemer(5000);
220
+ if (!nextMsg) break;
221
+ await hydrate(nextMsg);
222
+ logMsg(nextMsg, `stream-action-followup-${i}`);
223
+ const nextText = getFullText(nextMsg);
224
+ const nextLower = normalizeLower(stripAnsi(nextText));
225
+ const nextCoins = parseCoins(nextText);
226
+ if (nextCoins > bestCoins || (bestCoins === 0 && isActionResultText(nextLower))) {
227
+ bestText = nextText;
228
+ bestCoins = nextCoins;
229
+ response = nextMsg;
230
+ }
231
+ }
232
+ text = bestText;
233
+ lower = normalizeLower(stripAnsi(text));
205
234
  }
206
235
  } catch (e) {
207
236
  LOG.error(`[stream] Action click failed: ${e.message}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dankgrinder",
3
- "version": "5.12.0",
3
+ "version": "5.13.0",
4
4
  "description": "Dank Memer automation engine — grind coins while you sleep",
5
5
  "bin": {
6
6
  "dankgrinder": "bin/dankgrinder.js"