dankgrinder 5.0.3 → 5.0.6
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 +50 -26
- package/lib/commands/utils.js +12 -68
- package/lib/grinder.js +16 -13
- package/package.json +1 -1
|
@@ -224,7 +224,6 @@ async function runInventory({ channel, waitForDankMemer, client, accountId, redi
|
|
|
224
224
|
while (page < total) {
|
|
225
225
|
const buttons = getAllButtons(response);
|
|
226
226
|
const enabled = buttons.filter(b => !b.disabled);
|
|
227
|
-
const expectedNextPage = page; // page is 1-based, paginator target indexes are usually 0-based
|
|
228
227
|
|
|
229
228
|
const parseTargetPage = (customId) => {
|
|
230
229
|
if (!customId) return null;
|
|
@@ -236,7 +235,7 @@ async function runInventory({ channel, waitForDankMemer, client, accountId, redi
|
|
|
236
235
|
const id = String(b.customId || '');
|
|
237
236
|
if (!/paginator-inventory-list/i.test(id)) return false;
|
|
238
237
|
if (!/setpage/i.test(id)) return false;
|
|
239
|
-
return parseTargetPage(id) ===
|
|
238
|
+
return parseTargetPage(id) === page;
|
|
240
239
|
});
|
|
241
240
|
|
|
242
241
|
if (!nextBtn) {
|
|
@@ -244,7 +243,6 @@ async function runInventory({ channel, waitForDankMemer, client, accountId, redi
|
|
|
244
243
|
const id = (b.customId || '').toLowerCase();
|
|
245
244
|
const label = (b.label || '').toLowerCase();
|
|
246
245
|
const emoji = (b.emoji?.name || '').toLowerCase();
|
|
247
|
-
// Exclude backward/first/last page buttons
|
|
248
246
|
if (id.includes('prev') || id.includes('first') || id.includes('last') || id.includes('back')) return false;
|
|
249
247
|
if (label.includes('prev') || label.includes('back') || label === '◀' || label === '⏮' || label === '←' || label === '⏭') return false;
|
|
250
248
|
if (emoji.includes('arrowleft') || emoji.includes('doubleleft') || emoji.includes('doubleright')) return false;
|
|
@@ -254,36 +252,62 @@ async function runInventory({ channel, waitForDankMemer, client, accountId, redi
|
|
|
254
252
|
});
|
|
255
253
|
}
|
|
256
254
|
|
|
257
|
-
if (!nextBtn)
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
255
|
+
if (!nextBtn) break;
|
|
256
|
+
|
|
257
|
+
await humanDelay(200, 400);
|
|
258
|
+
|
|
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
|
+
});
|
|
261
275
|
|
|
262
|
-
await humanDelay(150, 350);
|
|
263
276
|
try {
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
response = result;
|
|
267
|
-
} else {
|
|
268
|
-
await sleep(1200);
|
|
269
|
-
}
|
|
270
|
-
} catch (e) {
|
|
271
|
-
LOG.error(`[inv] Next page click failed: ${e.message}`);
|
|
277
|
+
await safeClickButton(response, nextBtn);
|
|
278
|
+
} catch {
|
|
272
279
|
break;
|
|
273
280
|
}
|
|
274
281
|
|
|
275
|
-
//
|
|
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
|
+
// Force-refetch CV2 with retries until page actually changes
|
|
276
289
|
delete response._cv2;
|
|
277
290
|
delete response._cv2text;
|
|
278
291
|
delete response._cv2buttons;
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
292
|
+
|
|
293
|
+
let pageChanged = false;
|
|
294
|
+
for (let attempt = 0; attempt < 4; attempt++) {
|
|
295
|
+
await sleep(attempt === 0 ? 600 : 1200);
|
|
296
|
+
if (isCV2(response)) await ensureCV2(response, true);
|
|
297
|
+
const pageInfo = parsePageInfo(response);
|
|
298
|
+
if (pageInfo.page > page) {
|
|
299
|
+
page = pageInfo.page;
|
|
300
|
+
total = pageInfo.total;
|
|
301
|
+
pageChanged = true;
|
|
302
|
+
break;
|
|
303
|
+
}
|
|
304
|
+
// Clear CV2 cache again for next retry
|
|
305
|
+
delete response._cv2;
|
|
306
|
+
delete response._cv2text;
|
|
307
|
+
delete response._cv2buttons;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
if (!pageChanged) break;
|
|
287
311
|
allItems.push(...parseInventoryPage(response));
|
|
288
312
|
}
|
|
289
313
|
|
|
@@ -388,7 +412,7 @@ async function getAllInventories(redis, accountIds) {
|
|
|
388
412
|
}
|
|
389
413
|
|
|
390
414
|
module.exports = {
|
|
391
|
-
runInventory, fetchItemValues, enrichItems,
|
|
415
|
+
runInventory, fetchItemValues, enrichItems, parsePageInfo,
|
|
392
416
|
getCachedInventory, getAllInventories,
|
|
393
417
|
updateInventoryItem, deleteInventoryItem,
|
|
394
418
|
binarySearchItem, itemNameTrie, itemValueCache,
|
package/lib/commands/utils.js
CHANGED
|
@@ -59,11 +59,7 @@ const c = {
|
|
|
59
59
|
let _dashboardActive = false;
|
|
60
60
|
function setDashboardActive(val) { _dashboardActive = val; }
|
|
61
61
|
|
|
62
|
-
function log(
|
|
63
|
-
if (_dashboardActive) return;
|
|
64
|
-
const time = new Date().toLocaleTimeString('en-US', { hour12: true, hour: '2-digit', minute: '2-digit', second: '2-digit' });
|
|
65
|
-
console.log(` ${c.dim}${time}${c.reset} ${label} ${msg}`);
|
|
66
|
-
}
|
|
62
|
+
function log() {}
|
|
67
63
|
|
|
68
64
|
const LOG = {
|
|
69
65
|
info: (msg) => log(`${c.cyan}│${c.reset}`, msg),
|
|
@@ -73,7 +69,7 @@ const LOG = {
|
|
|
73
69
|
cmd: (msg) => log(`${c.magenta}▸${c.reset}`, msg),
|
|
74
70
|
coin: (msg) => log(`${c.yellow}$${c.reset}`, msg),
|
|
75
71
|
buy: (msg) => log(`${c.blue}♦${c.reset}`, msg),
|
|
76
|
-
debug: (
|
|
72
|
+
debug: () => {},
|
|
77
73
|
};
|
|
78
74
|
|
|
79
75
|
// ── Pre-compiled Regex (avoid recompilation in hot paths) ────
|
|
@@ -259,9 +255,9 @@ function flattenComponents(components) {
|
|
|
259
255
|
}
|
|
260
256
|
|
|
261
257
|
function getAllButtons(msg) {
|
|
262
|
-
if (msg._cv2buttons?.length > 0) return msg._cv2buttons;
|
|
258
|
+
if (msg._cv2buttons?.length > 0) return msg._cv2buttons.filter(b => b.style !== 'LINK' && b.style !== 5);
|
|
263
259
|
const all = flattenComponents(msg.components);
|
|
264
|
-
return all.filter(c => c.type === 2 || c.type === 'BUTTON');
|
|
260
|
+
return all.filter(c => (c.type === 2 || c.type === 'BUTTON') && c.style !== 'LINK' && c.style !== 5);
|
|
265
261
|
}
|
|
266
262
|
|
|
267
263
|
function getAllSelectMenus(msg) {
|
|
@@ -286,6 +282,10 @@ function findSelectMenuOption(msg, label) {
|
|
|
286
282
|
// When CV2 fallback is used, waits for the message to update so callers always
|
|
287
283
|
// get the updated message back (instead of null, which broke multi-round games).
|
|
288
284
|
async function safeClickButton(msg, button) {
|
|
285
|
+
// Skip LINK buttons (external URLs) — they have style=LINK/5 and no customId
|
|
286
|
+
if (button.style === 'LINK' || button.style === 5 || (!button.customId && !button.custom_id && typeof button.click !== 'function')) {
|
|
287
|
+
return null;
|
|
288
|
+
}
|
|
289
289
|
if (typeof button.click === 'function') {
|
|
290
290
|
return button.click();
|
|
291
291
|
}
|
|
@@ -344,66 +344,10 @@ function getHoldTightReason(msg) {
|
|
|
344
344
|
return match ? match[1].toLowerCase() : null;
|
|
345
345
|
}
|
|
346
346
|
|
|
347
|
-
//
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
if (msg.content) LOG.debug(`[${label}] content: "${msg.content.substring(0, 200).replace(/\n/g, ' ')}"`);
|
|
352
|
-
// Embeds
|
|
353
|
-
for (const e of msg.embeds || []) {
|
|
354
|
-
if (e.title) LOG.debug(`[${label}] title: "${e.title}"`);
|
|
355
|
-
if (e.description) LOG.debug(`[${label}] desc: "${e.description.substring(0, 200).replace(/\n/g, ' ')}"`);
|
|
356
|
-
for (const f of e.fields || []) LOG.debug(`[${label}] field: "${f.name}" = "${(f.value || '').substring(0, 150)}"`);
|
|
357
|
-
if (e.footer?.text) LOG.debug(`[${label}] footer: "${e.footer.text}"`);
|
|
358
|
-
if (e.image?.url) LOG.debug(`[${label}] image: ${e.image.url.substring(0, 80)}`);
|
|
359
|
-
}
|
|
360
|
-
// Components (buttons, selects, CV2 text)
|
|
361
|
-
for (const row of msg.components || []) {
|
|
362
|
-
if (!row) continue;
|
|
363
|
-
if (row.type === 'TEXT_DISPLAY' && row.content)
|
|
364
|
-
LOG.debug(`[${label}] cv2-text: "${row.content.substring(0, 200).replace(/\n/g, ' ')}"`);
|
|
365
|
-
if (row.type === 'CONTAINER' || row.type === 'SECTION') {
|
|
366
|
-
for (const comp of row.components || []) {
|
|
367
|
-
if (comp.type === 'TEXT_DISPLAY' && comp.content)
|
|
368
|
-
LOG.debug(`[${label}] cv2-section: "${comp.content.substring(0, 200).replace(/\n/g, ' ')}"`);
|
|
369
|
-
if (comp.components) {
|
|
370
|
-
for (const sub of comp.components) {
|
|
371
|
-
if (sub.type === 'TEXT_DISPLAY' && sub.content)
|
|
372
|
-
LOG.debug(`[${label}] cv2-nested: "${sub.content.substring(0, 200).replace(/\n/g, ' ')}"`);
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
for (const comp of row.components || []) {
|
|
378
|
-
if (comp.type === 'BUTTON' || comp.type === 2)
|
|
379
|
-
LOG.debug(`[${label}] btn: "${comp.label}" emoji=${comp.emoji?.name || '-'} disabled=${comp.disabled} style=${comp.style} id=${(comp.customId || '').substring(0, 40)}`);
|
|
380
|
-
if (comp.type === 'STRING_SELECT' || comp.type === 3)
|
|
381
|
-
LOG.debug(`[${label}] select: ${comp.customId} [${comp.options?.map(o => `${o.label}${o.default ? '*' : ''}`).join(', ')}]`);
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
// ── Dump full message raw (for debugging) ────────────────────
|
|
387
|
-
function dumpMessage(msg, label) {
|
|
388
|
-
console.log(`\n═══ [${label}] ═══`);
|
|
389
|
-
console.log(` author: ${msg.author?.tag} (${msg.author?.id})`);
|
|
390
|
-
console.log(` content: "${msg.content || ''}"`);
|
|
391
|
-
console.log(` embeds (${msg.embeds?.length || 0}):`);
|
|
392
|
-
for (const e of msg.embeds || []) {
|
|
393
|
-
console.log(JSON.stringify({
|
|
394
|
-
title: e.title, description: e.description,
|
|
395
|
-
fields: e.fields?.map(f => ({ name: f.name, value: f.value })),
|
|
396
|
-
footer: e.footer?.text, color: e.color,
|
|
397
|
-
}, null, 2));
|
|
398
|
-
}
|
|
399
|
-
console.log(` components (${msg.components?.length || 0}):`);
|
|
400
|
-
for (const row of msg.components || []) {
|
|
401
|
-
for (const comp of row.components || []) {
|
|
402
|
-
console.log(` type=${comp.type} label="${comp.label}" customId="${comp.customId}" disabled=${comp.disabled} style=${comp.style}`);
|
|
403
|
-
if (comp.options) console.log(` options: ${JSON.stringify(comp.options.map(o => ({ label: o.label, value: o.value, default: o.default })))}`);
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
}
|
|
347
|
+
// logMsg / dumpMessage — disabled in production (no-ops).
|
|
348
|
+
// Enable by setting DEBUG_MSGS=1 env var for troubleshooting.
|
|
349
|
+
function logMsg() {}
|
|
350
|
+
function dumpMessage() {}
|
|
407
351
|
|
|
408
352
|
// ── CV2 (Components V2) Support ──────────────────────────────
|
|
409
353
|
// Discord's CV2 messages (flag 32768) aren't parsed by the selfbot library.
|
package/lib/grinder.js
CHANGED
|
@@ -453,17 +453,13 @@ function renderDashboard() {
|
|
|
453
453
|
|
|
454
454
|
lines.push(bar);
|
|
455
455
|
|
|
456
|
-
//
|
|
456
|
+
// Absolute cursor home — always draw from row 1
|
|
457
457
|
process.stdout.write('\x1b[H');
|
|
458
|
-
const prevLines = dashboardLines;
|
|
459
458
|
for (const line of lines) {
|
|
460
459
|
process.stdout.write(c.clearLine + '\r' + line + '\n');
|
|
461
460
|
}
|
|
462
|
-
//
|
|
463
|
-
|
|
464
|
-
for (let i = 0; i < maxClear; i++) {
|
|
465
|
-
process.stdout.write(c.clearLine + '\r\n');
|
|
466
|
-
}
|
|
461
|
+
// Erase everything below the dashboard (clears ghost bars, trailing lines)
|
|
462
|
+
process.stdout.write('\x1b[J');
|
|
467
463
|
dashboardLines = lines.length;
|
|
468
464
|
dashboardRendering = false;
|
|
469
465
|
}
|
|
@@ -2266,11 +2262,8 @@ class AccountWorker {
|
|
|
2266
2262
|
} catch {}
|
|
2267
2263
|
}
|
|
2268
2264
|
|
|
2269
|
-
// Let Discord gateway settle
|
|
2265
|
+
// Let Discord gateway settle
|
|
2270
2266
|
await new Promise(r => setTimeout(r, 2500));
|
|
2271
|
-
// Run initial inventory check (awaited) before grind loop starts
|
|
2272
|
-
await this.checkInventory().catch(() => {});
|
|
2273
|
-
this.grindLoop();
|
|
2274
2267
|
resolve();
|
|
2275
2268
|
});
|
|
2276
2269
|
|
|
@@ -2395,7 +2388,7 @@ async function start(apiKey, apiUrl) {
|
|
|
2395
2388
|
console.log(` ${checks.join(' ')}`);
|
|
2396
2389
|
console.log('');
|
|
2397
2390
|
|
|
2398
|
-
//
|
|
2391
|
+
// Phase 1: Login all accounts (staggered to avoid 429s)
|
|
2399
2392
|
const BATCH_SIZE = 5;
|
|
2400
2393
|
const BATCH_DELAY_MS = 3000;
|
|
2401
2394
|
for (let i = 0; i < accounts.length; i++) {
|
|
@@ -2405,12 +2398,22 @@ async function start(apiKey, apiUrl) {
|
|
|
2405
2398
|
workerMap.set(accounts[i].id, worker);
|
|
2406
2399
|
await worker.start();
|
|
2407
2400
|
if ((i + 1) % BATCH_SIZE === 0 && i + 1 < accounts.length) {
|
|
2408
|
-
log('info', `${c.dim}
|
|
2401
|
+
log('info', `${c.dim}Logged in ${i + 1}/${accounts.length}, next batch in ${BATCH_DELAY_MS / 1000}s...${c.reset}`);
|
|
2409
2402
|
await new Promise(r => setTimeout(r, BATCH_DELAY_MS));
|
|
2410
2403
|
hintGC();
|
|
2411
2404
|
}
|
|
2412
2405
|
}
|
|
2413
2406
|
|
|
2407
|
+
// Phase 2: Run inventory on ALL accounts (must complete before any grinding)
|
|
2408
|
+
log('info', `${c.dim}Checking inventory for all ${workers.length} accounts...${c.reset}`);
|
|
2409
|
+
await Promise.all(workers.map(w => w.checkInventory().catch(() => {})));
|
|
2410
|
+
log('success', `${c.dim}All inventories checked. Starting grind loops...${c.reset}`);
|
|
2411
|
+
|
|
2412
|
+
// Phase 3: Start all grind loops
|
|
2413
|
+
for (const w of workers) {
|
|
2414
|
+
if (!shutdownCalled) w.grindLoop();
|
|
2415
|
+
}
|
|
2416
|
+
|
|
2414
2417
|
startTime = Date.now();
|
|
2415
2418
|
dashboardStarted = true;
|
|
2416
2419
|
setDashboardActive(true);
|