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 +3 -1
- package/src/ai-integration.js +185 -0
- package/src/client.js +19 -0
- package/src/http-client.js +276 -0
- package/src/index.js +4 -0
- package/src/message.js +18 -0
- package/src/scheduler.js +322 -0
- package/src/storage.js +421 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "waengine",
|
|
3
|
-
"version": "1.0.
|
|
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 =====
|
package/src/scheduler.js
ADDED
|
@@ -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;
|