waengine 1.0.7 → 1.0.9

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,322 @@
1
+ // Scheduler System für WAEngine
2
+ import cron from 'node-cron';
3
+
4
+ export class Scheduler {
5
+ constructor(client) {
6
+ this.client = client;
7
+ this.jobs = new Map();
8
+ this.storage = client.storage;
9
+
10
+ // Gespeicherte Jobs beim Start laden
11
+ this.loadScheduledJobs();
12
+
13
+ console.log('📅 Scheduler initialisiert');
14
+ }
15
+
16
+ // ===== CRON SCHEDULING =====
17
+
18
+ schedule(cronExpression, chatId, message, options = {}) {
19
+ const jobId = options.id || `job_${Date.now()}`;
20
+
21
+ if (!cron.validate(cronExpression)) {
22
+ throw new Error(`❌ Ungültiger Cron-Ausdruck: ${cronExpression}`);
23
+ }
24
+
25
+ const job = cron.schedule(cronExpression, async () => {
26
+ try {
27
+ await this.executeJob(chatId, message, options);
28
+ } catch (error) {
29
+ console.error(`❌ Scheduled Job Fehler (${jobId}):`, error);
30
+ }
31
+ }, {
32
+ scheduled: false,
33
+ timezone: options.timezone || 'Europe/Berlin'
34
+ });
35
+
36
+ // Job speichern
37
+ const jobData = {
38
+ id: jobId,
39
+ type: 'cron',
40
+ cronExpression,
41
+ chatId,
42
+ message,
43
+ options,
44
+ createdAt: new Date().toISOString(),
45
+ active: true
46
+ };
47
+
48
+ this.jobs.set(jobId, { job, data: jobData });
49
+ this.saveJob(jobData);
50
+
51
+ // Job starten
52
+ job.start();
53
+
54
+ console.log(`📅 Cron Job erstellt: ${jobId} (${cronExpression})`);
55
+ return jobId;
56
+ }
57
+
58
+ // ===== ONE-TIME SCHEDULING =====
59
+
60
+ scheduleOnce(date, chatId, message, options = {}) {
61
+ const jobId = options.id || `once_${Date.now()}`;
62
+ const targetDate = new Date(date);
63
+ const now = new Date();
64
+
65
+ if (targetDate <= now) {
66
+ throw new Error('❌ Datum muss in der Zukunft liegen!');
67
+ }
68
+
69
+ const delay = targetDate - now;
70
+
71
+ const timeout = setTimeout(async () => {
72
+ try {
73
+ await this.executeJob(chatId, message, options);
74
+ this.removeJob(jobId); // Job nach Ausführung entfernen
75
+ } catch (error) {
76
+ console.error(`❌ Scheduled Job Fehler (${jobId}):`, error);
77
+ }
78
+ }, delay);
79
+
80
+ // Job speichern
81
+ const jobData = {
82
+ id: jobId,
83
+ type: 'once',
84
+ scheduledFor: targetDate.toISOString(),
85
+ chatId,
86
+ message,
87
+ options,
88
+ createdAt: new Date().toISOString(),
89
+ active: true
90
+ };
91
+
92
+ this.jobs.set(jobId, { timeout, data: jobData });
93
+ this.saveJob(jobData);
94
+
95
+ console.log(`📅 One-time Job erstellt: ${jobId} (${targetDate})`);
96
+ return jobId;
97
+ }
98
+
99
+ // ===== CONVENIENCE METHODS =====
100
+
101
+ daily(time, chatId, message, options = {}) {
102
+ const [hour, minute] = time.split(':');
103
+ const cronExpression = `${minute || 0} ${hour} * * *`;
104
+ return this.schedule(cronExpression, chatId, message, { ...options, type: 'daily' });
105
+ }
106
+
107
+ weekly(day, time, chatId, message, options = {}) {
108
+ const [hour, minute] = time.split(':');
109
+ const dayMap = {
110
+ 'monday': 1, 'tuesday': 2, 'wednesday': 3, 'thursday': 4,
111
+ 'friday': 5, 'saturday': 6, 'sunday': 0
112
+ };
113
+ const dayNum = dayMap[day.toLowerCase()] ?? day;
114
+ const cronExpression = `${minute || 0} ${hour} * * ${dayNum}`;
115
+ return this.schedule(cronExpression, chatId, message, { ...options, type: 'weekly' });
116
+ }
117
+
118
+ monthly(day, time, chatId, message, options = {}) {
119
+ const [hour, minute] = time.split(':');
120
+ const cronExpression = `${minute || 0} ${hour} ${day} * *`;
121
+ return this.schedule(cronExpression, chatId, message, { ...options, type: 'monthly' });
122
+ }
123
+
124
+ // ===== WAITING SYSTEM =====
125
+
126
+ createWaiting() {
127
+ return {
128
+ after: {
129
+ message: (ms) => {
130
+ return new Promise(resolve => {
131
+ setTimeout(resolve, ms);
132
+ });
133
+ }
134
+ }
135
+ };
136
+ }
137
+
138
+ // ===== JOB EXECUTION =====
139
+
140
+ async executeJob(chatId, message, options = {}) {
141
+ try {
142
+ // Message kann String oder Function sein
143
+ let finalMessage = message;
144
+
145
+ if (typeof message === 'function') {
146
+ finalMessage = await message();
147
+ }
148
+
149
+ // Template-Variablen ersetzen
150
+ if (typeof finalMessage === 'string') {
151
+ finalMessage = this.processTemplate(finalMessage);
152
+ }
153
+
154
+ // Nachricht senden
155
+ await this.client.socket.sendMessage(chatId, { text: finalMessage });
156
+
157
+ console.log(`📅 Scheduled message sent to ${chatId}: ${finalMessage.substring(0, 50)}...`);
158
+
159
+ // Statistik speichern
160
+ this.storage.write.in("scheduler-stats").increment("totalExecuted", 1);
161
+
162
+ } catch (error) {
163
+ console.error('❌ Job Execution Fehler:', error);
164
+ throw error;
165
+ }
166
+ }
167
+
168
+ // ===== TEMPLATE PROCESSING =====
169
+
170
+ processTemplate(text) {
171
+ const now = new Date();
172
+
173
+ return text
174
+ .replace('{time}', now.toLocaleTimeString('de-DE'))
175
+ .replace('{date}', now.toLocaleDateString('de-DE'))
176
+ .replace('{datetime}', now.toLocaleString('de-DE'))
177
+ .replace('{day}', now.toLocaleDateString('de-DE', { weekday: 'long' }))
178
+ .replace('{timestamp}', now.getTime());
179
+ }
180
+
181
+ // ===== JOB MANAGEMENT =====
182
+
183
+ removeJob(jobId) {
184
+ const jobEntry = this.jobs.get(jobId);
185
+
186
+ if (!jobEntry) {
187
+ return false;
188
+ }
189
+
190
+ // Job stoppen
191
+ if (jobEntry.job) {
192
+ jobEntry.job.stop();
193
+ } else if (jobEntry.timeout) {
194
+ clearTimeout(jobEntry.timeout);
195
+ }
196
+
197
+ // Aus Memory und Storage entfernen
198
+ this.jobs.delete(jobId);
199
+ this.deleteJob(jobId);
200
+
201
+ console.log(`📅 Job entfernt: ${jobId}`);
202
+ return true;
203
+ }
204
+
205
+ pauseJob(jobId) {
206
+ const jobEntry = this.jobs.get(jobId);
207
+
208
+ if (jobEntry?.job) {
209
+ jobEntry.job.stop();
210
+ jobEntry.data.active = false;
211
+ this.saveJob(jobEntry.data);
212
+ return true;
213
+ }
214
+
215
+ return false;
216
+ }
217
+
218
+ resumeJob(jobId) {
219
+ const jobEntry = this.jobs.get(jobId);
220
+
221
+ if (jobEntry?.job) {
222
+ jobEntry.job.start();
223
+ jobEntry.data.active = true;
224
+ this.saveJob(jobEntry.data);
225
+ return true;
226
+ }
227
+
228
+ return false;
229
+ }
230
+
231
+ // ===== STORAGE METHODS =====
232
+
233
+ saveJob(jobData) {
234
+ const jobs = this.storage.read.from("scheduled-jobs").all() || [];
235
+ const existingIndex = jobs.findIndex(job => job.id === jobData.id);
236
+
237
+ if (existingIndex >= 0) {
238
+ jobs[existingIndex] = jobData;
239
+ } else {
240
+ jobs.push(jobData);
241
+ }
242
+
243
+ this.storage.write.in("scheduled-jobs").data(jobs);
244
+ }
245
+
246
+ deleteJob(jobId) {
247
+ const jobs = this.storage.read.from("scheduled-jobs").all() || [];
248
+ const updatedJobs = jobs.filter(job => job.id !== jobId);
249
+ this.storage.write.in("scheduled-jobs").data(updatedJobs);
250
+ }
251
+
252
+ loadScheduledJobs() {
253
+ const jobs = this.storage.read.from("scheduled-jobs").all() || [];
254
+
255
+ for (const jobData of jobs) {
256
+ try {
257
+ if (jobData.type === 'cron' && jobData.active) {
258
+ // Cron Jobs wieder starten
259
+ const job = cron.schedule(jobData.cronExpression, async () => {
260
+ await this.executeJob(jobData.chatId, jobData.message, jobData.options);
261
+ }, {
262
+ scheduled: true,
263
+ timezone: jobData.options.timezone || 'Europe/Berlin'
264
+ });
265
+
266
+ this.jobs.set(jobData.id, { job, data: jobData });
267
+ console.log(`📅 Cron Job wiederhergestellt: ${jobData.id}`);
268
+
269
+ } else if (jobData.type === 'once') {
270
+ // One-time Jobs prüfen ob noch gültig
271
+ const targetDate = new Date(jobData.scheduledFor);
272
+ const now = new Date();
273
+
274
+ if (targetDate > now) {
275
+ const delay = targetDate - now;
276
+ const timeout = setTimeout(async () => {
277
+ await this.executeJob(jobData.chatId, jobData.message, jobData.options);
278
+ this.removeJob(jobData.id);
279
+ }, delay);
280
+
281
+ this.jobs.set(jobData.id, { timeout, data: jobData });
282
+ console.log(`📅 One-time Job wiederhergestellt: ${jobData.id}`);
283
+ } else {
284
+ // Abgelaufene Jobs entfernen
285
+ this.deleteJob(jobData.id);
286
+ }
287
+ }
288
+ } catch (error) {
289
+ console.error(`❌ Fehler beim Laden von Job ${jobData.id}:`, error);
290
+ }
291
+ }
292
+ }
293
+
294
+ // ===== STATISTICS =====
295
+
296
+ getStats() {
297
+ const jobs = Array.from(this.jobs.values());
298
+ const scheduledJobs = this.storage.read.from("scheduled-jobs").all() || [];
299
+ const stats = this.storage.read.from("scheduler-stats").all() || {};
300
+
301
+ return {
302
+ activeJobs: jobs.length,
303
+ totalJobs: scheduledJobs.length,
304
+ cronJobs: jobs.filter(j => j.data.type === 'cron').length,
305
+ onceJobs: jobs.filter(j => j.data.type === 'once').length,
306
+ totalExecuted: stats.totalExecuted || 0,
307
+ jobs: jobs.map(j => ({
308
+ id: j.data.id,
309
+ type: j.data.type,
310
+ chatId: j.data.chatId,
311
+ active: j.data.active,
312
+ createdAt: j.data.createdAt
313
+ }))
314
+ };
315
+ }
316
+
317
+ // ===== JOB LISTING =====
318
+
319
+ listJobs() {
320
+ return Array.from(this.jobs.values()).map(jobEntry => jobEntry.data);
321
+ }
322
+ }