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.
@@ -0,0 +1,347 @@
1
+ /**
2
+ * Advanced Cooldown Manager with Smart CD Calculations
3
+ *
4
+ * Features:
5
+ * - EMA-based cooldown prediction (learns from history)
6
+ * - Command chaining with optimal timing
7
+ * - Priority queue for scheduling
8
+ * - Redis-backed shared state (cluster mode)
9
+ * - Predictive readiness estimation
10
+ */
11
+
12
+ 'use strict';
13
+
14
+ const { LRUCache, MinHeap } = require('./structures');
15
+ const { calcSmartCooldown } = require('./antiDetect');
16
+
17
+ // ── Command Cooldown Config ───────────────────────────────────
18
+ const COMMAND_CONFIG = Object.freeze({
19
+ // Base cooldowns in seconds
20
+ beg: { base: 120, priority: 3, category: 'income' },
21
+ crime: { base: 1200, priority: 2, category: 'income' },
22
+ search: { base: 150, priority: 3, category: 'income' },
23
+ work: { base: 3600, priority: 1, category: 'income' },
24
+ dig: { base: 600, priority: 2, category: 'income' },
25
+ fish: { base: 900, priority: 2, category: 'income' },
26
+ hunt: { base: 120, priority: 3, category: 'income' },
27
+ farm: { base: 300, priority: 2, category: 'income' },
28
+ stream: { base: 120, priority: 3, category: 'income' },
29
+ trivia: { base: 180, priority: 2, category: 'income' },
30
+ highlow: { base: 120, priority: 2, category: 'income' },
31
+ gamble: { base: 30, priority: 1, category: 'gambling' },
32
+ blackjack: { base: 30, priority: 1, category: 'gambling' },
33
+ roulette: { base: 30, priority: 1, category: 'gambling' },
34
+ slots: { base: 30, priority: 1, category: 'gambling' },
35
+ adventure: { base: 1800, priority: 1, category: 'special' },
36
+ postmeme: { base: 300, priority: 2, category: 'social' },
37
+ meme: { base: 300, priority: 2, category: 'social' },
38
+ vote: { base: 7200, priority: 1, category: 'special' },
39
+ daily: { base: 86400, priority: 1, category: 'special' },
40
+ weekly: { base: 604800, priority: 1, category: 'special' },
41
+ monthly: { base: 2592000, priority: 1, category: 'special' },
42
+ });
43
+
44
+ // ── EMA Calculator ────────────────────────────────────────────
45
+ /**
46
+ * Exponential Moving Average for cooldown prediction.
47
+ * Gives more weight to recent observations.
48
+ */
49
+ class EMACalculator {
50
+ constructor(alpha = 0.3) {
51
+ this.alpha = alpha; // Smoothing factor (0-1)
52
+ this.ema = null;
53
+ }
54
+
55
+ update(value) {
56
+ if (this.ema === null) {
57
+ this.ema = value;
58
+ } else {
59
+ this.ema = this.alpha * value + (1 - this.alpha) * this.ema;
60
+ }
61
+ return this.ema;
62
+ }
63
+
64
+ getPrediction() {
65
+ return this.ema;
66
+ }
67
+
68
+ reset() {
69
+ this.ema = null;
70
+ }
71
+ }
72
+
73
+ // ── Cooldown Tracker ──────────────────────────────────────────
74
+ class CooldownTracker {
75
+ constructor(userId, redis = null, clusterEnabled = false) {
76
+ this.userId = userId;
77
+ this.redis = redis;
78
+ this.clusterEnabled = clusterEnabled;
79
+
80
+ // Local caches
81
+ this.cooldowns = new Map(); // command -> readyAt (timestamp)
82
+ this.emaTrackers = new Map(); // command -> EMACalculator
83
+ this.historyCache = new LRUCache(50); // Last 50 cooldown observations per command
84
+ }
85
+
86
+ /**
87
+ * Record a cooldown for a command.
88
+ * @param {string} command - Command name
89
+ * @param {number} cooldownSec - Observed cooldown in seconds
90
+ */
91
+ recordCooldown(command, cooldownSec) {
92
+ const now = Date.now();
93
+ const readyAt = now + (cooldownSec * 1000);
94
+
95
+ // Store in local map
96
+ this.cooldowns.set(command, readyAt);
97
+
98
+ // Update EMA prediction
99
+ if (!this.emaTrackers.has(command)) {
100
+ this.emaTrackers.set(command, new EMACalculator(0.3));
101
+ }
102
+ const ema = this.emaTrackers.get(command);
103
+ ema.update(cooldownSec);
104
+
105
+ // Store in Redis for cluster mode
106
+ if (this.redis && this.clusterEnabled) {
107
+ const key = `dkg:cd:${this.userId}:${command}`;
108
+ this.redis.setex(key, cooldownSec + 60, JSON.stringify({
109
+ readyAt,
110
+ ema: ema.getPrediction(),
111
+ recorded: now,
112
+ })).catch(() => {});
113
+ }
114
+
115
+ // Cache history
116
+ const history = this.historyCache.get(command) || [];
117
+ history.push({ cooldownSec, timestamp: now });
118
+ if (history.length > 50) history.shift();
119
+ this.historyCache.set(command, history);
120
+ }
121
+
122
+ /**
123
+ * Check if a command is ready.
124
+ * @param {string} command - Command name
125
+ * @returns {Object} - { ready: boolean, waitMs: number, predicted: number|null }
126
+ */
127
+ isReady(command) {
128
+ const readyAt = this.cooldowns.get(command);
129
+ if (!readyAt) {
130
+ return { ready: true, waitMs: 0, predicted: null };
131
+ }
132
+
133
+ const now = Date.now();
134
+ const waitMs = Math.max(0, readyAt - now);
135
+
136
+ // Add smart buffer
137
+ const config = COMMAND_CONFIG[command];
138
+ if (config) {
139
+ const buffered = calcSmartCooldown(waitMs / 1000, { addBuffer: true, addVariance: false });
140
+ return {
141
+ ready: waitMs <= 0,
142
+ waitMs: waitMs > 0 ? buffered * 1000 : 0,
143
+ predicted: this.emaTrackers.get(command)?.getPrediction() || null,
144
+ };
145
+ }
146
+
147
+ return {
148
+ ready: waitMs <= 0,
149
+ waitMs,
150
+ predicted: this.emaTrackers.get(command)?.getPrediction() || null,
151
+ };
152
+ }
153
+
154
+ /**
155
+ * Get predicted cooldown for a command.
156
+ * @param {string} command - Command name
157
+ * @returns {number|null} - Predicted cooldown in seconds
158
+ */
159
+ getPredictedCooldown(command) {
160
+ return this.emaTrackers.get(command)?.getPrediction() || null;
161
+ }
162
+
163
+ /**
164
+ * Get optimal command order based on cooldowns.
165
+ * @param {string[]} commands - List of commands to consider
166
+ * @returns {Array} - Sorted commands with timing info
167
+ */
168
+ getOptimalOrder(commands) {
169
+ const now = Date.now();
170
+ const order = commands.map(cmd => {
171
+ const config = COMMAND_CONFIG[cmd] || { priority: 5 };
172
+ const status = this.isReady(cmd);
173
+ return {
174
+ command: cmd,
175
+ ready: status.ready,
176
+ waitMs: status.waitMs,
177
+ priority: config.priority,
178
+ category: config.category,
179
+ predicted: status.predicted,
180
+ };
181
+ });
182
+
183
+ // Sort: ready commands first (by priority), then by wait time
184
+ order.sort((a, b) => {
185
+ if (a.ready && !b.ready) return -1;
186
+ if (!a.ready && b.ready) return 1;
187
+ if (a.ready && b.ready) return a.priority - b.priority;
188
+ return a.waitMs - b.waitMs;
189
+ });
190
+
191
+ return order;
192
+ }
193
+
194
+ /**
195
+ * Get all commands that will be ready within a time window.
196
+ * @param {number} windowMs - Time window in milliseconds
197
+ * @returns {Array} - Commands ready within window
198
+ */
199
+ getReadyInWindow(windowMs) {
200
+ const now = Date.now();
201
+ const ready = [];
202
+
203
+ for (const [command, readyAt] of this.cooldowns.entries()) {
204
+ const waitMs = readyAt - now;
205
+ if (waitMs <= windowMs) {
206
+ ready.push({
207
+ command,
208
+ waitMs: Math.max(0, waitMs),
209
+ readyAt,
210
+ });
211
+ }
212
+ }
213
+
214
+ ready.sort((a, b) => a.waitMs - b.waitMs);
215
+ return ready;
216
+ }
217
+
218
+ /**
219
+ * Clear cooldown for a command.
220
+ * @param {string} command - Command name
221
+ */
222
+ clearCooldown(command) {
223
+ this.cooldowns.delete(command);
224
+ }
225
+
226
+ /**
227
+ * Clear all cooldowns.
228
+ */
229
+ clearAll() {
230
+ this.cooldowns.clear();
231
+ }
232
+ }
233
+
234
+ // ── Command Scheduler (Priority Queue) ────────────────────────
235
+ class CommandScheduler {
236
+ constructor(tracker) {
237
+ this.tracker = tracker;
238
+ this.queue = new MinHeap((a, b) => a.executeAt - b.executeAt);
239
+ this.pending = new Map(); // command -> node reference
240
+ }
241
+
242
+ /**
243
+ * Schedule a command for execution.
244
+ * @param {string} command - Command name
245
+ * @param {Function} executeFn - Function to execute
246
+ * @param {number} executeAt - Timestamp to execute at
247
+ */
248
+ schedule(command, executeFn, executeAt) {
249
+ // Remove existing scheduled command if any
250
+ this.unschedule(command);
251
+
252
+ const node = {
253
+ command,
254
+ executeFn,
255
+ executeAt,
256
+ added: Date.now(),
257
+ };
258
+
259
+ this.queue.push(node);
260
+ this.pending.set(command, node);
261
+
262
+ return node;
263
+ }
264
+
265
+ /**
266
+ * Cancel a scheduled command.
267
+ * @param {string} command - Command name
268
+ */
269
+ unschedule(command) {
270
+ const node = this.pending.get(command);
271
+ if (node) {
272
+ this.queue.remove(node);
273
+ this.pending.delete(command);
274
+ }
275
+ }
276
+
277
+ /**
278
+ * Get next command to execute.
279
+ * @returns {Object|null} - Next command node or null
280
+ */
281
+ peekNext() {
282
+ return this.queue.peek();
283
+ }
284
+
285
+ /**
286
+ * Execute next command if it's time.
287
+ * @returns {Object|null} - Executed command node or null
288
+ */
289
+ async executeNext() {
290
+ const node = this.queue.peek();
291
+ if (!node) return null;
292
+
293
+ const now = Date.now();
294
+ if (node.executeAt <= now) {
295
+ this.queue.pop();
296
+ this.pending.delete(node.command);
297
+
298
+ try {
299
+ await node.executeFn();
300
+ return node;
301
+ } catch (e) {
302
+ console.error(`[scheduler] Error executing ${node.command}:`, e);
303
+ throw e;
304
+ }
305
+ }
306
+
307
+ return null;
308
+ }
309
+
310
+ /**
311
+ * Get queue size.
312
+ */
313
+ size() {
314
+ return this.queue.size();
315
+ }
316
+ }
317
+
318
+ // ── Factory Function ──────────────────────────────────────────
319
+ /**
320
+ * Create a cooldown manager for a user.
321
+ * @param {string} userId - User ID
322
+ * @param {Object} options
323
+ * @param {Object} options.redis - Redis client (optional)
324
+ * @param {boolean} options.clusterEnabled - Cluster mode flag
325
+ * @returns {Object} - { tracker, scheduler, COMMAND_CONFIG }
326
+ */
327
+ function createCooldownManager(userId, options = {}) {
328
+ const { redis = null, clusterEnabled = false } = options;
329
+
330
+ const tracker = new CooldownTracker(userId, redis, clusterEnabled);
331
+ const scheduler = new CommandScheduler(tracker);
332
+
333
+ return {
334
+ tracker,
335
+ scheduler,
336
+ COMMAND_CONFIG,
337
+ };
338
+ }
339
+
340
+ // ── Exports ───────────────────────────────────────────────────
341
+ module.exports = {
342
+ EMACalculator,
343
+ CooldownTracker,
344
+ CommandScheduler,
345
+ createCooldownManager,
346
+ COMMAND_CONFIG,
347
+ };