waengine 2.3.8 → 2.3.10

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/README.md CHANGED
@@ -100,7 +100,7 @@ client.on('truly_connected', (data) => {
100
100
  - **Button Messages** - Interactive buttons with callbacks
101
101
  - **List Messages** - Organized lists with sections
102
102
  - **Template Messages** - Reusable message templates
103
- - **Carousel Messages** - Swipeable card carousels
103
+ - **Interactive Carousel** - REAL swipeable carousel in ONE message with VIDEO support! 🎬
104
104
 
105
105
  ### 👥 **Advanced Group Features**
106
106
  - **Group Settings** - Control who can send messages
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "waengine",
3
- "version": "2.3.8",
3
+ "version": "2.3.10",
4
4
  "description": "🚀 WAEngine - The most powerful WhatsApp Bot Library with 860+ Working Features, Complete Baileys Integration & Production-Ready Stability",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -237,45 +237,109 @@ export class AdvancedMessage {
237
237
 
238
238
  async sendCarouselMessage(cards) {
239
239
  try {
240
- // WhatsApp Carousel ist experimentell
240
+ // REAL WhatsApp Interactive Carousel - ONE message with swipeable cards!
241
241
  const carouselMessage = {
242
242
  interactiveMessage: {
243
+ header: {
244
+ title: 'Carousel',
245
+ hasMediaAttachment: false
246
+ },
247
+ body: {
248
+ text: 'Swipe to see all options'
249
+ },
243
250
  carouselMessage: {
244
- cards: cards.map(card => ({
245
- header: {
246
- title: card.title,
247
- subtitle: card.subtitle,
248
- imageMessage: card.image ? { url: card.image } : undefined
249
- },
250
- body: { text: card.body },
251
- footer: { text: card.footer || "" },
252
- nativeFlowMessage: {
253
- buttons: card.buttons || []
251
+ cards: cards.map((card, index) => {
252
+ const carouselCard = {
253
+ body: {
254
+ text: card.body || ''
255
+ },
256
+ footer: {
257
+ text: card.footer || `${index + 1}/${cards.length}`
258
+ },
259
+ nativeFlowMessage: {
260
+ buttons: []
261
+ }
262
+ };
263
+
264
+ // Add header with media (video or image)
265
+ if (card.video) {
266
+ carouselCard.header = {
267
+ title: card.title,
268
+ subtitle: card.subtitle || '',
269
+ hasMediaAttachment: true,
270
+ videoMessage: {
271
+ url: card.video,
272
+ gifPlayback: card.gifPlayback || false
273
+ }
274
+ };
275
+ } else if (card.image) {
276
+ carouselCard.header = {
277
+ title: card.title,
278
+ subtitle: card.subtitle || '',
279
+ hasMediaAttachment: true,
280
+ imageMessage: {
281
+ url: card.image
282
+ }
283
+ };
284
+ } else {
285
+ carouselCard.header = {
286
+ title: card.title,
287
+ subtitle: card.subtitle || '',
288
+ hasMediaAttachment: false
289
+ };
254
290
  }
255
- }))
291
+
292
+ // Add buttons
293
+ if (card.buttons && card.buttons.length > 0) {
294
+ carouselCard.nativeFlowMessage.buttons = card.buttons.map(btn => ({
295
+ name: 'quick_reply',
296
+ buttonParamsJson: JSON.stringify({
297
+ display_text: btn.text || btn.title,
298
+ id: btn.id || `btn_${index}`
299
+ })
300
+ }));
301
+ }
302
+
303
+ return carouselCard;
304
+ })
256
305
  }
257
306
  }
258
307
  };
259
-
308
+
260
309
  return await this.socket.sendMessage(this.msg.from, carouselMessage);
310
+
261
311
  } catch (error) {
262
- console.error('❌ Carousel Message fehlgeschlagen:', error);
263
- // Fallback: Sende Cards einzeln
264
- const results = [];
265
- for (const card of cards) {
266
- try {
267
- let cardText = `🎴 **${card.title}**\n`;
268
- if (card.subtitle) cardText += `${card.subtitle}\n\n`;
269
- cardText += card.body;
270
- if (card.footer) cardText += `\n\n_${card.footer}_`;
271
-
272
- const result = await this.msg.reply(cardText);
273
- results.push(result);
274
- } catch (cardError) {
275
- console.error('❌ Card fehlgeschlagen:', cardError);
276
- }
312
+ console.error('❌ Interactive Carousel fehlgeschlagen:', error);
313
+
314
+ // Fallback: List Message (closest to carousel)
315
+ try {
316
+ const listMessage = {
317
+ text: 'Select an option',
318
+ footer: `${cards.length} options available`,
319
+ title: 'Carousel',
320
+ buttonText: 'View Options',
321
+ sections: [{
322
+ title: 'All Options',
323
+ rows: cards.map((card, i) => ({
324
+ title: card.title,
325
+ description: card.body || card.subtitle || '',
326
+ rowId: `carousel_${i}`
327
+ }))
328
+ }]
329
+ };
330
+
331
+ return await this.socket.sendMessage(this.msg.from, listMessage);
332
+
333
+ } catch (listError) {
334
+ console.error('❌ List fallback fehlgeschlagen:', listError);
335
+
336
+ // Final fallback: Text
337
+ const fallbackText = cards.map((card, i) =>
338
+ `${i + 1}. **${card.title}**\n${card.body || ''}`
339
+ ).join('\n\n');
340
+
341
+ return await this.msg.reply(fallbackText);
277
342
  }
278
- return results;
279
343
  }
280
344
  }
281
345
  }
@@ -0,0 +1,282 @@
1
+ // ===== CONSOLE RENDERER - Schöne Console Outputs mit Prefix & Farben =====
2
+
3
+ // ANSI Color Codes
4
+ const colors = {
5
+ // Basic Colors
6
+ black: '\x1b[30m',
7
+ red: '\x1b[31m',
8
+ green: '\x1b[32m',
9
+ yellow: '\x1b[33m',
10
+ blue: '\x1b[34m',
11
+ magenta: '\x1b[35m',
12
+ cyan: '\x1b[36m',
13
+ white: '\x1b[37m',
14
+
15
+ // Bright Colors
16
+ brightBlack: '\x1b[90m',
17
+ brightRed: '\x1b[91m',
18
+ brightGreen: '\x1b[92m',
19
+ brightYellow: '\x1b[93m',
20
+ brightBlue: '\x1b[94m',
21
+ brightMagenta: '\x1b[95m',
22
+ brightCyan: '\x1b[96m',
23
+ brightWhite: '\x1b[97m',
24
+
25
+ // Background Colors
26
+ bgBlack: '\x1b[40m',
27
+ bgRed: '\x1b[41m',
28
+ bgGreen: '\x1b[42m',
29
+ bgYellow: '\x1b[43m',
30
+ bgBlue: '\x1b[44m',
31
+ bgMagenta: '\x1b[45m',
32
+ bgCyan: '\x1b[46m',
33
+ bgWhite: '\x1b[47m',
34
+
35
+ // Styles
36
+ bold: '\x1b[1m',
37
+ dim: '\x1b[2m',
38
+ italic: '\x1b[3m',
39
+ underline: '\x1b[4m',
40
+ blink: '\x1b[5m',
41
+ reverse: '\x1b[7m',
42
+ hidden: '\x1b[8m',
43
+
44
+ // Reset
45
+ reset: '\x1b[0m'
46
+ };
47
+
48
+ // Global Config
49
+ let globalConfig = {
50
+ prefix: null,
51
+ prefixColor: 'cyan',
52
+ defaultColor: 'white',
53
+ errorColor: 'red',
54
+ warnColor: 'yellow',
55
+ infoColor: 'blue',
56
+ successColor: 'green',
57
+ enabled: true
58
+ };
59
+
60
+ // Original console methods
61
+ const originalConsole = {
62
+ log: console.log,
63
+ error: console.error,
64
+ warn: console.warn,
65
+ info: console.info
66
+ };
67
+
68
+ // Helper: Get color code
69
+ function getColor(colorName) {
70
+ return colors[colorName] || colors.white;
71
+ }
72
+
73
+ // Helper: Format prefix
74
+ function formatPrefix(prefix, color) {
75
+ if (!prefix) return '';
76
+ const colorCode = getColor(color);
77
+ return `${colorCode}[${prefix}]${colors.reset} `;
78
+ }
79
+
80
+ // Helper: Apply color to text
81
+ function colorize(text, color) {
82
+ const colorCode = getColor(color);
83
+ return `${colorCode}${text}${colors.reset}`;
84
+ }
85
+
86
+ // Patch console.log
87
+ console.log = function(...args) {
88
+ if (!globalConfig.enabled) {
89
+ return originalConsole.log(...args);
90
+ }
91
+
92
+ const prefix = formatPrefix(globalConfig.prefix, globalConfig.prefixColor);
93
+ const colorCode = getColor(globalConfig.defaultColor);
94
+
95
+ if (prefix) {
96
+ originalConsole.log(prefix + colorCode, ...args, colors.reset);
97
+ } else {
98
+ originalConsole.log(colorCode, ...args, colors.reset);
99
+ }
100
+ };
101
+
102
+ // Patch console.error
103
+ console.error = function(...args) {
104
+ if (!globalConfig.enabled) {
105
+ return originalConsole.error(...args);
106
+ }
107
+
108
+ const prefix = formatPrefix(globalConfig.prefix, globalConfig.prefixColor);
109
+ const colorCode = getColor(globalConfig.errorColor);
110
+
111
+ if (prefix) {
112
+ originalConsole.error(prefix + colorCode, ...args, colors.reset);
113
+ } else {
114
+ originalConsole.error(colorCode, ...args, colors.reset);
115
+ }
116
+ };
117
+
118
+ // Patch console.warn
119
+ console.warn = function(...args) {
120
+ if (!globalConfig.enabled) {
121
+ return originalConsole.warn(...args);
122
+ }
123
+
124
+ const prefix = formatPrefix(globalConfig.prefix, globalConfig.prefixColor);
125
+ const colorCode = getColor(globalConfig.warnColor);
126
+
127
+ if (prefix) {
128
+ originalConsole.warn(prefix + colorCode, ...args, colors.reset);
129
+ } else {
130
+ originalConsole.warn(colorCode, ...args, colors.reset);
131
+ }
132
+ };
133
+
134
+ // Patch console.info
135
+ console.info = function(...args) {
136
+ if (!globalConfig.enabled) {
137
+ return originalConsole.info(...args);
138
+ }
139
+
140
+ const prefix = formatPrefix(globalConfig.prefix, globalConfig.prefixColor);
141
+ const colorCode = getColor(globalConfig.infoColor);
142
+
143
+ if (prefix) {
144
+ originalConsole.info(prefix + colorCode, ...args, colors.reset);
145
+ } else {
146
+ originalConsole.info(colorCode, ...args, colors.reset);
147
+ }
148
+ };
149
+
150
+ // ===== PUBLIC API =====
151
+
152
+ export const render = {
153
+ // Set prefix name
154
+ consolePrefix(name) {
155
+ globalConfig.prefix = name;
156
+ console.log(`✅ Console Prefix gesetzt: [${name}]`);
157
+ },
158
+
159
+ // Set prefix color
160
+ consolePrefixColor(color) {
161
+ if (!colors[color]) {
162
+ console.warn(`⚠️ Unbekannte Farbe: ${color}`);
163
+ return;
164
+ }
165
+ globalConfig.prefixColor = color;
166
+ console.log(`🎨 Prefix Farbe gesetzt: ${color}`);
167
+ },
168
+
169
+ // Set default console.log color
170
+ consoleColor(color) {
171
+ if (!colors[color]) {
172
+ console.warn(`⚠️ Unbekannte Farbe: ${color}`);
173
+ return;
174
+ }
175
+ globalConfig.defaultColor = color;
176
+ console.log(`🎨 Console Farbe gesetzt: ${color}`);
177
+ },
178
+
179
+ // Set console.error color
180
+ consoleError(color) {
181
+ if (!colors[color]) {
182
+ console.warn(`⚠️ Unbekannte Farbe: ${color}`);
183
+ return;
184
+ }
185
+ globalConfig.errorColor = color;
186
+ console.log(`🎨 Error Farbe gesetzt: ${color}`);
187
+ },
188
+
189
+ // Set console.warn color
190
+ consoleWarn(color) {
191
+ if (!colors[color]) {
192
+ console.warn(`⚠️ Unbekannte Farbe: ${color}`);
193
+ return;
194
+ }
195
+ globalConfig.warnColor = color;
196
+ console.log(`🎨 Warn Farbe gesetzt: ${color}`);
197
+ },
198
+
199
+ // Set console.info color
200
+ consoleInfo(color) {
201
+ if (!colors[color]) {
202
+ console.warn(`⚠️ Unbekannte Farbe: ${color}`);
203
+ return;
204
+ }
205
+ globalConfig.infoColor = color;
206
+ console.log(`🎨 Info Farbe gesetzt: ${color}`);
207
+ },
208
+
209
+ // Enable/Disable rendering
210
+ enable() {
211
+ globalConfig.enabled = true;
212
+ console.log('✅ Console Rendering aktiviert');
213
+ },
214
+
215
+ disable() {
216
+ globalConfig.enabled = false;
217
+ originalConsole.log('⚠️ Console Rendering deaktiviert');
218
+ },
219
+
220
+ // Reset to defaults
221
+ reset() {
222
+ globalConfig = {
223
+ prefix: null,
224
+ prefixColor: 'cyan',
225
+ defaultColor: 'white',
226
+ errorColor: 'red',
227
+ warnColor: 'yellow',
228
+ infoColor: 'blue',
229
+ successColor: 'green',
230
+ enabled: true
231
+ };
232
+ console.log('🔄 Console Rendering zurückgesetzt');
233
+ },
234
+
235
+ // Get current config
236
+ getConfig() {
237
+ return { ...globalConfig };
238
+ },
239
+
240
+ // Custom colored output
241
+ print(text, color = 'white') {
242
+ const prefix = formatPrefix(globalConfig.prefix, globalConfig.prefixColor);
243
+ const colored = colorize(text, color);
244
+ originalConsole.log(prefix + colored);
245
+ },
246
+
247
+ // Success message
248
+ success(text) {
249
+ const prefix = formatPrefix(globalConfig.prefix, globalConfig.prefixColor);
250
+ const colored = colorize(text, globalConfig.successColor);
251
+ originalConsole.log(prefix + colored);
252
+ },
253
+
254
+ // Available colors
255
+ colors: Object.keys(colors).filter(c => c !== 'reset'),
256
+
257
+ // Show color palette
258
+ showColors() {
259
+ originalConsole.log('\n🎨 Verfügbare Farben:\n');
260
+
261
+ const colorList = Object.keys(colors).filter(c => c !== 'reset');
262
+
263
+ colorList.forEach(colorName => {
264
+ const sample = `${colors[colorName]}${colorName}${colors.reset}`;
265
+ originalConsole.log(` ${sample}`);
266
+ });
267
+
268
+ originalConsole.log('');
269
+ }
270
+ };
271
+
272
+ // Export colors for direct use
273
+ export { colors };
274
+
275
+ // Auto-initialize message
276
+ if (globalConfig.enabled) {
277
+ originalConsole.log(
278
+ `${colors.brightCyan}╔════════════════════════════════════════╗${colors.reset}\n` +
279
+ `${colors.brightCyan}║ 🎨 Console Renderer aktiviert! ║${colors.reset}\n` +
280
+ `${colors.brightCyan}╚════════════════════════════════════════╝${colors.reset}`
281
+ );
282
+ }
@@ -0,0 +1,299 @@
1
+ // ===== ADVANCED HTTP CLIENT - Axios-Style für WAEngine =====
2
+ // Wie curl, aber besser! 🚀
3
+
4
+ import axios from 'axios';
5
+
6
+ export class HTTP {
7
+ constructor(config = {}) {
8
+ this.config = {
9
+ timeout: config.timeout || 30000,
10
+ retries: config.retries || 3,
11
+ headers: config.headers || {},
12
+ baseURL: config.baseURL || '',
13
+ ...config
14
+ };
15
+
16
+ // Axios Instance
17
+ this.client = axios.create(this.config);
18
+
19
+ // Request/Response Interceptors
20
+ this.setupInterceptors();
21
+ }
22
+
23
+ setupInterceptors() {
24
+ // Request Interceptor
25
+ this.client.interceptors.request.use(
26
+ (config) => {
27
+ console.log(`🌐 ${config.method.toUpperCase()} ${config.url}`);
28
+ return config;
29
+ },
30
+ (error) => Promise.reject(error)
31
+ );
32
+
33
+ // Response Interceptor
34
+ this.client.interceptors.response.use(
35
+ (response) => {
36
+ console.log(`✅ ${response.status} ${response.config.url}`);
37
+ return response;
38
+ },
39
+ (error) => {
40
+ console.error(`❌ ${error.response?.status || 'ERROR'} ${error.config?.url}`);
41
+ return Promise.reject(error);
42
+ }
43
+ );
44
+ }
45
+
46
+ // ===== BASIC METHODS (Axios-Style) =====
47
+
48
+ async get(url, config = {}) {
49
+ try {
50
+ const response = await this.client.get(url, config);
51
+ return response.data;
52
+ } catch (error) {
53
+ throw this.handleError(error);
54
+ }
55
+ }
56
+
57
+ async post(url, data = {}, config = {}) {
58
+ try {
59
+ const response = await this.client.post(url, data, config);
60
+ return response.data;
61
+ } catch (error) {
62
+ throw this.handleError(error);
63
+ }
64
+ }
65
+
66
+ async put(url, data = {}, config = {}) {
67
+ try {
68
+ const response = await this.client.put(url, data, config);
69
+ return response.data;
70
+ } catch (error) {
71
+ throw this.handleError(error);
72
+ }
73
+ }
74
+
75
+ async patch(url, data = {}, config = {}) {
76
+ try {
77
+ const response = await this.client.patch(url, data, config);
78
+ return response.data;
79
+ } catch (error) {
80
+ throw this.handleError(error);
81
+ }
82
+ }
83
+
84
+ async delete(url, config = {}) {
85
+ try {
86
+ const response = await this.client.delete(url, config);
87
+ return response.data;
88
+ } catch (error) {
89
+ throw this.handleError(error);
90
+ }
91
+ }
92
+
93
+ async head(url, config = {}) {
94
+ try {
95
+ const response = await this.client.head(url, config);
96
+ return response.headers;
97
+ } catch (error) {
98
+ throw this.handleError(error);
99
+ }
100
+ }
101
+
102
+ async options(url, config = {}) {
103
+ try {
104
+ const response = await this.client.options(url, config);
105
+ return response.data;
106
+ } catch (error) {
107
+ throw this.handleError(error);
108
+ }
109
+ }
110
+
111
+ // ===== CURL-STYLE REQUEST =====
112
+
113
+ async curl(options) {
114
+ /*
115
+ Curl-Style Request:
116
+
117
+ await http.curl({
118
+ url: 'https://api.example.com/data',
119
+ method: 'POST',
120
+ headers: { 'Authorization': 'Bearer token' },
121
+ data: { key: 'value' },
122
+ params: { page: 1 },
123
+ timeout: 5000
124
+ });
125
+ */
126
+
127
+ try {
128
+ const response = await this.client(options);
129
+ return response.data;
130
+ } catch (error) {
131
+ throw this.handleError(error);
132
+ }
133
+ }
134
+
135
+ // ===== ADVANCED FEATURES =====
136
+
137
+ // Download File
138
+ async download(url, savePath) {
139
+ try {
140
+ const response = await this.client.get(url, {
141
+ responseType: 'arraybuffer'
142
+ });
143
+
144
+ const fs = await import('fs');
145
+ await fs.promises.writeFile(savePath, response.data);
146
+
147
+ return {
148
+ success: true,
149
+ path: savePath,
150
+ size: response.data.length,
151
+ contentType: response.headers['content-type']
152
+ };
153
+ } catch (error) {
154
+ throw this.handleError(error);
155
+ }
156
+ }
157
+
158
+ // Upload File
159
+ async upload(url, filePath, fieldName = 'file') {
160
+ try {
161
+ const fs = await import('fs');
162
+ const FormData = (await import('form-data')).default;
163
+
164
+ const form = new FormData();
165
+ form.append(fieldName, fs.createReadStream(filePath));
166
+
167
+ const response = await this.client.post(url, form, {
168
+ headers: form.getHeaders()
169
+ });
170
+
171
+ return response.data;
172
+ } catch (error) {
173
+ throw this.handleError(error);
174
+ }
175
+ }
176
+
177
+ // JSON Request
178
+ async json(url, data = null, method = 'GET') {
179
+ const config = {
180
+ headers: {
181
+ 'Content-Type': 'application/json',
182
+ 'Accept': 'application/json'
183
+ }
184
+ };
185
+
186
+ if (method === 'GET') {
187
+ return await this.get(url, config);
188
+ } else {
189
+ return await this.post(url, data, config);
190
+ }
191
+ }
192
+
193
+ // Form Data Request
194
+ async form(url, data = {}) {
195
+ const FormData = (await import('form-data')).default;
196
+ const form = new FormData();
197
+
198
+ for (const [key, value] of Object.entries(data)) {
199
+ form.append(key, value);
200
+ }
201
+
202
+ return await this.post(url, form, {
203
+ headers: form.getHeaders()
204
+ });
205
+ }
206
+
207
+ // Parallel Requests
208
+ async all(requests) {
209
+ try {
210
+ const promises = requests.map(req => {
211
+ if (typeof req === 'string') {
212
+ return this.get(req);
213
+ } else {
214
+ return this.curl(req);
215
+ }
216
+ });
217
+
218
+ return await Promise.all(promises);
219
+ } catch (error) {
220
+ throw this.handleError(error);
221
+ }
222
+ }
223
+
224
+ // Retry Request
225
+ async retry(fn, retries = 3, delay = 1000) {
226
+ for (let i = 0; i < retries; i++) {
227
+ try {
228
+ return await fn();
229
+ } catch (error) {
230
+ if (i === retries - 1) throw error;
231
+ console.log(`🔄 Retry ${i + 1}/${retries}...`);
232
+ await this.sleep(delay * (i + 1));
233
+ }
234
+ }
235
+ }
236
+
237
+ // Stream Response
238
+ async stream(url, onData, onEnd) {
239
+ try {
240
+ const response = await this.client.get(url, {
241
+ responseType: 'stream'
242
+ });
243
+
244
+ response.data.on('data', onData);
245
+ response.data.on('end', onEnd);
246
+
247
+ return response.data;
248
+ } catch (error) {
249
+ throw this.handleError(error);
250
+ }
251
+ }
252
+
253
+ // ===== HELPER METHODS =====
254
+
255
+ handleError(error) {
256
+ if (error.response) {
257
+ // Server responded with error
258
+ return new Error(`HTTP ${error.response.status}: ${error.response.statusText}`);
259
+ } else if (error.request) {
260
+ // No response received
261
+ return new Error('No response from server');
262
+ } else {
263
+ // Request setup error
264
+ return new Error(error.message);
265
+ }
266
+ }
267
+
268
+ sleep(ms) {
269
+ return new Promise(resolve => setTimeout(resolve, ms));
270
+ }
271
+
272
+ // Set Default Headers
273
+ setHeader(key, value) {
274
+ this.client.defaults.headers.common[key] = value;
275
+ }
276
+
277
+ // Set Authorization
278
+ setAuth(token, type = 'Bearer') {
279
+ this.setHeader('Authorization', `${type} ${token}`);
280
+ }
281
+
282
+ // Set Base URL
283
+ setBaseURL(url) {
284
+ this.client.defaults.baseURL = url;
285
+ }
286
+ }
287
+
288
+ // ===== GLOBAL HTTP INSTANCE =====
289
+ export const http = new HTTP();
290
+
291
+ // ===== QUICK METHODS =====
292
+ export const get = (url, config) => http.get(url, config);
293
+ export const post = (url, data, config) => http.post(url, data, config);
294
+ export const put = (url, data, config) => http.put(url, data, config);
295
+ export const patch = (url, data, config) => http.patch(url, data, config);
296
+ export const del = (url, config) => http.delete(url, config);
297
+ export const curl = (options) => http.curl(options);
298
+ export const download = (url, path) => http.download(url, path);
299
+ export const upload = (url, file, field) => http.upload(url, file, field);
package/src/index.js CHANGED
@@ -15,6 +15,9 @@ export { Message } from "./message.js";
15
15
  export { ConsoleLogger, logger } from "./console-logger.js";
16
16
  export { ErrorHandler, defaultErrorHandler } from "./error-handler.js";
17
17
 
18
+ // ===== CONSOLE RENDERER - NEU! =====
19
+ export { render, colors } from "./console-renderer.js";
20
+
18
21
  // ===== ULTRA-ROBUSTE RECOVERY SYSTEME - NEU v2.0.0! =====
19
22
  export { ConnectionRecovery } from "./connection-recovery.js";
20
23
  export { AuthRecovery } from "./auth-recovery.js";
package/src/message.js CHANGED
@@ -727,6 +727,100 @@ export class Message {
727
727
  }
728
728
  }
729
729
 
730
+ // ===== MESSAGE DEL - Lösche beliebige Message (Admin erforderlich in Gruppen) =====
731
+ async MessageDel(messageId, participantJid = null) {
732
+ try {
733
+ // Wenn keine Parameter, lösche die aktuelle Message
734
+ if (!messageId) {
735
+ return await this.deleteMessage();
736
+ }
737
+
738
+ // Prüfe in Gruppen ob Bot Admin ist
739
+ if (this.isGroup) {
740
+ const isBotAdmin = await this.isGroupAdmin(this.client.socket.user.id);
741
+ if (!isBotAdmin) {
742
+ console.log('⚠️ Bot ist kein Admin - kann nur eigene Messages löschen');
743
+ return {
744
+ success: false,
745
+ reason: 'not_admin',
746
+ error: 'Bot muss Admin sein um Messages von anderen zu löschen'
747
+ };
748
+ }
749
+ }
750
+
751
+ // Bot JID ermitteln
752
+ const botJid = this.client.socket.user?.id;
753
+ const botNumber = botJid?.split('@')[0]?.split(':')[0];
754
+
755
+ // Prüfe ob Message vom Bot ist
756
+ let isFromBot = false;
757
+ if (participantJid) {
758
+ const participantNumber = participantJid?.split('@')[0]?.split(':')[0];
759
+ isFromBot = botNumber === participantNumber;
760
+ }
761
+
762
+ // Erstelle Message Key
763
+ const messageKey = {
764
+ remoteJid: this.from,
765
+ id: messageId,
766
+ fromMe: isFromBot
767
+ };
768
+
769
+ // In Gruppen: participant hinzufügen wenn nicht vom Bot
770
+ if (this.isGroup && participantJid && !isFromBot) {
771
+ messageKey.participant = participantJid;
772
+ }
773
+
774
+ console.log('🗑️ MessageDel - Lösche Message:', {
775
+ messageId: messageId,
776
+ participant: participantJid,
777
+ isFromBot: isFromBot,
778
+ isGroup: this.isGroup,
779
+ key: messageKey
780
+ });
781
+
782
+ // Sende Delete Request
783
+ const result = await this.client.socket.sendMessage(this.from, {
784
+ delete: messageKey
785
+ });
786
+
787
+ console.log('✅ MessageDel erfolgreich:', result);
788
+ return {
789
+ success: true,
790
+ messageId: messageId,
791
+ participant: participantJid,
792
+ wasFromBot: isFromBot,
793
+ result: result
794
+ };
795
+
796
+ } catch (error) {
797
+ console.error('❌ MessageDel Fehler:', error.message);
798
+
799
+ // Spezifische Fehlerbehandlung
800
+ if (error.message?.includes('not-found') || error.message?.includes('404')) {
801
+ return {
802
+ success: false,
803
+ reason: 'message_not_found',
804
+ error: 'Message nicht gefunden oder zu alt'
805
+ };
806
+ }
807
+
808
+ if (error.message?.includes('forbidden') || error.message?.includes('403')) {
809
+ return {
810
+ success: false,
811
+ reason: 'no_permission',
812
+ error: 'Keine Berechtigung - Bot muss Admin sein'
813
+ };
814
+ }
815
+
816
+ return {
817
+ success: false,
818
+ reason: 'unknown_error',
819
+ error: error.message
820
+ };
821
+ }
822
+ }
823
+
730
824
  // ===== MEDIA DOWNLOAD - NEU in v1.7.9! =====
731
825
 
732
826
  async downloadMedia(options = {}) {
@@ -6,54 +6,173 @@ export class UIComponents {
6
6
  // ===== CAROUSEL MESSAGES =====
7
7
 
8
8
  /**
9
- * Send carousel message with multiple cards
9
+ * Send REAL carousel message with swipeable cards in ONE message
10
+ * Uses WhatsApp Interactive Message API
10
11
  */
11
12
  async sendCarousel(chatId, cards, options = {}) {
12
13
  try {
13
- // WhatsApp doesn't support native carousels, so we create a rich text alternative
14
- let carouselText = `🎠 **${options.title || 'Carousel'}**\n\n`;
15
-
16
- cards.forEach((card, index) => {
17
- carouselText += `**${index + 1}. ${card.title}**\n`;
18
- if (card.subtitle) carouselText += `${card.subtitle}\n`;
19
- if (card.body) carouselText += `${card.body}\n`;
20
- if (card.price) carouselText += `💰 ${card.price}\n`;
21
-
22
- // Add buttons as text options
23
- if (card.buttons && card.buttons.length > 0) {
24
- carouselText += `Options: `;
25
- card.buttons.forEach((btn, btnIndex) => {
26
- carouselText += `[${btnIndex + 1}] ${btn.title} `;
27
- });
28
- carouselText += `\n`;
14
+ // Build proper WhatsApp Interactive Carousel Message
15
+ const carouselMessage = {
16
+ interactiveMessage: {
17
+ header: {
18
+ title: options.title || 'Carousel',
19
+ hasMediaAttachment: false
20
+ },
21
+ body: {
22
+ text: options.body || 'Swipe to see all options'
23
+ },
24
+ footer: {
25
+ text: options.footer || ''
26
+ },
27
+ carouselMessage: {
28
+ cards: cards.map((card, index) => {
29
+ const carouselCard = {
30
+ body: {
31
+ text: card.body || card.description || ''
32
+ },
33
+ footer: {
34
+ text: card.footer || `${index + 1}/${cards.length}`
35
+ },
36
+ nativeFlowMessage: {
37
+ buttons: []
38
+ }
39
+ };
40
+
41
+ // Add header with media
42
+ if (card.video) {
43
+ carouselCard.header = {
44
+ title: card.title,
45
+ subtitle: card.subtitle || '',
46
+ hasMediaAttachment: true,
47
+ videoMessage: {
48
+ url: card.video,
49
+ gifPlayback: card.gifPlayback || false
50
+ }
51
+ };
52
+ } else if (card.image) {
53
+ carouselCard.header = {
54
+ title: card.title,
55
+ subtitle: card.subtitle || '',
56
+ hasMediaAttachment: true,
57
+ imageMessage: {
58
+ url: card.image
59
+ }
60
+ };
61
+ } else {
62
+ carouselCard.header = {
63
+ title: card.title,
64
+ subtitle: card.subtitle || '',
65
+ hasMediaAttachment: false
66
+ };
67
+ }
68
+
69
+ // Add buttons
70
+ if (card.buttons && card.buttons.length > 0) {
71
+ carouselCard.nativeFlowMessage.buttons = card.buttons.map(btn => ({
72
+ name: 'quick_reply',
73
+ buttonParamsJson: JSON.stringify({
74
+ display_text: btn.title || btn.text,
75
+ id: btn.id || `btn_${index}_${btn.title}`
76
+ })
77
+ }));
78
+ }
79
+
80
+ return carouselCard;
81
+ })
82
+ }
29
83
  }
30
-
31
- carouselText += `\n`;
32
- });
33
-
34
- if (options.footer) {
35
- carouselText += `\n_${options.footer}_`;
36
- }
84
+ };
37
85
 
38
- // Send with image if provided
39
- if (cards[0]?.image) {
40
- await this.client.socket.sendMessage(chatId, {
41
- image: { url: cards[0].image },
42
- caption: carouselText
43
- });
44
- } else {
45
- await this.client.socket.sendMessage(chatId, { text: carouselText });
46
- }
86
+ // Send the carousel
87
+ const result = await this.client.socket.sendMessage(chatId, carouselMessage);
47
88
 
48
- return { success: true, type: 'carousel', cards: cards.length };
89
+ return {
90
+ success: true,
91
+ type: 'interactive_carousel',
92
+ cards: cards.length,
93
+ messageId: result.key.id,
94
+ mediaTypes: {
95
+ videos: cards.filter(c => c.video).length,
96
+ images: cards.filter(c => c.image).length,
97
+ text: cards.filter(c => !c.video && !c.image).length
98
+ }
99
+ };
49
100
 
50
101
  } catch (error) {
51
- // Fallback to simple text
52
- const fallbackText = `📋 **${options.title || 'Options'}**\n\n` +
53
- cards.map((card, i) => `${i + 1}. ${card.title}`).join('\n');
102
+ console.error('❌ Interactive Carousel failed, trying fallback:', error);
54
103
 
55
- await this.client.socket.sendMessage(chatId, { text: fallbackText });
56
- return { success: true, type: 'fallback', cards: cards.length };
104
+ // Fallback 1: Try native carousel format
105
+ try {
106
+ const nativeCarousel = {
107
+ viewOnceMessage: {
108
+ message: {
109
+ messageContextInfo: {
110
+ deviceListMetadata: {},
111
+ deviceListMetadataVersion: 2
112
+ },
113
+ interactiveMessage: {
114
+ nativeFlowMessage: {
115
+ buttons: cards.map((card, i) => ({
116
+ name: 'single_select',
117
+ buttonParamsJson: JSON.stringify({
118
+ title: card.title,
119
+ sections: [{
120
+ title: card.subtitle || '',
121
+ rows: [{
122
+ title: card.title,
123
+ description: card.body,
124
+ id: `card_${i}`
125
+ }]
126
+ }]
127
+ })
128
+ }))
129
+ }
130
+ }
131
+ }
132
+ }
133
+ };
134
+
135
+ const result = await this.client.socket.sendMessage(chatId, nativeCarousel);
136
+ return { success: true, type: 'native_carousel', cards: cards.length };
137
+
138
+ } catch (nativeError) {
139
+ console.error('❌ Native carousel also failed, using list fallback:', nativeError);
140
+
141
+ // Fallback 2: Use List Message (closest to carousel)
142
+ try {
143
+ const listMessage = {
144
+ text: options.title || 'Select an option',
145
+ footer: options.footer || `${cards.length} options available`,
146
+ title: options.title || 'Carousel',
147
+ buttonText: 'View Options',
148
+ sections: [{
149
+ title: 'All Options',
150
+ rows: cards.map((card, i) => ({
151
+ title: card.title,
152
+ description: card.body || card.subtitle || '',
153
+ rowId: `carousel_${i}`
154
+ }))
155
+ }]
156
+ };
157
+
158
+ const result = await this.client.socket.sendMessage(chatId, listMessage);
159
+ return { success: true, type: 'list_fallback', cards: cards.length };
160
+
161
+ } catch (listError) {
162
+ console.error('❌ List fallback failed, using text:', listError);
163
+
164
+ // Fallback 3: Simple text with emojis
165
+ const fallbackText = `🎠 **${options.title || 'Carousel'}**\n\n` +
166
+ cards.map((card, i) =>
167
+ `${i + 1}️⃣ **${card.title}**\n` +
168
+ `${card.subtitle ? ` ${card.subtitle}\n` : ''}` +
169
+ ` ${card.body || ''}\n`
170
+ ).join('\n');
171
+
172
+ await this.client.socket.sendMessage(chatId, { text: fallbackText });
173
+ return { success: true, type: 'text_fallback', cards: cards.length };
174
+ }
175
+ }
57
176
  }
58
177
  }
59
178