waengine 1.7.3 → 1.7.4

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,577 @@
1
+ import { getStorage } from "./storage.js";
2
+ import cron from "node-cron";
3
+
4
+ export class AdvancedScheduler {
5
+ constructor(client) {
6
+ this.client = client;
7
+ this.storage = getStorage();
8
+ this.scheduledTasks = new Map();
9
+ this.cronJobs = new Map();
10
+ this.recurringTasks = new Map();
11
+ this.taskQueue = [];
12
+ this.isProcessing = false;
13
+
14
+ this.initializeScheduler();
15
+ }
16
+
17
+ // ===== INITIALIZATION =====
18
+
19
+ initializeScheduler() {
20
+ this.loadScheduledTasks();
21
+ this.startTaskProcessor();
22
+ this.startMaintenanceJob();
23
+ }
24
+
25
+ loadScheduledTasks() {
26
+ const tasks = this.storage.read.from("scheduler").get("tasks") || {};
27
+ const cronTasks = this.storage.read.from("scheduler").get("cronTasks") || {};
28
+
29
+ // Restore scheduled tasks
30
+ Object.entries(tasks).forEach(([id, task]) => {
31
+ if (task.executeAt > Date.now()) {
32
+ this.scheduledTasks.set(id, task);
33
+ }
34
+ });
35
+
36
+ // Restore cron jobs
37
+ Object.entries(cronTasks).forEach(([id, task]) => {
38
+ this.createCronJob(id, task.pattern, task.action, task.data);
39
+ });
40
+ }
41
+
42
+ startTaskProcessor() {
43
+ setInterval(() => {
44
+ if (!this.isProcessing && this.taskQueue.length > 0) {
45
+ this.processTaskQueue();
46
+ }
47
+ }, 1000);
48
+ }
49
+
50
+ startMaintenanceJob() {
51
+ // Clean up expired tasks every hour
52
+ cron.schedule('0 * * * *', () => {
53
+ this.cleanupExpiredTasks();
54
+ });
55
+ }
56
+
57
+ // ===== SCHEDULING METHODS =====
58
+
59
+ scheduleMessage(chatId, message, executeAt, options = {}) {
60
+ const taskId = this.generateTaskId();
61
+ const task = {
62
+ id: taskId,
63
+ type: 'message',
64
+ chatId,
65
+ message,
66
+ executeAt: new Date(executeAt).getTime(),
67
+ options,
68
+ status: 'scheduled',
69
+ createdAt: Date.now()
70
+ };
71
+
72
+ this.scheduledTasks.set(taskId, task);
73
+ this.saveTask(taskId, task);
74
+
75
+ return taskId;
76
+ }
77
+
78
+ scheduleRecurringMessage(chatId, message, pattern, options = {}) {
79
+ const taskId = this.generateTaskId();
80
+ const task = {
81
+ id: taskId,
82
+ type: 'recurring_message',
83
+ chatId,
84
+ message,
85
+ pattern,
86
+ options,
87
+ status: 'active',
88
+ createdAt: Date.now()
89
+ };
90
+
91
+ this.createCronJob(taskId, pattern, 'sendMessage', { chatId, message, options });
92
+ this.recurringTasks.set(taskId, task);
93
+ this.saveCronTask(taskId, task);
94
+
95
+ return taskId;
96
+ }
97
+
98
+ scheduleCustomAction(actionName, data, executeAt, options = {}) {
99
+ const taskId = this.generateTaskId();
100
+ const task = {
101
+ id: taskId,
102
+ type: 'custom_action',
103
+ actionName,
104
+ data,
105
+ executeAt: new Date(executeAt).getTime(),
106
+ options,
107
+ status: 'scheduled',
108
+ createdAt: Date.now()
109
+ };
110
+
111
+ this.scheduledTasks.set(taskId, task);
112
+ this.saveTask(taskId, task);
113
+
114
+ return taskId;
115
+ }
116
+
117
+ scheduleRecurringAction(actionName, data, pattern, options = {}) {
118
+ const taskId = this.generateTaskId();
119
+ const task = {
120
+ id: taskId,
121
+ type: 'recurring_action',
122
+ actionName,
123
+ data,
124
+ pattern,
125
+ options,
126
+ status: 'active',
127
+ createdAt: Date.now()
128
+ };
129
+
130
+ this.createCronJob(taskId, pattern, actionName, data);
131
+ this.recurringTasks.set(taskId, task);
132
+ this.saveCronTask(taskId, task);
133
+
134
+ return taskId;
135
+ }
136
+
137
+ // ===== BULK SCHEDULING =====
138
+
139
+ scheduleBulkMessages(messages, executeAt, options = {}) {
140
+ const taskIds = [];
141
+ const delay = options.delay || 1000; // 1 second delay between messages
142
+
143
+ messages.forEach((msg, index) => {
144
+ const adjustedTime = new Date(executeAt).getTime() + (index * delay);
145
+ const taskId = this.scheduleMessage(msg.chatId, msg.message, adjustedTime, msg.options);
146
+ taskIds.push(taskId);
147
+ });
148
+
149
+ return taskIds;
150
+ }
151
+
152
+ scheduleMessageChain(chatId, messages, startTime, options = {}) {
153
+ const taskIds = [];
154
+ const delay = options.delay || 2000; // 2 seconds delay between chain messages
155
+
156
+ messages.forEach((message, index) => {
157
+ const executeTime = new Date(startTime).getTime() + (index * delay);
158
+ const taskId = this.scheduleMessage(chatId, message, executeTime, options);
159
+ taskIds.push(taskId);
160
+ });
161
+
162
+ return taskIds;
163
+ }
164
+
165
+ // ===== CONDITIONAL SCHEDULING =====
166
+
167
+ scheduleConditionalMessage(chatId, message, condition, checkInterval = 60000) {
168
+ const taskId = this.generateTaskId();
169
+ const task = {
170
+ id: taskId,
171
+ type: 'conditional_message',
172
+ chatId,
173
+ message,
174
+ condition,
175
+ checkInterval,
176
+ status: 'waiting',
177
+ createdAt: Date.now(),
178
+ lastCheck: Date.now()
179
+ };
180
+
181
+ this.scheduledTasks.set(taskId, task);
182
+ this.saveTask(taskId, task);
183
+
184
+ // Start checking condition
185
+ this.startConditionalCheck(taskId);
186
+
187
+ return taskId;
188
+ }
189
+
190
+ startConditionalCheck(taskId) {
191
+ const task = this.scheduledTasks.get(taskId);
192
+ if (!task) return;
193
+
194
+ const checkCondition = async () => {
195
+ try {
196
+ const conditionMet = await this.evaluateCondition(task.condition);
197
+ if (conditionMet) {
198
+ await this.executeTask(task);
199
+ this.cancelTask(taskId);
200
+ } else {
201
+ task.lastCheck = Date.now();
202
+ setTimeout(checkCondition, task.checkInterval);
203
+ }
204
+ } catch (error) {
205
+ console.error(`Condition check failed for task ${taskId}:`, error);
206
+ this.cancelTask(taskId);
207
+ }
208
+ };
209
+
210
+ setTimeout(checkCondition, task.checkInterval);
211
+ }
212
+
213
+ // ===== SMART SCHEDULING =====
214
+
215
+ scheduleSmartMessage(chatId, message, options = {}) {
216
+ const smartOptions = {
217
+ timezone: options.timezone || 'UTC',
218
+ preferredHours: options.preferredHours || [9, 10, 11, 14, 15, 16, 17],
219
+ avoidWeekends: options.avoidWeekends || false,
220
+ userActivity: options.considerUserActivity || false,
221
+ ...options
222
+ };
223
+
224
+ const optimalTime = this.calculateOptimalTime(chatId, smartOptions);
225
+ return this.scheduleMessage(chatId, message, optimalTime, smartOptions);
226
+ }
227
+
228
+ calculateOptimalTime(chatId, options) {
229
+ const now = new Date();
230
+ let optimalTime = new Date(now);
231
+
232
+ // Add base delay
233
+ optimalTime.setMinutes(optimalTime.getMinutes() + (options.minDelay || 5));
234
+
235
+ // Adjust for preferred hours
236
+ if (options.preferredHours && options.preferredHours.length > 0) {
237
+ const currentHour = optimalTime.getHours();
238
+ if (!options.preferredHours.includes(currentHour)) {
239
+ const nextPreferredHour = options.preferredHours.find(h => h > currentHour) || options.preferredHours[0];
240
+ if (nextPreferredHour > currentHour) {
241
+ optimalTime.setHours(nextPreferredHour, 0, 0, 0);
242
+ } else {
243
+ optimalTime.setDate(optimalTime.getDate() + 1);
244
+ optimalTime.setHours(nextPreferredHour, 0, 0, 0);
245
+ }
246
+ }
247
+ }
248
+
249
+ // Avoid weekends if specified
250
+ if (options.avoidWeekends) {
251
+ const dayOfWeek = optimalTime.getDay();
252
+ if (dayOfWeek === 0 || dayOfWeek === 6) { // Sunday or Saturday
253
+ const daysToAdd = dayOfWeek === 0 ? 1 : (7 - dayOfWeek + 1);
254
+ optimalTime.setDate(optimalTime.getDate() + daysToAdd);
255
+ optimalTime.setHours(options.preferredHours[0] || 9, 0, 0, 0);
256
+ }
257
+ }
258
+
259
+ return optimalTime;
260
+ }
261
+
262
+ // ===== TASK MANAGEMENT =====
263
+
264
+ cancelTask(taskId) {
265
+ const task = this.scheduledTasks.get(taskId) || this.recurringTasks.get(taskId);
266
+ if (!task) return false;
267
+
268
+ // Cancel cron job if it exists
269
+ if (this.cronJobs.has(taskId)) {
270
+ this.cronJobs.get(taskId).destroy();
271
+ this.cronJobs.delete(taskId);
272
+ }
273
+
274
+ // Remove from maps
275
+ this.scheduledTasks.delete(taskId);
276
+ this.recurringTasks.delete(taskId);
277
+
278
+ // Remove from storage
279
+ this.removeTask(taskId);
280
+
281
+ return true;
282
+ }
283
+
284
+ pauseTask(taskId) {
285
+ const task = this.scheduledTasks.get(taskId) || this.recurringTasks.get(taskId);
286
+ if (!task) return false;
287
+
288
+ task.status = 'paused';
289
+
290
+ if (this.cronJobs.has(taskId)) {
291
+ this.cronJobs.get(taskId).stop();
292
+ }
293
+
294
+ this.saveTask(taskId, task);
295
+ return true;
296
+ }
297
+
298
+ resumeTask(taskId) {
299
+ const task = this.scheduledTasks.get(taskId) || this.recurringTasks.get(taskId);
300
+ if (!task) return false;
301
+
302
+ task.status = task.type.includes('recurring') ? 'active' : 'scheduled';
303
+
304
+ if (this.cronJobs.has(taskId)) {
305
+ this.cronJobs.get(taskId).start();
306
+ }
307
+
308
+ this.saveTask(taskId, task);
309
+ return true;
310
+ }
311
+
312
+ updateTask(taskId, updates) {
313
+ const task = this.scheduledTasks.get(taskId) || this.recurringTasks.get(taskId);
314
+ if (!task) return false;
315
+
316
+ Object.assign(task, updates);
317
+
318
+ // If it's a recurring task and pattern changed, recreate cron job
319
+ if (task.type.includes('recurring') && updates.pattern) {
320
+ if (this.cronJobs.has(taskId)) {
321
+ this.cronJobs.get(taskId).destroy();
322
+ }
323
+ this.createCronJob(taskId, updates.pattern, task.actionName || 'sendMessage', task.data || { chatId: task.chatId, message: task.message });
324
+ }
325
+
326
+ this.saveTask(taskId, task);
327
+ return true;
328
+ }
329
+
330
+ // ===== TASK EXECUTION =====
331
+
332
+ async processTaskQueue() {
333
+ this.isProcessing = true;
334
+
335
+ try {
336
+ const now = Date.now();
337
+ const tasksToExecute = [];
338
+
339
+ // Find tasks ready for execution
340
+ for (const [taskId, task] of this.scheduledTasks) {
341
+ if (task.status === 'scheduled' && task.executeAt <= now) {
342
+ tasksToExecute.push(task);
343
+ }
344
+ }
345
+
346
+ // Execute tasks
347
+ for (const task of tasksToExecute) {
348
+ try {
349
+ await this.executeTask(task);
350
+ this.scheduledTasks.delete(task.id);
351
+ this.removeTask(task.id);
352
+ } catch (error) {
353
+ console.error(`Task execution failed for ${task.id}:`, error);
354
+ task.status = 'failed';
355
+ task.error = error.message;
356
+ this.saveTask(task.id, task);
357
+ }
358
+ }
359
+ } finally {
360
+ this.isProcessing = false;
361
+ }
362
+ }
363
+
364
+ async executeTask(task) {
365
+ switch (task.type) {
366
+ case 'message':
367
+ case 'conditional_message':
368
+ await this.client.sendMessage(task.chatId, task.message, task.options);
369
+ break;
370
+
371
+ case 'custom_action':
372
+ await this.executeCustomAction(task.actionName, task.data);
373
+ break;
374
+
375
+ default:
376
+ throw new Error(`Unknown task type: ${task.type}`);
377
+ }
378
+
379
+ // Log execution
380
+ console.log(`✅ Task executed: ${task.id} (${task.type})`);
381
+ }
382
+
383
+ async executeCustomAction(actionName, data) {
384
+ // This method can be extended to handle custom actions
385
+ if (typeof this.client[actionName] === 'function') {
386
+ await this.client[actionName](data);
387
+ } else {
388
+ throw new Error(`Custom action not found: ${actionName}`);
389
+ }
390
+ }
391
+
392
+ // ===== CRON JOB MANAGEMENT =====
393
+
394
+ createCronJob(taskId, pattern, action, data) {
395
+ try {
396
+ const job = cron.schedule(pattern, async () => {
397
+ try {
398
+ if (action === 'sendMessage') {
399
+ await this.client.sendMessage(data.chatId, data.message, data.options);
400
+ } else {
401
+ await this.executeCustomAction(action, data);
402
+ }
403
+ console.log(`🔄 Recurring task executed: ${taskId}`);
404
+ } catch (error) {
405
+ console.error(`Recurring task failed: ${taskId}`, error);
406
+ }
407
+ }, {
408
+ scheduled: true
409
+ });
410
+
411
+ this.cronJobs.set(taskId, job);
412
+ return job;
413
+ } catch (error) {
414
+ console.error(`Failed to create cron job: ${taskId}`, error);
415
+ throw error;
416
+ }
417
+ }
418
+
419
+ // ===== CONDITION EVALUATION =====
420
+
421
+ async evaluateCondition(condition) {
422
+ try {
423
+ if (typeof condition === 'function') {
424
+ return await condition();
425
+ }
426
+
427
+ if (typeof condition === 'object') {
428
+ return await this.evaluateObjectCondition(condition);
429
+ }
430
+
431
+ return Boolean(condition);
432
+ } catch (error) {
433
+ console.error('Condition evaluation failed:', error);
434
+ return false;
435
+ }
436
+ }
437
+
438
+ async evaluateObjectCondition(condition) {
439
+ const { type, ...params } = condition;
440
+
441
+ switch (type) {
442
+ case 'time':
443
+ return this.checkTimeCondition(params);
444
+ case 'user_online':
445
+ return await this.checkUserOnlineCondition(params);
446
+ case 'message_count':
447
+ return await this.checkMessageCountCondition(params);
448
+ default:
449
+ return false;
450
+ }
451
+ }
452
+
453
+ checkTimeCondition(params) {
454
+ const now = new Date();
455
+ const { hour, minute = 0, dayOfWeek } = params;
456
+
457
+ if (dayOfWeek !== undefined && now.getDay() !== dayOfWeek) {
458
+ return false;
459
+ }
460
+
461
+ return now.getHours() === hour && now.getMinutes() >= minute;
462
+ }
463
+
464
+ async checkUserOnlineCondition(params) {
465
+ // This would need to be implemented based on your WhatsApp client's capabilities
466
+ // For now, return true as a placeholder
467
+ return true;
468
+ }
469
+
470
+ async checkMessageCountCondition(params) {
471
+ // This would check message count in a chat
472
+ // Implementation depends on your storage system
473
+ return true;
474
+ }
475
+
476
+ // ===== UTILITY METHODS =====
477
+
478
+ generateTaskId() {
479
+ return `task_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
480
+ }
481
+
482
+ saveTask(taskId, task) {
483
+ const tasks = this.storage.read.from("scheduler").get("tasks") || {};
484
+ tasks[taskId] = task;
485
+ this.storage.write.to("scheduler").set("tasks", tasks);
486
+ }
487
+
488
+ saveCronTask(taskId, task) {
489
+ const cronTasks = this.storage.read.from("scheduler").get("cronTasks") || {};
490
+ cronTasks[taskId] = task;
491
+ this.storage.write.to("scheduler").set("cronTasks", cronTasks);
492
+ }
493
+
494
+ removeTask(taskId) {
495
+ const tasks = this.storage.read.from("scheduler").get("tasks") || {};
496
+ const cronTasks = this.storage.read.from("scheduler").get("cronTasks") || {};
497
+
498
+ delete tasks[taskId];
499
+ delete cronTasks[taskId];
500
+
501
+ this.storage.write.to("scheduler").set("tasks", tasks);
502
+ this.storage.write.to("scheduler").set("cronTasks", cronTasks);
503
+ }
504
+
505
+ cleanupExpiredTasks() {
506
+ const now = Date.now();
507
+ const expiredTasks = [];
508
+
509
+ for (const [taskId, task] of this.scheduledTasks) {
510
+ if (task.status === 'failed' || (task.executeAt && task.executeAt < now - 86400000)) { // 24 hours old
511
+ expiredTasks.push(taskId);
512
+ }
513
+ }
514
+
515
+ expiredTasks.forEach(taskId => {
516
+ this.scheduledTasks.delete(taskId);
517
+ this.removeTask(taskId);
518
+ });
519
+
520
+ console.log(`🧹 Cleaned up ${expiredTasks.length} expired tasks`);
521
+ }
522
+
523
+ // ===== QUERY METHODS =====
524
+
525
+ getAllTasks() {
526
+ return {
527
+ scheduled: Array.from(this.scheduledTasks.values()),
528
+ recurring: Array.from(this.recurringTasks.values())
529
+ };
530
+ }
531
+
532
+ getTask(taskId) {
533
+ return this.scheduledTasks.get(taskId) || this.recurringTasks.get(taskId);
534
+ }
535
+
536
+ getTasksByChat(chatId) {
537
+ const scheduled = Array.from(this.scheduledTasks.values()).filter(task => task.chatId === chatId);
538
+ const recurring = Array.from(this.recurringTasks.values()).filter(task => task.chatId === chatId);
539
+
540
+ return { scheduled, recurring };
541
+ }
542
+
543
+ getTasksByStatus(status) {
544
+ const scheduled = Array.from(this.scheduledTasks.values()).filter(task => task.status === status);
545
+ const recurring = Array.from(this.recurringTasks.values()).filter(task => task.status === status);
546
+
547
+ return { scheduled, recurring };
548
+ }
549
+
550
+ getUpcomingTasks(limit = 10) {
551
+ return Array.from(this.scheduledTasks.values())
552
+ .filter(task => task.status === 'scheduled')
553
+ .sort((a, b) => a.executeAt - b.executeAt)
554
+ .slice(0, limit);
555
+ }
556
+
557
+ // ===== STATISTICS =====
558
+
559
+ getSchedulerStats() {
560
+ const scheduled = this.scheduledTasks.size;
561
+ const recurring = this.recurringTasks.size;
562
+ const cronJobs = this.cronJobs.size;
563
+
564
+ const statusCounts = {};
565
+ for (const task of [...this.scheduledTasks.values(), ...this.recurringTasks.values()]) {
566
+ statusCounts[task.status] = (statusCounts[task.status] || 0) + 1;
567
+ }
568
+
569
+ return {
570
+ totalTasks: scheduled + recurring,
571
+ scheduledTasks: scheduled,
572
+ recurringTasks: recurring,
573
+ activeCronJobs: cronJobs,
574
+ statusBreakdown: statusCounts
575
+ };
576
+ }
577
+ }