waengine 1.0.7 → 1.0.8

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "waengine",
3
- "version": "1.0.7",
3
+ "version": "1.0.8",
4
4
  "description": "🚀 WAEngine - The most powerful WhatsApp Bot Library with Multi-Device Support & EasyBot API",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -50,6 +50,8 @@
50
50
  },
51
51
  "dependencies": {
52
52
  "@whiskeysockets/baileys": "^7.0.0-rc.9",
53
+ "axios": "^1.13.4",
54
+ "node-cron": "^3.0.3",
53
55
  "pino": "^8.0.0",
54
56
  "playwright": "^1.58.1",
55
57
  "qrcode": "^1.5.4",
@@ -0,0 +1,185 @@
1
+ // AI Integration für WAEngine
2
+ import axios from 'axios';
3
+
4
+ export class AIIntegration {
5
+ constructor(options = {}) {
6
+ this.apiKey = options.apiKey || process.env.OPENAI_API_KEY;
7
+ this.baseURL = options.baseURL || 'https://api.openai.com/v1';
8
+ this.model = options.model || 'gpt-3.5-turbo';
9
+ this.enabled = !!this.apiKey;
10
+
11
+ if (!this.enabled) {
12
+ console.log('⚠️ AI Integration deaktiviert - Kein API Key gefunden');
13
+ } else {
14
+ console.log('🤖 AI Integration aktiviert');
15
+ }
16
+ }
17
+
18
+ // ===== CHAT COMPLETION =====
19
+
20
+ async chat(prompt, options = {}) {
21
+ if (!this.enabled) {
22
+ throw new Error('❌ AI Integration nicht konfiguriert! Setze OPENAI_API_KEY');
23
+ }
24
+
25
+ try {
26
+ const response = await axios.post(`${this.baseURL}/chat/completions`, {
27
+ model: options.model || this.model,
28
+ messages: [
29
+ {
30
+ role: 'system',
31
+ content: options.systemPrompt || 'Du bist ein hilfreicher WhatsApp Bot Assistent. Antworte kurz und freundlich auf Deutsch.'
32
+ },
33
+ {
34
+ role: 'user',
35
+ content: prompt
36
+ }
37
+ ],
38
+ max_tokens: options.maxTokens || 500,
39
+ temperature: options.temperature || 0.7
40
+ }, {
41
+ headers: {
42
+ 'Authorization': `Bearer ${this.apiKey}`,
43
+ 'Content-Type': 'application/json'
44
+ }
45
+ });
46
+
47
+ return response.data.choices[0].message.content.trim();
48
+ } catch (error) {
49
+ console.error('❌ AI Chat Fehler:', error.response?.data || error.message);
50
+ throw new Error(`AI Fehler: ${error.response?.data?.error?.message || error.message}`);
51
+ }
52
+ }
53
+
54
+ // ===== SMART RESPONSES =====
55
+
56
+ async smartReply(message, context = {}) {
57
+ const prompt = `
58
+ Kontext: WhatsApp Bot Nachricht
59
+ User schrieb: "${message}"
60
+ Chat-Type: ${context.isGroup ? 'Gruppe' : 'Privat'}
61
+ ${context.userName ? `User: ${context.userName}` : ''}
62
+
63
+ Antworte natürlich und hilfreich. Maximal 2 Sätze.
64
+ `.trim();
65
+
66
+ return await this.chat(prompt, {
67
+ systemPrompt: 'Du bist ein freundlicher WhatsApp Bot. Antworte natürlich und kurz.',
68
+ maxTokens: 200
69
+ });
70
+ }
71
+
72
+ // ===== TEXT ANALYSIS =====
73
+
74
+ async analyzeMessage(text) {
75
+ const prompt = `
76
+ Analysiere diese Nachricht:
77
+ "${text}"
78
+
79
+ Gib zurück als JSON:
80
+ {
81
+ "sentiment": "positive/negative/neutral",
82
+ "language": "de/en/es/fr/etc",
83
+ "category": "question/command/greeting/complaint/other",
84
+ "toxicity": "low/medium/high",
85
+ "confidence": 0.0-1.0
86
+ }
87
+ `.trim();
88
+
89
+ try {
90
+ const response = await this.chat(prompt, {
91
+ systemPrompt: 'Du bist ein Text-Analyse-Experte. Antworte nur mit gültigem JSON.',
92
+ maxTokens: 150
93
+ });
94
+
95
+ return JSON.parse(response);
96
+ } catch (error) {
97
+ return {
98
+ sentiment: 'neutral',
99
+ language: 'unknown',
100
+ category: 'other',
101
+ toxicity: 'low',
102
+ confidence: 0.5
103
+ };
104
+ }
105
+ }
106
+
107
+ // ===== MODERATION =====
108
+
109
+ async moderateContent(text) {
110
+ try {
111
+ const response = await axios.post(`${this.baseURL}/moderations`, {
112
+ input: text
113
+ }, {
114
+ headers: {
115
+ 'Authorization': `Bearer ${this.apiKey}`,
116
+ 'Content-Type': 'application/json'
117
+ }
118
+ });
119
+
120
+ const result = response.data.results[0];
121
+
122
+ return {
123
+ flagged: result.flagged,
124
+ categories: result.categories,
125
+ scores: result.category_scores,
126
+ safe: !result.flagged
127
+ };
128
+ } catch (error) {
129
+ console.error('❌ Moderation Fehler:', error.message);
130
+ return { flagged: false, safe: true };
131
+ }
132
+ }
133
+
134
+ // ===== TRANSLATION =====
135
+
136
+ async translate(text, targetLang = 'de') {
137
+ const prompt = `Übersetze folgenden Text nach ${targetLang}. Antworte nur mit der Übersetzung:\n\n"${text}"`;
138
+
139
+ return await this.chat(prompt, {
140
+ systemPrompt: 'Du bist ein professioneller Übersetzer. Antworte nur mit der Übersetzung.',
141
+ maxTokens: 300
142
+ });
143
+ }
144
+
145
+ // ===== SUMMARIZATION =====
146
+
147
+ async summarize(text, maxLength = 100) {
148
+ const prompt = `Fasse folgenden Text in maximal ${maxLength} Zeichen zusammen:\n\n"${text}"`;
149
+
150
+ return await this.chat(prompt, {
151
+ systemPrompt: 'Du bist ein Experte für Textzusammenfassungen. Sei präzise und kurz.',
152
+ maxTokens: Math.ceil(maxLength / 2)
153
+ });
154
+ }
155
+
156
+ // ===== QUESTION ANSWERING =====
157
+
158
+ async answerQuestion(question, context = '') {
159
+ const prompt = context ?
160
+ `Kontext: ${context}\n\nFrage: ${question}` :
161
+ question;
162
+
163
+ return await this.chat(prompt, {
164
+ systemPrompt: 'Du bist ein hilfreicher Assistent. Beantworte Fragen präzise und verständlich.',
165
+ maxTokens: 400
166
+ });
167
+ }
168
+
169
+ // ===== CREATIVE WRITING =====
170
+
171
+ async generateText(prompt, style = 'normal') {
172
+ const stylePrompts = {
173
+ funny: 'Schreibe lustig und humorvoll',
174
+ formal: 'Schreibe formal und professionell',
175
+ casual: 'Schreibe locker und umgangssprachlich',
176
+ creative: 'Schreibe kreativ und fantasievoll',
177
+ normal: 'Schreibe natürlich und verständlich'
178
+ };
179
+
180
+ return await this.chat(prompt, {
181
+ systemPrompt: `Du bist ein kreativer Schreiber. ${stylePrompts[style] || stylePrompts.normal}.`,
182
+ maxTokens: 600
183
+ });
184
+ }
185
+ }
package/src/client.js CHANGED
@@ -4,6 +4,10 @@ import { generateQRCode, closeBrowser } from "./qr.js";
4
4
  import { Message } from "./message.js";
5
5
  import { SessionManager } from "./session-manager.js";
6
6
  import { PrefixManager } from "./prefix-manager.js";
7
+ import { getStorage } from "./storage.js";
8
+ import { AIIntegration } from "./ai-integration.js";
9
+ import { HTTPClient } from "./http-client.js";
10
+ import { Scheduler } from "./scheduler.js";
7
11
 
8
12
  export class WhatsAppClient {
9
13
  constructor(options = {}) {
@@ -30,6 +34,21 @@ export class WhatsAppClient {
30
34
  this.prefixManager = new PrefixManager('./data');
31
35
  this.commands = new Map();
32
36
 
37
+ // Storage System
38
+ this.storage = getStorage();
39
+
40
+ // AI Integration
41
+ this.ai = new AIIntegration(options.ai || {});
42
+
43
+ // HTTP Client
44
+ this.http = new HTTPClient(options.http || {});
45
+
46
+ // Scheduler System
47
+ this.scheduler = new Scheduler(this);
48
+
49
+ // Waiting System für Messages
50
+ this.waiting = this.scheduler.createWaiting();
51
+
33
52
  // Deine eigenen API-Objekte
34
53
  this.get = new GetAPI(this);
35
54
  this.add = new AddAPI(this);
@@ -0,0 +1,276 @@
1
+ // HTTP Client für WAEngine
2
+ import axios from 'axios';
3
+
4
+ export class HTTPClient {
5
+ constructor(options = {}) {
6
+ this.timeout = options.timeout || 10000;
7
+ this.retries = options.retries || 3;
8
+ this.baseHeaders = options.headers || {};
9
+
10
+ // Axios Instance erstellen
11
+ this.axios = axios.create({
12
+ timeout: this.timeout,
13
+ headers: this.baseHeaders
14
+ });
15
+
16
+ console.log('🌐 HTTP Client initialisiert');
17
+ }
18
+
19
+ // ===== BASIC HTTP METHODS =====
20
+
21
+ async get(url, options = {}) {
22
+ return await this.request('GET', url, null, options);
23
+ }
24
+
25
+ async post(url, data = null, options = {}) {
26
+ return await this.request('POST', url, data, options);
27
+ }
28
+
29
+ async put(url, data = null, options = {}) {
30
+ return await this.request('PUT', url, data, options);
31
+ }
32
+
33
+ async delete(url, options = {}) {
34
+ return await this.request('DELETE', url, null, options);
35
+ }
36
+
37
+ // ===== CORE REQUEST METHOD =====
38
+
39
+ async request(method, url, data = null, options = {}) {
40
+ let lastError;
41
+
42
+ for (let attempt = 1; attempt <= this.retries; attempt++) {
43
+ try {
44
+ const config = {
45
+ method,
46
+ url,
47
+ ...options
48
+ };
49
+
50
+ if (data) {
51
+ config.data = data;
52
+ }
53
+
54
+ const response = await this.axios(config);
55
+ return response.data;
56
+
57
+ } catch (error) {
58
+ lastError = error;
59
+
60
+ if (attempt < this.retries && this.shouldRetry(error)) {
61
+ console.log(`🔄 HTTP Retry ${attempt}/${this.retries} für ${url}`);
62
+ await this.delay(1000 * attempt); // Exponential backoff
63
+ } else {
64
+ break;
65
+ }
66
+ }
67
+ }
68
+
69
+ throw new Error(`HTTP Request failed: ${lastError.message}`);
70
+ }
71
+
72
+ shouldRetry(error) {
73
+ // Retry bei Netzwerk-Fehlern oder 5xx Status Codes
74
+ return !error.response || error.response.status >= 500;
75
+ }
76
+
77
+ delay(ms) {
78
+ return new Promise(resolve => setTimeout(resolve, ms));
79
+ }
80
+
81
+ // ===== API WRAPPERS =====
82
+
83
+ // Weather API
84
+ async getWeather(city, apiKey = process.env.WEATHER_API_KEY) {
85
+ if (!apiKey) {
86
+ throw new Error('❌ Weather API Key fehlt! Setze WEATHER_API_KEY');
87
+ }
88
+
89
+ try {
90
+ const data = await this.get(`https://api.openweathermap.org/data/2.5/weather`, {
91
+ params: {
92
+ q: city,
93
+ appid: apiKey,
94
+ units: 'metric',
95
+ lang: 'de'
96
+ }
97
+ });
98
+
99
+ return {
100
+ city: data.name,
101
+ country: data.sys.country,
102
+ temperature: Math.round(data.main.temp),
103
+ description: data.weather[0].description,
104
+ humidity: data.main.humidity,
105
+ windSpeed: data.wind.speed,
106
+ icon: data.weather[0].icon
107
+ };
108
+ } catch (error) {
109
+ throw new Error(`Wetter-Fehler: ${error.message}`);
110
+ }
111
+ }
112
+
113
+ // News API
114
+ async getNews(category = 'general', apiKey = process.env.NEWS_API_KEY) {
115
+ if (!apiKey) {
116
+ throw new Error('❌ News API Key fehlt! Setze NEWS_API_KEY');
117
+ }
118
+
119
+ try {
120
+ const data = await this.get(`https://newsapi.org/v2/top-headlines`, {
121
+ params: {
122
+ country: 'de',
123
+ category: category,
124
+ apiKey: apiKey,
125
+ pageSize: 5
126
+ }
127
+ });
128
+
129
+ return data.articles.map(article => ({
130
+ title: article.title,
131
+ description: article.description,
132
+ url: article.url,
133
+ source: article.source.name,
134
+ publishedAt: new Date(article.publishedAt).toLocaleDateString('de-DE')
135
+ }));
136
+ } catch (error) {
137
+ throw new Error(`News-Fehler: ${error.message}`);
138
+ }
139
+ }
140
+
141
+ // Crypto Prices
142
+ async getCryptoPrice(symbol = 'bitcoin') {
143
+ try {
144
+ const data = await this.get(`https://api.coingecko.com/api/v3/simple/price`, {
145
+ params: {
146
+ ids: symbol,
147
+ vs_currencies: 'eur,usd',
148
+ include_24hr_change: true
149
+ }
150
+ });
151
+
152
+ const coin = data[symbol];
153
+ return {
154
+ symbol: symbol,
155
+ priceEUR: coin.eur,
156
+ priceUSD: coin.usd,
157
+ change24h: coin.eur_24h_change?.toFixed(2) || 0
158
+ };
159
+ } catch (error) {
160
+ throw new Error(`Crypto-Fehler: ${error.message}`);
161
+ }
162
+ }
163
+
164
+ // QR Code Generator
165
+ async generateQRCode(text, size = 200) {
166
+ try {
167
+ const url = `https://api.qrserver.com/v1/create-qr-code/?size=${size}x${size}&data=${encodeURIComponent(text)}`;
168
+
169
+ // Return URL instead of downloading
170
+ return {
171
+ url: url,
172
+ text: text,
173
+ size: size
174
+ };
175
+ } catch (error) {
176
+ throw new Error(`QR-Code-Fehler: ${error.message}`);
177
+ }
178
+ }
179
+
180
+ // URL Shortener
181
+ async shortenUrl(longUrl, apiKey = process.env.BITLY_API_KEY) {
182
+ if (!apiKey) {
183
+ // Fallback zu kostenlosem Service
184
+ try {
185
+ const data = await this.post('https://is.gd/create.php', null, {
186
+ params: {
187
+ format: 'json',
188
+ url: longUrl
189
+ }
190
+ });
191
+
192
+ return {
193
+ shortUrl: data.shorturl,
194
+ longUrl: longUrl,
195
+ service: 'is.gd'
196
+ };
197
+ } catch (error) {
198
+ throw new Error(`URL-Shortener-Fehler: ${error.message}`);
199
+ }
200
+ }
201
+
202
+ // Bitly API
203
+ try {
204
+ const data = await this.post('https://api-ssl.bitly.com/v4/shorten', {
205
+ long_url: longUrl
206
+ }, {
207
+ headers: {
208
+ 'Authorization': `Bearer ${apiKey}`,
209
+ 'Content-Type': 'application/json'
210
+ }
211
+ });
212
+
213
+ return {
214
+ shortUrl: data.link,
215
+ longUrl: longUrl,
216
+ service: 'bitly'
217
+ };
218
+ } catch (error) {
219
+ throw new Error(`Bitly-Fehler: ${error.message}`);
220
+ }
221
+ }
222
+
223
+ // Random Facts
224
+ async getRandomFact() {
225
+ try {
226
+ const data = await this.get('https://uselessfacts.jsph.pl/random.json?language=en');
227
+ return data.text;
228
+ } catch (error) {
229
+ throw new Error(`Random-Fact-Fehler: ${error.message}`);
230
+ }
231
+ }
232
+
233
+ // Random Joke
234
+ async getRandomJoke() {
235
+ try {
236
+ const data = await this.get('https://official-joke-api.appspot.com/random_joke');
237
+ return `${data.setup}\n\n${data.punchline}`;
238
+ } catch (error) {
239
+ throw new Error(`Joke-Fehler: ${error.message}`);
240
+ }
241
+ }
242
+
243
+ // IP Info
244
+ async getIPInfo(ip = '') {
245
+ try {
246
+ const url = ip ? `http://ip-api.com/json/${ip}` : 'http://ip-api.com/json/';
247
+ const data = await this.get(url);
248
+
249
+ return {
250
+ ip: data.query,
251
+ country: data.country,
252
+ city: data.city,
253
+ region: data.regionName,
254
+ timezone: data.timezone,
255
+ isp: data.isp
256
+ };
257
+ } catch (error) {
258
+ throw new Error(`IP-Info-Fehler: ${error.message}`);
259
+ }
260
+ }
261
+
262
+ // Website Screenshot (via API)
263
+ async getWebsiteScreenshot(url, width = 1280, height = 720) {
264
+ try {
265
+ const screenshotUrl = `https://api.screenshotmachine.com/?key=${process.env.SCREENSHOT_API_KEY}&url=${encodeURIComponent(url)}&dimension=${width}x${height}`;
266
+
267
+ return {
268
+ screenshotUrl: screenshotUrl,
269
+ originalUrl: url,
270
+ dimensions: `${width}x${height}`
271
+ };
272
+ } catch (error) {
273
+ throw new Error(`Screenshot-Fehler: ${error.message}`);
274
+ }
275
+ }
276
+ }
package/src/index.js CHANGED
@@ -2,6 +2,10 @@ export { WhatsAppClient } from "./client.js";
2
2
  export { MultiWhatsAppClient } from "./multi-client.js";
3
3
  export { DeviceManager } from "./device-manager.js";
4
4
  export { PrefixManager } from "./prefix-manager.js";
5
+ export { WAStorage, createStorage, getStorage, write, read, del } from "./storage.js";
6
+ export { AIIntegration } from "./ai-integration.js";
7
+ export { HTTPClient } from "./http-client.js";
8
+ export { Scheduler } from "./scheduler.js";
5
9
  export { EasyBot, createBot, createMultiBot, quickBot, bot, multiBot } from "./easy-bot.js";
6
10
  export { generateQRCode } from "./qr.js";
7
11
  export { Message } from "./message.js";
package/src/message.js CHANGED
@@ -1,4 +1,5 @@
1
1
  // Message-Klasse mit deinen eigenen Funktionen
2
+ import { getStorage } from "./storage.js";
2
3
 
3
4
  export class Message {
4
5
  constructor(client, data) {
@@ -17,6 +18,23 @@ export class Message {
17
18
  this.command = null;
18
19
  this.args = [];
19
20
  this.commandText = null;
21
+
22
+ // Storage System
23
+ this.storage = getStorage();
24
+ this.write = this.storage.write;
25
+ this.read = this.storage.read;
26
+ this.delete = this.storage.delete;
27
+
28
+ // Waiting System - DEINE COOLE API!
29
+ this.waiting = {
30
+ after: {
31
+ message: (ms) => {
32
+ return new Promise(resolve => {
33
+ setTimeout(resolve, ms);
34
+ });
35
+ }
36
+ }
37
+ };
20
38
  }
21
39
 
22
40
  // ===== REPLY FUNCTIONS =====
@@ -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
+ }
package/src/storage.js ADDED
@@ -0,0 +1,421 @@
1
+ // WAEngine Storage System - Einfaches Daten-Management
2
+ import fs from 'fs';
3
+ import path from 'path';
4
+
5
+ export class WAStorage {
6
+ constructor(baseDir = './waengine-data') {
7
+ this.baseDir = baseDir;
8
+ this.cache = new Map();
9
+ this.ensureBaseDir();
10
+
11
+ console.log(`💾 WAStorage initialisiert: ${baseDir}`);
12
+ }
13
+
14
+ // ===== SETUP =====
15
+
16
+ ensureBaseDir() {
17
+ if (!fs.existsSync(this.baseDir)) {
18
+ fs.mkdirSync(this.baseDir, { recursive: true });
19
+ console.log(`📁 Storage Directory erstellt: ${this.baseDir}`);
20
+ }
21
+ }
22
+
23
+ getFilePath(fileName) {
24
+ // Automatisch .json hinzufügen falls nicht vorhanden
25
+ if (!fileName.endsWith('.json')) {
26
+ fileName += '.json';
27
+ }
28
+ return path.join(this.baseDir, fileName);
29
+ }
30
+
31
+ // ===== WRITE SYSTEM =====
32
+
33
+ write = {
34
+ // write.in("datei") - Schreibt Daten in Datei
35
+ in: (fileName) => {
36
+ return {
37
+ // write.in("datei").data(object)
38
+ data: (data) => {
39
+ return this.writeData(fileName, data);
40
+ },
41
+
42
+ // write.in("datei").append(object)
43
+ append: (data) => {
44
+ return this.appendData(fileName, data);
45
+ },
46
+
47
+ // write.in("datei").set(key, value)
48
+ set: (key, value) => {
49
+ return this.setKey(fileName, key, value);
50
+ },
51
+
52
+ // write.in("datei").push(value) - Für Arrays
53
+ push: (value) => {
54
+ return this.pushToArray(fileName, value);
55
+ },
56
+
57
+ // write.in("datei").increment(key, amount)
58
+ increment: (key, amount = 1) => {
59
+ return this.incrementKey(fileName, key, amount);
60
+ }
61
+ };
62
+ }
63
+ };
64
+
65
+ // ===== READ SYSTEM =====
66
+
67
+ read = {
68
+ // read.from("datei")
69
+ from: (fileName) => {
70
+ return {
71
+ // read.from("datei").all()
72
+ all: () => {
73
+ return this.readData(fileName);
74
+ },
75
+
76
+ // read.from("datei").get(key)
77
+ get: (key) => {
78
+ return this.getKey(fileName, key);
79
+ },
80
+
81
+ // read.from("datei").keys()
82
+ keys: () => {
83
+ const data = this.readData(fileName);
84
+ return data ? Object.keys(data) : [];
85
+ },
86
+
87
+ // read.from("datei").values()
88
+ values: () => {
89
+ const data = this.readData(fileName);
90
+ return data ? Object.values(data) : [];
91
+ },
92
+
93
+ // read.from("datei").length() - Für Arrays
94
+ length: () => {
95
+ const data = this.readData(fileName);
96
+ return Array.isArray(data) ? data.length : 0;
97
+ },
98
+
99
+ // read.from("datei").exists()
100
+ exists: () => {
101
+ return this.fileExists(fileName);
102
+ }
103
+ };
104
+ }
105
+ };
106
+
107
+ // ===== DELETE SYSTEM =====
108
+
109
+ delete = {
110
+ // delete.from("datei")
111
+ from: (fileName) => {
112
+ return {
113
+ // delete.from("datei").all()
114
+ all: () => {
115
+ return this.deleteFile(fileName);
116
+ },
117
+
118
+ // delete.from("datei").key(key)
119
+ key: (key) => {
120
+ return this.deleteKey(fileName, key);
121
+ },
122
+
123
+ // delete.from("datei").where(condition)
124
+ where: (condition) => {
125
+ return this.deleteWhere(fileName, condition);
126
+ }
127
+ };
128
+ }
129
+ };
130
+
131
+ // ===== CORE METHODS =====
132
+
133
+ writeData(fileName, data) {
134
+ try {
135
+ const filePath = this.getFilePath(fileName);
136
+ fs.writeFileSync(filePath, JSON.stringify(data, null, 2));
137
+ this.cache.set(fileName, data);
138
+ console.log(`💾 Daten geschrieben: ${fileName}`);
139
+ return true;
140
+ } catch (error) {
141
+ console.error(`❌ Fehler beim Schreiben in ${fileName}:`, error);
142
+ return false;
143
+ }
144
+ }
145
+
146
+ readData(fileName) {
147
+ try {
148
+ // Cache prüfen
149
+ if (this.cache.has(fileName)) {
150
+ return this.cache.get(fileName);
151
+ }
152
+
153
+ const filePath = this.getFilePath(fileName);
154
+
155
+ if (!fs.existsSync(filePath)) {
156
+ return null;
157
+ }
158
+
159
+ const data = JSON.parse(fs.readFileSync(filePath, 'utf8'));
160
+ this.cache.set(fileName, data);
161
+ return data;
162
+ } catch (error) {
163
+ console.error(`❌ Fehler beim Lesen von ${fileName}:`, error);
164
+ return null;
165
+ }
166
+ }
167
+
168
+ appendData(fileName, newData) {
169
+ const existingData = this.readData(fileName) || [];
170
+
171
+ if (Array.isArray(existingData)) {
172
+ existingData.push(newData);
173
+ } else {
174
+ // Wenn Object, merge
175
+ Object.assign(existingData, newData);
176
+ }
177
+
178
+ return this.writeData(fileName, existingData);
179
+ }
180
+
181
+ setKey(fileName, key, value) {
182
+ let data = this.readData(fileName) || {};
183
+
184
+ // Nested keys unterstützen (z.B. "user.settings.theme")
185
+ if (key.includes('.')) {
186
+ const keys = key.split('.');
187
+ let current = data;
188
+
189
+ for (let i = 0; i < keys.length - 1; i++) {
190
+ if (!current[keys[i]]) {
191
+ current[keys[i]] = {};
192
+ }
193
+ current = current[keys[i]];
194
+ }
195
+
196
+ current[keys[keys.length - 1]] = value;
197
+ } else {
198
+ data[key] = value;
199
+ }
200
+
201
+ return this.writeData(fileName, data);
202
+ }
203
+
204
+ getKey(fileName, key) {
205
+ const data = this.readData(fileName);
206
+ if (!data) return null;
207
+
208
+ // Nested keys unterstützen
209
+ if (key.includes('.')) {
210
+ const keys = key.split('.');
211
+ let current = data;
212
+
213
+ for (const k of keys) {
214
+ if (current[k] === undefined) {
215
+ return null;
216
+ }
217
+ current = current[k];
218
+ }
219
+
220
+ return current;
221
+ }
222
+
223
+ return data[key];
224
+ }
225
+
226
+ pushToArray(fileName, value) {
227
+ let data = this.readData(fileName);
228
+
229
+ if (!data) {
230
+ data = [];
231
+ } else if (!Array.isArray(data)) {
232
+ console.error(`❌ ${fileName} ist kein Array!`);
233
+ return false;
234
+ }
235
+
236
+ data.push(value);
237
+ return this.writeData(fileName, data);
238
+ }
239
+
240
+ incrementKey(fileName, key, amount = 1) {
241
+ const currentValue = this.getKey(fileName, key) || 0;
242
+ const newValue = currentValue + amount;
243
+ return this.setKey(fileName, key, newValue);
244
+ }
245
+
246
+ deleteFile(fileName) {
247
+ try {
248
+ const filePath = this.getFilePath(fileName);
249
+
250
+ if (fs.existsSync(filePath)) {
251
+ fs.unlinkSync(filePath);
252
+ this.cache.delete(fileName);
253
+ console.log(`🗑️ Datei gelöscht: ${fileName}`);
254
+ return true;
255
+ }
256
+
257
+ return false;
258
+ } catch (error) {
259
+ console.error(`❌ Fehler beim Löschen von ${fileName}:`, error);
260
+ return false;
261
+ }
262
+ }
263
+
264
+ deleteKey(fileName, key) {
265
+ let data = this.readData(fileName);
266
+ if (!data) return false;
267
+
268
+ if (key.includes('.')) {
269
+ // Nested key löschen
270
+ const keys = key.split('.');
271
+ let current = data;
272
+
273
+ for (let i = 0; i < keys.length - 1; i++) {
274
+ if (!current[keys[i]]) {
275
+ return false;
276
+ }
277
+ current = current[keys[i]];
278
+ }
279
+
280
+ delete current[keys[keys.length - 1]];
281
+ } else {
282
+ delete data[key];
283
+ }
284
+
285
+ return this.writeData(fileName, data);
286
+ }
287
+
288
+ deleteWhere(fileName, condition) {
289
+ let data = this.readData(fileName);
290
+ if (!data) return false;
291
+
292
+ if (Array.isArray(data)) {
293
+ // Array filtern
294
+ const originalLength = data.length;
295
+ data = data.filter(item => !condition(item));
296
+
297
+ if (data.length !== originalLength) {
298
+ return this.writeData(fileName, data);
299
+ }
300
+ } else {
301
+ // Object filtern
302
+ let changed = false;
303
+
304
+ for (const [key, value] of Object.entries(data)) {
305
+ if (condition(value, key)) {
306
+ delete data[key];
307
+ changed = true;
308
+ }
309
+ }
310
+
311
+ if (changed) {
312
+ return this.writeData(fileName, data);
313
+ }
314
+ }
315
+
316
+ return false;
317
+ }
318
+
319
+ fileExists(fileName) {
320
+ const filePath = this.getFilePath(fileName);
321
+ return fs.existsSync(filePath);
322
+ }
323
+
324
+ // ===== UTILITY METHODS =====
325
+
326
+ // Alle Dateien auflisten
327
+ listFiles() {
328
+ try {
329
+ const files = fs.readdirSync(this.baseDir);
330
+ return files.filter(file => file.endsWith('.json')).map(file => file.replace('.json', ''));
331
+ } catch (error) {
332
+ console.error('❌ Fehler beim Auflisten der Dateien:', error);
333
+ return [];
334
+ }
335
+ }
336
+
337
+ // Cache leeren
338
+ clearCache() {
339
+ this.cache.clear();
340
+ console.log('🧹 Storage Cache geleert');
341
+ }
342
+
343
+ // Backup erstellen
344
+ backup(backupDir = './waengine-backup') {
345
+ try {
346
+ if (!fs.existsSync(backupDir)) {
347
+ fs.mkdirSync(backupDir, { recursive: true });
348
+ }
349
+
350
+ const files = fs.readdirSync(this.baseDir);
351
+ let backedUp = 0;
352
+
353
+ for (const file of files) {
354
+ const srcPath = path.join(this.baseDir, file);
355
+ const destPath = path.join(backupDir, file);
356
+ fs.copyFileSync(srcPath, destPath);
357
+ backedUp++;
358
+ }
359
+
360
+ console.log(`💾 Backup erstellt: ${backedUp} Dateien in ${backupDir}`);
361
+ return backupDir;
362
+ } catch (error) {
363
+ console.error('❌ Backup Fehler:', error);
364
+ return false;
365
+ }
366
+ }
367
+
368
+ // Storage Statistics
369
+ getStats() {
370
+ const files = this.listFiles();
371
+ let totalSize = 0;
372
+
373
+ for (const file of files) {
374
+ try {
375
+ const filePath = this.getFilePath(file);
376
+ const stats = fs.statSync(filePath);
377
+ totalSize += stats.size;
378
+ } catch (error) {
379
+ // Ignore
380
+ }
381
+ }
382
+
383
+ return {
384
+ totalFiles: files.length,
385
+ totalSize: totalSize,
386
+ totalSizeFormatted: this.formatBytes(totalSize),
387
+ cacheSize: this.cache.size,
388
+ baseDir: this.baseDir,
389
+ files: files
390
+ };
391
+ }
392
+
393
+ formatBytes(bytes) {
394
+ if (bytes === 0) return '0 Bytes';
395
+ const k = 1024;
396
+ const sizes = ['Bytes', 'KB', 'MB', 'GB'];
397
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
398
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
399
+ }
400
+ }
401
+
402
+ // ===== GLOBAL STORAGE INSTANCE =====
403
+
404
+ let globalStorage = null;
405
+
406
+ export function createStorage(baseDir) {
407
+ return new WAStorage(baseDir);
408
+ }
409
+
410
+ export function getStorage() {
411
+ if (!globalStorage) {
412
+ globalStorage = new WAStorage();
413
+ }
414
+ return globalStorage;
415
+ }
416
+
417
+ // ===== SHORTHAND FUNCTIONS =====
418
+
419
+ export const write = getStorage().write;
420
+ export const read = getStorage().read;
421
+ export const del = getStorage().delete;