dankgrinder 5.22.0 → 5.24.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/inventory.js +42 -9
- package/lib/grinder.js +99 -37
- package/package.json +1 -1
|
@@ -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 {
|
|
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);
|
|
@@ -226,6 +233,7 @@ async function runInventory({ channel, waitForDankMemer, client, accountId, redi
|
|
|
226
233
|
allItems.push(...parseInventoryPage(response));
|
|
227
234
|
|
|
228
235
|
let guard = 0;
|
|
236
|
+
let noChangeCount = 0;
|
|
229
237
|
while (page < total && guard < Math.max(20, total + 6)) {
|
|
230
238
|
guard++;
|
|
231
239
|
const buttons = getAllButtons(response);
|
|
@@ -305,12 +313,17 @@ async function runInventory({ channel, waitForDankMemer, client, accountId, redi
|
|
|
305
313
|
delete response._cv2buttons;
|
|
306
314
|
|
|
307
315
|
let pageChanged = false;
|
|
308
|
-
for (let attempt = 0; attempt <
|
|
309
|
-
await sleep(attempt === 0 ?
|
|
316
|
+
for (let attempt = 0; attempt < 7; attempt++) {
|
|
317
|
+
await sleep(attempt === 0 ? 700 : 1100 + Math.min(attempt * 150, 600));
|
|
310
318
|
try {
|
|
311
|
-
const fresh = await
|
|
319
|
+
const fresh = await response.fetch(true);
|
|
312
320
|
if (fresh) response = fresh;
|
|
313
|
-
} catch {
|
|
321
|
+
} catch {
|
|
322
|
+
try {
|
|
323
|
+
const fresh = await channel.messages.fetch(response.id);
|
|
324
|
+
if (fresh) response = fresh;
|
|
325
|
+
} catch {}
|
|
326
|
+
}
|
|
314
327
|
if (isCV2(response)) await ensureCV2(response, true);
|
|
315
328
|
const pageInfo = parsePageInfo(response);
|
|
316
329
|
if (pageInfo.page > page && !visitedPages.has(pageInfo.page)) {
|
|
@@ -330,7 +343,16 @@ async function runInventory({ channel, waitForDankMemer, client, accountId, redi
|
|
|
330
343
|
delete response._cv2buttons;
|
|
331
344
|
}
|
|
332
345
|
|
|
333
|
-
if (!pageChanged)
|
|
346
|
+
if (!pageChanged) {
|
|
347
|
+
noChangeCount++;
|
|
348
|
+
if (noChangeCount < 3) {
|
|
349
|
+
LOG.warn(`[inv] Page stuck at ${page}/${total}; retrying paginator click (${noChangeCount}/2)`);
|
|
350
|
+
await sleep(900 + Math.floor(Math.random() * 500));
|
|
351
|
+
continue;
|
|
352
|
+
}
|
|
353
|
+
break;
|
|
354
|
+
}
|
|
355
|
+
noChangeCount = 0;
|
|
334
356
|
allItems.push(...parseInventoryPage(response));
|
|
335
357
|
}
|
|
336
358
|
|
|
@@ -346,13 +368,19 @@ async function runInventory({ channel, waitForDankMemer, client, accountId, redi
|
|
|
346
368
|
}
|
|
347
369
|
|
|
348
370
|
const items = Object.values(itemMap);
|
|
349
|
-
|
|
371
|
+
const pagesVisited = visitedPages.size;
|
|
372
|
+
const pagesTotal = Math.max(total || 1, 1);
|
|
373
|
+
const complete = page >= total || pagesVisited >= pagesTotal;
|
|
374
|
+
LOG.success(`[inv] Found ${items.length} unique items across ${pagesVisited}/${pagesTotal} pages`);
|
|
375
|
+
if (!complete) {
|
|
376
|
+
LOG.warn(`[inv] Incomplete pagination detected: stopped at page ${page}/${pagesTotal}`);
|
|
377
|
+
}
|
|
350
378
|
|
|
351
379
|
const { totalValue, totalMarket } = await enrichItems(items);
|
|
352
380
|
LOG.info(`[inv] Net value: ${c.bold}${c.green}⏣ ${totalValue.toLocaleString()}${c.reset} Market: ${c.bold}⏣ ${totalMarket.toLocaleString()}${c.reset}`);
|
|
353
381
|
|
|
354
382
|
// Store in Redis — NO EXPIRY (permanent until next update)
|
|
355
|
-
if (redis && accountId) {
|
|
383
|
+
if (complete && redis && accountId) {
|
|
356
384
|
try {
|
|
357
385
|
const payload = {
|
|
358
386
|
items,
|
|
@@ -366,6 +394,8 @@ async function runInventory({ channel, waitForDankMemer, client, accountId, redi
|
|
|
366
394
|
} catch (e) {
|
|
367
395
|
LOG.error(`[inv] Redis store failed: ${e.message}`);
|
|
368
396
|
}
|
|
397
|
+
} else if (!complete) {
|
|
398
|
+
LOG.warn('[inv] Skipping Redis store for incomplete inventory scan');
|
|
369
399
|
}
|
|
370
400
|
|
|
371
401
|
return {
|
|
@@ -374,6 +404,9 @@ async function runInventory({ channel, waitForDankMemer, client, accountId, redi
|
|
|
374
404
|
totalValue,
|
|
375
405
|
totalMarket,
|
|
376
406
|
coins: 0,
|
|
407
|
+
complete,
|
|
408
|
+
pagesVisited,
|
|
409
|
+
pagesTotal,
|
|
377
410
|
};
|
|
378
411
|
}
|
|
379
412
|
|
package/lib/grinder.js
CHANGED
|
@@ -1085,42 +1085,78 @@ class AccountWorker {
|
|
|
1085
1085
|
|
|
1086
1086
|
// ── Check Balance ───────────────────────────────────────────
|
|
1087
1087
|
async checkInventory(options = {}) {
|
|
1088
|
-
const {
|
|
1089
|
-
|
|
1090
|
-
|
|
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
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
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 new Promise((r) => setTimeout(r, 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;
|
|
@@ -2402,17 +2438,31 @@ async function start(apiKey, apiUrl) {
|
|
|
2402
2438
|
console.log('');
|
|
2403
2439
|
|
|
2404
2440
|
// Phase 1: Login all accounts (staggered to avoid 429s)
|
|
2405
|
-
const
|
|
2406
|
-
const
|
|
2441
|
+
const LOGIN_PROGRESS_EVERY = 5;
|
|
2442
|
+
const parsedGapMin = Number.parseInt(String(process.env.LOGIN_GAP_MIN_MS || '100'), 10);
|
|
2443
|
+
const parsedGapMax = Number.parseInt(String(process.env.LOGIN_GAP_MAX_MS || '300'), 10);
|
|
2444
|
+
const LOGIN_GAP_MIN_MS = Number.isFinite(parsedGapMin) && parsedGapMin >= 0 ? parsedGapMin : 100;
|
|
2445
|
+
const LOGIN_GAP_MAX_MS = Number.isFinite(parsedGapMax) && parsedGapMax >= LOGIN_GAP_MIN_MS ? parsedGapMax : Math.max(LOGIN_GAP_MIN_MS, 300);
|
|
2446
|
+
|
|
2447
|
+
const randomLoginGap = () => {
|
|
2448
|
+
if (LOGIN_GAP_MAX_MS <= LOGIN_GAP_MIN_MS) return LOGIN_GAP_MIN_MS;
|
|
2449
|
+
return LOGIN_GAP_MIN_MS + Math.floor(Math.random() * (LOGIN_GAP_MAX_MS - LOGIN_GAP_MIN_MS + 1));
|
|
2450
|
+
};
|
|
2451
|
+
|
|
2407
2452
|
for (let i = 0; i < accounts.length; i++) {
|
|
2408
2453
|
if (shutdownCalled) break;
|
|
2409
2454
|
const worker = new AccountWorker(accounts[i], i);
|
|
2410
2455
|
workers.push(worker);
|
|
2411
2456
|
workerMap.set(accounts[i].id, worker);
|
|
2412
2457
|
await worker.start();
|
|
2413
|
-
if (
|
|
2414
|
-
|
|
2415
|
-
|
|
2458
|
+
if (i + 1 < accounts.length) {
|
|
2459
|
+
const gapMs = randomLoginGap();
|
|
2460
|
+
if ((i + 1) % LOGIN_PROGRESS_EVERY === 0) {
|
|
2461
|
+
log('info', `${c.dim}Logged in ${i + 1}/${accounts.length}, next account in ${gapMs}ms...${c.reset}`);
|
|
2462
|
+
}
|
|
2463
|
+
await new Promise(r => setTimeout(r, gapMs));
|
|
2464
|
+
}
|
|
2465
|
+
if ((i + 1) % LOGIN_PROGRESS_EVERY === 0) {
|
|
2416
2466
|
hintGC();
|
|
2417
2467
|
}
|
|
2418
2468
|
}
|
|
@@ -2425,14 +2475,26 @@ async function start(apiKey, apiUrl) {
|
|
|
2425
2475
|
const label = w?.username || w?.account?.label || w?.account?.id || `account-${i + 1}`;
|
|
2426
2476
|
log('info', `${c.dim}[inv-startup] ${i + 1}/${workers.length} ${label}${c.reset}`);
|
|
2427
2477
|
try {
|
|
2428
|
-
await w.checkInventory({
|
|
2429
|
-
|
|
2478
|
+
const invRes = await w.checkInventory({
|
|
2479
|
+
force: true,
|
|
2480
|
+
startupProgress: { current: i + 1, total: workers.length },
|
|
2481
|
+
requireComplete: true,
|
|
2482
|
+
maxAttempts: 3,
|
|
2483
|
+
});
|
|
2484
|
+
if (invRes?.ok) invDone++;
|
|
2485
|
+
else invFailed++;
|
|
2430
2486
|
} catch {
|
|
2431
2487
|
invFailed++;
|
|
2432
2488
|
}
|
|
2433
2489
|
const invComplete = invDone + invFailed;
|
|
2434
2490
|
log('info', `${c.dim}[inv-startup-progress] ${invComplete}/${workers.length} complete (${invDone} ok, ${invFailed} failed)${c.reset}`);
|
|
2435
2491
|
}));
|
|
2492
|
+
|
|
2493
|
+
if (invFailed > 0) {
|
|
2494
|
+
log('error', `${c.red}Inventory phase incomplete: ${invDone}/${workers.length} complete, ${invFailed} failed/incomplete. Not starting grind loops.${c.reset}`);
|
|
2495
|
+
return;
|
|
2496
|
+
}
|
|
2497
|
+
|
|
2436
2498
|
const invSummaryColor = invFailed > 0 ? c.yellow : c.green;
|
|
2437
2499
|
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
2500
|
|