dankgrinder 5.0.1 → 5.0.2

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.
@@ -282,7 +282,9 @@ function findSelectMenuOption(msg, label) {
282
282
  return null;
283
283
  }
284
284
 
285
- // Safe button click — tries library methods first, falls back to raw HTTP for CV2
285
+ // Safe button click — tries library methods first, falls back to raw HTTP for CV2.
286
+ // When CV2 fallback is used, waits for the message to update so callers always
287
+ // get the updated message back (instead of null, which broke multi-round games).
286
288
  async function safeClickButton(msg, button) {
287
289
  if (typeof button.click === 'function') {
288
290
  return button.click();
@@ -295,9 +297,29 @@ async function safeClickButton(msg, button) {
295
297
  // Fall through to CV2 raw interaction fallback.
296
298
  }
297
299
  }
298
- // CV2 fallback: send interaction via raw HTTP
300
+ // CV2 fallback: send interaction via raw HTTP, then wait for the message
301
+ // to update so we can return the updated message to the caller.
299
302
  if (id) {
300
303
  await clickCV2Button(msg, id);
304
+ // Wait for Dank Memer to process the interaction and update the message
305
+ const updatedMsg = await new Promise((resolve) => {
306
+ const timeout = setTimeout(() => {
307
+ msg.client?.removeListener?.('messageUpdate', handler);
308
+ resolve(null);
309
+ }, 8000);
310
+ const handler = (_, newMsg) => {
311
+ if (newMsg.id === msg.id) {
312
+ clearTimeout(timeout);
313
+ msg.client?.removeListener?.('messageUpdate', handler);
314
+ resolve(newMsg);
315
+ }
316
+ };
317
+ msg.client?.on?.('messageUpdate', handler);
318
+ });
319
+ if (updatedMsg) {
320
+ await ensureCV2(updatedMsg);
321
+ return updatedMsg;
322
+ }
301
323
  return null;
302
324
  }
303
325
  throw new Error('No click method available on button');
package/lib/grinder.js CHANGED
@@ -453,19 +453,16 @@ function renderDashboard() {
453
453
 
454
454
  lines.push(bar);
455
455
 
456
+ // Use absolute cursor positioning (row 1, col 1) to avoid ghost bar drift
457
+ process.stdout.write('\x1b[H');
456
458
  const prevLines = dashboardLines;
457
- if (prevLines > 0) {
458
- process.stdout.write(c.cursorUp(prevLines));
459
- }
460
459
  for (const line of lines) {
461
460
  process.stdout.write(c.clearLine + '\r' + line + '\n');
462
461
  }
463
- // Clear trailing old lines when dashboard shrinks (prevents ghost bars)
464
- if (lines.length < prevLines) {
465
- for (let i = lines.length; i < prevLines; i++) {
466
- process.stdout.write(c.clearLine + '\r\n');
467
- }
468
- process.stdout.write(c.cursorUp(prevLines - lines.length));
462
+ // Clear any trailing lines from previous (larger) render
463
+ const maxClear = Math.max(prevLines - lines.length, 0) + 3;
464
+ for (let i = 0; i < maxClear; i++) {
465
+ process.stdout.write(c.clearLine + '\r\n');
469
466
  }
470
467
  dashboardLines = lines.length;
471
468
  dashboardRendering = false;
@@ -1535,11 +1532,13 @@ class AccountWorker {
1535
1532
 
1536
1533
  if (cmdResult.holdTightReason) {
1537
1534
  const reason = cmdResult.holdTightReason;
1538
- this.log('warn', `Hold Tight: /${reason} — 35s cooldown`);
1535
+ const holdSec = 35;
1536
+ this.log('warn', `Hold Tight: /${reason} — ${holdSec}s global cooldown`);
1539
1537
  const reasonMap = { postmemes: 'pm', highlow: 'hl', blackjack: 'bj', 'work shift': 'work shift' };
1540
1538
  const mappedCmd = reasonMap[reason] || reason;
1541
- await this.setCooldown(mappedCmd, 35);
1542
- await this.setCooldown(cmdName, 35);
1539
+ await this.setCooldown(mappedCmd, holdSec);
1540
+ await this.setCooldown(cmdName, holdSec);
1541
+ this.globalCooldownUntil = Math.max(this.globalCooldownUntil, Date.now() + holdSec * 1000);
1543
1542
  }
1544
1543
 
1545
1544
  this.stats.successes++;
@@ -2025,6 +2024,14 @@ class AccountWorker {
2025
2024
  await this.runCommand(item.cmd, prefix);
2026
2025
  const earned = this.stats.coins - beforeCoins;
2027
2026
 
2027
+ // Grace period for interactive (button-click) commands — Dank Memer
2028
+ // needs time to process the interaction before accepting the next command.
2029
+ // Without this, the next command gets "Hold Tight" errors.
2030
+ const INTERACTIVE_CMDS = new Set(['hl', 'blackjack', 'trivia', 'scratch', 'adventure', 'stream', 'fish']);
2031
+ if (INTERACTIVE_CMDS.has(item.cmd)) {
2032
+ await new Promise(r => setTimeout(r, 2500 + Math.random() * 1500));
2033
+ }
2034
+
2028
2035
  // EMA: track smoothed earnings per command for adaptive scheduling
2029
2036
  if (earned > 0) {
2030
2037
  this._earningsEMA.update(earned);
@@ -2075,14 +2082,8 @@ class AccountWorker {
2075
2082
  if (this.cycleCount > 0 && this.cycleCount % 10 === 0) this.printStats();
2076
2083
  // Rebuild BloomFilter every 50 cycles to clear expired entries
2077
2084
  if (this.cycleCount > 0 && this.cycleCount % 50 === 0) this._rebuildBloom();
2078
- if (this.cycleCount > 0 && this.cycleCount % 5 === 0) {
2079
- this.busy = true;
2080
- await this.checkBalance();
2081
- this.busy = false;
2082
- }
2083
-
2084
2085
  if (this.running && !shutdownCalled) {
2085
- const nextDelay = this.failStreak > 0 ? 1500 : 500;
2086
+ const nextDelay = this.failStreak > 0 ? 3000 : 1500;
2086
2087
  this.tickTimeout = setTimeout(() => this.tick(), nextDelay);
2087
2088
  }
2088
2089
  }
@@ -2090,7 +2091,6 @@ class AccountWorker {
2090
2091
  async grindLoop() {
2091
2092
  if (this.running) return;
2092
2093
  this.running = true;
2093
- this.busy = false;
2094
2094
  this.paused = false;
2095
2095
  this.dashboardPaused = false;
2096
2096
  this.failStreak = 0;
@@ -2280,8 +2280,6 @@ class AccountWorker {
2280
2280
 
2281
2281
  // Let Discord gateway settle before sending first command
2282
2282
  await new Promise(r => setTimeout(r, 2500));
2283
- await this.checkBalance();
2284
- this.checkInventory().catch(() => {});
2285
2283
  this.grindLoop();
2286
2284
  resolve();
2287
2285
  });
@@ -2365,9 +2363,9 @@ async function start(apiKey, apiUrl) {
2365
2363
 
2366
2364
  const checks = [];
2367
2365
  checks.push(`${rgb(52, 211, 153)}✓${c.reset} ${c.white}API${c.reset}`);
2368
- checks.push(REDIS_URL ? `${rgb(52, 211, 153)}✓${c.reset} ${c.white}Redis${c.reset}` : `${c.dim}○ Redis${c.reset}`);
2369
- checks.push(hasZlib ? `${rgb(52, 211, 153)}✓${c.reset} ${c.white}zlib${c.reset}` : `${rgb(251, 191, 36)}○${c.reset} ${c.dim}zlib (npm i zlib-sync)${c.reset}`);
2370
- checks.push(WEBHOOK_URL ? `${rgb(52, 211, 153)}✓${c.reset} ${c.white}Webhook${c.reset}` : `${c.dim}○ Webhook${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}`);
2371
2369
  if (CLUSTER_ENABLED) {
2372
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}`);
2373
2371
  }
@@ -2421,11 +2419,13 @@ async function start(apiKey, apiUrl) {
2421
2419
  }
2422
2420
  }
2423
2421
 
2424
- console.log('');
2425
2422
  startTime = Date.now();
2426
2423
  dashboardStarted = true;
2427
2424
  setDashboardActive(true);
2425
+ // Clear entire screen so startup logs don't create ghost bars
2426
+ process.stdout.write('\x1b[2J\x1b[H');
2428
2427
  process.stdout.write(c.hide);
2428
+ dashboardLines = 0;
2429
2429
 
2430
2430
  setInterval(() => scheduleRender(), 1000);
2431
2431
  scheduleRender();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dankgrinder",
3
- "version": "5.0.1",
3
+ "version": "5.0.2",
4
4
  "description": "Dank Memer automation engine — grind coins while you sleep",
5
5
  "bin": {
6
6
  "dankgrinder": "bin/dankgrinder.js"