waengine 2.3.8-rc.1 β†’ 2.3.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
+ - **Carousel Messages** - Swipeable card carousels 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-rc.1",
3
+ "version": "2.3.9",
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,30 +237,58 @@ export class AdvancedMessage {
237
237
 
238
238
  async sendCarouselMessage(cards) {
239
239
  try {
240
- // WhatsApp Carousel ist experimentell
241
- const carouselMessage = {
242
- interactiveMessage: {
243
- 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 || []
254
- }
255
- }))
256
- }
240
+ // WhatsApp Carousel mit Video & Image Support
241
+ const results = [];
242
+
243
+ for (let i = 0; i < cards.length; i++) {
244
+ const card = cards[i];
245
+
246
+ // Build card text
247
+ let cardText = `🎴 **${i + 1}/${cards.length}: ${card.title}**\n`;
248
+ if (card.subtitle) cardText += `${card.subtitle}\n\n`;
249
+ if (card.body) cardText += `${card.body}`;
250
+ if (card.footer) cardText += `\n\n_${card.footer}_`;
251
+
252
+ // Add buttons as text
253
+ if (card.buttons && card.buttons.length > 0) {
254
+ cardText += `\n\n**Options:**\n`;
255
+ card.buttons.forEach((btn, btnIndex) => {
256
+ cardText += `${btnIndex + 1}. ${btn.text || btn.title}\n`;
257
+ });
257
258
  }
258
- };
259
-
260
- return await this.socket.sendMessage(this.msg.from, carouselMessage);
259
+
260
+ // Send with appropriate media type
261
+ let result;
262
+ if (card.video) {
263
+ // Video card
264
+ result = await this.socket.sendMessage(this.msg.from, {
265
+ video: { url: card.video },
266
+ caption: cardText,
267
+ gifPlayback: card.gifPlayback || false
268
+ });
269
+ } else if (card.image) {
270
+ // Image card
271
+ result = await this.socket.sendMessage(this.msg.from, {
272
+ image: { url: card.image },
273
+ caption: cardText
274
+ });
275
+ } else {
276
+ // Text-only card
277
+ result = await this.msg.reply(cardText);
278
+ }
279
+
280
+ results.push(result);
281
+
282
+ // Small delay between cards
283
+ if (i < cards.length - 1) {
284
+ await new Promise(resolve => setTimeout(resolve, 500));
285
+ }
286
+ }
287
+
288
+ return results;
261
289
  } catch (error) {
262
290
  console.error('❌ Carousel Message fehlgeschlagen:', error);
263
- // Fallback: Sende Cards einzeln
291
+ // Fallback: Sende Cards als Text
264
292
  const results = [];
265
293
  for (const card of cards) {
266
294
  try {
@@ -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";
@@ -7,10 +7,11 @@ export class UIComponents {
7
7
 
8
8
  /**
9
9
  * Send carousel message with multiple cards
10
+ * Supports: images, videos, and text-only cards
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
+ // WhatsApp doesn't support native carousels, so we create a rich media alternative
14
15
  let carouselText = `🎠 **${options.title || 'Carousel'}**\n\n`;
15
16
 
16
17
  cards.forEach((card, index) => {
@@ -35,19 +36,68 @@ export class UIComponents {
35
36
  carouselText += `\n_${options.footer}_`;
36
37
  }
37
38
 
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 });
39
+ // Send each card with its media (image or video)
40
+ for (let i = 0; i < cards.length; i++) {
41
+ const card = cards[i];
42
+
43
+ // Build card text
44
+ let cardText = `**${i + 1}/${cards.length}: ${card.title}**\n`;
45
+ if (card.subtitle) cardText += `${card.subtitle}\n`;
46
+ if (card.body) cardText += `${card.body}\n`;
47
+ if (card.price) cardText += `πŸ’° ${card.price}\n`;
48
+
49
+ if (card.buttons && card.buttons.length > 0) {
50
+ cardText += `\nOptions: `;
51
+ card.buttons.forEach((btn, btnIndex) => {
52
+ cardText += `[${btnIndex + 1}] ${btn.title} `;
53
+ });
54
+ }
55
+
56
+ if (options.footer) {
57
+ cardText += `\n\n_${options.footer}_`;
58
+ }
59
+
60
+ // Send with appropriate media type
61
+ if (card.video) {
62
+ // Video card
63
+ await this.client.socket.sendMessage(chatId, {
64
+ video: { url: card.video },
65
+ caption: cardText,
66
+ gifPlayback: card.gifPlayback || false
67
+ });
68
+ } else if (card.image) {
69
+ // Image card
70
+ await this.client.socket.sendMessage(chatId, {
71
+ image: { url: card.image },
72
+ caption: cardText
73
+ });
74
+ } else {
75
+ // Text-only card
76
+ await this.client.socket.sendMessage(chatId, {
77
+ text: cardText
78
+ });
79
+ }
80
+
81
+ // Small delay between cards to prevent spam
82
+ if (i < cards.length - 1) {
83
+ await new Promise(resolve => setTimeout(resolve, 500));
84
+ }
46
85
  }
47
86
 
48
- return { success: true, type: 'carousel', cards: cards.length };
87
+ return {
88
+ success: true,
89
+ type: 'carousel',
90
+ cards: cards.length,
91
+ mediaTypes: {
92
+ videos: cards.filter(c => c.video).length,
93
+ images: cards.filter(c => c.image).length,
94
+ text: cards.filter(c => !c.video && !c.image).length
95
+ }
96
+ };
49
97
 
50
98
  } catch (error) {
99
+ console.error('❌ Carousel error:', error);
100
+
51
101
  // Fallback to simple text
52
102
  const fallbackText = `πŸ“‹ **${options.title || 'Options'}**\n\n` +
53
103
  cards.map((card, i) => `${i + 1}. ${card.title}`).join('\n');