dankgrinder 8.90.0 → 8.92.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/grinder.js CHANGED
@@ -665,6 +665,12 @@ class AccountWorker {
665
665
  this._sellRunning = false;
666
666
  this.failStreak = 0;
667
667
  this.globalCooldownUntil = 0;
668
+ // Level quest system — blocks grinding until quests complete
669
+ this._level = 1;
670
+ this._levelQuestActive = false;
671
+ this._levelQuestQueue = [];
672
+ this._levelQuestDone = new Set();
673
+ this._questBetOverride = null;
668
674
  this.commandQueue = null;
669
675
  this.lastHealthCheck = Date.now();
670
676
  this.doneToday = new Map();
@@ -1479,7 +1485,7 @@ class AccountWorker {
1479
1485
  client: this.client,
1480
1486
  safeAnswers: cmdName === 'search' ? safeParseJSON(this.account.search_answers, []) :
1481
1487
  cmdName === 'crime' ? safeParseJSON(this.account.crime_answers, []) : [],
1482
- betAmount: ['blackjack'].includes(cmdName) ? bjBet : gambBet,
1488
+ betAmount: this._questBetOverride || (['blackjack'].includes(cmdName) ? bjBet : gambBet),
1483
1489
  accountId: this.account.id,
1484
1490
  redis,
1485
1491
  };
@@ -1538,6 +1544,16 @@ class AccountWorker {
1538
1544
  return;
1539
1545
  }
1540
1546
 
1547
+ // Level-locked detection — "not unlocked" means this command needs quests
1548
+ if (resultLower.includes('not unlocked') || resultLower.includes('complete tasks to unlock')) {
1549
+ const lvMatch = result.match(/level\s*(\d+)/i);
1550
+ if (lvMatch) {
1551
+ const targetLv = parseInt(lvMatch[1]);
1552
+ cmdResult.levelLocked = targetLv;
1553
+ this.log('warn', `Command /${cmdName} requires Level ${targetLv} quests`);
1554
+ }
1555
+ }
1556
+
1541
1557
  // Captcha/verification detection — Aho-Corasick O(n) single-pass match
1542
1558
  // Instead of 8 separate .includes() calls (each O(n)), one automaton pass
1543
1559
  if (captchaDetector.hasAny(resultLower)) {
@@ -1708,6 +1724,14 @@ class AccountWorker {
1708
1724
  this.globalCooldownUntil = Math.max(this.globalCooldownUntil, Date.now() + holdSec * 1000);
1709
1725
  }
1710
1726
 
1727
+ // Detect level-locked command → start quest mode
1728
+ if (cmdResult.levelLocked) {
1729
+ const targetLv = cmdResult.levelLocked;
1730
+ if (this._startLevelQuests(targetLv)) {
1731
+ this.log('info', `[QUEST] Detected locked command — target Level ${targetLv}`);
1732
+ }
1733
+ }
1734
+
1711
1735
  this.stats.successes++;
1712
1736
  // Format result with clean command name and colored earnings
1713
1737
  const formattedResult = formatCommandResult(cmdName, result, earned, spent);
@@ -1856,6 +1880,35 @@ class AccountWorker {
1856
1880
  // Alert is NOT scheduled — it's reactive (listener-based, see grindLoop)
1857
1881
  ].map(Object.freeze);
1858
1882
 
1883
+ // ── Level Quest System — blocks grinding until quests complete ──
1884
+ static LEVEL_QUESTS = {
1885
+ 1: [
1886
+ { cmd: 'beg', times: 2 },
1887
+ { cmd: 'search', times: 2 },
1888
+ { cmd: 'tidy', times: 2 },
1889
+ ],
1890
+ 3: [
1891
+ { cmd: 'work shift', times: 1 },
1892
+ { cmd: 'shop sell common coin 1', times: 2 },
1893
+ ],
1894
+ 5: [
1895
+ { cmd: 'slots', times: 1, bet: 50000 },
1896
+ { cmd: 'cointoss', times: 1, bet: 50000 },
1897
+ { cmd: 'snakeeyes', times: 1, bet: 50000 },
1898
+ ],
1899
+ };
1900
+
1901
+ _startLevelQuests(targetLevel) {
1902
+ if (this._levelQuestDone.has(targetLevel)) return false;
1903
+ const quests = AccountWorker.LEVEL_QUESTS[targetLevel];
1904
+ if (!quests || quests.length === 0) return false;
1905
+ this._levelQuestQueue = quests.map(q => ({ ...q, level: targetLevel }));
1906
+ this._levelQuestActive = true;
1907
+ this._questBetOverride = null;
1908
+ this.log('info', `[QUEST] Level ${targetLevel} quests started — grinding PAUSED`);
1909
+ return true;
1910
+ }
1911
+
1859
1912
  async buildCommandQueue() {
1860
1913
  const heap = new MinHeap();
1861
1914
  const now = Date.now();
@@ -2106,6 +2159,27 @@ class AccountWorker {
2106
2159
 
2107
2160
  // ── Main Non-Blocking Grind Scheduler ───────────────────────
2108
2161
  async tick() {
2162
+ // BLOCK: quest mode active — run quests only, no normal grinding
2163
+ if (this._levelQuestActive && this._levelQuestQueue.length > 0) {
2164
+ const quest = this._levelQuestQueue[0];
2165
+ this._questBetOverride = quest.bet || null;
2166
+ const prefix = this.account.use_slash ? '/' : 'pls';
2167
+ this.setStatus(`[L${quest.level}] QUEST ${quest.cmd} (${quest.times}x left)`);
2168
+ this.lastStatus = `[L${quest.level}] ${quest.cmd}`;
2169
+ await this.runCommand(quest.cmd, prefix);
2170
+ this._questBetOverride = null;
2171
+ quest.times--;
2172
+ if (quest.times <= 0) this._levelQuestQueue.shift();
2173
+ if (this._levelQuestQueue.length === 0) {
2174
+ this._levelQuestActive = false;
2175
+ this._levelQuestDone.add(quest.level);
2176
+ this.log('success', `[QUEST] Level ${quest.level} quests DONE — resuming grinding`);
2177
+ this.setStatus('idle');
2178
+ }
2179
+ this.tickTimeout = setTimeout(() => this.tick(), 2500);
2180
+ return;
2181
+ }
2182
+
2109
2183
  if (!this.running || shutdownCalled) return;
2110
2184
  if (this.paused) {
2111
2185
  this.setStatus('PAUSED (captcha)');
@@ -3042,6 +3116,7 @@ async function start(apiKey, apiUrl, opts = {}) {
3042
3116
  let shutdownInProgress = false;
3043
3117
 
3044
3118
  async function gracefulShutdown(signal) {
3119
+ const chalk = require('chalk');
3045
3120
  if (shutdownInProgress) return;
3046
3121
  shutdownInProgress = true;
3047
3122
  shutdownCalled = true;
@@ -3057,42 +3132,46 @@ async function start(apiKey, apiUrl, opts = {}) {
3057
3132
  let finalCoins = 0;
3058
3133
  let finalCmds = 0;
3059
3134
 
3060
- let table = ' ╭───────────────┬────────────────┬──────────┬────────╮\n';
3061
- table += ' │ ACCOUNT │ GAINED │ COMMANDS │ OK % │\n';
3062
- table += ' ├───────────────┼────────────────┼──────────┼────────┤\n';
3135
+ const figlet = require('figlet');
3136
+
3137
+ let table = '';
3138
+
3139
+
3140
+ table += ' ' + chalk.dim('────────────────────────────────────────────────────────────────────────────────────────────────') + '\n\n';
3141
+ table += ' ' + chalk.dim('ACCOUNT'.padEnd(25)) + chalk.dim('GAINED'.padEnd(15)) + chalk.dim('COMMANDS'.padEnd(10)) + chalk.dim('OK %') + '\n\n';
3063
3142
 
3064
3143
  for (const wk of workers) {
3065
3144
  const rate = wk.stats.commands > 0 ? ((wk.stats.successes / wk.stats.commands) * 100).toFixed(0) : 0;
3066
3145
  finalCoins += wk.stats.coins || 0;
3067
3146
  finalCmds += wk.stats.commands || 0;
3068
3147
 
3069
- const unRaw = (wk.username || '?').substring(0, 13);
3148
+ const unRaw = (wk.username || '?').substring(0, 23);
3070
3149
  const coinsRaw = '+⏣' + (wk.stats.coins || 0).toLocaleString();
3071
3150
  const cmdsRaw = String(wk.stats.commands || 0);
3072
3151
  const okRaw = rate + '%';
3073
3152
 
3074
- const un = unRaw.padEnd(13);
3075
- const coins = coinsRaw.substring(0, 14).padEnd(14);
3076
- const cmds = cmdsRaw.substring(0, 8).padEnd(8);
3153
+ const un = unRaw.padEnd(25);
3154
+ const coins = coinsRaw.substring(0, 14).padEnd(15);
3155
+ const cmds = cmdsRaw.substring(0, 8).padEnd(10);
3077
3156
  const ok = okRaw.substring(0, 6).padEnd(6);
3078
3157
 
3079
- table += ` ${c.cyan}${un}${c.reset} ${c.green}${coins}${c.reset} ${cmds} ${ok} │\n`;
3158
+ table += ` ${c.cyan}${un}${c.reset} ${c.green}${coins}${c.reset} ${cmds} ${ok}\n`;
3080
3159
  }
3081
3160
 
3082
3161
  if (workers.length === 0) {
3083
- table += ` ${c.dim}${'No workers...'.padEnd(13)}${c.reset} ${''.padEnd(14)} ${''.padEnd(8)} ${''.padEnd(6)} │\n`;
3162
+ table += ` ${c.dim}${'No workers...'.padEnd(25)}${c.reset} ${''.padEnd(15)} ${''.padEnd(10)} ${''.padEnd(6)}\n`;
3084
3163
  }
3085
3164
 
3086
- table += ' ╰───────────────┴────────────────┴──────────┴────────╯\n';
3165
+ table += '\n ' + chalk.dim('────────────────────────────────────────────────────────────────────────────────────────────────') + '\n';
3087
3166
  process.stdout.write(table);
3088
3167
 
3089
3168
  const memFinal = Math.round((process.memoryUsage?.rss?.() ?? process.memoryUsage().rss) / 1048576);
3090
3169
  const cpm = globalCmdRate.getRate().toFixed(1);
3091
3170
 
3092
- console.log(` ${c.bold}======================================================${c.reset}`);
3093
- console.log(` ${c.bold}TOTAL:${c.reset} ${c.green}+⏣${finalCoins.toLocaleString()}${c.reset} in ${ui.formatUptime()}`);
3094
- console.log(` ${c.bold}STATS:${c.reset} ${finalCmds} commands | ~${cpm} cmd/m | ${memFinal}MB used`);
3095
- console.log(` ${c.bold}======================================================${c.reset}\n`);
3171
+ console.log('\n ' + chalk.magenta('◆') + ' ' + chalk.bold('TOTAL') + ' ' + chalk.green('+⏣' + finalCoins.toLocaleString()) + ' ' + chalk.dim('in ' + ui.formatUptime()));
3172
+ console.log(' ' + chalk.cyan('◆') + ' ' + chalk.bold('STATS') + ' ' + finalCmds + chalk.dim(' cmds │ ') + '~' + cpm + chalk.dim(' cmd/m │ ') + memFinal + chalk.dim('MB RAM'));
3173
+ console.log('\n');
3174
+
3096
3175
 
3097
3176
  // Stop workers immediately (don't wait) — instant shutdown
3098
3177
  for (const wk of workers) {
@@ -3123,7 +3202,7 @@ async function start(apiKey, apiUrl, opts = {}) {
3123
3202
 
3124
3203
  const disResult = redis?.disconnect?.();
3125
3204
  if (disResult && typeof disResult.catch === 'function') disResult.catch(() => {});
3126
- console.log(`${c.green}Goodbye!${c.reset}\n`);
3205
+ console.log(chalk.magenta + figlet.textSync('GOODBYE!', { font: 'ANSI Shadow' }) + c.reset + '\n');
3127
3206
  // Force exit so Ctrl+C always terminates immediately
3128
3207
  setTimeout(() => process.exit(0), 2000);
3129
3208
  process.exit(0);
package/lib/rawLogger.js CHANGED
@@ -294,7 +294,11 @@ function detectCommand(d) {
294
294
  if (anyOf(embedText, ['fishing', 'fish'])) return 'fish';
295
295
  if (has(embedText, 'hold tight')) return 'holdtight';
296
296
 
297
- // ── allText fallback — catches CV2 messages where d.components is a JSON string ──
297
+ // Level-gated command lock
298
+ if (anyOf(allText, ['not unlocked', 'complete tasks to unlock'])) {
299
+ const lvMatch = allText.match(/level\s*(\d+)/i);
300
+ return lvMatch ? 'level_locked' : 'unknown';
301
+ }
298
302
  if (anyOf(allText, ['hunting', 'went hunting', 'hunting rifle', 'your aim was so bad', 'animals laughed', 'animals attacked', 'barely escaped', 'fell asleep in a tree', 'caught nothing', 'brought back literally nothing', 'rifle broke', 'imagine going into the woods'])) return 'hunt';
299
303
  if (anyOf(allText, ['digging', 'you dug', 'dug in the dirt', 'found nothing while', 'what are the odds lol'])) return 'dig';
300
304
  if (anyOf(allText, ['brought back']) && anyOf(allText, ['ant', 'worm', 'stickbug', 'ladybug'])) return 'dig';
@@ -712,6 +716,7 @@ module.exports = {
712
716
  onDmEvent,
713
717
  onNextEphemeral,
714
718
  setVerbose,
719
+ store,
715
720
  // Memory reads
716
721
  getRawMessage,
717
722
  getLastRaw,
package/lib/ui.js CHANGED
@@ -156,11 +156,13 @@ function render() {
156
156
  // 2. MINIMALIST STATS + TREND SPARKLINE
157
157
  const profStr = totalCoins > 0 ? '+' + formatBal(totalCoins) : '+0';
158
158
  const graph = getSparkline();
159
-
160
- out += ' ' + chalk.cyan('') + chalk.bold(' ONLINE ') + chalk.white(onlineCount + ' / ' + _workers.length) + '\n';
161
- out += ' ' + chalk.blue('') + chalk.bold(' UPTIME ') + chalk.white(fmtUptime()) + '\n';
162
- out += ' ' + chalk.green('') + chalk.bold(' BALANCE ') + chalk.white('⏣ ' + formatBal(totalBal)) +
163
- chalk.green(' (profit: ' + profStr + ')') + chalk.dim(' │ TREND: [') + graph + chalk.dim(']') + '\n';
159
+
160
+ const secOnline = chalk.dim('Online') + ' ' + chalk.bold.cyan(`${onlineCount}/${_workers.length}`);
161
+ const secUptime = chalk.dim('Uptime') + ' ' + chalk.bold.blue(fmtUptime());
162
+ const secBal = chalk.dim('Bank') + ' ' + chalk.bold.green(`⏣ ${formatBal(totalBal)}`) + chalk.greenBright(` (${profStr})`);
163
+ const secTrend = chalk.dim('Trend') + ' ' + chalk.magenta(graph);
164
+
165
+ out += ` ${secOnline} ${chalk.dim('│')} ${secUptime} ${chalk.dim('│')} ${secBal} ${chalk.dim('│')} ${secTrend}\n`;
164
166
 
165
167
  // Divider
166
168
  const divWidth = Math.min(C - 4, 100);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dankgrinder",
3
- "version": "8.90.0",
3
+ "version": "8.92.0",
4
4
  "description": "Dank Memer automation engine — grind coins while you sleep",
5
5
  "bin": {
6
6
  "dankgrinder": "bin/dankgrinder.js"