dankgrinder 8.94.0 → 8.96.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
@@ -671,6 +671,8 @@ class AccountWorker {
671
671
  this._levelQuestQueue = [];
672
672
  this._levelQuestDone = new Set();
673
673
  this._questBetOverride = null;
674
+ this._commandRunning = false; // prevents grinding commands from overlapping with quest commands
675
+ this._verifyLevelUnlock = null; // holds level to verify after quest completion
674
676
  this.commandQueue = null;
675
677
  this.lastHealthCheck = Date.now();
676
678
  this.doneToday = new Map();
@@ -1729,8 +1731,11 @@ class AccountWorker {
1729
1731
  // Detect level-locked command → start quest mode
1730
1732
  if (cmdResult.levelLocked) {
1731
1733
  const targetLv = cmdResult.levelLocked;
1734
+ this.log('warn', `Command /${cmdName} is LOCKED at Level ${targetLv} — starting quest unlock flow`);
1732
1735
  if (this._startLevelQuests(targetLv)) {
1733
- this.log('info', `[QUEST] Detected locked command target Level ${targetLv}`);
1736
+ this.log('info', `[QUEST] Quest mode activated for Level ${targetLv} — grinding will resume after quests complete`);
1737
+ } else {
1738
+ this.log('warn', `[QUEST] Level ${targetLv} quests already done or not defined — grinding continues (may re-trigger)`);
1734
1739
  }
1735
1740
  }
1736
1741
 
@@ -1908,7 +1913,12 @@ class AccountWorker {
1908
1913
  this._levelQuestQueue = quests.map(q => ({ ...q, level: targetLevel }));
1909
1914
  this._levelQuestActive = true;
1910
1915
  this._questBetOverride = null;
1916
+ // Clear commandQueue so no stale grinding commands fire after quests finish
1917
+ this.commandQueue = null;
1918
+ this._commandRunning = false; // cancel any in-flight grinding command
1911
1919
  this.log('info', `[QUEST] Level ${targetLevel} quests started — grinding PAUSED`);
1920
+ const questList = this._levelQuestQueue.map(q => `"${q.cmd}" x${q.times}`).join(', ');
1921
+ this.log('info', `[QUEST] Quests: ${questList}`);
1912
1922
  return true;
1913
1923
  }
1914
1924
 
@@ -2162,6 +2172,33 @@ class AccountWorker {
2162
2172
 
2163
2173
  // ── Main Non-Blocking Grind Scheduler ───────────────────────
2164
2174
  async tick() {
2175
+ // ── Level-unlock verification: runs after quests complete ──
2176
+ if (this._verifyLevelUnlock) {
2177
+ const targetLv = this._verifyLevelUnlock;
2178
+ this.setStatus(`verifying level ${targetLv}...`);
2179
+ try {
2180
+ const profile = await this.checkProfile(true);
2181
+ const currentLv = profile.level || this._level || 0;
2182
+ if (currentLv >= targetLv) {
2183
+ this.log('success', `[QUEST] Level ${targetLv} verified ✓ — level unlocked!`);
2184
+ this._level = currentLv;
2185
+ } else {
2186
+ this.log('warn', `[QUEST] Level ${targetLv} NOT verified — still at ${currentLv}. Re-triggering quests.`);
2187
+ // Re-trigger quests for this level
2188
+ this._levelQuestDone.delete(targetLv);
2189
+ this._startLevelQuests(targetLv);
2190
+ this.tickTimeout = setTimeout(() => this.tick(), 2000);
2191
+ return;
2192
+ }
2193
+ } catch (e) {
2194
+ this.log('warn', `[QUEST] Level verification failed: ${e.message} — continuing anyway`);
2195
+ }
2196
+ this._verifyLevelUnlock = null;
2197
+ this.setStatus('idle');
2198
+ this.tickTimeout = setTimeout(() => this.tick(), 2000);
2199
+ return;
2200
+ }
2201
+
2165
2202
  // BLOCK: quest mode active — run quests only, no normal grinding
2166
2203
  if (this._levelQuestActive && this._levelQuestQueue.length > 0) {
2167
2204
  const quest = this._levelQuestQueue[0];
@@ -2169,15 +2206,26 @@ class AccountWorker {
2169
2206
  const prefix = this.account.use_slash ? '/' : 'pls';
2170
2207
  this.setStatus(`[L${quest.level}] QUEST ${quest.cmd} (${quest.times}x left)`);
2171
2208
  this.lastStatus = `[L${quest.level}] ${quest.cmd}`;
2209
+ this._commandRunning = true;
2210
+ this.busy = true; // also set busy so grinding tick path blocks while quest runs
2172
2211
  await this.runCommand(quest.cmd, prefix);
2212
+ this._commandRunning = false;
2213
+ this.busy = false;
2173
2214
  this._questBetOverride = null;
2174
2215
  quest.times--;
2175
2216
  if (quest.times <= 0) this._levelQuestQueue.shift();
2176
2217
  if (this._levelQuestQueue.length === 0) {
2177
2218
  this._levelQuestActive = false;
2178
2219
  this._levelQuestDone.add(quest.level);
2179
- this.log('success', `[QUEST] Level ${quest.level} quests DONE — resuming grinding`);
2180
- this.setStatus('idle');
2220
+ const justCompletedLevel = quest.level;
2221
+ this.log('success', `[QUEST] Level ${justCompletedLevel} quests DONE — verifying unlock...`);
2222
+ // Null out commandQueue so buildCommandQueue picks only unlocked commands
2223
+ this.commandQueue = null;
2224
+ this.setStatus('verifying level...');
2225
+ // Wait a moment for Dank Memer to process the unlock, then verify level
2226
+ this._verifyLevelUnlock = justCompletedLevel;
2227
+ this.tickTimeout = setTimeout(() => this.tick(), 3000);
2228
+ return;
2181
2229
  }
2182
2230
  this.tickTimeout = setTimeout(() => this.tick(), 2500);
2183
2231
  return;
@@ -2198,6 +2246,11 @@ class AccountWorker {
2198
2246
  this.tickTimeout = setTimeout(() => this.tick(), 2000);
2199
2247
  return;
2200
2248
  }
2249
+ // Block grinding if a quest command is currently running (race condition guard)
2250
+ if (this._commandRunning) {
2251
+ this.tickTimeout = setTimeout(() => this.tick(), 1500);
2252
+ return;
2253
+ }
2201
2254
 
2202
2255
  const now = Date.now();
2203
2256
 
package/lib/rawLogger.js CHANGED
@@ -528,14 +528,42 @@ function attachDmLogger(client, opts = {}) {
528
528
  if (targetAuthorId && d.author?.id !== targetAuthorId) return;
529
529
 
530
530
  const content = d.content || '';
531
+ // Also extract individual embed field values for richer level-up detection
532
+ const embedFieldsText = (d.embeds || []).map(e => {
533
+ const fields = (e.fields || []).map(f => `${f.name || ''} ${f.value || ''}`).join('\n');
534
+ return `${e.title || ''} ${e.description || ''} ${e.footer?.text || ''} ${e.author?.name || ''} ${fields}`;
535
+ }).join('\n');
531
536
  const embedText = extractEmbedText(d.embeds).toLowerCase();
532
- const allText = (content + '\n' + embedText).toLowerCase();
537
+ const allText = (content + '\n' + embedFieldsText + '\n' + embedText).toLowerCase();
533
538
 
534
539
  // Detect event type
535
540
  let dmEvent = null;
536
- if (allText.includes('leveled up') || allText.includes('level up')) {
537
- const m = allText.match(/level\s+(\d+)\s+to\s+(\d+)/i);
538
- dmEvent = { type: 'levelup', from: m ? parseInt(m[1]) : 0, to: m ? parseInt(m[2]) : 0 };
541
+ if (allText.includes('leveled up') || allText.includes('level up') || allText.includes('to level')) {
542
+ // Try multiple level-up patterns (Dank Memer varies the message format):
543
+ // "You leveled up from level X to Y"
544
+ // "You leveled up! You're now level Y"
545
+ // "You are now level Y"
546
+ // "You advanced to level Y"
547
+ // "Congratulations! Level Y"
548
+ let fromLv = 0, toLv = 0;
549
+ const m1 = allText.match(/level\s+(\d+)\s+to\s+level?\s*(\d+)/i);
550
+ const m2 = allText.match(/leveled?\s*up[!\s]+(?:.*?)?(?:to\s+)?(?:level\s+)?(\d+)/i);
551
+ const m3 = allText.match(/(?:you(?:'re| are) now|now at|advanced? to|reached|congratulations.*?level)\s+(?:level\s+)?(\d+)/i);
552
+ const m4 = allText.match(/(?:now\s+)?(?:at\s+)?level\s*(\d+)/i);
553
+ const m5 = allText.match(/to level\s*(\d+)/i);
554
+
555
+ if (m1) { fromLv = parseInt(m1[1]); toLv = parseInt(m1[2]); }
556
+ else if (m2) { toLv = parseInt(m2[1]); }
557
+ else if (m3) { toLv = parseInt(m3[1]); }
558
+ else if (m4) { toLv = parseInt(m4[1]); }
559
+ else if (m5) { toLv = parseInt(m5[1]); }
560
+
561
+ if (toLv > 0) {
562
+ dmEvent = { type: 'levelup', from: fromLv, to: toLv };
563
+ } else if ((allText.includes('leveled up') || allText.includes('level up')) && verboseMode) {
564
+ // Level-up keyword found but no number matched — log for debugging
565
+ console.log(` [DM LEVELUP] detected keyword but no number matched in: "${allText.substring(0, 200)}"`);
566
+ }
539
567
  } else if (allText.includes('lifesaver protected') || allText.includes('you died')) {
540
568
  // Parse lifesaver count from button labels: "You have 0 Life Saver left"
541
569
  let lsLeft = -1;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dankgrinder",
3
- "version": "8.94.0",
3
+ "version": "8.96.0",
4
4
  "description": "Dank Memer automation engine — grind coins while you sleep",
5
5
  "bin": {
6
6
  "dankgrinder": "bin/dankgrinder.js"