dankgrinder 8.100.0 → 8.101.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/commands/adventure.js +5 -1
- package/lib/commands/beg.js +5 -1
- package/lib/commands/blackjack.js +5 -1
- package/lib/commands/crime.js +5 -1
- package/lib/commands/dig.js +5 -1
- package/lib/commands/farm.js +7 -2
- package/lib/commands/fish.js +5 -1
- package/lib/commands/gamble.js +6 -1
- package/lib/commands/highlow.js +5 -1
- package/lib/commands/hunt.js +5 -1
- package/lib/commands/postmemes.js +5 -1
- package/lib/commands/scratch.js +5 -1
- package/lib/commands/search.js +5 -1
- package/lib/commands/stream.js +5 -1
- package/lib/commands/trivia.js +5 -1
- package/lib/commands/utils.js +53 -0
- package/lib/commands/work.js +5 -1
- package/lib/grinder.js +104 -15
- package/package.json +1 -1
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
const {
|
|
37
37
|
LOG, c, sleep, humanDelay, getFullText, parseCoins,
|
|
38
38
|
getAllButtons, getAllSelectMenus, findButton,
|
|
39
|
-
safeClickButton, isHoldTight, logMsg,
|
|
39
|
+
safeClickButton, isHoldTight, logMsg, checkLevelLock,
|
|
40
40
|
} = require('./utils');
|
|
41
41
|
|
|
42
42
|
const RE_DISCORD_TIMESTAMP = /<t:(\d+)(?::[tTdDfFR])?>/g;
|
|
@@ -279,6 +279,10 @@ async function runAdventure({ channel, waitForDankMemer, client }) {
|
|
|
279
279
|
return { result: 'hold tight', coins: 0, nextCooldownSec: 35 };
|
|
280
280
|
}
|
|
281
281
|
|
|
282
|
+
// Level-lock detection — returns early if feature is gated
|
|
283
|
+
const lvLock = await checkLevelLock(response, 'adventure');
|
|
284
|
+
if (lvLock) return lvLock;
|
|
285
|
+
|
|
282
286
|
logMsg(response, 'adventure-initial');
|
|
283
287
|
|
|
284
288
|
const text = getFullText(response);
|
package/lib/commands/beg.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Simple command: send "pls beg", parse coins from response.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
const { LOG, c, getFullText, parseCoins, logMsg, isHoldTight, getHoldTightReason, sleep } = require('./utils');
|
|
6
|
+
const { LOG, c, getFullText, parseCoins, logMsg, isHoldTight, getHoldTightReason, sleep, checkLevelLock } = require('./utils');
|
|
7
7
|
|
|
8
8
|
const RE_NEWLINE = /\n/g;
|
|
9
9
|
|
|
@@ -31,6 +31,10 @@ async function runBeg({ channel, waitForDankMemer }) {
|
|
|
31
31
|
return { result: `hold tight (${reason || 'unknown'})`, coins: 0, holdTightReason: reason };
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
// Level-lock detection — returns early if feature is gated
|
|
35
|
+
const lvLock = await checkLevelLock(response, 'beg');
|
|
36
|
+
if (lvLock) return lvLock;
|
|
37
|
+
|
|
34
38
|
logMsg(response, 'beg');
|
|
35
39
|
const text = getFullText(response);
|
|
36
40
|
const coins = parseCoins(text);
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
const {
|
|
8
8
|
LOG, c, getFullText, parseCoins, getAllButtons, safeClickButton,
|
|
9
|
-
logMsg, isHoldTight, getHoldTightReason, sleep, humanDelay, ensureCV2,
|
|
9
|
+
logMsg, isHoldTight, getHoldTightReason, sleep, humanDelay, ensureCV2, checkLevelLock,
|
|
10
10
|
} = require('./utils');
|
|
11
11
|
|
|
12
12
|
const RE_BACKTICK_SCORE = /`\s*(\d+)\s*`/;
|
|
@@ -154,6 +154,10 @@ async function runBlackjack({ channel, waitForDankMemer, betAmount = 5000 }) {
|
|
|
154
154
|
return { result: `hold tight (${reason || 'unknown'})`, coins: 0, holdTightReason: reason };
|
|
155
155
|
}
|
|
156
156
|
|
|
157
|
+
// Level-lock detection — returns early if feature is gated
|
|
158
|
+
const lvLock = await checkLevelLock(current, 'bj');
|
|
159
|
+
if (lvLock) return lvLock;
|
|
160
|
+
|
|
157
161
|
logMsg(current, 'bj');
|
|
158
162
|
|
|
159
163
|
const MAX_ROUNDS = 10;
|
package/lib/commands/crime.js
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
const {
|
|
12
12
|
LOG, c, getFullText, parseCoins, getAllButtons, safeClickButton,
|
|
13
13
|
logMsg, isHoldTight, getHoldTightReason, sleep, humanDelay,
|
|
14
|
-
isCV2, ensureCV2,
|
|
14
|
+
isCV2, ensureCV2, checkLevelLock,
|
|
15
15
|
} = require('./utils');
|
|
16
16
|
const { Trie, VoseAlias, LRUCache } = require('../structures');
|
|
17
17
|
|
|
@@ -101,6 +101,10 @@ async function runCrime({ channel, waitForDankMemer, safeAnswers }) {
|
|
|
101
101
|
return { result: `hold tight (${reason || 'unknown'})`, coins: 0, holdTightReason: reason };
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
+
// Level-lock detection — returns early if feature is gated
|
|
105
|
+
const lvLock = await checkLevelLock(response, 'crime');
|
|
106
|
+
if (lvLock) return lvLock;
|
|
107
|
+
|
|
104
108
|
if (isCV2(response)) await ensureCV2(response);
|
|
105
109
|
logMsg(response, 'crime');
|
|
106
110
|
let buttons = getAllButtons(response);
|
package/lib/commands/dig.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
const {
|
|
7
7
|
LOG, c, getFullText, parseCoins, getAllButtons, safeClickButton, humanDelay,
|
|
8
8
|
logMsg, isHoldTight, getHoldTightReason, sleep, needsItem,
|
|
9
|
-
isCV2, ensureCV2, stripAnsi,
|
|
9
|
+
isCV2, ensureCV2, stripAnsi, checkLevelLock,
|
|
10
10
|
} = require('./utils');
|
|
11
11
|
const { buyItem } = require('./shop');
|
|
12
12
|
|
|
@@ -111,6 +111,10 @@ async function runDig({ channel, waitForDankMemer, client }) {
|
|
|
111
111
|
return { result: `hold tight (${reason || 'unknown'})`, coins: 0, holdTightReason: reason };
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
+
// Level-lock detection — returns early if feature is gated
|
|
115
|
+
const lvLock = await checkLevelLock(response, 'dig');
|
|
116
|
+
if (lvLock) return lvLock;
|
|
117
|
+
|
|
114
118
|
if (isCV2(response)) await ensureCV2(response);
|
|
115
119
|
logMsg(response, 'dig');
|
|
116
120
|
const text = getFullText(response);
|
package/lib/commands/farm.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const {
|
|
2
2
|
LOG, c, getFullText, parseCoins, getAllButtons, getAllSelectMenus,
|
|
3
3
|
safeClickButton, logMsg, isHoldTight, getHoldTightReason, sleep, humanDelay,
|
|
4
|
-
isCV2, ensureCV2, stripAnsi, needsItem, clickCV2SelectMenu,
|
|
4
|
+
isCV2, ensureCV2, stripAnsi, needsItem, clickCV2SelectMenu, checkLevelLock,
|
|
5
5
|
} = require('./utils');
|
|
6
6
|
const { buyItem, buyItemsBatch } = require('./shop');
|
|
7
7
|
const rawLogger = require('../../lib/rawLogger');
|
|
@@ -1317,7 +1317,7 @@ async function runFarm({ channel, waitForDankMemer, client, redis, accountId, fo
|
|
|
1317
1317
|
|
|
1318
1318
|
let text = getFullText(response);
|
|
1319
1319
|
let clean = brief(text, 600);
|
|
1320
|
-
|
|
1320
|
+
let lower = clean.toLowerCase();
|
|
1321
1321
|
|
|
1322
1322
|
// Subcommand required — retry once.
|
|
1323
1323
|
if (lower.includes('must specify a subcommand')) {
|
|
@@ -1331,8 +1331,13 @@ async function runFarm({ channel, waitForDankMemer, client, redis, accountId, fo
|
|
|
1331
1331
|
logFarmState('retry-view', response);
|
|
1332
1332
|
text = getFullText(response);
|
|
1333
1333
|
clean = brief(text, 600);
|
|
1334
|
+
lower = clean.toLowerCase();
|
|
1334
1335
|
}
|
|
1335
1336
|
|
|
1337
|
+
// Level-locked detection parity with other handlers (pm/hl/etc).
|
|
1338
|
+
const lvLock = await checkLevelLock(response, 'farm');
|
|
1339
|
+
if (lvLock) return lvLock;
|
|
1340
|
+
|
|
1336
1341
|
const cd = parseFarmCooldownSec(text);
|
|
1337
1342
|
if (cd || lower.includes('already farmed') || lower.includes('farm again') || lower.includes('on cooldown')) {
|
|
1338
1343
|
return { result: `farm cooldown (${Math.ceil((cd || 10) / 60)}m)`, coins: 0, nextCooldownSec: cd || 10 };
|
package/lib/commands/fish.js
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
const {
|
|
18
18
|
LOG, c, getFullText, parseCoins, getAllButtons, safeClickButton,
|
|
19
19
|
logMsg, isHoldTight, getHoldTightReason, sleep, humanDelay,
|
|
20
|
-
isCV2, ensureCV2,
|
|
20
|
+
isCV2, ensureCV2, checkLevelLock,
|
|
21
21
|
} = require('./utils');
|
|
22
22
|
const { downloadImage, extractImageUrl, findSafeCells } = require('./fishVision');
|
|
23
23
|
|
|
@@ -319,6 +319,10 @@ async function runFish({ channel, waitForDankMemer }) {
|
|
|
319
319
|
return { result: `hold tight (${reason || 'unknown'})`, coins: 0, holdTightReason: reason };
|
|
320
320
|
}
|
|
321
321
|
|
|
322
|
+
// Level-lock detection — returns early if feature is gated
|
|
323
|
+
const lvLock = await checkLevelLock(response, 'fish');
|
|
324
|
+
if (lvLock) return lvLock;
|
|
325
|
+
|
|
322
326
|
logMsg(response, 'fish');
|
|
323
327
|
const text = getFullText(response);
|
|
324
328
|
|
package/lib/commands/gamble.js
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
const {
|
|
13
13
|
LOG, c, getFullText, parseCoins, parseNetCoins, getAllButtons, safeClickButton,
|
|
14
|
-
logMsg, isHoldTight, getHoldTightReason, sleep, humanDelay, ensureCV2,
|
|
14
|
+
logMsg, isHoldTight, getHoldTightReason, sleep, humanDelay, ensureCV2, checkLevelLock,
|
|
15
15
|
} = require('./utils');
|
|
16
16
|
const { EMA, AhoCorasick, SlidingWindowCounter } = require('../structures');
|
|
17
17
|
|
|
@@ -108,6 +108,11 @@ async function commonChecks(response, cmdName) {
|
|
|
108
108
|
return { skip: true, ret: { result: `hold tight (${reason || 'unknown'})`, coins: 0, holdTightReason: reason } };
|
|
109
109
|
}
|
|
110
110
|
const text = getFullText(response);
|
|
111
|
+
|
|
112
|
+
// Level-locked detection — uses shared helper
|
|
113
|
+
const lvLock = await checkLevelLock(response, cmdName);
|
|
114
|
+
if (lvLock) return { skip: true, ret: lvLock };
|
|
115
|
+
|
|
111
116
|
const minBet = checkMinBet(text);
|
|
112
117
|
if (minBet !== 0) {
|
|
113
118
|
const val = minBet > 0 ? minBet : 0;
|
package/lib/commands/highlow.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
const {
|
|
8
8
|
LOG, c, getFullText, parseCoins, getAllButtons, safeClickButton,
|
|
9
|
-
logMsg, isHoldTight, getHoldTightReason, sleep,
|
|
9
|
+
logMsg, isHoldTight, getHoldTightReason, sleep, checkLevelLock,
|
|
10
10
|
} = require('./utils');
|
|
11
11
|
|
|
12
12
|
const RE_HINT_BOLD = /hint.*?\*\*(\d+)\*\*/i;
|
|
@@ -144,6 +144,10 @@ async function runHighLow({ channel, waitForDankMemer }) {
|
|
|
144
144
|
return { result: `hold tight (${reason || 'unknown'})`, coins: 0, holdTightReason: reason };
|
|
145
145
|
}
|
|
146
146
|
|
|
147
|
+
// Level-lock detection — returns early if feature is gated
|
|
148
|
+
const lvLock = await checkLevelLock(response, 'hl');
|
|
149
|
+
if (lvLock) return lvLock;
|
|
150
|
+
|
|
147
151
|
logMsg(response, 'hl');
|
|
148
152
|
const { result, coins, lost } = await playHighLow(response);
|
|
149
153
|
|
package/lib/commands/hunt.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
const {
|
|
8
8
|
LOG, c, getFullText, parseCoins, getAllButtons, safeClickButton,
|
|
9
9
|
logMsg, isHoldTight, getHoldTightReason, sleep, needsItem,
|
|
10
|
-
isCV2, ensureCV2, stripAnsi,
|
|
10
|
+
isCV2, ensureCV2, stripAnsi, checkLevelLock,
|
|
11
11
|
} = require('./utils');
|
|
12
12
|
const { buyItem } = require('./shop');
|
|
13
13
|
|
|
@@ -95,6 +95,10 @@ async function runHunt({ channel, waitForDankMemer, client }) {
|
|
|
95
95
|
return { result: `hold tight (${reason || 'unknown'})`, coins: 0, needsRifle: false, holdTightReason: reason };
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
+
// Level-lock detection — returns early if feature is gated
|
|
99
|
+
const lvLock = await checkLevelLock(response, 'hunt');
|
|
100
|
+
if (lvLock) return lvLock;
|
|
101
|
+
|
|
98
102
|
if (isCV2(response)) await ensureCV2(response);
|
|
99
103
|
logMsg(response, 'hunt');
|
|
100
104
|
const text = getFullText(response);
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
const {
|
|
16
16
|
LOG, c, getFullText, parseCoins, getAllButtons, getAllSelectMenus,
|
|
17
17
|
findButton, safeClickButton, logMsg, isHoldTight, getHoldTightReason,
|
|
18
|
-
sleep, humanDelay,
|
|
18
|
+
sleep, humanDelay, checkLevelLock,
|
|
19
19
|
} = require('./utils');
|
|
20
20
|
|
|
21
21
|
const RE_COOLDOWN_MIN = /(\d+)\s*minute/i;
|
|
@@ -52,6 +52,10 @@ async function runPostMemes({ channel, waitForDankMemer }) {
|
|
|
52
52
|
return { result: `hold tight (${reason || 'unknown'})`, coins: 0, holdTightReason: reason };
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
// Level-lock detection — returns early if feature is gated
|
|
56
|
+
const lvLock = await checkLevelLock(response, 'pm');
|
|
57
|
+
if (lvLock) return lvLock;
|
|
58
|
+
|
|
55
59
|
logMsg(response, 'pm');
|
|
56
60
|
|
|
57
61
|
// Check for cooldown or direct text response (no select menus)
|
package/lib/commands/scratch.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
const {
|
|
8
8
|
LOG, c, getFullText, parseCoins, getAllButtons, safeClickButton,
|
|
9
|
-
logMsg, isHoldTight, getHoldTightReason, sleep, humanDelay,
|
|
9
|
+
logMsg, isHoldTight, getHoldTightReason, sleep, humanDelay, checkLevelLock,
|
|
10
10
|
} = require('./utils');
|
|
11
11
|
const { meetsLevelRequirement } = require('./profile');
|
|
12
12
|
|
|
@@ -42,6 +42,10 @@ async function runScratch({ channel, waitForDankMemer, accountId, redis }) {
|
|
|
42
42
|
return { result: `hold tight (${reason || 'unknown'})`, coins: 0, holdTightReason: reason };
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
+
// Level-lock detection — returns early if feature is gated
|
|
46
|
+
const lvLock = await checkLevelLock(response, 'scratch');
|
|
47
|
+
if (lvLock) return lvLock;
|
|
48
|
+
|
|
45
49
|
logMsg(response, 'scratch');
|
|
46
50
|
const buttons = getAllButtons(response);
|
|
47
51
|
|
package/lib/commands/search.js
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
const {
|
|
12
12
|
LOG, c, getFullText, parseCoins, getAllButtons, safeClickButton,
|
|
13
13
|
logMsg, isHoldTight, getHoldTightReason, sleep, humanDelay,
|
|
14
|
-
isCV2, ensureCV2,
|
|
14
|
+
isCV2, ensureCV2, checkLevelLock,
|
|
15
15
|
} = require('./utils');
|
|
16
16
|
const { VoseAlias, Trie, EMA, LRUCache } = require('../structures');
|
|
17
17
|
|
|
@@ -99,6 +99,10 @@ async function runSearch({ channel, waitForDankMemer, safeAnswers }) {
|
|
|
99
99
|
return { result: `hold tight (${reason || 'unknown'})`, coins: 0, holdTightReason: reason };
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
+
// Level-lock detection — returns early if feature is gated
|
|
103
|
+
const lvLock = await checkLevelLock(response, 'search');
|
|
104
|
+
if (lvLock) return lvLock;
|
|
105
|
+
|
|
102
106
|
if (isCV2(response)) await ensureCV2(response);
|
|
103
107
|
logMsg(response, 'search');
|
|
104
108
|
let buttons = getAllButtons(response);
|
package/lib/commands/stream.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const {
|
|
2
2
|
LOG, c, getFullText, parseCoins, getAllButtons, getAllSelectMenus,
|
|
3
3
|
safeClickButton, logMsg, isHoldTight, getHoldTightReason, sleep, humanDelay, needsItem,
|
|
4
|
-
isCV2, ensureCV2, stripAnsi, clickCV2Button,
|
|
4
|
+
isCV2, ensureCV2, stripAnsi, clickCV2Button, checkLevelLock,
|
|
5
5
|
} = require('./utils');
|
|
6
6
|
const { buyItem } = require('./shop');
|
|
7
7
|
|
|
@@ -209,6 +209,10 @@ async function runStream({ channel, waitForDankMemer, client }) {
|
|
|
209
209
|
return { result: `hold tight (${reason || 'unknown'})`, coins: 0, holdTightReason: reason, nextCooldownSec: 30 };
|
|
210
210
|
}
|
|
211
211
|
|
|
212
|
+
// Level-lock detection — returns early if feature is gated
|
|
213
|
+
const lvLock = await checkLevelLock(response, 'stream');
|
|
214
|
+
if (lvLock) return lvLock;
|
|
215
|
+
|
|
212
216
|
await hydrate(response);
|
|
213
217
|
logMsg(response, 'stream');
|
|
214
218
|
let text = getFullText(response);
|
package/lib/commands/trivia.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
const {
|
|
11
11
|
LOG, c, getFullText, parseCoins, getAllButtons, safeClickButton,
|
|
12
|
-
logMsg, isHoldTight, getHoldTightReason, sleep, humanDelay,
|
|
12
|
+
logMsg, isHoldTight, getHoldTightReason, sleep, humanDelay, checkLevelLock,
|
|
13
13
|
} = require('./utils');
|
|
14
14
|
|
|
15
15
|
const RE_TRIVIA_MD_BOLD = /\*\*/g;
|
|
@@ -94,6 +94,10 @@ async function runTrivia({ channel, waitForDankMemer, redis }) {
|
|
|
94
94
|
return { result: `hold tight (${reason || 'unknown'})`, coins: 0, holdTightReason: reason };
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
+
// Level-lock detection — returns early if feature is gated
|
|
98
|
+
const lvLock = await checkLevelLock(response, 'trivia');
|
|
99
|
+
if (lvLock) return lvLock;
|
|
100
|
+
|
|
97
101
|
logMsg(response, 'trivia');
|
|
98
102
|
const buttons = getAllButtons(response);
|
|
99
103
|
|
package/lib/commands/utils.js
CHANGED
|
@@ -703,6 +703,58 @@ function needsItem(text) {
|
|
|
703
703
|
return match;
|
|
704
704
|
}
|
|
705
705
|
|
|
706
|
+
// ── Level-Lock Detection (shared across ALL command handlers) ─
|
|
707
|
+
// Dank Memer replies "You have not unlocked this feature yet!"
|
|
708
|
+
// with the required level (e.g. "Level 5"). This helper checks
|
|
709
|
+
// the response text and returns early if the command is gated.
|
|
710
|
+
//
|
|
711
|
+
// CRITICAL: The "not unlocked" message is almost always a CV2 message
|
|
712
|
+
// (Components V2, flags & 32768). getFullText() returns empty for CV2
|
|
713
|
+
// unless ensureCV2() has been called first to hydrate _cv2text.
|
|
714
|
+
// We also check rawLogger as a secondary source since it captures
|
|
715
|
+
// the raw gateway data including CV2 component text.
|
|
716
|
+
const RE_LEVEL_NUM = /level\s*(\d+)/i;
|
|
717
|
+
|
|
718
|
+
async function checkLevelLock(response, cmdName) {
|
|
719
|
+
if (!response) return null;
|
|
720
|
+
|
|
721
|
+
// 1. Hydrate CV2 if needed so getFullText() has content
|
|
722
|
+
if (isCV2(response)) {
|
|
723
|
+
try { await ensureCV2(response); } catch (e) { /* best effort */ }
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
// 2. Primary: getFullText (works for both embed and CV2 messages)
|
|
727
|
+
let text = getFullText(response);
|
|
728
|
+
|
|
729
|
+
// 3. Secondary: rawLogger captures gateway data for CV2 messages
|
|
730
|
+
// that ensureCV2 might have missed (timing, cache)
|
|
731
|
+
try {
|
|
732
|
+
const chId = response.channelId || response.channel?.id;
|
|
733
|
+
if (chId) {
|
|
734
|
+
const raw = rawLogger.getLastRaw(chId);
|
|
735
|
+
if (raw) {
|
|
736
|
+
const rawText = (raw.content || '') + ' ' + (raw.cv2Text || '') + ' ' + (raw.embedText || '');
|
|
737
|
+
text = text + ' ' + rawText;
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
// Also try by message ID
|
|
741
|
+
const rawById = rawLogger.getRawMessage(response.id);
|
|
742
|
+
if (rawById) {
|
|
743
|
+
const rawText2 = (rawById.content || '') + ' ' + (rawById.cv2Text || '') + ' ' + (rawById.embedText || '');
|
|
744
|
+
text = text + ' ' + rawText2;
|
|
745
|
+
}
|
|
746
|
+
} catch (e) { /* rawLogger not available in tests */ }
|
|
747
|
+
|
|
748
|
+
const lower = text.toLowerCase();
|
|
749
|
+
if (lower.includes('not unlocked') || lower.includes('have not unlocked')) {
|
|
750
|
+
const lvMatch = text.match(RE_LEVEL_NUM);
|
|
751
|
+
const targetLv = lvMatch ? parseInt(lvMatch[1]) : 5;
|
|
752
|
+
LOG.warn(`[${cmdName}] LEVEL LOCKED — requires Level ${targetLv} quests`);
|
|
753
|
+
return { result: `level locked (L${targetLv})`, coins: 0, levelLocked: targetLv };
|
|
754
|
+
}
|
|
755
|
+
return null;
|
|
756
|
+
}
|
|
757
|
+
|
|
706
758
|
module.exports = {
|
|
707
759
|
DANK_MEMER_ID,
|
|
708
760
|
c,
|
|
@@ -729,6 +781,7 @@ module.exports = {
|
|
|
729
781
|
ensureCV2,
|
|
730
782
|
clickCV2Button,
|
|
731
783
|
clickCV2SelectMenu,
|
|
784
|
+
checkLevelLock,
|
|
732
785
|
// Shared structures and optimized constants
|
|
733
786
|
strings,
|
|
734
787
|
cv2Cache,
|
package/lib/commands/work.js
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
const {
|
|
19
19
|
LOG, c, getFullText, parseCoins, getAllButtons, safeClickButton,
|
|
20
20
|
logMsg, isHoldTight, getHoldTightReason, sleep, humanDelay,
|
|
21
|
-
isCV2, ensureCV2, stripAnsi,
|
|
21
|
+
isCV2, ensureCV2, stripAnsi, checkLevelLock,
|
|
22
22
|
} = require('./utils');
|
|
23
23
|
|
|
24
24
|
const RE_MEMORY_BACKTICK_CHUNK = /`([^`]+)`/g;
|
|
@@ -421,6 +421,10 @@ async function runWorkShift({ channel, waitForDankMemer }) {
|
|
|
421
421
|
return { result: `hold tight (${reason || 'unknown'})`, coins: 0, holdTightReason: reason, nextCooldownSec: 30 };
|
|
422
422
|
}
|
|
423
423
|
|
|
424
|
+
// Level-lock detection — returns early if feature is gated
|
|
425
|
+
const lvLock = await checkLevelLock(current, 'work');
|
|
426
|
+
if (lvLock) return lvLock;
|
|
427
|
+
|
|
424
428
|
if (isCV2(current)) await ensureCV2(current);
|
|
425
429
|
|
|
426
430
|
logMsg(current, 'work');
|
package/lib/grinder.js
CHANGED
|
@@ -671,6 +671,7 @@ class AccountWorker {
|
|
|
671
671
|
this._levelQuestQueue = [];
|
|
672
672
|
this._levelQuestDone = new Set();
|
|
673
673
|
this._questBetOverride = null;
|
|
674
|
+
this._lastCommandMeta = null;
|
|
674
675
|
this._commandRunning = false; // prevents grinding commands from overlapping with quest commands
|
|
675
676
|
this._verifyLevelUnlock = null; // holds level to verify after quest completion
|
|
676
677
|
this.commandQueue = null;
|
|
@@ -1385,6 +1386,7 @@ class AccountWorker {
|
|
|
1385
1386
|
// Each modular command handler sends the command, waits for response,
|
|
1386
1387
|
// handles Hold Tight / cooldowns / item-buying internally.
|
|
1387
1388
|
async runCommand(cmdName, prefix) {
|
|
1389
|
+
this._lastCommandMeta = { cmdName, nonPlay: false, spent: 0, earned: 0, holdTightReason: null, newMinBet: null, levelLocked: null };
|
|
1388
1390
|
let cmdString;
|
|
1389
1391
|
const bjBet = Math.max(5000, this.account.bet_amount || 5000);
|
|
1390
1392
|
const gambBet = Math.max(10000, this.account.bet_amount || 10000);
|
|
@@ -1526,6 +1528,7 @@ class AccountWorker {
|
|
|
1526
1528
|
|
|
1527
1529
|
// Rate limit detection — progressive backoff based on frequency
|
|
1528
1530
|
if (resultLower.includes('slow down') || resultLower.includes('rate limit') || resultLower.includes('too fast')) {
|
|
1531
|
+
this._lastCommandMeta.nonPlay = true;
|
|
1529
1532
|
this._rateLimitHits++;
|
|
1530
1533
|
// Progressive: 30s for first hit, then 60s, 120s, cap at 300s
|
|
1531
1534
|
const cooldownSec = Math.min(30 * Math.pow(2, Math.min(this._rateLimitHits - 1, 3)), 300);
|
|
@@ -1546,11 +1549,12 @@ class AccountWorker {
|
|
|
1546
1549
|
return;
|
|
1547
1550
|
}
|
|
1548
1551
|
|
|
1549
|
-
// Level-locked detection — check rawLogger (CV2/embed text)
|
|
1552
|
+
// Level-locked detection — check rawLogger (CV2/embed text) AND result text
|
|
1550
1553
|
const raw = rawLogger.getLastRaw(this.channel?.id);
|
|
1551
1554
|
const rawAllText = (raw?.content || '') + '\n' + (raw?.cv2Text || '') + '\n' + (raw?.embedText || '');
|
|
1552
|
-
|
|
1553
|
-
|
|
1555
|
+
const allDetectionText = rawAllText + '\n' + result;
|
|
1556
|
+
if (allDetectionText.toLowerCase().includes('not unlocked') || allDetectionText.toLowerCase().includes('have not unlocked')) {
|
|
1557
|
+
const lvMatch = allDetectionText.match(/level\s*(\d+)/i);
|
|
1554
1558
|
if (lvMatch) {
|
|
1555
1559
|
const targetLv = parseInt(lvMatch[1]);
|
|
1556
1560
|
cmdResult.levelLocked = targetLv;
|
|
@@ -1579,11 +1583,13 @@ class AccountWorker {
|
|
|
1579
1583
|
|
|
1580
1584
|
// Min bet detection — "You can't bet less than 10,000" or "cannot bet less than ⏣ 5,000"
|
|
1581
1585
|
if (resultLower.includes("can't bet less than") || resultLower.includes('cannot bet less than') || resultLower.includes('minimum bet')) {
|
|
1586
|
+
this._lastCommandMeta.nonPlay = true;
|
|
1582
1587
|
const betMatch = result.match(/less than\s*\*?\*?\s*[⏣o]?\s*([\d,]+)/i) || result.match(/(\d[\d,]+)/);
|
|
1583
1588
|
if (betMatch) {
|
|
1584
1589
|
const minBet = parseInt(betMatch[1].replace(/,/g, ''));
|
|
1585
1590
|
if (minBet > 0) {
|
|
1586
1591
|
this.account.bet_amount = minBet;
|
|
1592
|
+
this._lastCommandMeta.newMinBet = minBet;
|
|
1587
1593
|
this.log('info', `${cmdName} min bet raised → ⏣ ${minBet.toLocaleString()}`);
|
|
1588
1594
|
}
|
|
1589
1595
|
}
|
|
@@ -1591,6 +1597,8 @@ class AccountWorker {
|
|
|
1591
1597
|
}
|
|
1592
1598
|
// Also handle newMinBet from gamble handler
|
|
1593
1599
|
if (cmdResult.newMinBet && cmdResult.newMinBet > (this.account.bet_amount || 0)) {
|
|
1600
|
+
this._lastCommandMeta.nonPlay = true;
|
|
1601
|
+
this._lastCommandMeta.newMinBet = cmdResult.newMinBet;
|
|
1594
1602
|
this.account.bet_amount = cmdResult.newMinBet;
|
|
1595
1603
|
this.log('info', `${cmdName} min bet raised → ⏣ ${cmdResult.newMinBet.toLocaleString()}`);
|
|
1596
1604
|
return;
|
|
@@ -1676,6 +1684,8 @@ class AccountWorker {
|
|
|
1676
1684
|
|
|
1677
1685
|
const earned = Math.max(0, cmdResult.coins || 0);
|
|
1678
1686
|
const spent = Math.max(0, cmdResult.lost || 0);
|
|
1687
|
+
this._lastCommandMeta.earned = earned;
|
|
1688
|
+
this._lastCommandMeta.spent = spent;
|
|
1679
1689
|
// Track net earnings (add wins, subtract losses)
|
|
1680
1690
|
this.stats.coins += (earned - spent);
|
|
1681
1691
|
if (cmdResult.nextCooldownSec) {
|
|
@@ -1719,6 +1729,8 @@ class AccountWorker {
|
|
|
1719
1729
|
|
|
1720
1730
|
if (cmdResult.holdTightReason) {
|
|
1721
1731
|
const reason = cmdResult.holdTightReason;
|
|
1732
|
+
this._lastCommandMeta.nonPlay = true;
|
|
1733
|
+
this._lastCommandMeta.holdTightReason = reason;
|
|
1722
1734
|
const holdSec = 35;
|
|
1723
1735
|
this.log('warn', `Hold Tight: /${reason} — ${holdSec}s global cooldown`);
|
|
1724
1736
|
const reasonMap = { postmemes: 'pm', highlow: 'hl', blackjack: 'bj', 'work shift': 'work shift' };
|
|
@@ -1731,12 +1743,16 @@ class AccountWorker {
|
|
|
1731
1743
|
// Detect level-locked command → start quest mode
|
|
1732
1744
|
if (cmdResult.levelLocked) {
|
|
1733
1745
|
const targetLv = cmdResult.levelLocked;
|
|
1746
|
+
this._lastCommandMeta.nonPlay = true;
|
|
1747
|
+
this._lastCommandMeta.levelLocked = targetLv;
|
|
1734
1748
|
this.log('warn', `Command /${cmdName} is LOCKED at Level ${targetLv} — starting quest unlock flow`);
|
|
1735
1749
|
if (this._startLevelQuests(targetLv)) {
|
|
1736
1750
|
this.log('info', `[QUEST] Quest mode activated for Level ${targetLv} — grinding will resume after quests complete`);
|
|
1737
1751
|
} else {
|
|
1738
1752
|
this.log('warn', `[QUEST] Level ${targetLv} quests already done or not defined — grinding continues (may re-trigger)`);
|
|
1739
1753
|
}
|
|
1754
|
+
// Don't count as success — skip normal post-command processing
|
|
1755
|
+
return;
|
|
1740
1756
|
}
|
|
1741
1757
|
|
|
1742
1758
|
this.stats.successes++;
|
|
@@ -1773,6 +1789,7 @@ class AccountWorker {
|
|
|
1773
1789
|
} catch {}
|
|
1774
1790
|
}
|
|
1775
1791
|
} catch (err) {
|
|
1792
|
+
this._lastCommandMeta.nonPlay = true;
|
|
1776
1793
|
this.stats.errors++;
|
|
1777
1794
|
this.log('error', `${cmdString} failed: ${err.message}`);
|
|
1778
1795
|
await sendLog(this.username, cmdName, err.message, 'error');
|
|
@@ -1900,9 +1917,10 @@ class AccountWorker {
|
|
|
1900
1917
|
{ cmd: 'shop sell common coin 1', times: 2 },
|
|
1901
1918
|
],
|
|
1902
1919
|
5: [
|
|
1903
|
-
|
|
1904
|
-
{ cmd: '
|
|
1905
|
-
{ cmd: '
|
|
1920
|
+
// "Lose 50,000 coins in /slots" etc — loseTarget means keep playing until cumulative losses hit 50k
|
|
1921
|
+
{ cmd: 'slots', loseTarget: 50000, bet: 10000, times: 50 },
|
|
1922
|
+
{ cmd: 'cointoss', loseTarget: 50000, bet: 10000, times: 50 },
|
|
1923
|
+
{ cmd: 'snakeeyes', loseTarget: 50000, bet: 10000, times: 50 },
|
|
1906
1924
|
],
|
|
1907
1925
|
};
|
|
1908
1926
|
|
|
@@ -1910,14 +1928,17 @@ class AccountWorker {
|
|
|
1910
1928
|
if (this._levelQuestDone.has(targetLevel)) return false;
|
|
1911
1929
|
const quests = AccountWorker.LEVEL_QUESTS[targetLevel];
|
|
1912
1930
|
if (!quests || quests.length === 0) return false;
|
|
1913
|
-
this._levelQuestQueue = quests.map(q => ({ ...q, level: targetLevel }));
|
|
1931
|
+
this._levelQuestQueue = quests.map(q => ({ ...q, level: targetLevel, _lostSoFar: 0 }));
|
|
1914
1932
|
this._levelQuestActive = true;
|
|
1915
1933
|
this._questBetOverride = null;
|
|
1916
1934
|
// Clear commandQueue so no stale grinding commands fire after quests finish
|
|
1917
1935
|
this.commandQueue = null;
|
|
1918
1936
|
this._commandRunning = false; // cancel any in-flight grinding command
|
|
1919
1937
|
this.log('info', `[QUEST] Level ${targetLevel} quests started — grinding PAUSED`);
|
|
1920
|
-
const questList = this._levelQuestQueue.map(q =>
|
|
1938
|
+
const questList = this._levelQuestQueue.map(q => {
|
|
1939
|
+
if (q.loseTarget) return `"${q.cmd}" lose ⏣${q.loseTarget.toLocaleString()}`;
|
|
1940
|
+
return `"${q.cmd}" x${q.times}`;
|
|
1941
|
+
}).join(', ');
|
|
1921
1942
|
this.log('info', `[QUEST] Quests: ${questList}`);
|
|
1922
1943
|
return true;
|
|
1923
1944
|
}
|
|
@@ -1959,7 +1980,8 @@ class AccountWorker {
|
|
|
1959
1980
|
const ttlVal = ttlMap.get(key);
|
|
1960
1981
|
if (Number.isFinite(ttlVal) && ttlVal > 0) {
|
|
1961
1982
|
nextRunAt = now + ttlVal * 1000;
|
|
1962
|
-
|
|
1983
|
+
// Bloom uses accountId:cmd keys (not Redis key format)
|
|
1984
|
+
this._cooldownBloom.add(`${this.account.id}:${info.cmd}`);
|
|
1963
1985
|
this.log('info', `Restored cooldown for ${info.cmd}: ${ttlVal}s remaining`);
|
|
1964
1986
|
}
|
|
1965
1987
|
heap.push({ cmd: info.cmd, nextRunAt, priority: info.priority, info });
|
|
@@ -2202,18 +2224,85 @@ class AccountWorker {
|
|
|
2202
2224
|
// BLOCK: quest mode active — run quests only, no normal grinding
|
|
2203
2225
|
if (this._levelQuestActive && this._levelQuestQueue.length > 0) {
|
|
2204
2226
|
const quest = this._levelQuestQueue[0];
|
|
2205
|
-
|
|
2227
|
+
if (quest.loseTarget) {
|
|
2228
|
+
const remaining = Math.max(0, quest.loseTarget - (quest._lostSoFar || 0));
|
|
2229
|
+
const floorBet = quest._minBet || quest.bet || 1;
|
|
2230
|
+
this._questBetOverride = Math.max(floorBet, remaining);
|
|
2231
|
+
} else {
|
|
2232
|
+
this._questBetOverride = quest.bet || null;
|
|
2233
|
+
}
|
|
2234
|
+
const questBetUsed = this._questBetOverride;
|
|
2206
2235
|
const prefix = this.account.use_slash ? '/' : 'pls';
|
|
2207
|
-
|
|
2208
|
-
|
|
2236
|
+
|
|
2237
|
+
if (quest.loseTarget) {
|
|
2238
|
+
// Loss-target quest: keep playing until cumulative losses >= loseTarget
|
|
2239
|
+
const remaining = quest.loseTarget - (quest._lostSoFar || 0);
|
|
2240
|
+
this.setStatus(`[L${quest.level}] QUEST ${quest.cmd} — lose ⏣${remaining.toLocaleString()} more`);
|
|
2241
|
+
this.lastStatus = `[L${quest.level}] ${quest.cmd} lose ${remaining}`;
|
|
2242
|
+
} else {
|
|
2243
|
+
this.setStatus(`[L${quest.level}] QUEST ${quest.cmd} (${quest.times}x left)`);
|
|
2244
|
+
this.lastStatus = `[L${quest.level}] ${quest.cmd}`;
|
|
2245
|
+
}
|
|
2246
|
+
|
|
2247
|
+
// Capture balance before the command
|
|
2248
|
+
const balBefore = this.stats.balance || 0;
|
|
2249
|
+
|
|
2209
2250
|
this._commandRunning = true;
|
|
2210
|
-
this.busy = true;
|
|
2251
|
+
this.busy = true;
|
|
2211
2252
|
await this.runCommand(quest.cmd, prefix);
|
|
2253
|
+
const questMeta = this._lastCommandMeta || {};
|
|
2212
2254
|
this._commandRunning = false;
|
|
2213
2255
|
this.busy = false;
|
|
2214
2256
|
this._questBetOverride = null;
|
|
2215
|
-
|
|
2216
|
-
if
|
|
2257
|
+
|
|
2258
|
+
// Determine if quest step is complete
|
|
2259
|
+
let questStepDone = false;
|
|
2260
|
+
|
|
2261
|
+
if (quest.loseTarget) {
|
|
2262
|
+
// Track cumulative loss from balance diff
|
|
2263
|
+
const balAfter = this.stats.balance || 0;
|
|
2264
|
+
const balLoss = Math.max(0, balBefore - balAfter);
|
|
2265
|
+
const reportedLoss = Math.max(0, Number(questMeta.spent || 0));
|
|
2266
|
+
let lostThisRound = Math.max(balLoss, reportedLoss);
|
|
2267
|
+
|
|
2268
|
+
const nonPlay = !!questMeta.nonPlay;
|
|
2269
|
+
if (questMeta.newMinBet && Number.isFinite(questMeta.newMinBet) && questMeta.newMinBet > 0) {
|
|
2270
|
+
quest._minBet = Math.max(quest._minBet || 1, questMeta.newMinBet);
|
|
2271
|
+
}
|
|
2272
|
+
|
|
2273
|
+
// Fallback only for actual played rounds where parsed/observed loss was zero.
|
|
2274
|
+
if (!nonPlay && lostThisRound === 0 && (questBetUsed || quest.bet)) {
|
|
2275
|
+
lostThisRound = questBetUsed || quest.bet;
|
|
2276
|
+
}
|
|
2277
|
+
|
|
2278
|
+
if (!nonPlay) {
|
|
2279
|
+
quest._lostSoFar = (quest._lostSoFar || 0) + lostThisRound;
|
|
2280
|
+
}
|
|
2281
|
+
|
|
2282
|
+
this.log('info', `[QUEST] ${quest.cmd} — lost ⏣${quest._lostSoFar.toLocaleString()} / ⏣${quest.loseTarget.toLocaleString()}`);
|
|
2283
|
+
if (nonPlay) {
|
|
2284
|
+
this.log('warn', `[QUEST] ${quest.cmd} non-play response — progress unchanged`);
|
|
2285
|
+
}
|
|
2286
|
+
|
|
2287
|
+
if (quest._lostSoFar >= quest.loseTarget) {
|
|
2288
|
+
questStepDone = true;
|
|
2289
|
+
this.log('success', `[QUEST] ${quest.cmd} loss target reached! ⏣${quest._lostSoFar.toLocaleString()} lost`);
|
|
2290
|
+
}
|
|
2291
|
+
|
|
2292
|
+
// Safety attempts decrement only when a playable round happened.
|
|
2293
|
+
if (!nonPlay) quest.times--;
|
|
2294
|
+
if (quest.times <= 0) {
|
|
2295
|
+
questStepDone = true;
|
|
2296
|
+
this.log('info', `[QUEST] ${quest.cmd} max attempts reached — moving on`);
|
|
2297
|
+
}
|
|
2298
|
+
} else {
|
|
2299
|
+
// Normal times-based quest
|
|
2300
|
+
quest.times--;
|
|
2301
|
+
if (quest.times <= 0) questStepDone = true;
|
|
2302
|
+
}
|
|
2303
|
+
|
|
2304
|
+
if (questStepDone) this._levelQuestQueue.shift();
|
|
2305
|
+
|
|
2217
2306
|
if (this._levelQuestQueue.length === 0) {
|
|
2218
2307
|
this._levelQuestActive = false;
|
|
2219
2308
|
this._levelQuestDone.add(quest.level);
|