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 +1 -1
- package/package.json +1 -1
- package/src/advanced-features.js +93 -29
- package/src/console-renderer.js +282 -0
- package/src/http-advanced.js +299 -0
- package/src/index.js +3 -0
- package/src/message.js +94 -0
- package/src/ui-components.js +158 -39
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
|
|
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.
|
|
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",
|
package/src/advanced-features.js
CHANGED
|
@@ -237,45 +237,109 @@ export class AdvancedMessage {
|
|
|
237
237
|
|
|
238
238
|
async sendCarouselMessage(cards) {
|
|
239
239
|
try {
|
|
240
|
-
// WhatsApp Carousel
|
|
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
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
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
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
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 = {}) {
|
package/src/ui-components.js
CHANGED
|
@@ -6,54 +6,173 @@ export class UIComponents {
|
|
|
6
6
|
// ===== CAROUSEL MESSAGES =====
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
|
-
* Send carousel message with
|
|
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
|
-
//
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
|
39
|
-
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
56
|
-
|
|
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
|
|