dankgrinder 5.19.0 → 5.20.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.
@@ -218,10 +218,13 @@ async function runInventory({ channel, waitForDankMemer, client, accountId, redi
218
218
  const allItems = [];
219
219
  let { page, total } = parsePageInfo(response);
220
220
  LOG.info(`[inv] Page ${page}/${total}`);
221
+ const visitedPages = new Set([page]);
221
222
 
222
223
  allItems.push(...parseInventoryPage(response));
223
224
 
224
- while (page < total) {
225
+ let guard = 0;
226
+ while (page < total && guard < Math.max(20, total + 6)) {
227
+ guard++;
225
228
  const buttons = getAllButtons(response);
226
229
  const enabled = buttons.filter(b => !b.disabled);
227
230
 
@@ -231,13 +234,43 @@ async function runInventory({ channel, waitForDankMemer, client, accountId, redi
231
234
  return m ? parseInt(m[1], 10) : null;
232
235
  };
233
236
 
234
- let nextBtn = enabled.find((b) => {
235
- const id = String(b.customId || '');
236
- if (!/paginator-inventory-list/i.test(id)) return false;
237
- if (!/setpage/i.test(id)) return false;
238
- return parseTargetPage(id) === page;
237
+ const classifyButton = (b) => {
238
+ const id = String(b.customId || '').toLowerCase();
239
+ const label = String(b.label || '').toLowerCase();
240
+ const emoji = String(b.emoji?.name || '').toLowerCase();
241
+ return { id, label, emoji };
242
+ };
243
+
244
+ const directNext = enabled.find((b) => {
245
+ const { id, label, emoji } = classifyButton(b);
246
+ if (id.includes('last') || label === '⏭' || emoji.includes('doubleright')) return false;
247
+ return id.includes('next')
248
+ || label.includes('next')
249
+ || label === '▶'
250
+ || label === '→'
251
+ || emoji.includes('arrowright');
239
252
  });
240
253
 
254
+ let nextBtn = directNext || null;
255
+
256
+ if (!nextBtn) {
257
+ const paginatorCandidates = enabled
258
+ .map((b) => ({ b, id: String(b.customId || ''), target: parseTargetPage(b.customId || '') }))
259
+ .filter((x) => /paginator-inventory-list/i.test(x.id) && /setpage/i.test(x.id) && Number.isInteger(x.target));
260
+
261
+ // Prefer the smallest target greater than current page.
262
+ // Fallback to target===current page for legacy/offset paginator ids.
263
+ const gtCurrent = paginatorCandidates
264
+ .filter(x => x.target > page)
265
+ .sort((a, b) => a.target - b.target);
266
+ if (gtCurrent.length > 0) {
267
+ nextBtn = gtCurrent[0].b;
268
+ } else {
269
+ const eqCurrent = paginatorCandidates.find(x => x.target === page);
270
+ if (eqCurrent) nextBtn = eqCurrent.b;
271
+ }
272
+ }
273
+
241
274
  if (!nextBtn) {
242
275
  nextBtn = enabled.find((b) => {
243
276
  const id = (b.customId || '').toLowerCase();
@@ -256,35 +289,13 @@ async function runInventory({ channel, waitForDankMemer, client, accountId, redi
256
289
 
257
290
  await humanDelay(200, 400);
258
291
 
259
- // Set up messageUpdate listener BEFORE clicking so we don't miss the update
260
- const msgId = response.id;
261
- const updatePromise = new Promise((resolve) => {
262
- const timeout = setTimeout(() => {
263
- client.removeListener('messageUpdate', handler);
264
- resolve(null);
265
- }, 10000);
266
- const handler = (_, m) => {
267
- if (m.id === msgId) {
268
- clearTimeout(timeout);
269
- client.removeListener('messageUpdate', handler);
270
- resolve(m);
271
- }
272
- };
273
- client.on('messageUpdate', handler);
274
- });
275
-
276
292
  try {
277
- await safeClickButton(response, nextBtn);
293
+ const clicked = await safeClickButton(response, nextBtn);
294
+ if (clicked) response = clicked;
278
295
  } catch {
279
296
  break;
280
297
  }
281
298
 
282
- // Wait for actual message update from Discord (page content change)
283
- const updated = await updatePromise;
284
- if (updated) {
285
- response = updated;
286
- }
287
-
288
299
  // Force-refetch CV2 with retries until page actually changes
289
300
  delete response._cv2;
290
301
  delete response._cv2text;
@@ -293,12 +304,18 @@ async function runInventory({ channel, waitForDankMemer, client, accountId, redi
293
304
  let pageChanged = false;
294
305
  for (let attempt = 0; attempt < 4; attempt++) {
295
306
  await sleep(attempt === 0 ? 600 : 1200);
307
+ try {
308
+ const fresh = await channel.messages.fetch(response.id);
309
+ if (fresh) response = fresh;
310
+ } catch {}
296
311
  if (isCV2(response)) await ensureCV2(response, true);
297
312
  const pageInfo = parsePageInfo(response);
298
- if (pageInfo.page > page) {
313
+ if (pageInfo.page > page && !visitedPages.has(pageInfo.page)) {
299
314
  page = pageInfo.page;
300
315
  total = pageInfo.total;
316
+ visitedPages.add(page);
301
317
  pageChanged = true;
318
+ LOG.info(`[inv] Page ${page}/${total}`);
302
319
  break;
303
320
  }
304
321
  // Clear CV2 cache again for next retry
@@ -323,7 +340,7 @@ async function runInventory({ channel, waitForDankMemer, client, accountId, redi
323
340
  }
324
341
 
325
342
  const items = Object.values(itemMap);
326
- LOG.success(`[inv] Found ${items.length} unique items across ${total} pages`);
343
+ LOG.success(`[inv] Found ${items.length} unique items across ${visitedPages.size}/${total} pages`);
327
344
 
328
345
  const { totalValue, totalMarket } = await enrichItems(items);
329
346
  LOG.info(`[inv] Net value: ${c.bold}${c.green}⏣ ${totalValue.toLocaleString()}${c.reset} Market: ${c.bold}⏣ ${totalMarket.toLocaleString()}${c.reset}`);
package/lib/grinder.js CHANGED
@@ -1084,14 +1084,19 @@ class AccountWorker {
1084
1084
  }
1085
1085
 
1086
1086
  // ── Check Balance ───────────────────────────────────────────
1087
- async checkInventory() {
1087
+ async checkInventory(options = {}) {
1088
+ const { force = false, startupProgress = null } = options;
1088
1089
  if (this._invRunning) return;
1089
- if (this._lastInvCheck && Date.now() - this._lastInvCheck < 300_000) return;
1090
+ if (!force && this._lastInvCheck && Date.now() - this._lastInvCheck < 300_000) return;
1090
1091
  this._invRunning = true;
1091
1092
  this._lastInvCheck = Date.now();
1092
1093
  this.busy = true;
1093
1094
  try {
1094
- this.log('info', 'Checking inventory...');
1095
+ if (startupProgress && Number.isInteger(startupProgress.current) && Number.isInteger(startupProgress.total)) {
1096
+ this.log('info', `Checking inventory... (${startupProgress.current}/${startupProgress.total})`);
1097
+ } else {
1098
+ this.log('info', 'Checking inventory...');
1099
+ }
1095
1100
  const result = await commands.runInventory({
1096
1101
  channel: this.channel,
1097
1102
  waitForDankMemer: (timeout) => this.waitForDankMemer(timeout),
@@ -2411,8 +2416,22 @@ async function start(apiKey, apiUrl) {
2411
2416
 
2412
2417
  // Phase 2: Run inventory on ALL accounts (must complete before any grinding)
2413
2418
  log('info', `${c.dim}Checking inventory for all ${workers.length} accounts...${c.reset}`);
2414
- await Promise.all(workers.map(w => w.checkInventory().catch(() => {})));
2415
- log('success', `${c.dim}All inventories checked. Starting grind loops...${c.reset}`);
2419
+ let invDone = 0;
2420
+ let invFailed = 0;
2421
+ for (let i = 0; i < workers.length; i++) {
2422
+ const w = workers[i];
2423
+ const label = w?.username || w?.account?.label || w?.account?.id || `account-${i + 1}`;
2424
+ log('info', `${c.dim}[inv-startup] ${i + 1}/${workers.length} ${label}${c.reset}`);
2425
+ try {
2426
+ await w.checkInventory({ force: true, startupProgress: { current: i + 1, total: workers.length } });
2427
+ invDone++;
2428
+ } catch {
2429
+ invFailed++;
2430
+ }
2431
+ await new Promise(r => setTimeout(r, 800));
2432
+ }
2433
+ const invSummaryColor = invFailed > 0 ? c.yellow : c.green;
2434
+ log('success', `${c.dim}Inventory phase complete: ${invSummaryColor}${invDone}/${workers.length}${c.reset}${c.dim} done${invFailed > 0 ? `, ${c.yellow}${invFailed} failed${c.reset}${c.dim}` : ''}. Starting grind loops...${c.reset}`);
2416
2435
 
2417
2436
  // Phase 3: Start all grind loops
2418
2437
  for (const w of workers) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dankgrinder",
3
- "version": "5.19.0",
3
+ "version": "5.20.0",
4
4
  "description": "Dank Memer automation engine — grind coins while you sleep",
5
5
  "bin": {
6
6
  "dankgrinder": "bin/dankgrinder.js"