dankgrinder 5.0.2 → 5.0.4
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/utils.js +11 -63
- package/lib/grinder.js +43 -48
- package/package.json +1 -1
package/lib/commands/utils.js
CHANGED
|
@@ -73,7 +73,7 @@ const LOG = {
|
|
|
73
73
|
cmd: (msg) => log(`${c.magenta}▸${c.reset}`, msg),
|
|
74
74
|
coin: (msg) => log(`${c.yellow}$${c.reset}`, msg),
|
|
75
75
|
buy: (msg) => log(`${c.blue}♦${c.reset}`, msg),
|
|
76
|
-
debug: (
|
|
76
|
+
debug: () => {},
|
|
77
77
|
};
|
|
78
78
|
|
|
79
79
|
// ── Pre-compiled Regex (avoid recompilation in hot paths) ────
|
|
@@ -259,9 +259,9 @@ function flattenComponents(components) {
|
|
|
259
259
|
}
|
|
260
260
|
|
|
261
261
|
function getAllButtons(msg) {
|
|
262
|
-
if (msg._cv2buttons?.length > 0) return msg._cv2buttons;
|
|
262
|
+
if (msg._cv2buttons?.length > 0) return msg._cv2buttons.filter(b => b.style !== 'LINK' && b.style !== 5);
|
|
263
263
|
const all = flattenComponents(msg.components);
|
|
264
|
-
return all.filter(c => c.type === 2 || c.type === 'BUTTON');
|
|
264
|
+
return all.filter(c => (c.type === 2 || c.type === 'BUTTON') && c.style !== 'LINK' && c.style !== 5);
|
|
265
265
|
}
|
|
266
266
|
|
|
267
267
|
function getAllSelectMenus(msg) {
|
|
@@ -286,6 +286,10 @@ function findSelectMenuOption(msg, label) {
|
|
|
286
286
|
// When CV2 fallback is used, waits for the message to update so callers always
|
|
287
287
|
// get the updated message back (instead of null, which broke multi-round games).
|
|
288
288
|
async function safeClickButton(msg, button) {
|
|
289
|
+
// Skip LINK buttons (external URLs) — they have style=LINK/5 and no customId
|
|
290
|
+
if (button.style === 'LINK' || button.style === 5 || (!button.customId && !button.custom_id && typeof button.click !== 'function')) {
|
|
291
|
+
return null;
|
|
292
|
+
}
|
|
289
293
|
if (typeof button.click === 'function') {
|
|
290
294
|
return button.click();
|
|
291
295
|
}
|
|
@@ -344,66 +348,10 @@ function getHoldTightReason(msg) {
|
|
|
344
348
|
return match ? match[1].toLowerCase() : null;
|
|
345
349
|
}
|
|
346
350
|
|
|
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
|
-
}
|
|
351
|
+
// logMsg / dumpMessage — disabled in production (no-ops).
|
|
352
|
+
// Enable by setting DEBUG_MSGS=1 env var for troubleshooting.
|
|
353
|
+
function logMsg() {}
|
|
354
|
+
function dumpMessage() {}
|
|
407
355
|
|
|
408
356
|
// ── CV2 (Components V2) Support ──────────────────────────────
|
|
409
357
|
// 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
|
}
|
|
@@ -565,32 +561,20 @@ async function reportEarnings(accountId, accountName, earned, spent, command) {
|
|
|
565
561
|
earningsBatch.push({ account_id: accountId, account_name: accountName, earned, spent, command });
|
|
566
562
|
}
|
|
567
563
|
|
|
568
|
-
|
|
564
|
+
// Command feed sends directly (no batching) for real-time dashboard SSE updates.
|
|
565
|
+
// The dashboard live queue depends on instant delivery via eventBus.emit("sse").
|
|
566
|
+
async function reportCommandFeed(accountId, accountName, data) {
|
|
569
567
|
if (!API_URL) return;
|
|
568
|
+
const normalized = { ...data };
|
|
569
|
+
if (typeof normalized.command === 'string') normalized.command = stripAnsi(normalized.command).replace(/\s+/g, ' ').trim();
|
|
570
|
+
if (typeof normalized.result === 'string') normalized.result = stripAnsi(normalized.result).replace(/\s+/g, ' ').trim();
|
|
570
571
|
try {
|
|
571
|
-
await fetch(`${API_URL}/api/grinder/command-feed
|
|
572
|
+
await fetch(`${API_URL}/api/grinder/command-feed`, {
|
|
572
573
|
method: 'POST',
|
|
573
574
|
headers: { Authorization: `Bearer ${API_KEY}`, 'Content-Type': 'application/json' },
|
|
574
|
-
body: JSON.stringify({
|
|
575
|
+
body: JSON.stringify({ account_id: accountId, account_name: accountName, ...normalized }),
|
|
575
576
|
});
|
|
576
|
-
} catch {
|
|
577
|
-
for (const item of batch) {
|
|
578
|
-
try {
|
|
579
|
-
await fetch(`${API_URL}/api/grinder/command-feed`, {
|
|
580
|
-
method: 'POST',
|
|
581
|
-
headers: { Authorization: `Bearer ${API_KEY}`, 'Content-Type': 'application/json' },
|
|
582
|
-
body: JSON.stringify(item),
|
|
583
|
-
});
|
|
584
|
-
} catch {}
|
|
585
|
-
}
|
|
586
|
-
}
|
|
587
|
-
}, { maxSize: 100, flushMs: 2000 });
|
|
588
|
-
|
|
589
|
-
async function reportCommandFeed(accountId, accountName, data) {
|
|
590
|
-
const normalized = { ...data };
|
|
591
|
-
if (typeof normalized.command === 'string') normalized.command = stripAnsi(normalized.command).replace(/\s+/g, ' ').trim();
|
|
592
|
-
if (typeof normalized.result === 'string') normalized.result = stripAnsi(normalized.result).replace(/\s+/g, ' ').trim();
|
|
593
|
-
feedBatch.push({ account_id: accountId, account_name: accountName, ...normalized });
|
|
577
|
+
} catch { /* silent — dashboard just won't see this update */ }
|
|
594
578
|
}
|
|
595
579
|
|
|
596
580
|
function randomDelay(min, max) {
|
|
@@ -1886,7 +1870,7 @@ class AccountWorker {
|
|
|
1886
1870
|
this.tickTimeout = setTimeout(() => this.tick(), 5000);
|
|
1887
1871
|
return;
|
|
1888
1872
|
}
|
|
1889
|
-
if (this.busy) {
|
|
1873
|
+
if (this.busy || this._invRunning) {
|
|
1890
1874
|
this.tickTimeout = setTimeout(() => this.tick(), 2000);
|
|
1891
1875
|
return;
|
|
1892
1876
|
}
|
|
@@ -2189,9 +2173,9 @@ class AccountWorker {
|
|
|
2189
2173
|
// Handle pending actions from dashboard
|
|
2190
2174
|
if (Array.isArray(data.pendingActions)) {
|
|
2191
2175
|
for (const action of data.pendingActions) {
|
|
2192
|
-
if (action.action === 'check_inventory' && !this.busy) {
|
|
2176
|
+
if (action.action === 'check_inventory' && !this.busy && !this._invRunning) {
|
|
2193
2177
|
this.log('info', 'Dashboard requested inventory check');
|
|
2194
|
-
this.checkInventory().catch(() => {});
|
|
2178
|
+
await this.checkInventory().catch(() => {});
|
|
2195
2179
|
try {
|
|
2196
2180
|
await fetch(`${API_URL}/api/grinder/actions`, {
|
|
2197
2181
|
method: 'DELETE',
|
|
@@ -2278,9 +2262,8 @@ class AccountWorker {
|
|
|
2278
2262
|
} catch {}
|
|
2279
2263
|
}
|
|
2280
2264
|
|
|
2281
|
-
// Let Discord gateway settle
|
|
2265
|
+
// Let Discord gateway settle
|
|
2282
2266
|
await new Promise(r => setTimeout(r, 2500));
|
|
2283
|
-
this.grindLoop();
|
|
2284
2267
|
resolve();
|
|
2285
2268
|
});
|
|
2286
2269
|
|
|
@@ -2341,7 +2324,6 @@ async function start(apiKey, apiUrl) {
|
|
|
2341
2324
|
API_URL = apiUrl || process.env.DANKGRINDER_URL || 'http://localhost:3000';
|
|
2342
2325
|
REDIS_URL = process.env.REDIS_URL || '';
|
|
2343
2326
|
WEBHOOK_URL = process.env.WEBHOOK_URL || '';
|
|
2344
|
-
initRedis();
|
|
2345
2327
|
|
|
2346
2328
|
process.stdout.write('\x1b[2J\x1b[H');
|
|
2347
2329
|
const tw = Math.min(process.stdout.columns || 80, 78);
|
|
@@ -2361,16 +2343,6 @@ async function start(apiKey, apiUrl) {
|
|
|
2361
2343
|
);
|
|
2362
2344
|
console.log(bar);
|
|
2363
2345
|
|
|
2364
|
-
const checks = [];
|
|
2365
|
-
checks.push(`${rgb(52, 211, 153)}✓${c.reset} ${c.white}API${c.reset}`);
|
|
2366
|
-
if (REDIS_URL) checks.push(`${rgb(52, 211, 153)}✓${c.reset} ${c.white}Redis${c.reset}`);
|
|
2367
|
-
if (hasZlib) checks.push(`${rgb(52, 211, 153)}✓${c.reset} ${c.white}zlib${c.reset}`);
|
|
2368
|
-
if (WEBHOOK_URL) checks.push(`${rgb(52, 211, 153)}✓${c.reset} ${c.white}Webhook${c.reset}`);
|
|
2369
|
-
if (CLUSTER_ENABLED) {
|
|
2370
|
-
checks.push(`${rgb(52, 211, 153)}✓${c.reset} ${rgb(34, 211, 238)}Cluster${c.reset} ${c.dim}(${NODE_ID.substring(0, 12)})${c.reset}`);
|
|
2371
|
-
}
|
|
2372
|
-
console.log(` ${checks.join(' ')}`);
|
|
2373
|
-
|
|
2374
2346
|
log('info', `${c.dim}Fetching accounts...${c.reset}`);
|
|
2375
2347
|
|
|
2376
2348
|
let data = await fetchConfig(4, 2000);
|
|
@@ -2381,6 +2353,13 @@ async function start(apiKey, apiUrl) {
|
|
|
2381
2353
|
data = await fetchConfig(4, 2000);
|
|
2382
2354
|
}
|
|
2383
2355
|
|
|
2356
|
+
// Pull Redis/Webhook URLs from API config if not in env
|
|
2357
|
+
if (!REDIS_URL && data.redis_url) REDIS_URL = data.redis_url;
|
|
2358
|
+
if (!REDIS_URL && data.redisUrl) REDIS_URL = data.redisUrl;
|
|
2359
|
+
if (!WEBHOOK_URL && data.webhook_url) WEBHOOK_URL = data.webhook_url;
|
|
2360
|
+
if (!WEBHOOK_URL && data.webhookUrl) WEBHOOK_URL = data.webhookUrl;
|
|
2361
|
+
initRedis();
|
|
2362
|
+
|
|
2384
2363
|
let { accounts } = data;
|
|
2385
2364
|
if (!accounts || accounts.length === 0) {
|
|
2386
2365
|
log('error', 'No active accounts. Add them in the dashboard.');
|
|
@@ -2397,13 +2376,19 @@ async function start(apiKey, apiUrl) {
|
|
|
2397
2376
|
}
|
|
2398
2377
|
}
|
|
2399
2378
|
|
|
2379
|
+
const checks = [];
|
|
2380
|
+
checks.push(`${rgb(52, 211, 153)}✓${c.reset} ${c.white}API${c.reset}`);
|
|
2381
|
+
if (REDIS_URL) checks.push(redis ? `${rgb(52, 211, 153)}✓${c.reset} ${c.white}Redis${c.reset}` : `${rgb(251, 191, 36)}○${c.reset} ${c.dim}Redis (connecting...)${c.reset}`);
|
|
2382
|
+
if (hasZlib) checks.push(`${rgb(52, 211, 153)}✓${c.reset} ${c.white}zlib${c.reset}`);
|
|
2383
|
+
if (WEBHOOK_URL) checks.push(`${rgb(52, 211, 153)}✓${c.reset} ${c.white}Webhook${c.reset}`);
|
|
2384
|
+
if (CLUSTER_ENABLED) {
|
|
2385
|
+
checks.push(`${rgb(52, 211, 153)}✓${c.reset} ${rgb(34, 211, 238)}Cluster${c.reset} ${c.dim}(${NODE_ID.substring(0, 12)})${c.reset}`);
|
|
2386
|
+
}
|
|
2400
2387
|
checks.push(`${rgb(52, 211, 153)}✓${c.reset} ${c.white}${accounts.length} Account${accounts.length > 1 ? 's' : ''}${c.reset}`);
|
|
2401
|
-
process.stdout.write(c.cursorUp(1));
|
|
2402
|
-
process.stdout.write(c.clearLine + '\r');
|
|
2403
2388
|
console.log(` ${checks.join(' ')}`);
|
|
2404
2389
|
console.log('');
|
|
2405
2390
|
|
|
2406
|
-
//
|
|
2391
|
+
// Phase 1: Login all accounts (staggered to avoid 429s)
|
|
2407
2392
|
const BATCH_SIZE = 5;
|
|
2408
2393
|
const BATCH_DELAY_MS = 3000;
|
|
2409
2394
|
for (let i = 0; i < accounts.length; i++) {
|
|
@@ -2413,12 +2398,22 @@ async function start(apiKey, apiUrl) {
|
|
|
2413
2398
|
workerMap.set(accounts[i].id, worker);
|
|
2414
2399
|
await worker.start();
|
|
2415
2400
|
if ((i + 1) % BATCH_SIZE === 0 && i + 1 < accounts.length) {
|
|
2416
|
-
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}`);
|
|
2417
2402
|
await new Promise(r => setTimeout(r, BATCH_DELAY_MS));
|
|
2418
2403
|
hintGC();
|
|
2419
2404
|
}
|
|
2420
2405
|
}
|
|
2421
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
|
+
|
|
2422
2417
|
startTime = Date.now();
|
|
2423
2418
|
dashboardStarted = true;
|
|
2424
2419
|
setDashboardActive(true);
|