dankgrinder 5.24.0 → 5.260.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/antiDetect.js +211 -0
- package/lib/commands/adventure.js +36 -25
- package/lib/commands/dig.js +80 -15
- package/lib/commands/farm.js +14 -3
- package/lib/commands/stream.js +33 -3
- package/lib/commands/utils.js +20 -2
- package/lib/commands/work.js +37 -13
- package/lib/cooldownManager.js +347 -0
- package/lib/grinder.js +159 -55
- package/package.json +1 -1
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Advanced Anti-Detection System
|
|
3
|
+
*
|
|
4
|
+
* Features:
|
|
5
|
+
* - Adaptive human-like timing with context-aware delays
|
|
6
|
+
* - Micro-pause injection for natural interaction patterns
|
|
7
|
+
* - Activity-based variance (tired vs alert simulation)
|
|
8
|
+
* - Session behavior drift (mimics human consistency changes)
|
|
9
|
+
* - Randomized click timing within safe bounds
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
'use strict';
|
|
13
|
+
|
|
14
|
+
// ── Configuration ─────────────────────────────────────────────
|
|
15
|
+
const CONFIG = Object.freeze({
|
|
16
|
+
// Base delay ranges in ms (tuned for natural human timing)
|
|
17
|
+
MIN_DELAY: 500, // 0.5s minimum - natural human speed
|
|
18
|
+
MAX_DELAY: 1000, // 1.0s maximum - not too slow
|
|
19
|
+
|
|
20
|
+
// Context multipliers (subtle variations)
|
|
21
|
+
MULT_FIRST_ACTION: 1.2, // Slight hesitation on first action
|
|
22
|
+
MULT_REPEATED_ACTION: 0.85, // Slightly faster on repeated actions
|
|
23
|
+
MULT_HIGH_VALUE: 1.1, // Marginally slower for high-value actions
|
|
24
|
+
MULT_LOW_VALUE: 0.9, // Slightly faster for low-value actions
|
|
25
|
+
|
|
26
|
+
// Micro-pause settings (subtle human-like hesitation)
|
|
27
|
+
MICRO_PAUSE_CHANCE: 0.25, // 25% chance of micro-pause
|
|
28
|
+
MICRO_PAUSE_MIN: 50,
|
|
29
|
+
MICRO_PAUSE_MAX: 150,
|
|
30
|
+
|
|
31
|
+
// Minimal drift to avoid detectable patterns
|
|
32
|
+
DRIFT_FACTOR: 0.01,
|
|
33
|
+
DRIFT_MAX: 0.08, // Max 8% drift
|
|
34
|
+
|
|
35
|
+
// Pattern randomization
|
|
36
|
+
PATTERN_RESET_CHANCE: 0.2, // 20% chance to reset timing pattern
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// ── Session State ─────────────────────────────────────────────
|
|
40
|
+
const sessionState = {
|
|
41
|
+
actionCount: 0,
|
|
42
|
+
lastActionTime: 0,
|
|
43
|
+
currentDrift: 0,
|
|
44
|
+
patternSeed: Math.random(),
|
|
45
|
+
activityLevel: 1.0, // Starts neutral
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
// ── Seeded Random for Reproducible Patterns ──────────────────
|
|
49
|
+
function seededRandom(seed) {
|
|
50
|
+
const x = Math.sin(seed) * 10000;
|
|
51
|
+
return x - Math.floor(x);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// ── Adaptive Delay Calculation ────────────────────────────────
|
|
55
|
+
/**
|
|
56
|
+
* Calculate human-like delay based on context.
|
|
57
|
+
* @param {Object} options
|
|
58
|
+
* @param {string} options.context - Action context ('first', 'repeated', 'high-value', 'low-value', 'normal')
|
|
59
|
+
* @param {number} options.baseMin - Minimum delay override
|
|
60
|
+
* @param {number} options.baseMax - Maximum delay override
|
|
61
|
+
* @param {boolean} options.skipMicroPause - Force skip micro-pause
|
|
62
|
+
* @returns {Promise<number>} - Delay in ms
|
|
63
|
+
*/
|
|
64
|
+
async function calcAdaptiveDelay(options = {}) {
|
|
65
|
+
const {
|
|
66
|
+
context = 'normal',
|
|
67
|
+
baseMin = CONFIG.MIN_DELAY,
|
|
68
|
+
baseMax = CONFIG.MAX_DELAY,
|
|
69
|
+
skipMicroPause = false,
|
|
70
|
+
} = options;
|
|
71
|
+
|
|
72
|
+
sessionState.actionCount++;
|
|
73
|
+
const now = Date.now();
|
|
74
|
+
|
|
75
|
+
// Time since last action affects speed (rush vs relaxed)
|
|
76
|
+
const timeSinceLast = now - sessionState.lastActionTime;
|
|
77
|
+
const rushFactor = timeSinceLast < 500 ? 0.8 : timeSinceLast > 5000 ? 1.15 : 1.0;
|
|
78
|
+
|
|
79
|
+
// Apply context multiplier
|
|
80
|
+
let multiplier = 1.0;
|
|
81
|
+
switch (context) {
|
|
82
|
+
case 'first':
|
|
83
|
+
multiplier = CONFIG.MULT_FIRST_ACTION;
|
|
84
|
+
break;
|
|
85
|
+
case 'repeated':
|
|
86
|
+
multiplier = CONFIG.MULT_REPEATED_ACTION;
|
|
87
|
+
break;
|
|
88
|
+
case 'high-value':
|
|
89
|
+
multiplier = CONFIG.MULT_HIGH_VALUE;
|
|
90
|
+
break;
|
|
91
|
+
case 'low-value':
|
|
92
|
+
multiplier = CONFIG.MULT_LOW_VALUE;
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Apply drift (session behavior change)
|
|
97
|
+
sessionState.currentDrift += (Math.random() - 0.5) * CONFIG.DRIFT_FACTOR;
|
|
98
|
+
sessionState.currentDrift = Math.max(
|
|
99
|
+
-CONFIG.DRIFT_MAX,
|
|
100
|
+
Math.min(CONFIG.DRIFT_MAX, sessionState.currentDrift)
|
|
101
|
+
);
|
|
102
|
+
multiplier *= (1 + sessionState.currentDrift);
|
|
103
|
+
|
|
104
|
+
// Pattern reset for unpredictability
|
|
105
|
+
if (Math.random() < CONFIG.PATTERN_RESET_CHANCE) {
|
|
106
|
+
sessionState.patternSeed = Math.random();
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Calculate base delay with seeded randomness
|
|
110
|
+
const seed = sessionState.patternSeed + (sessionState.actionCount * 0.001);
|
|
111
|
+
const randomFactor = 0.5 + seededRandom(seed); // 0.5 to 1.5 range
|
|
112
|
+
const baseDelay = baseMin + (baseMax - baseMin) * randomFactor;
|
|
113
|
+
|
|
114
|
+
// Apply all modifiers
|
|
115
|
+
let finalDelay = baseDelay * multiplier * rushFactor;
|
|
116
|
+
|
|
117
|
+
// Add micro-pause (simulates human hesitation)
|
|
118
|
+
if (!skipMicroPause && Math.random() < CONFIG.MICRO_PAUSE_CHANCE) {
|
|
119
|
+
const microPause = CONFIG.MICRO_PAUSE_MIN +
|
|
120
|
+
(CONFIG.MICRO_PAUSE_MAX - CONFIG.MICRO_PAUSE_MIN) * seededRandom(seed + 0.5);
|
|
121
|
+
finalDelay += microPause;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Clamp to reasonable bounds
|
|
125
|
+
finalDelay = Math.max(50, Math.min(800, finalDelay));
|
|
126
|
+
|
|
127
|
+
sessionState.lastActionTime = Date.now();
|
|
128
|
+
|
|
129
|
+
return Math.round(finalDelay);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// ── Main Human Delay Function ─────────────────────────────────
|
|
133
|
+
/**
|
|
134
|
+
* Wait with human-like timing.
|
|
135
|
+
* @param {Object} options - Same as calcAdaptiveDelay
|
|
136
|
+
*/
|
|
137
|
+
async function humanDelay(options = {}) {
|
|
138
|
+
const delay = await calcAdaptiveDelay(options);
|
|
139
|
+
return new Promise(resolve => setTimeout(resolve, delay));
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// ── Reset Session State ───────────────────────────────────────
|
|
143
|
+
function resetSession() {
|
|
144
|
+
sessionState.actionCount = 0;
|
|
145
|
+
sessionState.lastActionTime = 0;
|
|
146
|
+
sessionState.currentDrift = 0;
|
|
147
|
+
sessionState.patternSeed = Math.random();
|
|
148
|
+
sessionState.activityLevel = 1.0;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// ── Get Session Stats (for debugging) ─────────────────────────
|
|
152
|
+
function getSessionStats() {
|
|
153
|
+
return {
|
|
154
|
+
actionCount: sessionState.actionCount,
|
|
155
|
+
currentDrift: sessionState.currentDrift,
|
|
156
|
+
timeSinceLast: Date.now() - sessionState.lastActionTime,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// ── Smart Cooldown Calculator ─────────────────────────────────
|
|
161
|
+
/**
|
|
162
|
+
* Calculate smart cooldown with safety buffer and variance.
|
|
163
|
+
* @param {number} baseCooldown - Base cooldown in seconds
|
|
164
|
+
* @param {Object} options
|
|
165
|
+
* @param {boolean} options.addBuffer - Add safety buffer
|
|
166
|
+
* @param {boolean} options.addVariance - Add random variance
|
|
167
|
+
* @returns {number} - Adjusted cooldown in seconds
|
|
168
|
+
*/
|
|
169
|
+
function calcSmartCooldown(baseCooldown, options = {}) {
|
|
170
|
+
const { addBuffer = true, addVariance = true } = options;
|
|
171
|
+
|
|
172
|
+
let adjusted = baseCooldown;
|
|
173
|
+
|
|
174
|
+
// Add safety buffer
|
|
175
|
+
if (addBuffer) {
|
|
176
|
+
adjusted += CONFIG.COOLDOWN_BUFFER_SEC;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Add small variance (±2%) to avoid detectable patterns
|
|
180
|
+
if (addVariance && baseCooldown > 10) {
|
|
181
|
+
const variance = (Math.random() - 0.5) * 0.04 * baseCooldown;
|
|
182
|
+
adjusted += variance;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return Math.max(5, Math.round(adjusted));
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// ── Exponential Backoff with Jitter ───────────────────────────
|
|
189
|
+
/**
|
|
190
|
+
* Calculate backoff delay for retries.
|
|
191
|
+
* @param {number} attempt - Attempt number (0-indexed)
|
|
192
|
+
* @param {number} baseMs - Base delay in ms
|
|
193
|
+
* @param {number} maxMs - Maximum delay in ms
|
|
194
|
+
* @returns {number} - Delay in ms
|
|
195
|
+
*/
|
|
196
|
+
function calcBackoff(attempt, baseMs = 1000, maxMs = 30000) {
|
|
197
|
+
const exponential = baseMs * Math.pow(2, attempt);
|
|
198
|
+
const jitter = exponential * 0.2 * (Math.random() - 0.5);
|
|
199
|
+
return Math.min(maxMs, Math.round(exponential + jitter));
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// ── Exports ───────────────────────────────────────────────────
|
|
203
|
+
module.exports = {
|
|
204
|
+
humanDelay,
|
|
205
|
+
calcAdaptiveDelay,
|
|
206
|
+
calcSmartCooldown,
|
|
207
|
+
calcBackoff,
|
|
208
|
+
resetSession,
|
|
209
|
+
getSessionStats,
|
|
210
|
+
CONFIG,
|
|
211
|
+
};
|
|
@@ -39,8 +39,7 @@ const {
|
|
|
39
39
|
safeClickButton, isHoldTight, logMsg,
|
|
40
40
|
} = require('./utils');
|
|
41
41
|
|
|
42
|
-
const RE_DISCORD_TIMESTAMP = /<t:(\d+)(?::[tTdDfFR])
|
|
43
|
-
const RE_ADVENTURE_AGAIN_LABEL = /adventure again in (\d+)\s*(minute|min|hour|second)/;
|
|
42
|
+
const RE_DISCORD_TIMESTAMP = /<t:(\d+)(?::[tTdDfFR])?>/g;
|
|
44
43
|
|
|
45
44
|
// ── Adventure type rotation (cycle through all types each run) ────
|
|
46
45
|
let lastAdventureIndex = -1;
|
|
@@ -270,7 +269,7 @@ async function runAdventure({ channel, waitForDankMemer, client }) {
|
|
|
270
269
|
|
|
271
270
|
if (!response) {
|
|
272
271
|
LOG.warn('[adventure] No response from Dank Memer');
|
|
273
|
-
return { result: 'no response', coins: 0, nextCooldownSec:
|
|
272
|
+
return { result: 'no response', coins: 0, nextCooldownSec: 180 };
|
|
274
273
|
}
|
|
275
274
|
|
|
276
275
|
// Check for Hold Tight
|
|
@@ -439,33 +438,45 @@ async function runAdventure({ channel, waitForDankMemer, client }) {
|
|
|
439
438
|
function buildResult(finalText, coins, interactions, rewards, msg) {
|
|
440
439
|
let nextCooldownSec = null;
|
|
441
440
|
|
|
442
|
-
// 1) Best:
|
|
443
|
-
|
|
444
|
-
const tsMatch = allText.match(RE_DISCORD_TIMESTAMP);
|
|
445
|
-
if (tsMatch) {
|
|
446
|
-
const unixTarget = parseInt(tsMatch[1]);
|
|
447
|
-
const nowUnix = Math.floor(Date.now() / 1000);
|
|
448
|
-
nextCooldownSec = Math.max(5, unixTarget - nowUnix);
|
|
449
|
-
LOG.info(`[adventure] Next at ${new Date(unixTarget * 1000).toLocaleTimeString()} (${c.yellow}${nextCooldownSec}s${c.reset})`);
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
// 2) Fallback: "Adventure again in X minutes" button label
|
|
453
|
-
if (!nextCooldownSec && msg) {
|
|
441
|
+
// 1) Best: button label after adventure ends (e.g. "Adventure again in 57m")
|
|
442
|
+
if (msg) {
|
|
454
443
|
const rows = msg.components || [];
|
|
455
444
|
for (let ri = 0; ri < rows.length; ri++) {
|
|
456
445
|
const comps = rows[ri].components || [];
|
|
457
446
|
for (let ci = 0; ci < comps.length; ci++) {
|
|
458
|
-
const
|
|
459
|
-
|
|
460
|
-
const
|
|
461
|
-
if (
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
447
|
+
const label = String(comps[ci]?.label || '').toLowerCase();
|
|
448
|
+
if (!label.includes('adventure') || !label.includes('again')) continue;
|
|
449
|
+
const unitMatch = label.match(/(\d+)\s*(h|hr|hrs|hour|hours|m|min|mins|minute|minutes|s|sec|secs|second|seconds)\b/i);
|
|
450
|
+
if (!unitMatch) continue;
|
|
451
|
+
const n = parseInt(unitMatch[1], 10);
|
|
452
|
+
const u = unitMatch[2].toLowerCase();
|
|
453
|
+
if (!Number.isFinite(n) || n <= 0) continue;
|
|
454
|
+
if (u.startsWith('h')) nextCooldownSec = n * 3600;
|
|
455
|
+
else if (u.startsWith('m')) nextCooldownSec = n * 60;
|
|
456
|
+
else nextCooldownSec = n;
|
|
457
|
+
LOG.info(`[adventure] Next cooldown from button: ${c.yellow}${nextCooldownSec}s${c.reset}`);
|
|
458
|
+
break;
|
|
468
459
|
}
|
|
460
|
+
if (nextCooldownSec) break;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// 2) Fallback: Unix timestamp <t:UNIX:*> in final text or embed
|
|
465
|
+
const allText = msg ? getFullText(msg) : finalText;
|
|
466
|
+
if (!nextCooldownSec) {
|
|
467
|
+
const nowUnix = Math.floor(Date.now() / 1000);
|
|
468
|
+
const tsMatches = Array.from(String(allText || '').matchAll(RE_DISCORD_TIMESTAMP));
|
|
469
|
+
let best = null;
|
|
470
|
+
for (const m of tsMatches) {
|
|
471
|
+
const unixTarget = parseInt(m[1], 10);
|
|
472
|
+
if (!Number.isFinite(unixTarget)) continue;
|
|
473
|
+
const diff = unixTarget - nowUnix;
|
|
474
|
+
if (diff <= 0) continue;
|
|
475
|
+
if (best == null || diff < best) best = diff;
|
|
476
|
+
}
|
|
477
|
+
if (best != null) {
|
|
478
|
+
nextCooldownSec = Math.max(5, best);
|
|
479
|
+
LOG.info(`[adventure] Next cooldown from timestamp: ${c.yellow}${nextCooldownSec}s${c.reset}`);
|
|
469
480
|
}
|
|
470
481
|
}
|
|
471
482
|
|
package/lib/commands/dig.js
CHANGED
|
@@ -4,11 +4,88 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
const {
|
|
7
|
-
LOG, c, getFullText, parseCoins,
|
|
7
|
+
LOG, c, getFullText, parseCoins, getAllButtons, safeClickButton, humanDelay,
|
|
8
|
+
logMsg, isHoldTight, getHoldTightReason, sleep, needsItem,
|
|
8
9
|
isCV2, ensureCV2, stripAnsi,
|
|
9
10
|
} = require('./utils');
|
|
10
11
|
const { buyItem } = require('./shop');
|
|
11
12
|
|
|
13
|
+
const DIG_LANE_LABELS = new Set(['left', 'middle', 'right']);
|
|
14
|
+
|
|
15
|
+
async function waitForDigUpdate(channel, messageId, baselineText, waitForDankMemer, timeoutMs = 9000) {
|
|
16
|
+
const started = Date.now();
|
|
17
|
+
while (Date.now() - started < timeoutMs) {
|
|
18
|
+
const msg = await waitForDankMemer(2200);
|
|
19
|
+
if (msg && msg.id === messageId) {
|
|
20
|
+
if (isCV2(msg)) await ensureCV2(msg, true);
|
|
21
|
+
const txt = stripAnsi(getFullText(msg)).replace(/\s+/g, ' ').trim();
|
|
22
|
+
if (!baselineText || (txt && txt !== baselineText)) return msg;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
await sleep(350);
|
|
26
|
+
try {
|
|
27
|
+
const fresh = await channel.messages.fetch(messageId);
|
|
28
|
+
if (!fresh) continue;
|
|
29
|
+
if (isCV2(fresh)) await ensureCV2(fresh, true);
|
|
30
|
+
const txt = stripAnsi(getFullText(fresh)).replace(/\s+/g, ' ').trim();
|
|
31
|
+
if (!baselineText || (txt && txt !== baselineText)) return fresh;
|
|
32
|
+
} catch {}
|
|
33
|
+
}
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function pickDigLaneButton(msg) {
|
|
38
|
+
const buttons = getAllButtons(msg).filter((b) => !b.disabled && DIG_LANE_LABELS.has(String(b.label || '').toLowerCase()));
|
|
39
|
+
if (buttons.length === 0) return null;
|
|
40
|
+
|
|
41
|
+
const textLower = stripAnsi(getFullText(msg)).replace(/\s+/g, ' ').trim().toLowerCase();
|
|
42
|
+
|
|
43
|
+
// If prompt text explicitly mentions a lane, follow it.
|
|
44
|
+
for (const lane of DIG_LANE_LABELS) {
|
|
45
|
+
if (new RegExp(`\\b${lane}\\b`, 'i').test(textLower)) {
|
|
46
|
+
const matched = buttons.find((b) => String(b.label || '').toLowerCase() === lane);
|
|
47
|
+
if (matched) return matched;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Fallback: random lane (still better than skipping the minigame).
|
|
52
|
+
return buttons[Math.floor(Math.random() * buttons.length)];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async function resolveDigFlow({ channel, waitForDankMemer, response }) {
|
|
56
|
+
let current = response;
|
|
57
|
+
|
|
58
|
+
for (let round = 0; round < 3; round++) {
|
|
59
|
+
if (isCV2(current)) await ensureCV2(current, true);
|
|
60
|
+
const laneBtn = pickDigLaneButton(current);
|
|
61
|
+
if (!laneBtn) break;
|
|
62
|
+
|
|
63
|
+
LOG.info(`[dig] Minigame: clicking "${laneBtn.label}"`);
|
|
64
|
+
const baseline = stripAnsi(getFullText(current)).replace(/\s+/g, ' ').trim();
|
|
65
|
+
await humanDelay(110, 240);
|
|
66
|
+
|
|
67
|
+
let clicked = null;
|
|
68
|
+
try { clicked = await safeClickButton(current, laneBtn); } catch {}
|
|
69
|
+
if (clicked) current = clicked;
|
|
70
|
+
|
|
71
|
+
const updated = await waitForDigUpdate(channel, current.id, baseline, waitForDankMemer, 10000);
|
|
72
|
+
if (updated) {
|
|
73
|
+
current = updated;
|
|
74
|
+
logMsg(current, `dig-minigame-${round + 1}`);
|
|
75
|
+
} else {
|
|
76
|
+
break;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const cleanText = stripAnsi(getFullText(current)).replace(/\s+/g, ' ').trim();
|
|
81
|
+
const coins = parseCoins(cleanText);
|
|
82
|
+
if (coins > 0) {
|
|
83
|
+
LOG.coin(`[dig] ${c.green}+⏣ ${coins.toLocaleString()}${c.reset}`);
|
|
84
|
+
return { result: `dig → +⏣ ${coins.toLocaleString()}`, coins };
|
|
85
|
+
}
|
|
86
|
+
return { result: cleanText.substring(0, 90) || 'done', coins: 0 };
|
|
87
|
+
}
|
|
88
|
+
|
|
12
89
|
/**
|
|
13
90
|
* @param {object} opts
|
|
14
91
|
* @param {object} opts.channel
|
|
@@ -66,25 +143,13 @@ async function runDig({ channel, waitForDankMemer, client }) {
|
|
|
66
143
|
if (r2) {
|
|
67
144
|
if (isCV2(r2)) await ensureCV2(r2);
|
|
68
145
|
logMsg(r2, 'dig-retry');
|
|
69
|
-
|
|
70
|
-
const coins = parseCoins(t2);
|
|
71
|
-
if (coins > 0) {
|
|
72
|
-
LOG.coin(`[dig] ${c.green}+⏣ ${coins.toLocaleString()}${c.reset}`);
|
|
73
|
-
return { result: `dig → +⏣ ${coins.toLocaleString()}`, coins };
|
|
74
|
-
}
|
|
75
|
-
return { result: stripAnsi(t2).replace(/\s+/g, ' ').trim().substring(0, 60), coins: 0 };
|
|
146
|
+
return await resolveDigFlow({ channel, waitForDankMemer, response: r2 });
|
|
76
147
|
}
|
|
77
148
|
}
|
|
78
149
|
return { result: 'need shovel (buy failed)', coins: 0 };
|
|
79
150
|
}
|
|
80
151
|
|
|
81
|
-
|
|
82
|
-
if (coins > 0) {
|
|
83
|
-
LOG.coin(`[dig] ${c.green}+⏣ ${coins.toLocaleString()}${c.reset}`);
|
|
84
|
-
return { result: `dig → +⏣ ${coins.toLocaleString()}`, coins };
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
return { result: cleanText.substring(0, 60) || 'done', coins: 0 };
|
|
152
|
+
return await resolveDigFlow({ channel, waitForDankMemer, response });
|
|
88
153
|
}
|
|
89
154
|
|
|
90
155
|
module.exports = { runDig };
|
package/lib/commands/farm.js
CHANGED
|
@@ -62,6 +62,13 @@ function parseFarmGrowReadySec(text) {
|
|
|
62
62
|
if (tsNearReady.length > 0) return Math.max(5, Math.min(...tsNearReady));
|
|
63
63
|
|
|
64
64
|
// Fallback textual patterns.
|
|
65
|
+
const willReadyH = clean.match(/(?:will\s+be\s+)?ready\s+in\s+(\d+)\s*hour/i);
|
|
66
|
+
if (willReadyH) return Math.max(5, parseInt(willReadyH[1], 10) * 3600);
|
|
67
|
+
const willReadyM = clean.match(/(?:will\s+be\s+)?ready\s+in\s+(\d+)\s*minute/i);
|
|
68
|
+
if (willReadyM) return Math.max(5, parseInt(willReadyM[1], 10) * 60);
|
|
69
|
+
const willReadyS = clean.match(/(?:will\s+be\s+)?ready\s+in\s+(\d+)\s*second/i);
|
|
70
|
+
if (willReadyS) return Math.max(5, parseInt(willReadyS[1], 10));
|
|
71
|
+
|
|
65
72
|
const h = clean.match(/ready\s+in\s+(\d+)\s*hour/i);
|
|
66
73
|
if (h) return Math.max(5, parseInt(h[1], 10) * 3600);
|
|
67
74
|
const mi = clean.match(/ready\s+in\s+(\d+)\s*minute/i);
|
|
@@ -977,7 +984,7 @@ async function runFarm({ channel, waitForDankMemer, client, _buyRetryDepth = 0,
|
|
|
977
984
|
|
|
978
985
|
if (!response) {
|
|
979
986
|
LOG.warn('[farm] No response');
|
|
980
|
-
return { result: 'no response', coins: 0 };
|
|
987
|
+
return { result: 'no response', coins: 0, nextCooldownSec: 90 };
|
|
981
988
|
}
|
|
982
989
|
|
|
983
990
|
if (isHoldTight(response)) {
|
|
@@ -1022,7 +1029,7 @@ async function runFarm({ channel, waitForDankMemer, client, _buyRetryDepth = 0,
|
|
|
1022
1029
|
LOG.warn('[farm] Subcommand required response detected; retrying with "pls farm view"');
|
|
1023
1030
|
await channel.send('pls farm view');
|
|
1024
1031
|
const retry = await waitForDankMemer(12000);
|
|
1025
|
-
|
|
1032
|
+
if (!retry) return { result: 'no response after farm view retry', coins: 0, nextCooldownSec: 120 };
|
|
1026
1033
|
response = retry;
|
|
1027
1034
|
if (isCV2(response)) await ensureCV2(response);
|
|
1028
1035
|
logMsg(response, 'farm-retry-view');
|
|
@@ -1505,7 +1512,11 @@ async function runFarm({ channel, waitForDankMemer, client, _buyRetryDepth = 0,
|
|
|
1505
1512
|
}
|
|
1506
1513
|
|
|
1507
1514
|
const coins = parseCoins(text);
|
|
1508
|
-
let nextCd = parseFarmCooldownSec(text) ||
|
|
1515
|
+
let nextCd = parseFarmCooldownSec(text) || 30;
|
|
1516
|
+
const growReadyEnd = parseFarmGrowReadySec(text);
|
|
1517
|
+
if (Number.isFinite(growReadyEnd) && growReadyEnd > 0) {
|
|
1518
|
+
nextCd = Math.max(nextCd, Math.min(6 * 3600, growReadyEnd + 2));
|
|
1519
|
+
}
|
|
1509
1520
|
const missingEnd = parseMissingFarmItem(text);
|
|
1510
1521
|
if (missingEnd) {
|
|
1511
1522
|
LOG.warn(`[farm] Missing ${c.bold}${missingEnd}${c.reset} after action — will retry in 1h`);
|
package/lib/commands/stream.js
CHANGED
|
@@ -8,6 +8,7 @@ const { buyItem } = require('./shop');
|
|
|
8
8
|
const STREAM_ITEMS = Object.freeze(['keyboard', 'mouse']);
|
|
9
9
|
const STREAM_ACTION_LABELS = Object.freeze(['run ad', 'read chat', 'collect donations']);
|
|
10
10
|
const RE_STREAM_INTERACT_MIN = /interact\s+with\s+your\s+stream\s+every\s+`?(\d+)`?\s*minutes?/i;
|
|
11
|
+
const RE_STREAM_TS = /<t:(\d+)(?::[tTdDfFR])?>/g;
|
|
11
12
|
|
|
12
13
|
function normalizeLower(text) {
|
|
13
14
|
return String(text || '')
|
|
@@ -123,11 +124,40 @@ function isActionResultText(lowerText) {
|
|
|
123
124
|
|
|
124
125
|
function parseStreamInteractCooldownSec(text) {
|
|
125
126
|
const clean = String(stripAnsi(text || '')).replace(/\s+/g, ' ').trim();
|
|
127
|
+
|
|
128
|
+
// Primary: explicit interaction cadence from dashboard text.
|
|
126
129
|
const mm = clean.match(RE_STREAM_INTERACT_MIN);
|
|
127
130
|
if (mm) {
|
|
128
131
|
const mins = parseInt(mm[1], 10);
|
|
129
132
|
if (Number.isFinite(mins) && mins > 0) return mins * 60;
|
|
130
133
|
}
|
|
134
|
+
|
|
135
|
+
// Secondary: derive from "last streamed" marker (10m base cooldown).
|
|
136
|
+
const lower = normalizeLower(clean);
|
|
137
|
+
const now = Math.floor(Date.now() / 1000);
|
|
138
|
+
const tsMatches = Array.from(clean.matchAll(RE_STREAM_TS));
|
|
139
|
+
for (const m of tsMatches) {
|
|
140
|
+
const ts = parseInt(m[1], 10);
|
|
141
|
+
if (!Number.isFinite(ts) || ts <= 0) continue;
|
|
142
|
+
const idx = m.index ?? 0;
|
|
143
|
+
const left = lower.slice(Math.max(0, idx - 70), idx);
|
|
144
|
+
if (!left.includes('last streamed')) continue;
|
|
145
|
+
const elapsed = Math.max(0, now - ts);
|
|
146
|
+
const remain = Math.max(5, 600 - elapsed);
|
|
147
|
+
return remain;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Textual fallback: "last streamed X minutes ago".
|
|
151
|
+
const ago = lower.match(/last\s+streamed[^\d]{0,24}(\d+)\s*(hour|hr|hrs|minute|min|mins|second|sec|secs)\s+ago/i);
|
|
152
|
+
if (ago) {
|
|
153
|
+
const n = parseInt(ago[1], 10);
|
|
154
|
+
const unit = ago[2].toLowerCase();
|
|
155
|
+
if (Number.isFinite(n) && n >= 0) {
|
|
156
|
+
const elapsed = unit.startsWith('h') ? n * 3600 : unit.startsWith('m') ? n * 60 : n;
|
|
157
|
+
return Math.max(5, 600 - elapsed);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
131
161
|
return 600;
|
|
132
162
|
}
|
|
133
163
|
|
|
@@ -171,7 +201,7 @@ async function runStream({ channel, waitForDankMemer, client }) {
|
|
|
171
201
|
|
|
172
202
|
if (!response) {
|
|
173
203
|
LOG.warn('[stream] No response');
|
|
174
|
-
return { result: 'no response', coins: 0 };
|
|
204
|
+
return { result: 'no response', coins: 0, nextCooldownSec: 120 };
|
|
175
205
|
}
|
|
176
206
|
|
|
177
207
|
if (isHoldTight(response)) {
|
|
@@ -199,14 +229,14 @@ async function runStream({ channel, waitForDankMemer, client }) {
|
|
|
199
229
|
|
|
200
230
|
for (const item of itemsToBuy) {
|
|
201
231
|
const bought = await buyItem({ channel, waitForDankMemer, client, itemName: item, quantity: 1 });
|
|
202
|
-
if (!bought) return { result: `need ${item} (buy failed)`, coins: 0 };
|
|
232
|
+
if (!bought) return { result: `need ${item} (buy failed)`, coins: 0, nextCooldownSec: 1800 };
|
|
203
233
|
await humanDelay(500, 1000);
|
|
204
234
|
}
|
|
205
235
|
|
|
206
236
|
await sleep(2000);
|
|
207
237
|
await channel.send('pls stream');
|
|
208
238
|
response = await waitForDankMemer(12000);
|
|
209
|
-
|
|
239
|
+
if (!response) return { result: 'no response after buy', coins: 0, nextCooldownSec: 180 };
|
|
210
240
|
await hydrate(response);
|
|
211
241
|
logMsg(response, 'stream-retry');
|
|
212
242
|
text = getFullText(response);
|
package/lib/commands/utils.js
CHANGED
|
@@ -94,8 +94,26 @@ const RE_HOLD_TIGHT_REASON = /Reason:\s*\/(\w+)/i;
|
|
|
94
94
|
// ── Sleep ────────────────────────────────────────────────────
|
|
95
95
|
function sleep(ms) { return new Promise(r => setTimeout(r, ms)); }
|
|
96
96
|
|
|
97
|
-
|
|
98
|
-
|
|
97
|
+
// ── Advanced Human Delay (Anti-Detection) ────────────────────
|
|
98
|
+
// Natural human-like timing: 0.5-1.0s base with subtle variations
|
|
99
|
+
let _antiDetect = null;
|
|
100
|
+
try {
|
|
101
|
+
_antiDetect = require('../antiDetect');
|
|
102
|
+
} catch {}
|
|
103
|
+
|
|
104
|
+
function humanDelay(contextOrMin, max) {
|
|
105
|
+
// Backward compatibility: if called with numeric args, use simple delay
|
|
106
|
+
if (typeof contextOrMin === 'number') {
|
|
107
|
+
const min = contextOrMin;
|
|
108
|
+
const maxVal = typeof max === 'number' ? max : 1000;
|
|
109
|
+
return new Promise(r => setTimeout(r, min + Math.random() * (maxVal - min)));
|
|
110
|
+
}
|
|
111
|
+
// New API: context-based adaptive delay (uses 0.5-1s range)
|
|
112
|
+
if (_antiDetect && typeof contextOrMin === 'object') {
|
|
113
|
+
return _antiDetect.humanDelay(contextOrMin);
|
|
114
|
+
}
|
|
115
|
+
// Default: natural human timing (0.5-1.0s)
|
|
116
|
+
return new Promise(r => setTimeout(r, 500 + Math.random() * 500));
|
|
99
117
|
}
|
|
100
118
|
|
|
101
119
|
// ── Message Parsing ──────────────────────────────────────────
|
package/lib/commands/work.js
CHANGED
|
@@ -23,7 +23,7 @@ const {
|
|
|
23
23
|
|
|
24
24
|
const RE_MEMORY_BACKTICK_CHUNK = /`([^`]+)`/g;
|
|
25
25
|
const RE_BACKTICK_STRIP = /`/g;
|
|
26
|
-
const RE_WORK_COOLDOWN_TS = /<t:(\d+)
|
|
26
|
+
const RE_WORK_COOLDOWN_TS = /<t:(\d+)(?::[tTdDfFR])?>/g;
|
|
27
27
|
const RE_WORK_COOLDOWN_MINUTES = /(\d+)\s*minute/i;
|
|
28
28
|
const RE_WORK_COOLDOWN_HOURS = /(\d+)\s*hour/i;
|
|
29
29
|
|
|
@@ -78,18 +78,42 @@ function parseMemoryOrder(text) {
|
|
|
78
78
|
* Patterns: "next shift <t:TIMESTAMP:R>", "X minutes", "X hours"
|
|
79
79
|
*/
|
|
80
80
|
function parseWorkCooldown(text) {
|
|
81
|
-
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
81
|
+
const clean = String(stripAnsi(text || ''));
|
|
82
|
+
const lower = normalizeLower(clean).replace(/\s+/g, ' ').trim();
|
|
83
|
+
|
|
84
|
+
// Unix timestamp pattern: <t:TIMESTAMP:*>
|
|
85
|
+
const now = Math.floor(Date.now() / 1000);
|
|
86
|
+
const tsMatches = Array.from(clean.matchAll(RE_WORK_COOLDOWN_TS));
|
|
87
|
+
if (tsMatches.length > 0) {
|
|
88
|
+
let best = null;
|
|
89
|
+
for (const m of tsMatches) {
|
|
90
|
+
const ts = parseInt(m[1], 10);
|
|
91
|
+
if (!Number.isFinite(ts)) continue;
|
|
92
|
+
const diff = ts - now;
|
|
93
|
+
if (diff <= 0) continue;
|
|
94
|
+
if (best == null || diff < best) best = diff;
|
|
95
|
+
}
|
|
96
|
+
if (best != null) return Math.max(5, best);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Compact forms frequently seen in buttons/messages: "1h", "54m", "30s"
|
|
100
|
+
if (/(next shift|work again|cooldown|already done|shift)/i.test(lower)) {
|
|
101
|
+
const short = lower.match(/(\d+)\s*(h|hr|hrs|m|min|mins|s|sec|secs)\b/i);
|
|
102
|
+
if (short) {
|
|
103
|
+
const n = parseInt(short[1], 10);
|
|
104
|
+
const u = short[2].toLowerCase();
|
|
105
|
+
if (Number.isFinite(n) && n > 0) {
|
|
106
|
+
if (u.startsWith('h')) return n * 3600;
|
|
107
|
+
if (u.startsWith('m')) return n * 60;
|
|
108
|
+
return n;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
88
111
|
}
|
|
112
|
+
|
|
89
113
|
// "X minutes" / "X hours" pattern
|
|
90
|
-
const minMatch =
|
|
114
|
+
const minMatch = clean.match(RE_WORK_COOLDOWN_MINUTES);
|
|
91
115
|
if (minMatch) return parseInt(minMatch[1]) * 60;
|
|
92
|
-
const hrMatch =
|
|
116
|
+
const hrMatch = clean.match(RE_WORK_COOLDOWN_HOURS);
|
|
93
117
|
if (hrMatch) return parseInt(hrMatch[1]) * 3600;
|
|
94
118
|
return null;
|
|
95
119
|
}
|
|
@@ -311,7 +335,7 @@ async function runWorkShift({ channel, waitForDankMemer }) {
|
|
|
311
335
|
|
|
312
336
|
if (!current) {
|
|
313
337
|
LOG.warn('[work] No response');
|
|
314
|
-
return { result: 'no response', coins: 0 };
|
|
338
|
+
return { result: 'no response', coins: 0, nextCooldownSec: 120 };
|
|
315
339
|
}
|
|
316
340
|
|
|
317
341
|
if (isHoldTight(current)) {
|
|
@@ -351,7 +375,7 @@ async function runWorkShift({ channel, waitForDankMemer }) {
|
|
|
351
375
|
|
|
352
376
|
await channel.send('pls work shift');
|
|
353
377
|
current = await waitForDankMemer(10000);
|
|
354
|
-
|
|
378
|
+
if (!current) return { result: 'no response after apply', coins: 0, nextCooldownSec: 180 };
|
|
355
379
|
if (isCV2(current)) await ensureCV2(current);
|
|
356
380
|
logMsg(current, 'work-after-apply');
|
|
357
381
|
text = getFullText(current);
|
|
@@ -425,7 +449,7 @@ async function runWorkShift({ channel, waitForDankMemer }) {
|
|
|
425
449
|
return { result: 'work shift completed', coins: 0, nextCooldownSec: finalCd || 3600 };
|
|
426
450
|
}
|
|
427
451
|
|
|
428
|
-
return { result: `work done`, coins: 0, nextCooldownSec: finalCd ||
|
|
452
|
+
return { result: `work done`, coins: 0, nextCooldownSec: finalCd || 1800 };
|
|
429
453
|
}
|
|
430
454
|
|
|
431
455
|
module.exports = { runWorkShift, autoApplyForJob, resignFromJob, JOBS };
|