dankgrinder 5.25.0 → 5.281.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/afkMode.js +349 -0
- package/lib/antiDetect.js +51 -1
- package/lib/grinder.js +174 -32
- package/package.json +5 -1
package/lib/afkMode.js
ADDED
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AFK Mode - Smart Command Queuing
|
|
3
|
+
*
|
|
4
|
+
* Users want to run the bot and forget about it. This module provides:
|
|
5
|
+
* - Smart prioritization based on cooldowns and earnings
|
|
6
|
+
* - Auto-deposit when wallet is full
|
|
7
|
+
* - Auto-sell junk items
|
|
8
|
+
* - Sleep mode (pause during night hours)
|
|
9
|
+
* - Smart bank management
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
'use strict';
|
|
13
|
+
|
|
14
|
+
// ── Configuration ─────────────────────────────────────────────
|
|
15
|
+
const AFK_CONFIG = Object.freeze({
|
|
16
|
+
// Wallet management
|
|
17
|
+
AUTO_DEPOSIT_THRESHOLD: 100000, // Deposit when wallet > 100k
|
|
18
|
+
AUTO_DEPOSIT_KEEP: 20000, // Keep 20k in wallet after deposit
|
|
19
|
+
|
|
20
|
+
// Inventory management
|
|
21
|
+
AUTO_SELL_THRESHOLD: 0.85, // Sell when inventory > 85% full
|
|
22
|
+
JUNK_ITEMS: new Set([ // Always sell these
|
|
23
|
+
'trash', 'junk', 'rock', 'stick', 'bottle', 'can', 'old shoe',
|
|
24
|
+
'broken compass', 'rusty key', 'tattered flag', 'empty chest',
|
|
25
|
+
]),
|
|
26
|
+
VALUABLE_ITEMS: new Set([ // Never sell these
|
|
27
|
+
'diamond', 'ruby', 'emerald', 'sapphire', 'gold bar', 'coin',
|
|
28
|
+
'legendary', 'mythical', 'artifact', 'treasure',
|
|
29
|
+
]),
|
|
30
|
+
|
|
31
|
+
// Sleep mode (optional - pause during these hours)
|
|
32
|
+
SLEEP_MODE_ENABLED: false,
|
|
33
|
+
SLEEP_START_HOUR: 2, // 2 AM
|
|
34
|
+
SLEEP_END_HOUR: 6, // 6 AM
|
|
35
|
+
|
|
36
|
+
// Command prioritization
|
|
37
|
+
HIGH_VALUE_COMMANDS: new Set(['daily', 'weekly', 'monthly', 'work', 'stream']),
|
|
38
|
+
INCOME_COMMANDS: new Set(['beg', 'search', 'crime', 'hunt', 'dig', 'fish']),
|
|
39
|
+
GAMBLE_COMMANDS: new Set(['blackjack', 'roulette', 'slots', 'snakeeyes', 'cointoss']),
|
|
40
|
+
|
|
41
|
+
// Gambling limits in AFK mode
|
|
42
|
+
GAMBLE_MAX_LOSSES: 10, // Stop after 10 losses
|
|
43
|
+
GAMBLE_MAX_LOSS_AMOUNT: 50000, // Stop after losing 50k
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// ── Command Priority Calculator ───────────────────────────────
|
|
47
|
+
/**
|
|
48
|
+
* Calculate priority score for a command.
|
|
49
|
+
* Higher score = should run sooner.
|
|
50
|
+
*/
|
|
51
|
+
function calculateCommandPriority(cmdName, cooldownMs, account) {
|
|
52
|
+
let score = 0;
|
|
53
|
+
|
|
54
|
+
// Ready commands get highest priority
|
|
55
|
+
if (cooldownMs <= 0) {
|
|
56
|
+
score += 1000;
|
|
57
|
+
} else {
|
|
58
|
+
// Penalize by wait time (in minutes)
|
|
59
|
+
score -= Math.floor(cooldownMs / 60000);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// High-value commands get bonus
|
|
63
|
+
if (AFK_CONFIG.HIGH_VALUE_COMMANDS.has(cmdName)) {
|
|
64
|
+
score += 500;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Income commands are important
|
|
68
|
+
if (AFK_CONFIG.INCOME_COMMANDS.has(cmdName)) {
|
|
69
|
+
score += 200;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Gambling commands are lower priority in AFK mode
|
|
73
|
+
if (AFK_CONFIG.GAMBLE_COMMANDS.has(cmdName)) {
|
|
74
|
+
score -= 100;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Periodic commands (daily/weekly/monthly) get huge bonus when ready
|
|
78
|
+
const now = Date.now();
|
|
79
|
+
const dailyCd = account?.doneToday?.get('daily') || 0;
|
|
80
|
+
const weeklyCd = account?.doneToday?.get('weekly') || 0;
|
|
81
|
+
const monthlyCd = account?.doneToday?.get('monthly') || 0;
|
|
82
|
+
|
|
83
|
+
if (cmdName === 'daily' && dailyCd <= now) score += 800;
|
|
84
|
+
if (cmdName === 'weekly' && weeklyCd <= now) score += 900;
|
|
85
|
+
if (cmdName === 'monthly' && monthlyCd <= now) score += 1000;
|
|
86
|
+
|
|
87
|
+
return score;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// ── Smart Command Queue Builder ───────────────────────────────
|
|
91
|
+
/**
|
|
92
|
+
* Build optimized command queue based on cooldowns and priorities.
|
|
93
|
+
*/
|
|
94
|
+
function buildSmartQueue(account, cooldowns) {
|
|
95
|
+
const enabledCmds = getEnabledCommands(account);
|
|
96
|
+
const now = Date.now();
|
|
97
|
+
|
|
98
|
+
// Calculate priority for each command
|
|
99
|
+
const priorities = enabledCmds.map(cmd => {
|
|
100
|
+
const cdUntil = cooldowns.get(cmd) || 0;
|
|
101
|
+
const cooldownMs = Math.max(0, cdUntil - now);
|
|
102
|
+
const priority = calculateCommandPriority(cmd, cooldownMs, account);
|
|
103
|
+
|
|
104
|
+
return { cmd, priority, cooldownMs };
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
// Sort by priority (highest first)
|
|
108
|
+
priorities.sort((a, b) => b.priority - a.priority);
|
|
109
|
+
|
|
110
|
+
// Return queue of command names
|
|
111
|
+
return priorities.map(p => p.cmd);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// ── Get Enabled Commands ──────────────────────────────────────
|
|
115
|
+
function getEnabledCommands(account) {
|
|
116
|
+
const cmdMap = [
|
|
117
|
+
{ key: 'cmd_beg', name: 'beg' },
|
|
118
|
+
{ key: 'cmd_search', name: 'search' },
|
|
119
|
+
{ key: 'cmd_crime', name: 'crime' },
|
|
120
|
+
{ key: 'cmd_hunt', name: 'hunt' },
|
|
121
|
+
{ key: 'cmd_dig', name: 'dig' },
|
|
122
|
+
{ key: 'cmd_fish', name: 'fish' },
|
|
123
|
+
{ key: 'cmd_farm', name: 'farm' },
|
|
124
|
+
{ key: 'cmd_work', name: 'work' },
|
|
125
|
+
{ key: 'cmd_stream', name: 'stream' },
|
|
126
|
+
{ key: 'cmd_daily', name: 'daily' },
|
|
127
|
+
{ key: 'cmd_weekly', name: 'weekly' },
|
|
128
|
+
{ key: 'cmd_monthly', name: 'monthly' },
|
|
129
|
+
{ key: 'cmd_highlow', name: 'hl' },
|
|
130
|
+
{ key: 'cmd_blackjack', name: 'blackjack' },
|
|
131
|
+
{ key: 'cmd_roulette', name: 'roulette' },
|
|
132
|
+
{ key: 'cmd_slots', name: 'slots' },
|
|
133
|
+
{ key: 'cmd_snakeeyes', name: 'snakeeyes' },
|
|
134
|
+
{ key: 'cmd_trivia', name: 'trivia' },
|
|
135
|
+
{ key: 'cmd_scratch', name: 'scratch' },
|
|
136
|
+
{ key: 'cmd_adventure', name: 'adventure' },
|
|
137
|
+
{ key: 'cmd_postmemes', name: 'postmemes' },
|
|
138
|
+
{ key: 'cmd_tidy', name: 'tidy' },
|
|
139
|
+
{ key: 'cmd_use', name: 'use' },
|
|
140
|
+
{ key: 'cmd_deposit', name: 'deposit' },
|
|
141
|
+
];
|
|
142
|
+
|
|
143
|
+
return cmdMap
|
|
144
|
+
.filter(c => account[c.key] === true)
|
|
145
|
+
.map(c => c.name);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// ── Auto-Deposit Check ────────────────────────────────────────
|
|
149
|
+
/**
|
|
150
|
+
* Check if auto-deposit should run.
|
|
151
|
+
*/
|
|
152
|
+
function shouldAutoDeposit(walletBalance, account) {
|
|
153
|
+
if (!account.cmd_deposit) return false;
|
|
154
|
+
|
|
155
|
+
const threshold = account.afk_deposit_threshold || AFK_CONFIG.AUTO_DEPOSIT_THRESHOLD;
|
|
156
|
+
return walletBalance > threshold;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// ── Auto-Sell Check ───────────────────────────────────────────
|
|
160
|
+
/**
|
|
161
|
+
* Check if inventory auto-sell should run.
|
|
162
|
+
*/
|
|
163
|
+
function shouldAutoSell(inventoryCount, maxInventory, account) {
|
|
164
|
+
if (!account.cmd_use) return false;
|
|
165
|
+
|
|
166
|
+
const threshold = account.afk_sell_threshold || AFK_CONFIG.AUTO_SELL_THRESHOLD;
|
|
167
|
+
return inventoryCount / maxInventory >= threshold;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// ── Get Items To Sell ─────────────────────────────────────────
|
|
171
|
+
/**
|
|
172
|
+
* Determine which items to auto-sell.
|
|
173
|
+
*/
|
|
174
|
+
function getItemsToSell(inventory) {
|
|
175
|
+
const toSell = [];
|
|
176
|
+
|
|
177
|
+
for (const item of inventory) {
|
|
178
|
+
const itemName = (item.name || '').toLowerCase();
|
|
179
|
+
|
|
180
|
+
// Always sell junk
|
|
181
|
+
if (AFK_CONFIG.JUNK_ITEMS.some(junk => itemName.includes(junk))) {
|
|
182
|
+
toSell.push(item.name);
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Never sell valuables
|
|
187
|
+
if (AFK_CONFIG.VALUABLE_ITEMS.some(val => itemName.includes(val))) {
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Sell common items (value < 1000)
|
|
192
|
+
if (item.value && item.value < 1000) {
|
|
193
|
+
toSell.push(item.name);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return toSell;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// ── Sleep Mode Check ──────────────────────────────────────────
|
|
201
|
+
/**
|
|
202
|
+
* Check if sleep mode is active.
|
|
203
|
+
*/
|
|
204
|
+
function isSleepMode() {
|
|
205
|
+
if (!AFK_CONFIG.SLEEP_MODE_ENABLED) return false;
|
|
206
|
+
|
|
207
|
+
const hour = new Date().getHours();
|
|
208
|
+
const start = AFK_CONFIG.SLEEP_START_HOUR;
|
|
209
|
+
const end = AFK_CONFIG.SLEEP_END_HOUR;
|
|
210
|
+
|
|
211
|
+
if (start < end) {
|
|
212
|
+
return hour >= start && hour < end;
|
|
213
|
+
}
|
|
214
|
+
// Handles overnight ranges (e.g., 22:00 - 06:00)
|
|
215
|
+
return hour >= start || hour < end;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// ── AFK Mode Presets ──────────────────────────────────────────
|
|
219
|
+
const AFK_PRESETS = {
|
|
220
|
+
// Casual: Run every 2 hours, low priority commands only
|
|
221
|
+
casual: {
|
|
222
|
+
commands: ['beg', 'search', 'daily', 'weekly', 'monthly'],
|
|
223
|
+
deposit_threshold: 50000,
|
|
224
|
+
sell_threshold: 0.9,
|
|
225
|
+
gamble_enabled: false,
|
|
226
|
+
},
|
|
227
|
+
|
|
228
|
+
// Grinder: Run all commands ASAP, maximize earnings
|
|
229
|
+
grinder: {
|
|
230
|
+
commands: 'all',
|
|
231
|
+
deposit_threshold: 100000,
|
|
232
|
+
sell_threshold: 0.8,
|
|
233
|
+
gamble_enabled: true,
|
|
234
|
+
gamble_limit: 20,
|
|
235
|
+
},
|
|
236
|
+
|
|
237
|
+
// Smart: AI-driven prioritization based on cooldowns
|
|
238
|
+
smart: {
|
|
239
|
+
commands: 'all',
|
|
240
|
+
deposit_threshold: 100000,
|
|
241
|
+
sell_threshold: 0.85,
|
|
242
|
+
gamble_enabled: true,
|
|
243
|
+
gamble_limit: 10,
|
|
244
|
+
use_priority_queue: true,
|
|
245
|
+
},
|
|
246
|
+
|
|
247
|
+
// Sleep: Only run long cooldown commands
|
|
248
|
+
sleep: {
|
|
249
|
+
commands: ['daily', 'weekly', 'monthly', 'work'],
|
|
250
|
+
deposit_threshold: 200000,
|
|
251
|
+
sell_threshold: 0.95,
|
|
252
|
+
gamble_enabled: false,
|
|
253
|
+
},
|
|
254
|
+
|
|
255
|
+
// Safe: No gambling, conservative play
|
|
256
|
+
safe: {
|
|
257
|
+
commands: 'all',
|
|
258
|
+
deposit_threshold: 100000,
|
|
259
|
+
sell_threshold: 0.85,
|
|
260
|
+
gamble_enabled: false,
|
|
261
|
+
},
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
// ── Apply Preset ──────────────────────────────────────────────
|
|
265
|
+
/**
|
|
266
|
+
* Apply an AFK preset to an account.
|
|
267
|
+
*/
|
|
268
|
+
function applyPreset(account, presetName) {
|
|
269
|
+
const preset = AFK_PRESETS[presetName];
|
|
270
|
+
if (!preset) return false;
|
|
271
|
+
|
|
272
|
+
// Set commands
|
|
273
|
+
if (preset.commands === 'all') {
|
|
274
|
+
// Enable all commands
|
|
275
|
+
account.cmd_beg = true;
|
|
276
|
+
account.cmd_search = true;
|
|
277
|
+
account.cmd_crime = true;
|
|
278
|
+
account.cmd_hunt = true;
|
|
279
|
+
account.cmd_dig = true;
|
|
280
|
+
account.cmd_fish = true;
|
|
281
|
+
account.cmd_farm = true;
|
|
282
|
+
account.cmd_work = true;
|
|
283
|
+
account.cmd_stream = true;
|
|
284
|
+
account.cmd_daily = true;
|
|
285
|
+
account.cmd_weekly = true;
|
|
286
|
+
account.cmd_monthly = true;
|
|
287
|
+
account.cmd_highlow = true;
|
|
288
|
+
account.cmd_blackjack = preset.gamble_enabled;
|
|
289
|
+
account.cmd_roulette = preset.gamble_enabled;
|
|
290
|
+
account.cmd_slots = preset.gamble_enabled;
|
|
291
|
+
account.cmd_snakeeyes = preset.gamble_enabled;
|
|
292
|
+
account.cmd_trivia = true;
|
|
293
|
+
account.cmd_scratch = true;
|
|
294
|
+
account.cmd_adventure = true;
|
|
295
|
+
account.cmd_postmemes = true;
|
|
296
|
+
account.cmd_tidy = true;
|
|
297
|
+
account.cmd_use = true;
|
|
298
|
+
account.cmd_deposit = true;
|
|
299
|
+
} else {
|
|
300
|
+
// Enable only specified commands
|
|
301
|
+
const cmdKeys = {
|
|
302
|
+
beg: 'cmd_beg', search: 'cmd_search', crime: 'cmd_crime',
|
|
303
|
+
hunt: 'cmd_hunt', dig: 'cmd_dig', fish: 'cmd_fish',
|
|
304
|
+
farm: 'cmd_farm', work: 'cmd_work', stream: 'cmd_stream',
|
|
305
|
+
daily: 'cmd_daily', weekly: 'cmd_weekly', monthly: 'cmd_monthly',
|
|
306
|
+
hl: 'cmd_highlow', blackjack: 'cmd_blackjack', roulette: 'cmd_roulette',
|
|
307
|
+
slots: 'cmd_slots', snakeeyes: 'cmd_snakeeyes', trivia: 'cmd_trivia',
|
|
308
|
+
scratch: 'cmd_scratch', adventure: 'cmd_adventure',
|
|
309
|
+
postmemes: 'cmd_postmemes', tidy: 'cmd_tidy',
|
|
310
|
+
use: 'cmd_use', deposit: 'cmd_deposit',
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
// Disable all first
|
|
314
|
+
Object.values(cmdKeys).forEach(key => { account[key] = false; });
|
|
315
|
+
|
|
316
|
+
// Enable selected
|
|
317
|
+
preset.commands.forEach(cmd => {
|
|
318
|
+
const key = cmdKeys[cmd];
|
|
319
|
+
if (key) account[key] = true;
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Set thresholds
|
|
324
|
+
account.afk_deposit_threshold = preset.deposit_threshold;
|
|
325
|
+
account.afk_sell_threshold = preset.sell_threshold;
|
|
326
|
+
account.afk_gamble_enabled = preset.gamble_enabled;
|
|
327
|
+
account.afk_gamble_limit = preset.gamble_limit || 10;
|
|
328
|
+
|
|
329
|
+
return true;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// ── Exports ───────────────────────────────────────────────────
|
|
333
|
+
module.exports = {
|
|
334
|
+
// Main functions
|
|
335
|
+
buildSmartQueue,
|
|
336
|
+
calculateCommandPriority,
|
|
337
|
+
shouldAutoDeposit,
|
|
338
|
+
shouldAutoSell,
|
|
339
|
+
getItemsToSell,
|
|
340
|
+
isSleepMode,
|
|
341
|
+
applyPreset,
|
|
342
|
+
|
|
343
|
+
// Getters
|
|
344
|
+
getEnabledCommands,
|
|
345
|
+
|
|
346
|
+
// Constants
|
|
347
|
+
AFK_CONFIG,
|
|
348
|
+
AFK_PRESETS,
|
|
349
|
+
};
|
package/lib/antiDetect.js
CHANGED
|
@@ -34,6 +34,16 @@ const CONFIG = Object.freeze({
|
|
|
34
34
|
|
|
35
35
|
// Pattern randomization
|
|
36
36
|
PATTERN_RESET_CHANCE: 0.2, // 20% chance to reset timing pattern
|
|
37
|
+
|
|
38
|
+
// Session-based behavior (humans get tired/faster over time)
|
|
39
|
+
SESSION_TIRE_FACTOR: 0.001, // Get 0.1% slower per action
|
|
40
|
+
SESSION_MAX_TIRED: 0.15, // Max 15% slower when tired
|
|
41
|
+
|
|
42
|
+
// Time-of-day awareness (humans are slower at night)
|
|
43
|
+
TIME_DAWN: { hour: 6, multiplier: 0.9 }, // Faster in morning
|
|
44
|
+
TIME_DAY: { hour: 12, multiplier: 1.0 }, // Normal during day
|
|
45
|
+
TIME_EVENING: { hour: 18, multiplier: 1.05 }, // Slightly slower evening
|
|
46
|
+
TIME_NIGHT: { hour: 23, multiplier: 1.15 }, // Slower at night
|
|
37
47
|
});
|
|
38
48
|
|
|
39
49
|
// ── Session State ─────────────────────────────────────────────
|
|
@@ -42,7 +52,9 @@ const sessionState = {
|
|
|
42
52
|
lastActionTime: 0,
|
|
43
53
|
currentDrift: 0,
|
|
44
54
|
patternSeed: Math.random(),
|
|
45
|
-
activityLevel: 1.0,
|
|
55
|
+
activityLevel: 1.0, // Starts neutral
|
|
56
|
+
tiredness: 0, // Increases with actions
|
|
57
|
+
sessionStart: Date.now(),
|
|
46
58
|
};
|
|
47
59
|
|
|
48
60
|
// ── Seeded Random for Reproducible Patterns ──────────────────
|
|
@@ -51,6 +63,30 @@ function seededRandom(seed) {
|
|
|
51
63
|
return x - Math.floor(x);
|
|
52
64
|
}
|
|
53
65
|
|
|
66
|
+
// ── Time-of-Day Multiplier ────────────────────────────────────
|
|
67
|
+
/**
|
|
68
|
+
* Get delay multiplier based on current hour.
|
|
69
|
+
* Humans are slower at night, faster in the morning.
|
|
70
|
+
*/
|
|
71
|
+
function getTimeOfDayMultiplier() {
|
|
72
|
+
const hour = new Date().getHours();
|
|
73
|
+
|
|
74
|
+
// Simple interpolation between time periods
|
|
75
|
+
if (hour >= 6 && hour < 12) {
|
|
76
|
+
// Morning: faster
|
|
77
|
+
return 0.9 + (hour - 6) * 0.016; // 0.9 → 1.0
|
|
78
|
+
} else if (hour >= 12 && hour < 18) {
|
|
79
|
+
// Day: normal
|
|
80
|
+
return 1.0 + (hour - 12) * 0.008; // 1.0 → 1.05
|
|
81
|
+
} else if (hour >= 18 && hour < 23) {
|
|
82
|
+
// Evening: slightly slower
|
|
83
|
+
return 1.05 + (hour - 18) * 0.02; // 1.05 → 1.15
|
|
84
|
+
} else {
|
|
85
|
+
// Night: slowest
|
|
86
|
+
return 1.15;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
54
90
|
// ── Adaptive Delay Calculation ────────────────────────────────
|
|
55
91
|
/**
|
|
56
92
|
* Calculate human-like delay based on context.
|
|
@@ -101,6 +137,14 @@ async function calcAdaptiveDelay(options = {}) {
|
|
|
101
137
|
);
|
|
102
138
|
multiplier *= (1 + sessionState.currentDrift);
|
|
103
139
|
|
|
140
|
+
// Apply tiredness factor (humans get slower over long sessions)
|
|
141
|
+
sessionState.tiredness += CONFIG.SESSION_TIRE_FACTOR;
|
|
142
|
+
sessionState.tiredness = Math.min(CONFIG.SESSION_MAX_TIRED, sessionState.tiredness);
|
|
143
|
+
multiplier *= (1 + sessionState.tiredness);
|
|
144
|
+
|
|
145
|
+
// Apply time-of-day multiplier
|
|
146
|
+
multiplier *= getTimeOfDayMultiplier();
|
|
147
|
+
|
|
104
148
|
// Pattern reset for unpredictability
|
|
105
149
|
if (Math.random() < CONFIG.PATTERN_RESET_CHANCE) {
|
|
106
150
|
sessionState.patternSeed = Math.random();
|
|
@@ -146,14 +190,20 @@ function resetSession() {
|
|
|
146
190
|
sessionState.currentDrift = 0;
|
|
147
191
|
sessionState.patternSeed = Math.random();
|
|
148
192
|
sessionState.activityLevel = 1.0;
|
|
193
|
+
sessionState.tiredness = 0;
|
|
194
|
+
sessionState.sessionStart = Date.now();
|
|
149
195
|
}
|
|
150
196
|
|
|
151
197
|
// ── Get Session Stats (for debugging) ─────────────────────────
|
|
152
198
|
function getSessionStats() {
|
|
199
|
+
const elapsed = Date.now() - sessionState.sessionStart;
|
|
153
200
|
return {
|
|
154
201
|
actionCount: sessionState.actionCount,
|
|
155
202
|
currentDrift: sessionState.currentDrift,
|
|
203
|
+
tiredness: sessionState.tiredness,
|
|
156
204
|
timeSinceLast: Date.now() - sessionState.lastActionTime,
|
|
205
|
+
sessionDuration: Math.round(elapsed / 60000), // minutes
|
|
206
|
+
timeMultiplier: getTimeOfDayMultiplier(),
|
|
157
207
|
};
|
|
158
208
|
}
|
|
159
209
|
|
package/lib/grinder.js
CHANGED
|
@@ -605,6 +605,51 @@ function safeParseJSON(str, fallback = []) {
|
|
|
605
605
|
try { return JSON.parse(str || '[]'); } catch { return fallback; }
|
|
606
606
|
}
|
|
607
607
|
|
|
608
|
+
// ── Command Result Formatter ──────────────────────────────────
|
|
609
|
+
// Clean command names and format results with color codes
|
|
610
|
+
const CMD_NAMES_CLEAN = {
|
|
611
|
+
bj: 'Blackjack', blackjack: 'Blackjack', hl: 'High Low', pm: 'Post Memes', postmemes: 'Post Memes',
|
|
612
|
+
ct: 'Coin Toss', cointoss: 'Coin Toss', se: 'Snake Eyes', snakeeyes: 'Snake Eyes',
|
|
613
|
+
hunt: 'Hunt', dig: 'Dig', fish: 'Fish', beg: 'Beg', search: 'Search', crime: 'Crime',
|
|
614
|
+
tidy: 'Tidy', farm: 'Farm', daily: 'Daily', weekly: 'Weekly', monthly: 'Monthly',
|
|
615
|
+
scratch: 'Scratch', adventure: 'Adventure', trivia: 'Trivia', stream: 'Stream',
|
|
616
|
+
drops: 'Drops', use: 'Use Item', dep: 'Deposit', deposit: 'Deposit', inv: 'Inventory',
|
|
617
|
+
work: 'Work', stream: 'Stream', roulette: 'Roulette', slots: 'Slots',
|
|
618
|
+
};
|
|
619
|
+
|
|
620
|
+
function formatCommandName(cmd) {
|
|
621
|
+
if (!cmd) return '?';
|
|
622
|
+
const clean = cmd.replace(/^pls\s+/, '').replace(/\s+\d+.*$/, '').trim().toLowerCase();
|
|
623
|
+
return CMD_NAMES_CLEAN[clean] || clean.charAt(0).toUpperCase() + clean.slice(1);
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
function formatCommandResult(cmdName, result, earned, spent) {
|
|
627
|
+
const name = formatCommandName(cmdName);
|
|
628
|
+
const net = (earned || 0) - (spent || 0);
|
|
629
|
+
|
|
630
|
+
// Extract clean result text
|
|
631
|
+
let cleanResult = stripAnsi(result || '').replace(/\n/g, ' ').substring(0, 40);
|
|
632
|
+
|
|
633
|
+
// Check for common failure/hold patterns
|
|
634
|
+
if (cleanResult.toLowerCase().includes('hold tight')) return `${name}: ${c.yellow}Hold Tight${c.reset}`;
|
|
635
|
+
if (cleanResult.toLowerCase().includes('cooldown')) return `${name}: ${c.dim}On Cooldown${c.reset}`;
|
|
636
|
+
if (cleanResult.toLowerCase().includes('no response')) return `${name}: ${c.red}No Response${c.reset}`;
|
|
637
|
+
|
|
638
|
+
// Format with earnings/losses
|
|
639
|
+
if (net > 0) {
|
|
640
|
+
return `${name}: ${c.green}+⏣ ${net.toLocaleString()}${c.reset}`;
|
|
641
|
+
} else if (net < 0) {
|
|
642
|
+
return `${name}: ${c.red}-⏣ ${Math.abs(net).toLocaleString()}${c.reset}`;
|
|
643
|
+
} else if (earned === 0 && spent === 0) {
|
|
644
|
+
// No coins changed - show result context
|
|
645
|
+
if (cleanResult.includes('completed') || cleanResult.includes('done')) {
|
|
646
|
+
return `${name}: ${c.dim}Done${c.reset}`;
|
|
647
|
+
}
|
|
648
|
+
return `${name}: ${c.dim}${cleanResult || 'Complete'}${c.reset}`;
|
|
649
|
+
}
|
|
650
|
+
return `${name}: ${c.dim}${cleanResult || 'Complete'}${c.reset}`;
|
|
651
|
+
}
|
|
652
|
+
|
|
608
653
|
// ── Coin Parser — prefers Net:/Winnings: fields, falls back to max ⏣ ──
|
|
609
654
|
function parseCoins(text) {
|
|
610
655
|
if (!text) return 0;
|
|
@@ -1139,7 +1184,9 @@ class AccountWorker {
|
|
|
1139
1184
|
accountId: this.account.id,
|
|
1140
1185
|
redis,
|
|
1141
1186
|
onPageProgress: ({ page, total }) => {
|
|
1142
|
-
|
|
1187
|
+
// Minimal progress update on same line
|
|
1188
|
+
const erase = '\x1b[2K\r';
|
|
1189
|
+
process.stdout.write(`${erase}${this.color}[inv] ${page}/${total}${c.reset}`);
|
|
1143
1190
|
},
|
|
1144
1191
|
});
|
|
1145
1192
|
|
|
@@ -1147,6 +1194,8 @@ class AccountWorker {
|
|
|
1147
1194
|
throw new Error(`incomplete pages (${result.pagesVisited || 0}/${result.pagesTotal || 0})`);
|
|
1148
1195
|
}
|
|
1149
1196
|
|
|
1197
|
+
// Add newline after inventory progress
|
|
1198
|
+
process.stdout.write('\n');
|
|
1150
1199
|
this.log('success', `Inventory: ${result.items?.length || 0} items, ⏣ ${(result.totalValue || 0).toLocaleString()} net`);
|
|
1151
1200
|
try {
|
|
1152
1201
|
await fetch(`${API_URL}/api/grinder/inventory`, {
|
|
@@ -1549,7 +1598,8 @@ class AccountWorker {
|
|
|
1549
1598
|
|
|
1550
1599
|
const earned = Math.max(0, cmdResult.coins || 0);
|
|
1551
1600
|
const spent = Math.max(0, cmdResult.lost || 0);
|
|
1552
|
-
|
|
1601
|
+
// Track net earnings (add wins, subtract losses)
|
|
1602
|
+
this.stats.coins += (earned - spent);
|
|
1553
1603
|
if (cmdResult.nextCooldownSec) {
|
|
1554
1604
|
await this.setCooldown(cmdName, cmdResult.nextCooldownSec);
|
|
1555
1605
|
this._lastCooldownOverride = cmdResult.nextCooldownSec;
|
|
@@ -1596,8 +1646,9 @@ class AccountWorker {
|
|
|
1596
1646
|
}
|
|
1597
1647
|
|
|
1598
1648
|
this.stats.successes++;
|
|
1599
|
-
|
|
1600
|
-
|
|
1649
|
+
// Format result with clean command name and colored earnings
|
|
1650
|
+
const formattedResult = formatCommandResult(cmdName, result, earned, spent);
|
|
1651
|
+
this.setStatus(formattedResult);
|
|
1601
1652
|
await sendLog(this.username, cmdName, result, 'success');
|
|
1602
1653
|
reportEarnings(this.account.id, this.username, earned, spent, cmdName);
|
|
1603
1654
|
|
|
@@ -2346,8 +2397,8 @@ class AccountWorker {
|
|
|
2346
2397
|
} catch {}
|
|
2347
2398
|
}
|
|
2348
2399
|
|
|
2349
|
-
// Let Discord gateway settle
|
|
2350
|
-
await new Promise(r => setTimeout(r,
|
|
2400
|
+
// Let Discord gateway settle (reduced for faster startup)
|
|
2401
|
+
await new Promise(r => setTimeout(r, 500));
|
|
2351
2402
|
resolve();
|
|
2352
2403
|
});
|
|
2353
2404
|
|
|
@@ -2472,47 +2523,62 @@ async function start(apiKey, apiUrl) {
|
|
|
2472
2523
|
console.log(` ${checks.join(' ')}`);
|
|
2473
2524
|
console.log('');
|
|
2474
2525
|
|
|
2475
|
-
// Phase 1: Login all accounts (
|
|
2476
|
-
const LOGIN_PROGRESS_EVERY =
|
|
2477
|
-
|
|
2478
|
-
const
|
|
2479
|
-
const
|
|
2480
|
-
const
|
|
2526
|
+
// Phase 1: Login all accounts (optimized for speed)
|
|
2527
|
+
const LOGIN_PROGRESS_EVERY = 10;
|
|
2528
|
+
// Reduced delays: 50-150ms between logins (faster startup for 1k+ accounts)
|
|
2529
|
+
const parsedGapMin = Number.parseInt(String(process.env.LOGIN_GAP_MIN_MS || '50'), 10);
|
|
2530
|
+
const parsedGapMax = Number.parseInt(String(process.env.LOGIN_GAP_MAX_MS || '150'), 10);
|
|
2531
|
+
const LOGIN_GAP_MIN_MS = Number.isFinite(parsedGapMin) && parsedGapMin >= 0 ? parsedGapMin : 50;
|
|
2532
|
+
const LOGIN_GAP_MAX_MS = Number.isFinite(parsedGapMax) && parsedGapMax >= LOGIN_GAP_MIN_MS ? parsedGapMax : Math.max(LOGIN_GAP_MIN_MS, 150);
|
|
2481
2533
|
|
|
2482
2534
|
const randomLoginGap = () => {
|
|
2483
2535
|
if (LOGIN_GAP_MAX_MS <= LOGIN_GAP_MIN_MS) return LOGIN_GAP_MIN_MS;
|
|
2484
2536
|
return LOGIN_GAP_MIN_MS + Math.floor(Math.random() * (LOGIN_GAP_MAX_MS - LOGIN_GAP_MIN_MS + 1));
|
|
2485
2537
|
};
|
|
2486
2538
|
|
|
2487
|
-
|
|
2539
|
+
// Parallel login in batches of 10 to avoid rate limits while being fast
|
|
2540
|
+
const BATCH_SIZE = 10;
|
|
2541
|
+
for (let i = 0; i < accounts.length; i += BATCH_SIZE) {
|
|
2488
2542
|
if (shutdownCalled) break;
|
|
2489
|
-
const
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
await
|
|
2493
|
-
|
|
2543
|
+
const batch = accounts.slice(i, Math.min(i + BATCH_SIZE, accounts.length));
|
|
2544
|
+
|
|
2545
|
+
// Login batch in parallel
|
|
2546
|
+
await Promise.all(batch.map(async (acc, idx) => {
|
|
2547
|
+
const worker = new AccountWorker(acc, i + idx);
|
|
2548
|
+
workers.push(worker);
|
|
2549
|
+
workerMap.set(acc.id, worker);
|
|
2550
|
+
await worker.start();
|
|
2551
|
+
}));
|
|
2552
|
+
|
|
2553
|
+
// Small gap between batches
|
|
2554
|
+
if (i + BATCH_SIZE < accounts.length) {
|
|
2494
2555
|
const gapMs = randomLoginGap();
|
|
2495
|
-
|
|
2496
|
-
log('info', `${c.dim}Logged in ${i + 1}/${accounts.length}, next account in ${gapMs}ms...${c.reset}`);
|
|
2497
|
-
}
|
|
2556
|
+
log('info', `${c.dim}Logged in ${Math.min(i + BATCH_SIZE, accounts.length)}/${accounts.length}...${c.reset}`);
|
|
2498
2557
|
await new Promise(r => setTimeout(r, gapMs));
|
|
2499
2558
|
}
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
}
|
|
2559
|
+
|
|
2560
|
+
hintGC();
|
|
2503
2561
|
}
|
|
2504
2562
|
|
|
2505
|
-
// Phase 2: Run inventory on ALL accounts (must complete before
|
|
2506
|
-
log('info', `${c.dim}Checking inventory for
|
|
2563
|
+
// Phase 2: Run inventory on ALL accounts in parallel (must complete before grinding)
|
|
2564
|
+
log('info', `${c.dim}Checking inventory for ${workers.length} accounts...${c.reset}`);
|
|
2565
|
+
|
|
2566
|
+
// Parallel inventory checks with single-line progress
|
|
2507
2567
|
let invDone = 0;
|
|
2508
2568
|
let invFailed = 0;
|
|
2569
|
+
const total = workers.length;
|
|
2570
|
+
|
|
2509
2571
|
await Promise.all(workers.map(async (w, i) => {
|
|
2510
|
-
const label = w?.username || w?.account?.label ||
|
|
2511
|
-
|
|
2572
|
+
const label = w?.username || w?.account?.label || 'account';
|
|
2573
|
+
|
|
2574
|
+
// Update progress on same line
|
|
2575
|
+
const progress = `[inv] ${invDone + invFailed + 1}/${total}: ${label}`;
|
|
2576
|
+
process.stdout.write(`\x1b[2K\r${c.dim}${progress}${c.reset}`);
|
|
2577
|
+
|
|
2512
2578
|
try {
|
|
2513
2579
|
const invRes = await w.checkInventory({
|
|
2514
2580
|
force: true,
|
|
2515
|
-
startupProgress: { current: i + 1, total
|
|
2581
|
+
startupProgress: { current: i + 1, total },
|
|
2516
2582
|
requireComplete: true,
|
|
2517
2583
|
maxAttempts: 3,
|
|
2518
2584
|
});
|
|
@@ -2521,10 +2587,12 @@ async function start(apiKey, apiUrl) {
|
|
|
2521
2587
|
} catch {
|
|
2522
2588
|
invFailed++;
|
|
2523
2589
|
}
|
|
2524
|
-
const invComplete = invDone + invFailed;
|
|
2525
|
-
log('info', `${c.dim}[inv-startup-progress] ${invComplete}/${workers.length} complete (${invDone} ok, ${invFailed} failed)${c.reset}`);
|
|
2526
2590
|
}));
|
|
2527
2591
|
|
|
2592
|
+
// Final newline and summary
|
|
2593
|
+
process.stdout.write('\n');
|
|
2594
|
+
log('success', `Inventory: ${invDone}/${total} done${invFailed > 0 ? `, ${c.yellow}${invFailed} failed${c.reset}` : ''}`);
|
|
2595
|
+
|
|
2528
2596
|
if (invFailed > 0) {
|
|
2529
2597
|
log('error', `${c.red}Inventory phase incomplete: ${invDone}/${workers.length} complete, ${invFailed} failed/incomplete. Not starting grind loops.${c.reset}`);
|
|
2530
2598
|
return;
|
|
@@ -2541,6 +2609,8 @@ async function start(apiKey, apiUrl) {
|
|
|
2541
2609
|
startTime = Date.now();
|
|
2542
2610
|
dashboardStarted = true;
|
|
2543
2611
|
setDashboardActive(true);
|
|
2612
|
+
// Setup keyboard shortcuts
|
|
2613
|
+
setupKeyboardShortcuts();
|
|
2544
2614
|
// Clear entire screen so startup logs don't create ghost bars
|
|
2545
2615
|
process.stdout.write('\x1b[2J\x1b[H');
|
|
2546
2616
|
process.stdout.write(c.hide);
|
|
@@ -2684,4 +2754,76 @@ async function start(apiKey, apiUrl) {
|
|
|
2684
2754
|
});
|
|
2685
2755
|
}
|
|
2686
2756
|
|
|
2687
|
-
|
|
2757
|
+
// ══════════════════════════════════════════════════════════════
|
|
2758
|
+
// ═ Keyboard Shortcuts (Quality of Life)
|
|
2759
|
+
// ══════════════════════════════════════════════════════════════
|
|
2760
|
+
// Single-key shortcuts for common actions
|
|
2761
|
+
function setupKeyboardShortcuts() {
|
|
2762
|
+
if (process.stdin.isTTY) {
|
|
2763
|
+
process.stdin.setRawMode(true);
|
|
2764
|
+
process.stdin.resume();
|
|
2765
|
+
process.stdin.setEncoding('utf8');
|
|
2766
|
+
|
|
2767
|
+
console.log(`\n ${c.dim}Keyboard shortcuts: ${c.reset}p=pause/resume all ${c.dim}·${c.reset} r=resume all ${c.dim}·${c.reset} s=status ${c.dim}·${c.reset} q=quit ${c.dim}·${c.reset} 1-9=toggle account`);
|
|
2768
|
+
|
|
2769
|
+
process.stdin.on('data', (key) => {
|
|
2770
|
+
const k = key.toString().toLowerCase();
|
|
2771
|
+
|
|
2772
|
+
// Ctrl+C or q = quit
|
|
2773
|
+
if (k === '\u0003' || k === 'q') {
|
|
2774
|
+
console.log(`\n\n ${c.yellow}Shutting down gracefully...${c.reset}`);
|
|
2775
|
+
process.emit('SIGINT');
|
|
2776
|
+
return;
|
|
2777
|
+
}
|
|
2778
|
+
|
|
2779
|
+
// p = pause all accounts
|
|
2780
|
+
if (k === 'p') {
|
|
2781
|
+
let paused = 0;
|
|
2782
|
+
workers.forEach(w => { if (w.running && !w.paused) { w.paused = true; paused++; } });
|
|
2783
|
+
console.log(`\n ${c.yellow}Paused ${paused} accounts${c.reset}`);
|
|
2784
|
+
return;
|
|
2785
|
+
}
|
|
2786
|
+
|
|
2787
|
+
// r = resume all accounts
|
|
2788
|
+
if (k === 'r') {
|
|
2789
|
+
let resumed = 0;
|
|
2790
|
+
workers.forEach(w => { if (w.paused) { w.paused = false; resumed++; } });
|
|
2791
|
+
console.log(`\n ${c.green}Resumed ${resumed} accounts${c.reset}`);
|
|
2792
|
+
return;
|
|
2793
|
+
}
|
|
2794
|
+
|
|
2795
|
+
// s = show status summary
|
|
2796
|
+
if (k === 's') {
|
|
2797
|
+
console.log(`\n ${c.bold}Status Summary:${c.reset}`);
|
|
2798
|
+
const active = workers.filter(w => w.running && !w.paused).length;
|
|
2799
|
+
const paused = workers.filter(w => w.paused).length;
|
|
2800
|
+
const offline = workers.filter(w => !w.running).length;
|
|
2801
|
+
const recovering = workers.filter(w => w._recoveryAttempts > 0 && w._errorCooldownUntil > Date.now()).length;
|
|
2802
|
+
console.log(` ${c.green}● ${active} active${c.reset} ${c.yellow}⏸ ${paused} paused${c.reset} ${c.red}○ ${offline} offline${c.reset} ${c.yellow}↻ ${recovering} recovering${c.reset}`);
|
|
2803
|
+
console.log(` ${c.dim}Total earnings: ⏣ ${workers.reduce((s, w) => s + (w.stats.coins || 0), 0).toLocaleString()}${c.reset}`);
|
|
2804
|
+
return;
|
|
2805
|
+
}
|
|
2806
|
+
|
|
2807
|
+
// 1-9 = toggle specific account (for small account counts)
|
|
2808
|
+
const num = parseInt(k, 10);
|
|
2809
|
+
if (num >= 1 && num <= 9 && workers[num - 1]) {
|
|
2810
|
+
const w = workers[num - 1];
|
|
2811
|
+
w.paused = !w.paused;
|
|
2812
|
+
console.log(`\n ${w.color}${w.username}${c.reset} ${w.paused ? c.yellow + 'paused' : c.green + 'resumed'}${c.reset}`);
|
|
2813
|
+
return;
|
|
2814
|
+
}
|
|
2815
|
+
|
|
2816
|
+
// ? = show help
|
|
2817
|
+
if (k === '?' || k === 'h') {
|
|
2818
|
+
console.log(`\n ${c.bold}Keyboard Shortcuts:${c.reset}`);
|
|
2819
|
+
console.log(` ${c.white}p${c.reset} Pause all accounts`);
|
|
2820
|
+
console.log(` ${c.white}r${c.reset} Resume all accounts`);
|
|
2821
|
+
console.log(` ${c.white}s${c.reset} Show status summary`);
|
|
2822
|
+
console.log(` ${c.white}q${c.reset} Quit gracefully`);
|
|
2823
|
+
console.log(` ${c.white}1-9${c.reset} Toggle account N`);
|
|
2824
|
+
console.log(` ${c.white}?${c.reset} Show this help`);
|
|
2825
|
+
return;
|
|
2826
|
+
}
|
|
2827
|
+
});
|
|
2828
|
+
}
|
|
2829
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dankgrinder",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.281.0",
|
|
4
4
|
"description": "Dank Memer automation engine — grind coins while you sleep",
|
|
5
5
|
"bin": {
|
|
6
6
|
"dankgrinder": "bin/dankgrinder.js"
|
|
@@ -19,6 +19,10 @@
|
|
|
19
19
|
],
|
|
20
20
|
"author": "DankGrinder",
|
|
21
21
|
"license": "MIT",
|
|
22
|
+
"scripts": {
|
|
23
|
+
"start": "node bin/dankgrinder.js",
|
|
24
|
+
"test": "node --test test/**/*.test.js"
|
|
25
|
+
},
|
|
22
26
|
"dependencies": {
|
|
23
27
|
"debug": "^4.4.0",
|
|
24
28
|
"discord.js-selfbot-v13": "3.5.0",
|