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.
- package/lib/commands/inventory.js +49 -32
- package/lib/grinder.js +24 -5
- package/package.json +1 -1
|
@@ -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
|
-
|
|
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
|
-
|
|
235
|
-
const id = String(b.customId || '');
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
return
|
|
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
|
-
|
|
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
|
-
|
|
2415
|
-
|
|
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) {
|