dankgrinder 5.22.0 → 5.23.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.
@@ -202,14 +202,21 @@ async function runInventory({ channel, waitForDankMemer, client, accountId, redi
202
202
 
203
203
  if (!response) {
204
204
  LOG.warn('[inv] No response');
205
- return { result: 'no response', items: [] };
205
+ return { result: 'no response', items: [], complete: false, pagesVisited: 0, pagesTotal: 0 };
206
206
  }
207
207
 
208
208
  if (isHoldTight(response)) {
209
209
  const reason = getHoldTightReason(response);
210
210
  LOG.warn(`[inv] Hold Tight${reason ? ` (reason: ${reason})` : ''}`);
211
211
  await sleep(30000);
212
- return { result: `hold tight (${reason || 'unknown'})`, items: [], holdTightReason: reason };
212
+ return {
213
+ result: `hold tight (${reason || 'unknown'})`,
214
+ items: [],
215
+ holdTightReason: reason,
216
+ complete: false,
217
+ pagesVisited: 0,
218
+ pagesTotal: 0,
219
+ };
213
220
  }
214
221
 
215
222
  if (isCV2(response)) await ensureCV2(response);
@@ -346,13 +353,19 @@ async function runInventory({ channel, waitForDankMemer, client, accountId, redi
346
353
  }
347
354
 
348
355
  const items = Object.values(itemMap);
349
- LOG.success(`[inv] Found ${items.length} unique items across ${visitedPages.size}/${total} pages`);
356
+ const pagesVisited = visitedPages.size;
357
+ const pagesTotal = Math.max(total || 1, 1);
358
+ const complete = page >= total || pagesVisited >= pagesTotal;
359
+ LOG.success(`[inv] Found ${items.length} unique items across ${pagesVisited}/${pagesTotal} pages`);
360
+ if (!complete) {
361
+ LOG.warn(`[inv] Incomplete pagination detected: stopped at page ${page}/${pagesTotal}`);
362
+ }
350
363
 
351
364
  const { totalValue, totalMarket } = await enrichItems(items);
352
365
  LOG.info(`[inv] Net value: ${c.bold}${c.green}⏣ ${totalValue.toLocaleString()}${c.reset} Market: ${c.bold}⏣ ${totalMarket.toLocaleString()}${c.reset}`);
353
366
 
354
367
  // Store in Redis — NO EXPIRY (permanent until next update)
355
- if (redis && accountId) {
368
+ if (complete && redis && accountId) {
356
369
  try {
357
370
  const payload = {
358
371
  items,
@@ -366,6 +379,8 @@ async function runInventory({ channel, waitForDankMemer, client, accountId, redi
366
379
  } catch (e) {
367
380
  LOG.error(`[inv] Redis store failed: ${e.message}`);
368
381
  }
382
+ } else if (!complete) {
383
+ LOG.warn('[inv] Skipping Redis store for incomplete inventory scan');
369
384
  }
370
385
 
371
386
  return {
@@ -374,6 +389,9 @@ async function runInventory({ channel, waitForDankMemer, client, accountId, redi
374
389
  totalValue,
375
390
  totalMarket,
376
391
  coins: 0,
392
+ complete,
393
+ pagesVisited,
394
+ pagesTotal,
377
395
  };
378
396
  }
379
397
 
package/lib/grinder.js CHANGED
@@ -1085,42 +1085,78 @@ class AccountWorker {
1085
1085
 
1086
1086
  // ── Check Balance ───────────────────────────────────────────
1087
1087
  async checkInventory(options = {}) {
1088
- const { force = false, startupProgress = null } = options;
1089
- if (this._invRunning) return;
1090
- if (!force && this._lastInvCheck && Date.now() - this._lastInvCheck < 300_000) return;
1088
+ const {
1089
+ force = false,
1090
+ startupProgress = null,
1091
+ requireComplete = false,
1092
+ maxAttempts = 1,
1093
+ } = options;
1094
+ if (this._invRunning) return { ok: false, skipped: 'busy' };
1095
+ if (!force && this._lastInvCheck && Date.now() - this._lastInvCheck < 300_000) return { ok: false, skipped: 'recent' };
1091
1096
  this._invRunning = true;
1092
1097
  this._lastInvCheck = Date.now();
1093
1098
  this.busy = true;
1094
1099
  try {
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
- }
1100
- const result = await commands.runInventory({
1101
- channel: this.channel,
1102
- waitForDankMemer: (timeout) => this.waitForDankMemer(timeout),
1103
- client: this.client,
1104
- accountId: this.account.id,
1105
- redis,
1106
- onPageProgress: ({ page, total }) => {
1107
- this.log('info', `Inventory pages: ${page}/${total}`);
1108
- },
1109
- });
1110
- this.log('success', `Inventory: ${result.items?.length || 0} items, ⏣ ${(result.totalValue || 0).toLocaleString()} net`);
1111
- try {
1112
- await fetch(`${API_URL}/api/grinder/inventory`, {
1113
- method: 'POST',
1114
- headers: { Authorization: `Bearer ${API_KEY}`, 'Content-Type': 'application/json' },
1115
- body: JSON.stringify({
1116
- account_id: this.account.id,
1117
- items: result.items || [],
1118
- totalValue: result.totalValue || 0,
1119
- }),
1120
- });
1121
- } catch {}
1100
+ const tries = Math.max(1, Number.isFinite(maxAttempts) ? maxAttempts : 1);
1101
+ let lastErr = null;
1102
+ for (let attempt = 1; attempt <= tries; attempt++) {
1103
+ if (startupProgress && Number.isInteger(startupProgress.current) && Number.isInteger(startupProgress.total)) {
1104
+ this.log('info', `Checking inventory... (${startupProgress.current}/${startupProgress.total}) [try ${attempt}/${tries}]`);
1105
+ } else {
1106
+ this.log('info', `Checking inventory...${tries > 1 ? ` [try ${attempt}/${tries}]` : ''}`);
1107
+ }
1108
+
1109
+ try {
1110
+ const result = await commands.runInventory({
1111
+ channel: this.channel,
1112
+ waitForDankMemer: (timeout) => this.waitForDankMemer(timeout),
1113
+ client: this.client,
1114
+ accountId: this.account.id,
1115
+ redis,
1116
+ onPageProgress: ({ page, total }) => {
1117
+ this.log('info', `Inventory pages: ${page}/${total}`);
1118
+ },
1119
+ });
1120
+
1121
+ if (requireComplete && !result.complete) {
1122
+ throw new Error(`incomplete pages (${result.pagesVisited || 0}/${result.pagesTotal || 0})`);
1123
+ }
1124
+
1125
+ this.log('success', `Inventory: ${result.items?.length || 0} items, ⏣ ${(result.totalValue || 0).toLocaleString()} net`);
1126
+ try {
1127
+ await fetch(`${API_URL}/api/grinder/inventory`, {
1128
+ method: 'POST',
1129
+ headers: { Authorization: `Bearer ${API_KEY}`, 'Content-Type': 'application/json' },
1130
+ body: JSON.stringify({
1131
+ account_id: this.account.id,
1132
+ items: result.items || [],
1133
+ totalValue: result.totalValue || 0,
1134
+ }),
1135
+ });
1136
+ } catch {}
1137
+
1138
+ return {
1139
+ ok: true,
1140
+ complete: !!result.complete,
1141
+ pagesVisited: result.pagesVisited || 0,
1142
+ pagesTotal: result.pagesTotal || 0,
1143
+ attempts: attempt,
1144
+ result,
1145
+ };
1146
+ } catch (e) {
1147
+ lastErr = e;
1148
+ if (attempt < tries) {
1149
+ this.log('warn', `Inventory attempt ${attempt}/${tries} failed (${e.message}). Retrying...`);
1150
+ await sleep(1500 + Math.floor(Math.random() * 1500));
1151
+ continue;
1152
+ }
1153
+ }
1154
+ }
1155
+
1156
+ throw lastErr || new Error('inventory check failed');
1122
1157
  } catch (e) {
1123
1158
  this.log('error', `Inventory check failed: ${e.message}`);
1159
+ return { ok: false, error: e.message };
1124
1160
  } finally {
1125
1161
  this._invRunning = false;
1126
1162
  this.busy = false;
@@ -2425,14 +2461,26 @@ async function start(apiKey, apiUrl) {
2425
2461
  const label = w?.username || w?.account?.label || w?.account?.id || `account-${i + 1}`;
2426
2462
  log('info', `${c.dim}[inv-startup] ${i + 1}/${workers.length} ${label}${c.reset}`);
2427
2463
  try {
2428
- await w.checkInventory({ force: true, startupProgress: { current: i + 1, total: workers.length } });
2429
- invDone++;
2464
+ const invRes = await w.checkInventory({
2465
+ force: true,
2466
+ startupProgress: { current: i + 1, total: workers.length },
2467
+ requireComplete: true,
2468
+ maxAttempts: 3,
2469
+ });
2470
+ if (invRes?.ok) invDone++;
2471
+ else invFailed++;
2430
2472
  } catch {
2431
2473
  invFailed++;
2432
2474
  }
2433
2475
  const invComplete = invDone + invFailed;
2434
2476
  log('info', `${c.dim}[inv-startup-progress] ${invComplete}/${workers.length} complete (${invDone} ok, ${invFailed} failed)${c.reset}`);
2435
2477
  }));
2478
+
2479
+ if (invFailed > 0) {
2480
+ log('error', `${c.red}Inventory phase incomplete: ${invDone}/${workers.length} complete, ${invFailed} failed/incomplete. Not starting grind loops.${c.reset}`);
2481
+ return;
2482
+ }
2483
+
2436
2484
  const invSummaryColor = invFailed > 0 ? c.yellow : c.green;
2437
2485
  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}`);
2438
2486
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dankgrinder",
3
- "version": "5.22.0",
3
+ "version": "5.23.0",
4
4
  "description": "Dank Memer automation engine — grind coins while you sleep",
5
5
  "bin": {
6
6
  "dankgrinder": "bin/dankgrinder.js"