wuzapi 1.4.0 → 1.5.1
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 +513 -1217
- package/dist/client.d.ts +2 -1
- package/dist/client.js +91 -0
- package/dist/client.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +12 -811
- package/dist/index.js.map +1 -1
- package/dist/modules/admin.d.ts +8 -0
- package/dist/modules/admin.js +37 -0
- package/dist/modules/admin.js.map +1 -0
- package/dist/modules/chat.d.ts +21 -1
- package/dist/modules/chat.js +173 -0
- package/dist/modules/chat.js.map +1 -0
- package/dist/modules/group.d.ts +25 -1
- package/dist/modules/group.js +142 -0
- package/dist/modules/group.js.map +1 -0
- package/dist/modules/newsletter.d.ts +9 -0
- package/dist/modules/newsletter.js +13 -0
- package/dist/modules/newsletter.js.map +1 -0
- package/dist/modules/session.d.ts +13 -1
- package/dist/modules/session.js +85 -0
- package/dist/modules/session.js.map +1 -0
- package/dist/modules/user.d.ts +5 -1
- package/dist/modules/user.js +40 -0
- package/dist/modules/user.js.map +1 -0
- package/dist/modules/webhook.d.ts +9 -1
- package/dist/modules/webhook.js +33 -0
- package/dist/modules/webhook.js.map +1 -0
- package/dist/types/chat.d.ts +55 -0
- package/dist/types/group.d.ts +52 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.js +396 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/newsletter.d.ts +17 -0
- package/dist/types/session.d.ts +16 -0
- package/dist/types/user.d.ts +5 -0
- package/dist/types/webhook.d.ts +9 -0
- package/dist/wuzapi-client.d.ts +2 -0
- package/dist/wuzapi-client.js +45 -0
- package/dist/wuzapi-client.js.map +1 -0
- package/package.json +1 -1
- package/dist/vite-env.d.ts +0 -1
package/README.md
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
A comprehensive TypeScript client library for the [WuzAPI WhatsApp API](https://github.com/asternic/wuzapi). This library provides a simple and intuitive interface to interact with WhatsApp through the WuzAPI service.
|
|
4
4
|
|
|
5
|
-
## Features
|
|
5
|
+
## 🚀 Features
|
|
6
6
|
|
|
7
|
-
- 🔥 **Full TypeScript Support** - Complete type definitions for all API endpoints
|
|
7
|
+
- 🔥 **Full TypeScript Support** - Complete type definitions for all API endpoints
|
|
8
8
|
- 🏗️ **Modular Architecture** - Organized by functionality (admin, session, chat, user, group, webhook)
|
|
9
9
|
- 🚀 **Promise-based** - Modern async/await support
|
|
10
10
|
- 🛡️ **Error Handling** - Comprehensive error handling with detailed error types
|
|
@@ -12,7 +12,7 @@ A comprehensive TypeScript client library for the [WuzAPI WhatsApp API](https://
|
|
|
12
12
|
- 🔧 **Easy Configuration** - Simple setup with minimal configuration
|
|
13
13
|
- 📖 **Well Documented** - Extensive documentation and examples
|
|
14
14
|
|
|
15
|
-
## Installation
|
|
15
|
+
## 📦 Installation
|
|
16
16
|
|
|
17
17
|
```bash
|
|
18
18
|
npm install wuzapi
|
|
@@ -24,9 +24,9 @@ or
|
|
|
24
24
|
yarn add wuzapi
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
-
## Quick Start
|
|
27
|
+
## ⚡ Quick Start
|
|
28
28
|
|
|
29
|
-
###
|
|
29
|
+
### Basic Setup
|
|
30
30
|
|
|
31
31
|
```typescript
|
|
32
32
|
import WuzapiClient from "wuzapi";
|
|
@@ -42,116 +42,225 @@ await client.session.connect({
|
|
|
42
42
|
Immediate: false,
|
|
43
43
|
});
|
|
44
44
|
|
|
45
|
-
// Send a
|
|
45
|
+
// Send a message
|
|
46
46
|
await client.chat.sendText({
|
|
47
47
|
Phone: "5491155554444",
|
|
48
|
-
Body: "Hello from WuzAPI!",
|
|
48
|
+
Body: "Hello from WuzAPI! 🎉",
|
|
49
49
|
});
|
|
50
|
+
```
|
|
50
51
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
52
|
+
### Login Options
|
|
53
|
+
|
|
54
|
+
#### Option 1: QR Code (Traditional)
|
|
55
|
+
```typescript
|
|
56
|
+
// Get QR code for scanning
|
|
57
|
+
const qr = await client.session.getQRCode();
|
|
58
|
+
console.log("Scan this QR code:", qr.QRCode);
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
#### Option 2: Phone Pairing (New!)
|
|
62
|
+
```typescript
|
|
63
|
+
// Pair using verification code (SMS/Call)
|
|
64
|
+
await client.session.pairPhone("5491155554444", "123456");
|
|
55
65
|
```
|
|
56
66
|
|
|
57
|
-
|
|
67
|
+
## 🔧 Configuration
|
|
58
68
|
|
|
59
69
|
```typescript
|
|
60
|
-
|
|
70
|
+
interface WuzapiConfig {
|
|
71
|
+
apiUrl: string; // Your WuzAPI server URL
|
|
72
|
+
token?: string; // Authentication token (can be provided per request)
|
|
73
|
+
}
|
|
61
74
|
|
|
62
|
-
//
|
|
75
|
+
// Global token approach
|
|
63
76
|
const client = new WuzapiClient({
|
|
64
77
|
apiUrl: "http://localhost:8080",
|
|
65
|
-
token: "
|
|
78
|
+
token: "your-token",
|
|
66
79
|
});
|
|
67
80
|
|
|
68
|
-
//
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
await client.session.connect(
|
|
73
|
-
{
|
|
74
|
-
Subscribe: ["Message", "ReadReceipt"],
|
|
75
|
-
Immediate: false,
|
|
76
|
-
},
|
|
77
|
-
{ token: userToken }
|
|
78
|
-
);
|
|
81
|
+
// Flexible token approach
|
|
82
|
+
const client = new WuzapiClient({
|
|
83
|
+
apiUrl: "http://localhost:8080",
|
|
84
|
+
});
|
|
79
85
|
|
|
80
|
-
//
|
|
86
|
+
// Use different tokens for different operations
|
|
81
87
|
await client.chat.sendText(
|
|
82
|
-
{
|
|
83
|
-
|
|
84
|
-
Body: "Hello from user-specific token!",
|
|
85
|
-
},
|
|
86
|
-
{ token: userToken }
|
|
88
|
+
{ Phone: "123", Body: "Hello" },
|
|
89
|
+
{ token: "user-specific-token" }
|
|
87
90
|
);
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## 💬 Essential Chat Operations
|
|
94
|
+
|
|
95
|
+
```typescript
|
|
96
|
+
// Send text message
|
|
97
|
+
await client.chat.sendText({
|
|
98
|
+
Phone: "5491155554444",
|
|
99
|
+
Body: "Hello World!",
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
// Send image
|
|
103
|
+
await client.chat.sendImage({
|
|
104
|
+
Phone: "5491155554444",
|
|
105
|
+
Image: "data:image/jpeg;base64,/9j/4AAQ...",
|
|
106
|
+
Caption: "Check this out!",
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
// Send interactive buttons
|
|
110
|
+
await client.chat.sendButtons({
|
|
111
|
+
Phone: "5491155554444",
|
|
112
|
+
Body: "Choose an option:",
|
|
113
|
+
Buttons: [
|
|
114
|
+
{ ButtonId: "yes", ButtonText: { DisplayText: "Yes" }, Type: 1 },
|
|
115
|
+
{ ButtonId: "no", ButtonText: { DisplayText: "No" }, Type: 1 },
|
|
116
|
+
],
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// Send list menu
|
|
120
|
+
await client.chat.sendList({
|
|
121
|
+
Phone: "5491155554444",
|
|
122
|
+
Body: "Select from menu:",
|
|
123
|
+
Title: "Options",
|
|
124
|
+
ButtonText: "View Menu",
|
|
125
|
+
Sections: [{
|
|
126
|
+
Title: "Main Options",
|
|
127
|
+
Rows: [
|
|
128
|
+
{ Title: "Option 1", Description: "First choice", RowId: "opt1" },
|
|
129
|
+
{ Title: "Option 2", Description: "Second choice", RowId: "opt2" },
|
|
130
|
+
],
|
|
131
|
+
}],
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
// Send poll
|
|
135
|
+
await client.chat.sendPoll({
|
|
136
|
+
Phone: "5491155554444",
|
|
137
|
+
Name: "What's your favorite color?",
|
|
138
|
+
Options: [{ Name: "Red" }, { Name: "Blue" }, { Name: "Green" }],
|
|
139
|
+
SelectableCount: 1,
|
|
140
|
+
});
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## 👥 Group Management
|
|
144
|
+
|
|
145
|
+
```typescript
|
|
146
|
+
// Create group
|
|
147
|
+
const group = await client.group.create("My Group", [
|
|
148
|
+
"5491155553934",
|
|
149
|
+
"5491155553935",
|
|
150
|
+
]);
|
|
88
151
|
|
|
89
|
-
//
|
|
90
|
-
const
|
|
152
|
+
// Get group info
|
|
153
|
+
const info = await client.group.getInfo(group.JID);
|
|
91
154
|
|
|
92
|
-
//
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
155
|
+
// Add participants
|
|
156
|
+
await client.group.updateParticipants(
|
|
157
|
+
group.JID,
|
|
158
|
+
"add",
|
|
159
|
+
["5491155553936"]
|
|
96
160
|
);
|
|
161
|
+
|
|
162
|
+
// Set group settings
|
|
163
|
+
await client.group.setName(group.JID, "New Group Name");
|
|
164
|
+
await client.group.setTopic(group.JID, "Welcome message");
|
|
165
|
+
await client.group.setAnnounce(group.JID, true); // Only admins can send
|
|
97
166
|
```
|
|
98
167
|
|
|
99
|
-
##
|
|
168
|
+
## 👤 User Operations
|
|
169
|
+
|
|
170
|
+
```typescript
|
|
171
|
+
// Check if numbers are WhatsApp users
|
|
172
|
+
const check = await client.user.check(["5491155554444"]);
|
|
173
|
+
|
|
174
|
+
// Get user info
|
|
175
|
+
const info = await client.user.getInfo(["5491155554444"]);
|
|
176
|
+
|
|
177
|
+
// Get contacts
|
|
178
|
+
const contacts = await client.user.getContacts();
|
|
179
|
+
|
|
180
|
+
// Send presence status
|
|
181
|
+
await client.user.sendPresence({
|
|
182
|
+
Phone: "5491155554444",
|
|
183
|
+
State: "available",
|
|
184
|
+
});
|
|
185
|
+
```
|
|
100
186
|
|
|
101
|
-
|
|
187
|
+
## 🔗 Webhook Setup
|
|
102
188
|
|
|
103
189
|
```typescript
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
token?: string; // Your user authentication token (optional)
|
|
107
|
-
}
|
|
190
|
+
// Set webhook URL
|
|
191
|
+
await client.webhook.setWebhook("https://your-server.com/webhook");
|
|
108
192
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
193
|
+
// Get webhook config
|
|
194
|
+
const config = await client.webhook.getWebhook();
|
|
195
|
+
|
|
196
|
+
// Update webhook
|
|
197
|
+
await client.webhook.updateWebhook("https://new-server.com/webhook");
|
|
112
198
|
```
|
|
113
199
|
|
|
114
|
-
|
|
200
|
+
## 📚 Examples
|
|
201
|
+
|
|
202
|
+
Check out the complete examples in the `examples/` directory:
|
|
203
|
+
|
|
204
|
+
- **[basic-usage.js](examples/basic-usage.js)** - Getting started, connection, basic operations
|
|
205
|
+
- **[advanced-features.js](examples/advanced-features.js)** - Phone pairing, interactive messages, advanced group management
|
|
206
|
+
- **[chatbot-example.js](examples/chatbot-example.js)** - Complete bot with commands and auto-replies
|
|
207
|
+
- **[webhook-events-example.js](examples/webhook-events-example.js)** - Comprehensive webhook event handling
|
|
115
208
|
|
|
116
|
-
|
|
209
|
+
### Run Examples
|
|
117
210
|
|
|
118
|
-
|
|
119
|
-
|
|
211
|
+
```bash
|
|
212
|
+
# Basic usage
|
|
213
|
+
node examples/basic-usage.js
|
|
214
|
+
|
|
215
|
+
# Advanced features
|
|
216
|
+
node examples/advanced-features.js
|
|
217
|
+
|
|
218
|
+
# Start chatbot
|
|
219
|
+
node examples/chatbot-example.js
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
## 🤖 Simple Bot Example
|
|
120
223
|
|
|
121
224
|
```typescript
|
|
122
|
-
|
|
123
|
-
const client = new WuzapiClient({
|
|
124
|
-
apiUrl: "http://localhost:8080",
|
|
125
|
-
token: "your-default-token",
|
|
126
|
-
});
|
|
225
|
+
import WuzapiClient from "wuzapi";
|
|
127
226
|
|
|
128
|
-
// Option 2: No global token, specify per request
|
|
129
227
|
const client = new WuzapiClient({
|
|
130
228
|
apiUrl: "http://localhost:8080",
|
|
131
|
-
|
|
229
|
+
token: "your-token",
|
|
132
230
|
});
|
|
133
231
|
|
|
134
|
-
//
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
token: "admin-token", // Default admin token
|
|
138
|
-
});
|
|
232
|
+
// Connect and wait for messages
|
|
233
|
+
await client.session.connect({ Subscribe: ["Message"] });
|
|
234
|
+
await client.webhook.setWebhook("https://your-server.com/webhook");
|
|
139
235
|
|
|
140
|
-
//
|
|
141
|
-
|
|
142
|
-
{
|
|
143
|
-
|
|
144
|
-
)
|
|
236
|
+
// In your webhook handler:
|
|
237
|
+
app.post("/webhook", async (req, res) => {
|
|
238
|
+
const { event } = req.body;
|
|
239
|
+
|
|
240
|
+
if (event?.Message?.conversation) {
|
|
241
|
+
const message = event.Message.conversation;
|
|
242
|
+
const from = event.Info.RemoteJid.replace("@s.whatsapp.net", "");
|
|
243
|
+
|
|
244
|
+
if (message.toLowerCase().includes("hello")) {
|
|
245
|
+
await client.chat.sendText({
|
|
246
|
+
Phone: from,
|
|
247
|
+
Body: "Hello! 👋 How can I help you?",
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
res.json({ success: true });
|
|
253
|
+
});
|
|
145
254
|
```
|
|
146
255
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
The client is organized into logical modules:
|
|
256
|
+
---
|
|
150
257
|
|
|
151
|
-
|
|
258
|
+
## 📖 Complete API Reference
|
|
152
259
|
|
|
153
|
-
|
|
260
|
+
<details>
|
|
261
|
+
<summary><strong>📱 Session Module</strong> - Connection and authentication</summary>
|
|
154
262
|
|
|
263
|
+
### Connection
|
|
155
264
|
```typescript
|
|
156
265
|
// Connect to WhatsApp
|
|
157
266
|
await client.session.connect({
|
|
@@ -162,15 +271,30 @@ await client.session.connect({
|
|
|
162
271
|
// Get connection status
|
|
163
272
|
const status = await client.session.getStatus();
|
|
164
273
|
|
|
165
|
-
// Get QR code for initial setup
|
|
166
|
-
const qr = await client.session.getQRCode();
|
|
167
|
-
|
|
168
274
|
// Disconnect (keeps session)
|
|
169
275
|
await client.session.disconnect();
|
|
170
276
|
|
|
171
277
|
// Logout (destroys session)
|
|
172
278
|
await client.session.logout();
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
### Authentication
|
|
282
|
+
```typescript
|
|
283
|
+
// Get QR code for scanning
|
|
284
|
+
const qr = await client.session.getQRCode();
|
|
285
|
+
|
|
286
|
+
// Pair phone using verification code (alternative to QR)
|
|
287
|
+
await client.session.pairPhone("5491155554444", "123456");
|
|
288
|
+
|
|
289
|
+
// Request message history sync
|
|
290
|
+
await client.session.requestHistory();
|
|
291
|
+
|
|
292
|
+
// Configure proxy
|
|
293
|
+
await client.session.setProxy("socks5://user:pass@proxy:port");
|
|
294
|
+
```
|
|
173
295
|
|
|
296
|
+
### S3 Storage
|
|
297
|
+
```typescript
|
|
174
298
|
// Configure S3 storage
|
|
175
299
|
await client.session.configureS3({
|
|
176
300
|
enabled: true,
|
|
@@ -183,29 +307,31 @@ await client.session.configureS3({
|
|
|
183
307
|
mediaDelivery: "both",
|
|
184
308
|
retentionDays: 30,
|
|
185
309
|
});
|
|
310
|
+
|
|
311
|
+
// Get S3 configuration
|
|
312
|
+
const s3Config = await client.session.getS3Config();
|
|
313
|
+
|
|
314
|
+
// Test S3 connection
|
|
315
|
+
await client.session.testS3();
|
|
316
|
+
|
|
317
|
+
// Delete S3 configuration
|
|
318
|
+
await client.session.deleteS3Config();
|
|
186
319
|
```
|
|
187
320
|
|
|
188
|
-
|
|
321
|
+
</details>
|
|
189
322
|
|
|
190
|
-
|
|
323
|
+
<details>
|
|
324
|
+
<summary><strong>💬 Chat Module</strong> - Send and manage messages</summary>
|
|
191
325
|
|
|
326
|
+
### Basic Messages
|
|
192
327
|
```typescript
|
|
193
|
-
// Send text message
|
|
328
|
+
// Send text message
|
|
194
329
|
await client.chat.sendText({
|
|
195
330
|
Phone: "5491155554444",
|
|
196
331
|
Body: "Hello World!",
|
|
197
332
|
Id: "optional-message-id",
|
|
198
333
|
});
|
|
199
334
|
|
|
200
|
-
// Send with specific token override
|
|
201
|
-
await client.chat.sendText(
|
|
202
|
-
{
|
|
203
|
-
Phone: "5491155554444",
|
|
204
|
-
Body: "Hello with custom token!",
|
|
205
|
-
},
|
|
206
|
-
{ token: "user-specific-token" }
|
|
207
|
-
);
|
|
208
|
-
|
|
209
335
|
// Reply to a message
|
|
210
336
|
await client.chat.sendText({
|
|
211
337
|
Phone: "5491155554444",
|
|
@@ -215,47 +341,97 @@ await client.chat.sendText({
|
|
|
215
341
|
Participant: "5491155553935@s.whatsapp.net",
|
|
216
342
|
},
|
|
217
343
|
});
|
|
344
|
+
```
|
|
218
345
|
|
|
346
|
+
### Media Messages
|
|
347
|
+
```typescript
|
|
219
348
|
// Send image
|
|
220
349
|
await client.chat.sendImage({
|
|
221
350
|
Phone: "5491155554444",
|
|
222
|
-
Image: "data:image/jpeg;base64,/9j/
|
|
351
|
+
Image: "data:image/jpeg;base64,/9j/4AAQ...",
|
|
223
352
|
Caption: "Check this out!",
|
|
224
353
|
});
|
|
225
354
|
|
|
226
|
-
// Send
|
|
227
|
-
await client.chat.
|
|
355
|
+
// Send audio
|
|
356
|
+
await client.chat.sendAudio({
|
|
357
|
+
Phone: "5491155554444",
|
|
358
|
+
Audio: "data:audio/ogg;base64,T2dnUw...",
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
// Send video
|
|
362
|
+
await client.chat.sendVideo({
|
|
363
|
+
Phone: "5491155554444",
|
|
364
|
+
Video: "data:video/mp4;base64,AAAAIGZ0eXA...",
|
|
365
|
+
Caption: "Video caption",
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
// Send document
|
|
369
|
+
await client.chat.sendDocument({
|
|
370
|
+
Phone: "5491155554444",
|
|
371
|
+
Document: "data:application/pdf;base64,JVBERi0x...",
|
|
372
|
+
FileName: "document.pdf",
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
// Send sticker
|
|
376
|
+
await client.chat.sendSticker({
|
|
377
|
+
Phone: "5491155554444",
|
|
378
|
+
Sticker: "data:image/webp;base64,UklGRuI...",
|
|
379
|
+
});
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
### Interactive Messages
|
|
383
|
+
```typescript
|
|
384
|
+
// Send buttons
|
|
385
|
+
await client.chat.sendButtons({
|
|
228
386
|
Phone: "5491155554444",
|
|
229
|
-
|
|
230
|
-
Footer: "
|
|
387
|
+
Body: "Choose an option:",
|
|
388
|
+
Footer: "Select one:",
|
|
231
389
|
Buttons: [
|
|
232
|
-
{
|
|
233
|
-
{
|
|
234
|
-
{ DisplayText: "Visit Site", Type: "url", Url: "https://example.com" },
|
|
235
|
-
{ DisplayText: "Call Us", Type: "call", PhoneNumber: "1155554444" },
|
|
390
|
+
{ ButtonId: "option1", ButtonText: { DisplayText: "Option 1" }, Type: 1 },
|
|
391
|
+
{ ButtonId: "option2", ButtonText: { DisplayText: "Option 2" }, Type: 1 },
|
|
236
392
|
],
|
|
237
393
|
});
|
|
238
394
|
|
|
239
|
-
// Send
|
|
240
|
-
await client.chat.
|
|
395
|
+
// Send list message
|
|
396
|
+
await client.chat.sendList({
|
|
241
397
|
Phone: "5491155554444",
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
398
|
+
Body: "Please select from the menu:",
|
|
399
|
+
Title: "Menu Options",
|
|
400
|
+
ButtonText: "View Menu",
|
|
401
|
+
Sections: [
|
|
402
|
+
{
|
|
403
|
+
Title: "Main Course",
|
|
404
|
+
Rows: [
|
|
405
|
+
{ Title: "Pizza", Description: "Delicious pizza", RowId: "pizza" },
|
|
406
|
+
{ Title: "Burger", Description: "Tasty burger", RowId: "burger" },
|
|
407
|
+
],
|
|
408
|
+
},
|
|
409
|
+
],
|
|
245
410
|
});
|
|
246
411
|
|
|
247
|
-
// Send
|
|
248
|
-
await client.chat.
|
|
412
|
+
// Send poll
|
|
413
|
+
await client.chat.sendPoll({
|
|
249
414
|
Phone: "5491155554444",
|
|
250
|
-
Name: "
|
|
251
|
-
|
|
252
|
-
|
|
415
|
+
Name: "What's your favorite color?",
|
|
416
|
+
Options: [{ Name: "Red" }, { Name: "Blue" }, { Name: "Green" }],
|
|
417
|
+
SelectableCount: 1,
|
|
253
418
|
});
|
|
419
|
+
```
|
|
254
420
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
421
|
+
### Message Management
|
|
422
|
+
```typescript
|
|
423
|
+
// Delete a message
|
|
424
|
+
await client.chat.deleteMessage({
|
|
425
|
+
Phone: "5491155554444",
|
|
426
|
+
Id: "message-id-to-delete",
|
|
427
|
+
Remote: true, // Delete for everyone
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
// Edit a message
|
|
431
|
+
await client.chat.editMessage({
|
|
432
|
+
Phone: "5491155554444",
|
|
433
|
+
MessageId: "message-id-to-edit",
|
|
434
|
+
NewText: "This is the updated message text",
|
|
259
435
|
});
|
|
260
436
|
|
|
261
437
|
// React to message
|
|
@@ -265,6 +441,40 @@ await client.chat.react({
|
|
|
265
441
|
Id: "message-id-to-react-to",
|
|
266
442
|
});
|
|
267
443
|
|
|
444
|
+
// Mark messages as read
|
|
445
|
+
await client.chat.markRead({
|
|
446
|
+
Id: ["message-id-1", "message-id-2"],
|
|
447
|
+
Chat: "5491155553934@s.whatsapp.net",
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
// Send chat presence (typing indicator)
|
|
451
|
+
await client.chat.sendPresence({
|
|
452
|
+
Phone: "5491155554444",
|
|
453
|
+
State: "composing", // or "paused"
|
|
454
|
+
Media: "text",
|
|
455
|
+
});
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
### Location and Contacts
|
|
459
|
+
```typescript
|
|
460
|
+
// Send location
|
|
461
|
+
await client.chat.sendLocation({
|
|
462
|
+
Phone: "5491155554444",
|
|
463
|
+
Latitude: 48.85837,
|
|
464
|
+
Longitude: 2.294481,
|
|
465
|
+
Name: "Eiffel Tower, Paris",
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
// Send contact
|
|
469
|
+
await client.chat.sendContact({
|
|
470
|
+
Phone: "5491155554444",
|
|
471
|
+
Name: "John Doe",
|
|
472
|
+
Vcard: "BEGIN:VCARD\nVERSION:3.0\nN:Doe;John;;;\nFN:John Doe\nTEL:+1234567890\nEND:VCARD",
|
|
473
|
+
});
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
### Media Download
|
|
477
|
+
```typescript
|
|
268
478
|
// Download media
|
|
269
479
|
const media = await client.chat.downloadImage({
|
|
270
480
|
Url: "https://mmg.whatsapp.net/d/f/...",
|
|
@@ -275,9 +485,10 @@ const media = await client.chat.downloadImage({
|
|
|
275
485
|
});
|
|
276
486
|
```
|
|
277
487
|
|
|
278
|
-
|
|
488
|
+
</details>
|
|
279
489
|
|
|
280
|
-
|
|
490
|
+
<details>
|
|
491
|
+
<summary><strong>👤 User Module</strong> - User information and contacts</summary>
|
|
281
492
|
|
|
282
493
|
```typescript
|
|
283
494
|
// Check if numbers are WhatsApp users
|
|
@@ -291,12 +502,21 @@ const avatar = await client.user.getAvatar("5491155554444", true); // true for p
|
|
|
291
502
|
|
|
292
503
|
// Get all contacts
|
|
293
504
|
const contacts = await client.user.getContacts();
|
|
505
|
+
|
|
506
|
+
// Send user presence (online/offline status)
|
|
507
|
+
await client.user.sendPresence({
|
|
508
|
+
Phone: "5491155554444",
|
|
509
|
+
State: "available", // or "unavailable"
|
|
510
|
+
LastSeen: Date.now(),
|
|
511
|
+
});
|
|
294
512
|
```
|
|
295
513
|
|
|
296
|
-
|
|
514
|
+
</details>
|
|
297
515
|
|
|
298
|
-
|
|
516
|
+
<details>
|
|
517
|
+
<summary><strong>👥 Group Module</strong> - Group management</summary>
|
|
299
518
|
|
|
519
|
+
### Basic Group Operations
|
|
300
520
|
```typescript
|
|
301
521
|
// List all groups
|
|
302
522
|
const groups = await client.group.list();
|
|
@@ -310,84 +530,108 @@ const newGroup = await client.group.create("My New Group", [
|
|
|
310
530
|
// Get group info
|
|
311
531
|
const groupInfo = await client.group.getInfo("120362023605733675@g.us");
|
|
312
532
|
|
|
533
|
+
// Leave a group
|
|
534
|
+
await client.group.leave("120362023605733675@g.us");
|
|
535
|
+
```
|
|
536
|
+
|
|
537
|
+
### Group Settings
|
|
538
|
+
```typescript
|
|
313
539
|
// Set group name
|
|
314
540
|
await client.group.setName("120362023605733675@g.us", "New Group Name");
|
|
315
541
|
|
|
316
|
-
// Set group
|
|
317
|
-
await client.group.
|
|
542
|
+
// Set group topic/description
|
|
543
|
+
await client.group.setTopic(
|
|
318
544
|
"120362023605733675@g.us",
|
|
319
|
-
"
|
|
545
|
+
"Welcome to our group! Please read the rules."
|
|
320
546
|
);
|
|
321
547
|
|
|
322
|
-
//
|
|
323
|
-
|
|
548
|
+
// Set group announcement setting (only admins can send messages)
|
|
549
|
+
await client.group.setAnnounce("120362023605733675@g.us", true);
|
|
324
550
|
|
|
325
551
|
// Set group locked (only admins can modify info)
|
|
326
552
|
await client.group.setLocked("120362023605733675@g.us", true);
|
|
327
553
|
|
|
328
554
|
// Set disappearing messages
|
|
329
555
|
await client.group.setEphemeral("120362023605733675@g.us", "24h"); // '24h', '7d', '90d', or 'off'
|
|
556
|
+
```
|
|
557
|
+
|
|
558
|
+
### Group Media
|
|
559
|
+
```typescript
|
|
560
|
+
// Set group photo (JPEG only)
|
|
561
|
+
await client.group.setPhoto(
|
|
562
|
+
"120362023605733675@g.us",
|
|
563
|
+
"data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQ..."
|
|
564
|
+
);
|
|
330
565
|
|
|
331
566
|
// Remove group photo
|
|
332
567
|
await client.group.removePhoto("120362023605733675@g.us");
|
|
333
568
|
```
|
|
334
569
|
|
|
335
|
-
###
|
|
570
|
+
### Invites and Participants
|
|
571
|
+
```typescript
|
|
572
|
+
// Get invite link
|
|
573
|
+
const invite = await client.group.getInviteLink("120362023605733675@g.us");
|
|
336
574
|
|
|
337
|
-
|
|
575
|
+
// Join a group using invite link
|
|
576
|
+
const joinResult = await client.group.join("https://chat.whatsapp.com/XXXXXXXXX");
|
|
338
577
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
const adminClient = new WuzapiClient({
|
|
342
|
-
apiUrl: "http://localhost:8080",
|
|
343
|
-
token: "your-admin-token",
|
|
344
|
-
});
|
|
578
|
+
// Get group invite information
|
|
579
|
+
const inviteInfo = await client.group.getInviteInfo("https://chat.whatsapp.com/XXXXXXXXX");
|
|
345
580
|
|
|
346
|
-
//
|
|
347
|
-
|
|
581
|
+
// Update group participants (add, remove, promote, demote)
|
|
582
|
+
await client.group.updateParticipants(
|
|
583
|
+
"120362023605733675@g.us",
|
|
584
|
+
"add", // "add", "remove", "promote", "demote"
|
|
585
|
+
["5491155553936", "5491155553937"]
|
|
586
|
+
);
|
|
587
|
+
```
|
|
348
588
|
|
|
349
|
-
|
|
350
|
-
const client = new WuzapiClient({
|
|
351
|
-
apiUrl: "http://localhost:8080",
|
|
352
|
-
// No default token or user token as default
|
|
353
|
-
});
|
|
589
|
+
</details>
|
|
354
590
|
|
|
355
|
-
|
|
591
|
+
<details>
|
|
592
|
+
<summary><strong>👨💼 Admin Module</strong> - User management (requires admin token)</summary>
|
|
593
|
+
|
|
594
|
+
```typescript
|
|
595
|
+
// List all users
|
|
356
596
|
const users = await client.admin.listUsers({ token: "admin-token" });
|
|
357
597
|
|
|
598
|
+
// Get a specific user by ID
|
|
599
|
+
const user = await client.admin.getUser(2, { token: "admin-token" });
|
|
600
|
+
|
|
358
601
|
// Add new user
|
|
359
|
-
const newUser = await client.admin.addUser(
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
proxyURL: "socks5://user:pass@proxy:port",
|
|
368
|
-
},
|
|
369
|
-
s3Config: {
|
|
370
|
-
enabled: true,
|
|
371
|
-
endpoint: "https://s3.amazonaws.com",
|
|
372
|
-
region: "us-east-1",
|
|
373
|
-
bucket: "user-media-bucket",
|
|
374
|
-
accessKey: "AKIA...",
|
|
375
|
-
secretKey: "secret...",
|
|
376
|
-
pathStyle: false,
|
|
377
|
-
mediaDelivery: "both",
|
|
378
|
-
retentionDays: 30,
|
|
379
|
-
},
|
|
602
|
+
const newUser = await client.admin.addUser({
|
|
603
|
+
name: "John Doe",
|
|
604
|
+
token: "user-token-123",
|
|
605
|
+
webhook: "https://example.com/webhook",
|
|
606
|
+
events: "Message,ReadReceipt",
|
|
607
|
+
proxyConfig: {
|
|
608
|
+
enabled: true,
|
|
609
|
+
proxyURL: "socks5://user:pass@proxy:port",
|
|
380
610
|
},
|
|
381
|
-
|
|
382
|
-
|
|
611
|
+
s3Config: {
|
|
612
|
+
enabled: true,
|
|
613
|
+
endpoint: "https://s3.amazonaws.com",
|
|
614
|
+
region: "us-east-1",
|
|
615
|
+
bucket: "user-media-bucket",
|
|
616
|
+
accessKey: "AKIA...",
|
|
617
|
+
secretKey: "secret...",
|
|
618
|
+
pathStyle: false,
|
|
619
|
+
mediaDelivery: "both",
|
|
620
|
+
retentionDays: 30,
|
|
621
|
+
},
|
|
622
|
+
}, { token: "admin-token" });
|
|
383
623
|
|
|
384
624
|
// Delete user
|
|
385
625
|
await client.admin.deleteUser(2, { token: "admin-token" });
|
|
626
|
+
|
|
627
|
+
// Delete user completely (full deletion including all data)
|
|
628
|
+
await client.admin.deleteUserComplete(2, { token: "admin-token" });
|
|
386
629
|
```
|
|
387
630
|
|
|
388
|
-
|
|
631
|
+
</details>
|
|
389
632
|
|
|
390
|
-
|
|
633
|
+
<details>
|
|
634
|
+
<summary><strong>🔗 Webhook Module</strong> - Webhook configuration</summary>
|
|
391
635
|
|
|
392
636
|
```typescript
|
|
393
637
|
// Set webhook URL
|
|
@@ -397,134 +641,44 @@ await client.webhook.setWebhook("https://my-server.com/webhook");
|
|
|
397
641
|
const webhookConfig = await client.webhook.getWebhook();
|
|
398
642
|
console.log("Webhook URL:", webhookConfig.webhook);
|
|
399
643
|
console.log("Subscribed events:", webhookConfig.subscribe);
|
|
400
|
-
```
|
|
401
644
|
|
|
402
|
-
|
|
645
|
+
// Update webhook URL
|
|
646
|
+
await client.webhook.updateWebhook("https://my-new-server.com/webhook");
|
|
403
647
|
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
648
|
+
// Delete webhook configuration
|
|
649
|
+
await client.webhook.deleteWebhook();
|
|
650
|
+
```
|
|
407
651
|
|
|
408
|
-
|
|
652
|
+
</details>
|
|
409
653
|
|
|
410
|
-
|
|
654
|
+
<details>
|
|
655
|
+
<summary><strong>📰 Newsletter Module</strong> - Newsletter management (Business accounts only)</summary>
|
|
411
656
|
|
|
412
|
-
```
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
"Sender": "5491155553934@s.whatsapp.net",
|
|
423
|
-
"IsFromMe": false,
|
|
424
|
-
"IsGroup": false
|
|
425
|
-
}
|
|
426
|
-
},
|
|
427
|
-
"Message": {
|
|
428
|
-
"conversation": "Hello, this is a test message!"
|
|
429
|
-
},
|
|
430
|
-
"IsEphemeral": false,
|
|
431
|
-
"IsViewOnce": false,
|
|
432
|
-
"IsDocumentWithCaption": false,
|
|
433
|
-
"IsEdit": false
|
|
434
|
-
}
|
|
435
|
-
}
|
|
657
|
+
```typescript
|
|
658
|
+
// List all subscribed newsletters
|
|
659
|
+
const newsletters = await client.newsletter.list();
|
|
660
|
+
|
|
661
|
+
newsletters.Newsletters.forEach((newsletter) => {
|
|
662
|
+
console.log(`Newsletter: ${newsletter.Name}`);
|
|
663
|
+
console.log(`Description: ${newsletter.Description}`);
|
|
664
|
+
console.log(`Handle: ${newsletter.Handle}`);
|
|
665
|
+
console.log(`State: ${newsletter.State}`);
|
|
666
|
+
});
|
|
436
667
|
```
|
|
437
668
|
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
```json
|
|
441
|
-
{
|
|
442
|
-
"event": {
|
|
443
|
-
"Info": {
|
|
444
|
-
"ID": "3EB06F9067F80BAB89FF",
|
|
445
|
-
"Type": "image",
|
|
446
|
-
"PushName": "John Doe",
|
|
447
|
-
"Timestamp": "2024-12-25T10:30:00Z",
|
|
448
|
-
"Source": {
|
|
449
|
-
"Chat": "5491155553934@s.whatsapp.net",
|
|
450
|
-
"Sender": "5491155553934@s.whatsapp.net",
|
|
451
|
-
"IsFromMe": false,
|
|
452
|
-
"IsGroup": false
|
|
453
|
-
}
|
|
454
|
-
},
|
|
455
|
-
"Message": {
|
|
456
|
-
"imageMessage": {
|
|
457
|
-
"caption": "Check out this photo!",
|
|
458
|
-
"mimetype": "image/jpeg",
|
|
459
|
-
"width": 1920,
|
|
460
|
-
"height": 1080,
|
|
461
|
-
"fileLength": 245632
|
|
462
|
-
}
|
|
463
|
-
},
|
|
464
|
-
"IsEphemeral": false,
|
|
465
|
-
"IsViewOnce": false
|
|
466
|
-
},
|
|
467
|
-
"s3": {
|
|
468
|
-
"url": "https://my-bucket.s3.us-east-1.amazonaws.com/users/abc123/inbox/5491155553934/2024/12/25/images/3EB06F9067F80BAB89FF.jpg",
|
|
469
|
-
"key": "users/abc123/inbox/5491155553934/2024/12/25/images/3EB06F9067F80BAB89FF.jpg",
|
|
470
|
-
"bucket": "my-bucket",
|
|
471
|
-
"size": 245632,
|
|
472
|
-
"mimeType": "image/jpeg",
|
|
473
|
-
"fileName": "3EB06F9067F80BAB89FF.jpg"
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
```
|
|
669
|
+
</details>
|
|
477
670
|
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
```json
|
|
481
|
-
{
|
|
482
|
-
"event": {
|
|
483
|
-
"Info": { "..." },
|
|
484
|
-
"Message": {
|
|
485
|
-
"imageMessage": {
|
|
486
|
-
"caption": "Check out this photo!",
|
|
487
|
-
"mimetype": "image/jpeg",
|
|
488
|
-
"width": 1920,
|
|
489
|
-
"height": 1080
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
},
|
|
493
|
-
"base64": "/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSj/2wBDAQcHBwoIChMKChMoGhYaKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCj/wAARCAABAAEDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAv/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFQEBAQAAAAAAAAAAAAAAAAAAAAX/xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwCdABmX/9k=",
|
|
494
|
-
"mimeType": "image/jpeg",
|
|
495
|
-
"fileName": "3EB06F9067F80BAB89FF.jpg",
|
|
496
|
-
"s3": {
|
|
497
|
-
"url": "https://my-bucket.s3.us-east-1.amazonaws.com/users/abc123/inbox/5491155553934/2024/12/25/images/3EB06F9067F80BAB89FF.jpg",
|
|
498
|
-
"key": "users/abc123/inbox/5491155553934/2024/12/25/images/3EB06F9067F80BAB89FF.jpg",
|
|
499
|
-
"bucket": "my-bucket",
|
|
500
|
-
"size": 245632,
|
|
501
|
-
"mimeType": "image/jpeg",
|
|
502
|
-
"fileName": "3EB06F9067F80BAB89FF.jpg"
|
|
503
|
-
}
|
|
504
|
-
}
|
|
505
|
-
```
|
|
671
|
+
---
|
|
506
672
|
|
|
507
|
-
|
|
673
|
+
## 🎣 Webhook Event Handling
|
|
508
674
|
|
|
509
|
-
|
|
510
|
-
{
|
|
511
|
-
"event": { "..." },
|
|
512
|
-
"base64": "/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSj/2wBDAQcHBwoIChMKChMoGhYaKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCj/wAARCAABAAEDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAv/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFQEBAQAAAAAAAAAAAAAAAAAAAAX/xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwCdABmX/9k=",
|
|
513
|
-
"mimeType": "image/jpeg",
|
|
514
|
-
"fileName": "3EB06F9067F80BAB89FF.jpg"
|
|
515
|
-
}
|
|
516
|
-
```
|
|
675
|
+
WuzAPI sends real-time events to your webhook endpoint. Here's how to handle them:
|
|
517
676
|
|
|
518
677
|
### Basic Webhook Setup
|
|
519
678
|
|
|
520
679
|
```typescript
|
|
521
680
|
import express from "express";
|
|
522
|
-
import WuzapiClient, {
|
|
523
|
-
EventType,
|
|
524
|
-
hasS3Media,
|
|
525
|
-
hasBase64Media,
|
|
526
|
-
WebhookPayload,
|
|
527
|
-
} from "wuzapi";
|
|
681
|
+
import WuzapiClient, { getMessageContent, hasS3Media } from "wuzapi";
|
|
528
682
|
|
|
529
683
|
const app = express();
|
|
530
684
|
app.use(express.json());
|
|
@@ -534,787 +688,73 @@ const client = new WuzapiClient({
|
|
|
534
688
|
token: "your-token",
|
|
535
689
|
});
|
|
536
690
|
|
|
537
|
-
// Webhook endpoint with S3 media support
|
|
538
691
|
app.post("/webhook", async (req, res) => {
|
|
539
692
|
try {
|
|
540
|
-
const webhookPayload
|
|
541
|
-
|
|
542
|
-
// Handle S3 media
|
|
693
|
+
const webhookPayload = req.body;
|
|
694
|
+
|
|
695
|
+
// Handle S3 media if present
|
|
543
696
|
if (hasS3Media(webhookPayload)) {
|
|
544
|
-
console.log("
|
|
545
|
-
url: webhookPayload.s3.url,
|
|
546
|
-
bucket: webhookPayload.s3.bucket,
|
|
547
|
-
size: webhookPayload.s3.size,
|
|
548
|
-
type: webhookPayload.s3.mimeType,
|
|
549
|
-
});
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
if (hasBase64Media(webhookPayload)) {
|
|
553
|
-
console.log("📦 Base64 media included");
|
|
554
|
-
// Process base64 media: webhookPayload.base64
|
|
697
|
+
console.log("S3 Media:", webhookPayload.s3.url);
|
|
555
698
|
}
|
|
556
|
-
|
|
557
|
-
// Extract the actual event data
|
|
699
|
+
|
|
558
700
|
const event = webhookPayload.event || webhookPayload;
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
await handlePresence(event);
|
|
571
|
-
break;
|
|
572
|
-
default:
|
|
573
|
-
console.log(`Unhandled event type: ${eventType}`);
|
|
574
|
-
}
|
|
575
|
-
|
|
576
|
-
res.status(200).json({ success: true });
|
|
577
|
-
} catch (error) {
|
|
578
|
-
console.error("Webhook error:", error);
|
|
579
|
-
res.status(500).json({ error: error.message });
|
|
580
|
-
}
|
|
581
|
-
});
|
|
582
|
-
|
|
583
|
-
// Helper function to detect event type
|
|
584
|
-
function detectEventType(event: any): EventType | string {
|
|
585
|
-
if (event.Message && event.Info) return EventType.MESSAGE;
|
|
586
|
-
if (event.MessageIDs && event.Type) return EventType.RECEIPT;
|
|
587
|
-
if (event.From && typeof event.Unavailable !== "undefined")
|
|
588
|
-
return EventType.PRESENCE;
|
|
589
|
-
if (event.State && event.MessageSource) return EventType.CHAT_PRESENCE;
|
|
590
|
-
if (event.Codes) return EventType.QR;
|
|
591
|
-
// Add more detection logic as needed
|
|
592
|
-
return "Unknown";
|
|
593
|
-
}
|
|
594
|
-
|
|
595
|
-
app.listen(3000, () => {
|
|
596
|
-
console.log("Webhook server running on port 3000");
|
|
597
|
-
});
|
|
598
|
-
```
|
|
599
|
-
|
|
600
|
-
### Message Events
|
|
601
|
-
|
|
602
|
-
Handle incoming messages of all types using the comprehensive Message types:
|
|
603
|
-
|
|
604
|
-
```typescript
|
|
605
|
-
import { getMessageContent } from "wuzapi";
|
|
606
|
-
|
|
607
|
-
async function handleMessage(event) {
|
|
608
|
-
const { Info, Message, IsEphemeral, IsViewOnce } = event;
|
|
609
|
-
const from = Info.RemoteJid.replace("@s.whatsapp.net", "");
|
|
610
|
-
const isGroup = Info.RemoteJid.includes("@g.us");
|
|
611
|
-
|
|
612
|
-
console.log(`New message from ${from}${isGroup ? " (group)" : ""}`);
|
|
613
|
-
|
|
614
|
-
if (IsEphemeral) console.log("⏱️ Ephemeral message");
|
|
615
|
-
if (IsViewOnce) console.log("👁️ View once message");
|
|
616
|
-
|
|
617
|
-
// Use the utility function to get structured message content
|
|
618
|
-
const messageContent = getMessageContent(Message);
|
|
619
|
-
|
|
620
|
-
if (!messageContent) {
|
|
621
|
-
console.log("❓ Unknown message type");
|
|
622
|
-
return;
|
|
623
|
-
}
|
|
624
|
-
|
|
625
|
-
// Handle different message types with full type safety
|
|
626
|
-
switch (messageContent.type) {
|
|
627
|
-
case "text":
|
|
628
|
-
console.log(`💬 Text: ${messageContent.content}`);
|
|
629
|
-
|
|
630
|
-
// Auto-reply to specific messages
|
|
631
|
-
if (messageContent.content.toLowerCase().includes("hello")) {
|
|
632
|
-
await client.chat.sendText({
|
|
633
|
-
Phone: from,
|
|
634
|
-
Body: "👋 Hello! Thanks for your message.",
|
|
635
|
-
});
|
|
636
|
-
}
|
|
637
|
-
break;
|
|
638
|
-
|
|
639
|
-
case "extendedText":
|
|
640
|
-
console.log(`📝 Extended text: ${messageContent.content.text}`);
|
|
641
|
-
if (messageContent.content.canonicalUrl) {
|
|
642
|
-
console.log(`🔗 Link preview: ${messageContent.content.canonicalUrl}`);
|
|
643
|
-
console.log(`📰 Title: ${messageContent.content.title}`);
|
|
644
|
-
}
|
|
645
|
-
break;
|
|
646
|
-
|
|
647
|
-
case "image":
|
|
648
|
-
const imageMsg = messageContent.content;
|
|
649
|
-
console.log(`🖼️ Image: ${imageMsg.caption || "No caption"}`);
|
|
650
|
-
console.log(`📏 Dimensions: ${imageMsg.width}x${imageMsg.height}`);
|
|
651
|
-
|
|
652
|
-
// Download the image with proper types
|
|
653
|
-
if (imageMsg.url && imageMsg.mediaKey) {
|
|
654
|
-
try {
|
|
655
|
-
const media = await client.chat.downloadImage({
|
|
656
|
-
Url: imageMsg.url,
|
|
657
|
-
MediaKey: Array.from(imageMsg.mediaKey),
|
|
658
|
-
Mimetype: imageMsg.mimetype,
|
|
659
|
-
FileSHA256: Array.from(imageMsg.fileSha256),
|
|
660
|
-
FileLength: imageMsg.fileLength,
|
|
661
|
-
});
|
|
662
|
-
console.log("✅ Image downloaded successfully");
|
|
663
|
-
} catch (error) {
|
|
664
|
-
console.log("❌ Failed to download image:", error.message);
|
|
665
|
-
}
|
|
666
|
-
}
|
|
667
|
-
break;
|
|
668
|
-
|
|
669
|
-
case "video":
|
|
670
|
-
const videoMsg = messageContent.content;
|
|
671
|
-
console.log(`🎥 Video: ${videoMsg.caption || "No caption"}`);
|
|
672
|
-
console.log(`⏱️ Duration: ${videoMsg.seconds}s`);
|
|
673
|
-
if (videoMsg.gifPlayback) {
|
|
674
|
-
console.log("🎬 GIF playback enabled");
|
|
675
|
-
}
|
|
676
|
-
break;
|
|
677
|
-
|
|
678
|
-
case "audio":
|
|
679
|
-
const audioMsg = messageContent.content;
|
|
680
|
-
console.log(`🎵 Audio: ${audioMsg.seconds}s`);
|
|
681
|
-
if (audioMsg.ptt) {
|
|
682
|
-
console.log("🎤 Voice note (Push-to-talk)");
|
|
683
|
-
}
|
|
684
|
-
break;
|
|
685
|
-
|
|
686
|
-
case "document":
|
|
687
|
-
const docMsg = messageContent.content;
|
|
688
|
-
console.log(`📄 Document: ${docMsg.fileName || docMsg.title}`);
|
|
689
|
-
console.log(`📊 Size: ${docMsg.fileLength} bytes`);
|
|
690
|
-
console.log(`📋 Type: ${docMsg.mimetype}`);
|
|
691
|
-
break;
|
|
692
|
-
|
|
693
|
-
case "location":
|
|
694
|
-
const locMsg = messageContent.content;
|
|
695
|
-
console.log(
|
|
696
|
-
`📍 Location: ${locMsg.degreesLatitude}, ${locMsg.degreesLongitude}`
|
|
697
|
-
);
|
|
698
|
-
if (locMsg.name) console.log(`🏷️ Name: ${locMsg.name}`);
|
|
699
|
-
if (locMsg.isLiveLocation) console.log("📡 Live location sharing");
|
|
700
|
-
break;
|
|
701
|
-
|
|
702
|
-
case "contact":
|
|
703
|
-
console.log(`👤 Contact: ${messageContent.content.displayName}`);
|
|
704
|
-
break;
|
|
705
|
-
|
|
706
|
-
case "sticker":
|
|
707
|
-
const stickerMsg = messageContent.content;
|
|
708
|
-
console.log(`😀 Sticker`);
|
|
709
|
-
if (stickerMsg.isAnimated) console.log("🎬 Animated");
|
|
710
|
-
if (stickerMsg.isLottie) console.log("🎨 Lottie");
|
|
711
|
-
break;
|
|
712
|
-
|
|
713
|
-
case "buttons":
|
|
714
|
-
const btnMsg = messageContent.content;
|
|
715
|
-
console.log(`🔘 Interactive buttons: ${btnMsg.contentText}`);
|
|
716
|
-
console.log(`🔢 ${btnMsg.buttons?.length || 0} buttons`);
|
|
717
|
-
break;
|
|
718
|
-
|
|
719
|
-
case "list":
|
|
720
|
-
const listMsg = messageContent.content;
|
|
721
|
-
console.log(`📋 List: ${listMsg.title}`);
|
|
722
|
-
console.log(`📝 ${listMsg.sections?.length || 0} sections`);
|
|
723
|
-
break;
|
|
724
|
-
|
|
725
|
-
case "buttonsResponse":
|
|
726
|
-
const btnResponse = messageContent.content;
|
|
727
|
-
console.log(`✅ Button clicked: ${btnResponse.selectedDisplayText}`);
|
|
728
|
-
console.log(`🆔 Button ID: ${btnResponse.selectedButtonId}`);
|
|
729
|
-
|
|
730
|
-
// Handle button responses
|
|
731
|
-
switch (btnResponse.selectedButtonId) {
|
|
732
|
-
case "help":
|
|
701
|
+
|
|
702
|
+
// Handle messages
|
|
703
|
+
if (event.Message && event.Info) {
|
|
704
|
+
const messageContent = getMessageContent(event.Message);
|
|
705
|
+
const from = event.Info.RemoteJid.replace("@s.whatsapp.net", "");
|
|
706
|
+
|
|
707
|
+
if (messageContent?.type === "text") {
|
|
708
|
+
console.log(`Message from ${from}: ${messageContent.content}`);
|
|
709
|
+
|
|
710
|
+
// Auto-reply
|
|
711
|
+
if (messageContent.content.toLowerCase().includes("hello")) {
|
|
733
712
|
await client.chat.sendText({
|
|
734
713
|
Phone: from,
|
|
735
|
-
Body: "
|
|
714
|
+
Body: "Hello! 👋 How can I help you?",
|
|
736
715
|
});
|
|
737
|
-
break;
|
|
738
|
-
case "info":
|
|
739
|
-
await client.chat.sendText({
|
|
740
|
-
Phone: from,
|
|
741
|
-
Body: "ℹ️ Here's some information...",
|
|
742
|
-
});
|
|
743
|
-
break;
|
|
744
|
-
}
|
|
745
|
-
break;
|
|
746
|
-
|
|
747
|
-
case "listResponse":
|
|
748
|
-
const listResponse = messageContent.content;
|
|
749
|
-
console.log(`✅ List selection: ${listResponse.title}`);
|
|
750
|
-
if (listResponse.singleSelectReply) {
|
|
751
|
-
console.log(
|
|
752
|
-
`🆔 Selected: ${listResponse.singleSelectReply.selectedRowId}`
|
|
753
|
-
);
|
|
754
|
-
}
|
|
755
|
-
break;
|
|
756
|
-
|
|
757
|
-
case "poll":
|
|
758
|
-
const pollMsg = messageContent.content;
|
|
759
|
-
console.log(`📊 Poll: ${pollMsg.name}`);
|
|
760
|
-
console.log(`🔢 Options: ${pollMsg.options?.length || 0}`);
|
|
761
|
-
break;
|
|
762
|
-
|
|
763
|
-
case "reaction":
|
|
764
|
-
const reactionMsg = messageContent.content;
|
|
765
|
-
console.log(`😊 Reaction: ${reactionMsg.text}`);
|
|
766
|
-
console.log(`📝 To message: ${reactionMsg.key?.id}`);
|
|
767
|
-
break;
|
|
768
|
-
|
|
769
|
-
case "groupInvite":
|
|
770
|
-
const inviteMsg = messageContent.content;
|
|
771
|
-
console.log(`👥 Group invite: ${inviteMsg.groupName}`);
|
|
772
|
-
console.log(`🔗 Code: ${inviteMsg.inviteCode}`);
|
|
773
|
-
break;
|
|
774
|
-
|
|
775
|
-
default:
|
|
776
|
-
console.log(`❓ Unhandled message type: ${messageContent.type}`);
|
|
777
|
-
}
|
|
778
|
-
}
|
|
779
|
-
```
|
|
780
|
-
|
|
781
|
-
### Read Receipts and Delivery Confirmations
|
|
782
|
-
|
|
783
|
-
Handle message delivery and read confirmations:
|
|
784
|
-
|
|
785
|
-
```typescript
|
|
786
|
-
async function handleReceipt(event) {
|
|
787
|
-
const { MessageSource, MessageIDs, Type, Timestamp } = event;
|
|
788
|
-
const from = MessageSource.Chat.User;
|
|
789
|
-
|
|
790
|
-
console.log(
|
|
791
|
-
`Receipt from ${from}: ${Type} for ${MessageIDs.length} message(s)`
|
|
792
|
-
);
|
|
793
|
-
|
|
794
|
-
switch (Type) {
|
|
795
|
-
case "delivery":
|
|
796
|
-
console.log(`✅ Messages delivered to ${from}`);
|
|
797
|
-
// Update your database to mark messages as delivered
|
|
798
|
-
break;
|
|
799
|
-
|
|
800
|
-
case "read":
|
|
801
|
-
console.log(`👀 Messages read by ${from}`);
|
|
802
|
-
// Update your database to mark messages as read
|
|
803
|
-
break;
|
|
804
|
-
|
|
805
|
-
case "played":
|
|
806
|
-
console.log(`▶️ Voice/video messages played by ${from}`);
|
|
807
|
-
// Update your database to mark media as played
|
|
808
|
-
break;
|
|
809
|
-
}
|
|
810
|
-
}
|
|
811
|
-
```
|
|
812
|
-
|
|
813
|
-
### Presence and Typing Indicators
|
|
814
|
-
|
|
815
|
-
Handle user online/offline status and typing indicators:
|
|
816
|
-
|
|
817
|
-
```typescript
|
|
818
|
-
// User online/offline status
|
|
819
|
-
async function handlePresence(event) {
|
|
820
|
-
const { From, Unavailable, LastSeen } = event;
|
|
821
|
-
const user = From.User;
|
|
822
|
-
|
|
823
|
-
if (Unavailable) {
|
|
824
|
-
console.log(`🔴 ${user} went offline (last seen: ${LastSeen})`);
|
|
825
|
-
} else {
|
|
826
|
-
console.log(`🟢 ${user} is online`);
|
|
827
|
-
}
|
|
828
|
-
}
|
|
829
|
-
|
|
830
|
-
// Typing indicators
|
|
831
|
-
async function handleChatPresence(event) {
|
|
832
|
-
const { MessageSource, State, Media } = event;
|
|
833
|
-
const from = MessageSource.Sender.User;
|
|
834
|
-
const isGroup = MessageSource.IsGroup;
|
|
835
|
-
|
|
836
|
-
if (State === "composing") {
|
|
837
|
-
if (Media === "text") {
|
|
838
|
-
console.log(`⌨️ ${from} is typing${isGroup ? " in group" : ""}...`);
|
|
839
|
-
} else {
|
|
840
|
-
console.log(
|
|
841
|
-
`📎 ${from} is sending ${Media}${isGroup ? " in group" : ""}...`
|
|
842
|
-
);
|
|
843
|
-
}
|
|
844
|
-
} else if (State === "paused") {
|
|
845
|
-
console.log(`⏸️ ${from} stopped typing`);
|
|
846
|
-
}
|
|
847
|
-
}
|
|
848
|
-
```
|
|
849
|
-
|
|
850
|
-
### Group Events
|
|
851
|
-
|
|
852
|
-
Handle group-related events:
|
|
853
|
-
|
|
854
|
-
```typescript
|
|
855
|
-
// Group info updates
|
|
856
|
-
async function handleGroupInfo(event) {
|
|
857
|
-
const { JID, GroupName, Participants, Sender } = event;
|
|
858
|
-
console.log(`👥 Group info updated for ${GroupName} (${JID.User})`);
|
|
859
|
-
console.log(`👤 Updated by: ${Sender.User}`);
|
|
860
|
-
console.log(`👥 Participants: ${Participants.length}`);
|
|
861
|
-
}
|
|
862
|
-
|
|
863
|
-
// When you're added to a group
|
|
864
|
-
async function handleJoinedGroup(event) {
|
|
865
|
-
const { Reason, Type, Participants } = event;
|
|
866
|
-
console.log(`🎉 Joined group! Reason: ${Reason}, Type: ${Type}`);
|
|
867
|
-
console.log(`👥 Group has ${Participants.length} participants`);
|
|
868
|
-
|
|
869
|
-
// Send welcome message
|
|
870
|
-
// Note: You'll need the group JID from the event context
|
|
871
|
-
}
|
|
872
|
-
```
|
|
873
|
-
|
|
874
|
-
### Connection Events
|
|
875
|
-
|
|
876
|
-
Handle connection status changes:
|
|
877
|
-
|
|
878
|
-
```typescript
|
|
879
|
-
async function handleConnected(event) {
|
|
880
|
-
console.log("🟢 Connected to WhatsApp!");
|
|
881
|
-
// Your bot is now ready to send messages
|
|
882
|
-
}
|
|
883
|
-
|
|
884
|
-
async function handleDisconnected(event) {
|
|
885
|
-
console.log("🔴 Disconnected from WhatsApp");
|
|
886
|
-
// Attempt to reconnect or notify administrators
|
|
887
|
-
}
|
|
888
|
-
|
|
889
|
-
async function handleLoggedOut(event) {
|
|
890
|
-
const { Reason, OnConnect } = event;
|
|
891
|
-
console.log(`🚪 Logged out from WhatsApp. Reason: ${Reason}`);
|
|
892
|
-
|
|
893
|
-
if (OnConnect) {
|
|
894
|
-
console.log("⚠️ Logout occurred during connection");
|
|
895
|
-
}
|
|
896
|
-
|
|
897
|
-
// You may need to scan QR code again
|
|
898
|
-
}
|
|
899
|
-
```
|
|
900
|
-
|
|
901
|
-
### QR Code Events
|
|
902
|
-
|
|
903
|
-
Handle QR code generation for initial setup:
|
|
904
|
-
|
|
905
|
-
```typescript
|
|
906
|
-
async function handleQR(event) {
|
|
907
|
-
const { Codes } = event;
|
|
908
|
-
console.log("📱 New QR codes received:");
|
|
909
|
-
|
|
910
|
-
Codes.forEach((code, index) => {
|
|
911
|
-
console.log(`📷 QR Code ${index + 1}: ${code}`);
|
|
912
|
-
// Display QR code to user or save to file
|
|
913
|
-
});
|
|
914
|
-
}
|
|
915
|
-
```
|
|
916
|
-
|
|
917
|
-
### Profile and Contact Updates
|
|
918
|
-
|
|
919
|
-
Handle profile picture and contact information changes:
|
|
920
|
-
|
|
921
|
-
```typescript
|
|
922
|
-
// Profile picture changes
|
|
923
|
-
async function handlePicture(event) {
|
|
924
|
-
const { JID, Author, Remove, Timestamp } = event;
|
|
925
|
-
const target = JID.User;
|
|
926
|
-
const changer = Author.User;
|
|
927
|
-
|
|
928
|
-
if (Remove) {
|
|
929
|
-
console.log(`🗑️ ${changer} removed profile picture for ${target}`);
|
|
930
|
-
} else {
|
|
931
|
-
console.log(`🖼️ ${changer} updated profile picture for ${target}`);
|
|
932
|
-
}
|
|
933
|
-
}
|
|
934
|
-
|
|
935
|
-
// Contact info updates
|
|
936
|
-
async function handleContact(event) {
|
|
937
|
-
const { JID, Found, FullName, PushName, BusinessName } = event;
|
|
938
|
-
console.log(`👤 Contact info: ${FullName || PushName} (${JID.User})`);
|
|
939
|
-
|
|
940
|
-
if (BusinessName) {
|
|
941
|
-
console.log(`🏢 Business: ${BusinessName}`);
|
|
942
|
-
}
|
|
943
|
-
|
|
944
|
-
console.log(`✅ Found in WhatsApp: ${Found}`);
|
|
945
|
-
}
|
|
946
|
-
|
|
947
|
-
// Name changes
|
|
948
|
-
async function handlePushName(event) {
|
|
949
|
-
const { JID, OldPushName, NewPushName } = event;
|
|
950
|
-
console.log(
|
|
951
|
-
`📝 ${JID.User} changed name from "${OldPushName}" to "${NewPushName}"`
|
|
952
|
-
);
|
|
953
|
-
}
|
|
954
|
-
```
|
|
955
|
-
|
|
956
|
-
### Error Handling
|
|
957
|
-
|
|
958
|
-
Handle various error events:
|
|
959
|
-
|
|
960
|
-
```typescript
|
|
961
|
-
// Undecryptable messages
|
|
962
|
-
async function handleUndecryptableMessage(event) {
|
|
963
|
-
const { Info, IsUnavailable, UnavailableType, DecryptFailMode } = event;
|
|
964
|
-
console.log(`❌ Failed to decrypt message from ${Info.Source.Sender.User}`);
|
|
965
|
-
console.log(
|
|
966
|
-
`📊 Unavailable: ${IsUnavailable}, Type: ${UnavailableType}, Mode: ${DecryptFailMode}`
|
|
967
|
-
);
|
|
968
|
-
|
|
969
|
-
// Log for debugging or request message retry
|
|
970
|
-
}
|
|
971
|
-
|
|
972
|
-
// Stream errors
|
|
973
|
-
async function handleStreamError(event) {
|
|
974
|
-
const { Code } = event;
|
|
975
|
-
console.log(`🚨 Stream error: ${Code}`);
|
|
976
|
-
|
|
977
|
-
// Handle specific error codes
|
|
978
|
-
switch (Code) {
|
|
979
|
-
case "conflict":
|
|
980
|
-
console.log("Another client connected with same credentials");
|
|
981
|
-
break;
|
|
982
|
-
case "stream:error":
|
|
983
|
-
console.log("General stream error occurred");
|
|
984
|
-
break;
|
|
985
|
-
default:
|
|
986
|
-
console.log("Unknown stream error");
|
|
987
|
-
}
|
|
988
|
-
}
|
|
989
|
-
```
|
|
990
|
-
|
|
991
|
-
### Complete Webhook Server Example
|
|
992
|
-
|
|
993
|
-
Here's a complete webhook server that handles all event types:
|
|
994
|
-
|
|
995
|
-
```typescript
|
|
996
|
-
import express from "express";
|
|
997
|
-
import WuzapiClient, {
|
|
998
|
-
Message,
|
|
999
|
-
Receipt,
|
|
1000
|
-
Presence,
|
|
1001
|
-
EventGroupInfo,
|
|
1002
|
-
} from "wuzapi";
|
|
1003
|
-
|
|
1004
|
-
const app = express();
|
|
1005
|
-
app.use(express.json());
|
|
1006
|
-
|
|
1007
|
-
const client = new WuzapiClient({
|
|
1008
|
-
apiUrl: process.env.WUZAPI_URL || "http://localhost:8080",
|
|
1009
|
-
token: process.env.WUZAPI_TOKEN || "your-token",
|
|
1010
|
-
});
|
|
1011
|
-
|
|
1012
|
-
// Event router
|
|
1013
|
-
const eventHandlers = {
|
|
1014
|
-
Message: handleMessage,
|
|
1015
|
-
Receipt: handleReceipt,
|
|
1016
|
-
Presence: handlePresence,
|
|
1017
|
-
ChatPresence: handleChatPresence,
|
|
1018
|
-
GroupInfo: handleGroupInfo,
|
|
1019
|
-
JoinedGroup: handleJoinedGroup,
|
|
1020
|
-
Connected: handleConnected,
|
|
1021
|
-
Disconnected: handleDisconnected,
|
|
1022
|
-
LoggedOut: handleLoggedOut,
|
|
1023
|
-
QR: handleQR,
|
|
1024
|
-
Picture: handlePicture,
|
|
1025
|
-
Contact: handleContact,
|
|
1026
|
-
PushName: handlePushName,
|
|
1027
|
-
UndecryptableMessage: handleUndecryptableMessage,
|
|
1028
|
-
StreamError: handleStreamError,
|
|
1029
|
-
};
|
|
1030
|
-
|
|
1031
|
-
app.post("/webhook", async (req, res) => {
|
|
1032
|
-
try {
|
|
1033
|
-
const eventData = req.body;
|
|
1034
|
-
|
|
1035
|
-
// Log all events for debugging
|
|
1036
|
-
console.log("📨 Webhook received:", JSON.stringify(eventData, null, 2));
|
|
1037
|
-
|
|
1038
|
-
// Route to appropriate handler based on event structure
|
|
1039
|
-
for (const [eventType, handler] of Object.entries(eventHandlers)) {
|
|
1040
|
-
if (isEventType(eventData, eventType)) {
|
|
1041
|
-
await handler(eventData);
|
|
1042
|
-
break;
|
|
1043
|
-
}
|
|
1044
|
-
}
|
|
1045
|
-
|
|
1046
|
-
res.status(200).json({ success: true });
|
|
1047
|
-
} catch (error) {
|
|
1048
|
-
console.error("❌ Webhook processing error:", error);
|
|
1049
|
-
res.status(500).json({ error: error.message });
|
|
1050
|
-
}
|
|
1051
|
-
});
|
|
1052
|
-
|
|
1053
|
-
// Helper function to identify event types
|
|
1054
|
-
function isEventType(event, type) {
|
|
1055
|
-
switch (type) {
|
|
1056
|
-
case "Message":
|
|
1057
|
-
return event.Message && event.Info;
|
|
1058
|
-
case "Receipt":
|
|
1059
|
-
return event.MessageIDs && event.Type;
|
|
1060
|
-
case "Presence":
|
|
1061
|
-
return event.From && typeof event.Unavailable !== "undefined";
|
|
1062
|
-
case "ChatPresence":
|
|
1063
|
-
return event.State && event.MessageSource;
|
|
1064
|
-
case "GroupInfo":
|
|
1065
|
-
return event.GroupName && event.Participants;
|
|
1066
|
-
case "QR":
|
|
1067
|
-
return event.Codes;
|
|
1068
|
-
// Add more type checks as needed
|
|
1069
|
-
default:
|
|
1070
|
-
return false;
|
|
1071
|
-
}
|
|
1072
|
-
}
|
|
1073
|
-
|
|
1074
|
-
// Health check
|
|
1075
|
-
app.get("/health", (req, res) => {
|
|
1076
|
-
res.json({ status: "healthy", timestamp: new Date().toISOString() });
|
|
1077
|
-
});
|
|
1078
|
-
|
|
1079
|
-
// Initialize
|
|
1080
|
-
async function initialize() {
|
|
1081
|
-
try {
|
|
1082
|
-
// Connect to WhatsApp with all events using EventType enum
|
|
1083
|
-
await client.session.connect({
|
|
1084
|
-
Subscribe: [
|
|
1085
|
-
EventType.MESSAGE,
|
|
1086
|
-
EventType.RECEIPT,
|
|
1087
|
-
EventType.PRESENCE,
|
|
1088
|
-
EventType.CHAT_PRESENCE,
|
|
1089
|
-
EventType.GROUP_INFO,
|
|
1090
|
-
EventType.CONTACT,
|
|
1091
|
-
EventType.PUSH_NAME,
|
|
1092
|
-
EventType.PICTURE,
|
|
1093
|
-
EventType.QR,
|
|
1094
|
-
EventType.CONNECTED,
|
|
1095
|
-
EventType.DISCONNECTED,
|
|
1096
|
-
EventType.LOGGED_OUT,
|
|
1097
|
-
EventType.UNDECRYPTABLE_MESSAGE,
|
|
1098
|
-
EventType.STREAM_ERROR,
|
|
1099
|
-
],
|
|
1100
|
-
Immediate: false,
|
|
1101
|
-
});
|
|
1102
|
-
|
|
1103
|
-
// Set webhook
|
|
1104
|
-
const webhookUrl =
|
|
1105
|
-
process.env.WEBHOOK_URL || "http://localhost:3000/webhook";
|
|
1106
|
-
await client.webhook.setWebhook(webhookUrl);
|
|
1107
|
-
|
|
1108
|
-
app.listen(3000, () => {
|
|
1109
|
-
console.log("🚀 Webhook server running on port 3000");
|
|
1110
|
-
console.log("🎉 Ready to receive WhatsApp events!");
|
|
1111
|
-
});
|
|
1112
|
-
} catch (error) {
|
|
1113
|
-
console.error("❌ Initialization failed:", error);
|
|
1114
|
-
process.exit(1);
|
|
1115
|
-
}
|
|
1116
|
-
}
|
|
1117
|
-
|
|
1118
|
-
initialize();
|
|
1119
|
-
```
|
|
1120
|
-
|
|
1121
|
-
### Event Types Reference
|
|
1122
|
-
|
|
1123
|
-
All webhook events are fully typed. Import the specific event types you need:
|
|
1124
|
-
|
|
1125
|
-
```typescript
|
|
1126
|
-
import {
|
|
1127
|
-
// Event type enum for all possible events
|
|
1128
|
-
EventType,
|
|
1129
|
-
|
|
1130
|
-
// Core message types
|
|
1131
|
-
Message,
|
|
1132
|
-
MessageEvent,
|
|
1133
|
-
MessageContent,
|
|
1134
|
-
getMessageContent,
|
|
1135
|
-
|
|
1136
|
-
// Specific message types
|
|
1137
|
-
ImageMessage,
|
|
1138
|
-
VideoMessage,
|
|
1139
|
-
AudioMessage,
|
|
1140
|
-
DocumentMessage,
|
|
1141
|
-
LocationMessage,
|
|
1142
|
-
ContactMessage,
|
|
1143
|
-
StickerMessage,
|
|
1144
|
-
ButtonsMessage,
|
|
1145
|
-
ListMessage,
|
|
1146
|
-
PollCreationMessage,
|
|
1147
|
-
ReactionMessage,
|
|
1148
|
-
|
|
1149
|
-
// Webhook payload types
|
|
1150
|
-
WebhookPayload,
|
|
1151
|
-
S3MediaInfo,
|
|
1152
|
-
S3OnlyWebhookPayload,
|
|
1153
|
-
Base64OnlyWebhookPayload,
|
|
1154
|
-
BothMediaWebhookPayload,
|
|
1155
|
-
hasS3Media,
|
|
1156
|
-
hasBase64Media,
|
|
1157
|
-
hasBothMedia,
|
|
1158
|
-
|
|
1159
|
-
// Event types
|
|
1160
|
-
Receipt,
|
|
1161
|
-
Presence,
|
|
1162
|
-
ChatPresence,
|
|
1163
|
-
EventGroupInfo,
|
|
1164
|
-
EventContact,
|
|
1165
|
-
QR,
|
|
1166
|
-
Picture,
|
|
1167
|
-
PushName,
|
|
1168
|
-
Connected,
|
|
1169
|
-
Disconnected,
|
|
1170
|
-
LoggedOut,
|
|
1171
|
-
UndecryptableMessage,
|
|
1172
|
-
StreamError,
|
|
1173
|
-
WhatsAppEvent,
|
|
1174
|
-
} from "wuzapi";
|
|
1175
|
-
|
|
1176
|
-
// Type-safe event handling with EventType enum
|
|
1177
|
-
async function handleTypedWebhook(webhookPayload: WebhookPayload) {
|
|
1178
|
-
const event = webhookPayload.event;
|
|
1179
|
-
|
|
1180
|
-
// Type-safe event detection using EventType enum
|
|
1181
|
-
const eventType = detectEventType(event);
|
|
1182
|
-
|
|
1183
|
-
switch (eventType) {
|
|
1184
|
-
case EventType.MESSAGE:
|
|
1185
|
-
const messageEvent = event as MessageEvent;
|
|
1186
|
-
const messageContent = getMessageContent(messageEvent.Message);
|
|
1187
|
-
|
|
1188
|
-
if (messageContent?.type === "image") {
|
|
1189
|
-
// TypeScript knows this is an ImageMessage
|
|
1190
|
-
const imageMsg: ImageMessage = messageContent.content;
|
|
1191
|
-
console.log(`Image dimensions: ${imageMsg.width}x${imageMsg.height}`);
|
|
1192
|
-
|
|
1193
|
-
// Handle S3 media if available
|
|
1194
|
-
if (hasS3Media(webhookPayload)) {
|
|
1195
|
-
console.log(`S3 URL: ${webhookPayload.s3.url}`);
|
|
1196
|
-
// Download from S3 or process as needed
|
|
1197
|
-
}
|
|
1198
|
-
|
|
1199
|
-
// Handle Base64 media if available
|
|
1200
|
-
if (hasBase64Media(webhookPayload)) {
|
|
1201
|
-
console.log("Processing base64 image data");
|
|
1202
|
-
// Process base64 data: webhookPayload.base64
|
|
1203
716
|
}
|
|
1204
717
|
}
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
const receiptEvent = event as Receipt;
|
|
1209
|
-
console.log(`Receipt type: ${receiptEvent.Type}`);
|
|
1210
|
-
break;
|
|
1211
|
-
|
|
1212
|
-
case EventType.PRESENCE:
|
|
1213
|
-
const presenceEvent = event as Presence;
|
|
1214
|
-
console.log(`User ${presenceEvent.From.User} presence update`);
|
|
1215
|
-
break;
|
|
1216
|
-
|
|
1217
|
-
default:
|
|
1218
|
-
console.log(`Unhandled event: ${eventType}`);
|
|
1219
|
-
}
|
|
1220
|
-
}
|
|
1221
|
-
|
|
1222
|
-
// Complete webhook server with S3 support
|
|
1223
|
-
app.post("/webhook", async (req, res) => {
|
|
1224
|
-
try {
|
|
1225
|
-
const payload: WebhookPayload = req.body;
|
|
1226
|
-
await handleTypedWebhook(payload);
|
|
1227
|
-
res.status(200).json({ success: true });
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
res.json({ success: true });
|
|
1228
721
|
} catch (error) {
|
|
1229
722
|
console.error("Webhook error:", error);
|
|
1230
723
|
res.status(500).json({ error: error.message });
|
|
1231
724
|
}
|
|
1232
725
|
});
|
|
1233
|
-
|
|
1234
|
-
// Helper function to handle all message types generically
|
|
1235
|
-
function processMessageContent(content: MessageContent) {
|
|
1236
|
-
switch (content.type) {
|
|
1237
|
-
case "text":
|
|
1238
|
-
// content.content is string
|
|
1239
|
-
return content.content.toUpperCase();
|
|
1240
|
-
|
|
1241
|
-
case "image":
|
|
1242
|
-
// content.content is ImageMessage
|
|
1243
|
-
return `Image: ${content.content.caption || "No caption"}`;
|
|
1244
|
-
|
|
1245
|
-
case "video":
|
|
1246
|
-
// content.content is VideoMessage
|
|
1247
|
-
return `Video (${content.content.seconds}s): ${
|
|
1248
|
-
content.content.caption || "No caption"
|
|
1249
|
-
}`;
|
|
1250
|
-
|
|
1251
|
-
// TypeScript ensures you handle all possible types
|
|
1252
|
-
default:
|
|
1253
|
-
return `Unknown message type: ${content.type}`;
|
|
1254
|
-
}
|
|
1255
|
-
}
|
|
1256
726
|
```
|
|
1257
727
|
|
|
1258
|
-
###
|
|
728
|
+
### Message Types
|
|
1259
729
|
|
|
1260
|
-
|
|
730
|
+
The `getMessageContent()` utility function returns structured message data:
|
|
1261
731
|
|
|
1262
732
|
```typescript
|
|
1263
|
-
|
|
1264
|
-
Message,
|
|
1265
|
-
ExtendedTextMessage,
|
|
1266
|
-
ButtonsMessage,
|
|
1267
|
-
ContextInfo,
|
|
1268
|
-
} from "wuzapi";
|
|
733
|
+
const messageContent = getMessageContent(event.Message);
|
|
1269
734
|
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
}
|
|
1285
|
-
|
|
1286
|
-
// Process interactive messages
|
|
1287
|
-
function handleInteractiveMessage(message: Message) {
|
|
1288
|
-
if (message.buttonsMessage) {
|
|
1289
|
-
const btns = message.buttonsMessage;
|
|
1290
|
-
console.log(`Interactive message: ${btns.contentText}`);
|
|
1291
|
-
|
|
1292
|
-
btns.buttons?.forEach((button) => {
|
|
1293
|
-
if (button.type === ButtonType.RESPONSE) {
|
|
1294
|
-
console.log(`Response button: ${button.buttonText?.displayText}`);
|
|
1295
|
-
} else if (button.type === ButtonType.NATIVE_FLOW) {
|
|
1296
|
-
console.log(`Native flow: ${button.nativeFlowInfo?.name}`);
|
|
1297
|
-
}
|
|
1298
|
-
});
|
|
1299
|
-
}
|
|
1300
|
-
|
|
1301
|
-
if (message.listMessage) {
|
|
1302
|
-
const list = message.listMessage;
|
|
1303
|
-
console.log(`List: ${list.title}`);
|
|
1304
|
-
|
|
1305
|
-
list.sections?.forEach((section) => {
|
|
1306
|
-
console.log(`Section: ${section.title}`);
|
|
1307
|
-
section.rows?.forEach((row) => {
|
|
1308
|
-
console.log(`- ${row.title}: ${row.description}`);
|
|
1309
|
-
});
|
|
1310
|
-
});
|
|
1311
|
-
}
|
|
735
|
+
switch (messageContent?.type) {
|
|
736
|
+
case "text":
|
|
737
|
+
console.log("Text:", messageContent.content);
|
|
738
|
+
break;
|
|
739
|
+
case "image":
|
|
740
|
+
console.log("Image:", messageContent.content.caption);
|
|
741
|
+
break;
|
|
742
|
+
case "buttonsResponse":
|
|
743
|
+
console.log("Button clicked:", messageContent.content.selectedButtonId);
|
|
744
|
+
break;
|
|
745
|
+
case "listResponse":
|
|
746
|
+
console.log("List selection:", messageContent.content.singleSelectReply?.selectedRowId);
|
|
747
|
+
break;
|
|
748
|
+
// ... handle other types
|
|
1312
749
|
}
|
|
1313
750
|
```
|
|
1314
751
|
|
|
1315
|
-
|
|
752
|
+
---
|
|
753
|
+
|
|
754
|
+
## 🛠️ Advanced Topics
|
|
1316
755
|
|
|
1317
|
-
|
|
756
|
+
<details>
|
|
757
|
+
<summary><strong>⚠️ Error Handling</strong></summary>
|
|
1318
758
|
|
|
1319
759
|
```typescript
|
|
1320
760
|
import { WuzapiError } from "wuzapi";
|
|
@@ -1337,52 +777,37 @@ try {
|
|
|
1337
777
|
}
|
|
1338
778
|
```
|
|
1339
779
|
|
|
1340
|
-
###
|
|
780
|
+
### Common Error Codes
|
|
781
|
+
- **401**: Authentication required
|
|
782
|
+
- **404**: Endpoint not found
|
|
783
|
+
- **500**: Server error
|
|
1341
784
|
|
|
1342
|
-
|
|
785
|
+
</details>
|
|
1343
786
|
|
|
1344
|
-
|
|
1345
|
-
|
|
787
|
+
<details>
|
|
788
|
+
<summary><strong>🔧 Custom Configuration</strong></summary>
|
|
1346
789
|
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
// No token provided
|
|
1351
|
-
});
|
|
790
|
+
```typescript
|
|
791
|
+
// Custom axios configuration
|
|
792
|
+
import { BaseClient } from "wuzapi";
|
|
1352
793
|
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
// "No authentication token provided. Either set a token in the client config or provide one in the request options."
|
|
794
|
+
class CustomClient extends BaseClient {
|
|
795
|
+
constructor(config) {
|
|
796
|
+
super(config);
|
|
797
|
+
|
|
798
|
+
// Add custom interceptors
|
|
799
|
+
this.axios.interceptors.request.use((config) => {
|
|
800
|
+
console.log("Making request:", config.url);
|
|
801
|
+
return config;
|
|
802
|
+
});
|
|
1363
803
|
}
|
|
1364
804
|
}
|
|
1365
|
-
|
|
1366
|
-
// Fix by providing token
|
|
1367
|
-
await client.chat.sendText(
|
|
1368
|
-
{
|
|
1369
|
-
Phone: "5491155554444",
|
|
1370
|
-
Body: "Now it works!",
|
|
1371
|
-
},
|
|
1372
|
-
{ token: "your-token" }
|
|
1373
|
-
);
|
|
1374
805
|
```
|
|
1375
806
|
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
- **Network Errors**: Connection issues, timeouts
|
|
1379
|
-
- **Authentication Errors**: Invalid tokens, permission denied
|
|
1380
|
-
- **API Errors**: Invalid parameters, service unavailable
|
|
1381
|
-
- **Validation Errors**: Missing required fields, invalid data formats
|
|
1382
|
-
|
|
1383
|
-
## TypeScript Support
|
|
807
|
+
</details>
|
|
1384
808
|
|
|
1385
|
-
|
|
809
|
+
<details>
|
|
810
|
+
<summary><strong>📝 TypeScript Support</strong></summary>
|
|
1386
811
|
|
|
1387
812
|
```typescript
|
|
1388
813
|
import {
|
|
@@ -1402,9 +827,10 @@ const request: SendTextRequest = {
|
|
|
1402
827
|
const response: SendMessageResponse = await client.chat.sendText(request);
|
|
1403
828
|
```
|
|
1404
829
|
|
|
1405
|
-
|
|
830
|
+
</details>
|
|
1406
831
|
|
|
1407
|
-
|
|
832
|
+
<details>
|
|
833
|
+
<summary><strong>🔄 Legacy Aliases</strong></summary>
|
|
1408
834
|
|
|
1409
835
|
```typescript
|
|
1410
836
|
// These are equivalent:
|
|
@@ -1415,130 +841,11 @@ await client.chat.sendText({ Phone: "123", Body: "Hi" });
|
|
|
1415
841
|
await client.message.sendText({ Phone: "123", Body: "Hi" }); // Alias
|
|
1416
842
|
```
|
|
1417
843
|
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
### Custom Axios Configuration
|
|
1421
|
-
|
|
1422
|
-
You can extend the base client for custom axios configuration:
|
|
1423
|
-
|
|
1424
|
-
```typescript
|
|
1425
|
-
import { BaseClient } from "wuzapi";
|
|
1426
|
-
|
|
1427
|
-
class CustomClient extends BaseClient {
|
|
1428
|
-
constructor(config: WuzapiConfig) {
|
|
1429
|
-
super(config);
|
|
1430
|
-
|
|
1431
|
-
// Add custom interceptors
|
|
1432
|
-
this.axios.interceptors.request.use((config) => {
|
|
1433
|
-
console.log("Making request:", config.url);
|
|
1434
|
-
return config;
|
|
1435
|
-
});
|
|
1436
|
-
}
|
|
1437
|
-
}
|
|
1438
|
-
```
|
|
1439
|
-
|
|
1440
|
-
### Ping Test
|
|
1441
|
-
|
|
1442
|
-
Test connectivity to the API:
|
|
1443
|
-
|
|
1444
|
-
```typescript
|
|
1445
|
-
const isConnected = await client.ping();
|
|
1446
|
-
if (isConnected) {
|
|
1447
|
-
console.log("API is reachable");
|
|
1448
|
-
} else {
|
|
1449
|
-
console.log("API is not reachable");
|
|
1450
|
-
}
|
|
1451
|
-
```
|
|
1452
|
-
|
|
1453
|
-
## Examples
|
|
1454
|
-
|
|
1455
|
-
For complete working examples, check the `examples/` directory:
|
|
1456
|
-
|
|
1457
|
-
- **`basic-usage.js`** - Basic client setup and usage
|
|
1458
|
-
- **`chatbot-example.js`** - Simple chatbot with command handling
|
|
1459
|
-
- **`webhook-events-example.js`** - Comprehensive webhook event handling
|
|
1460
|
-
|
|
1461
|
-
### Complete Chat Bot Example
|
|
1462
|
-
|
|
1463
|
-
```typescript
|
|
1464
|
-
import WuzapiClient from "wuzapi";
|
|
1465
|
-
|
|
1466
|
-
const client = new WuzapiClient({
|
|
1467
|
-
apiUrl: "http://localhost:8080",
|
|
1468
|
-
token: "your-token",
|
|
1469
|
-
});
|
|
1470
|
-
|
|
1471
|
-
async function startBot() {
|
|
1472
|
-
// Connect to WhatsApp
|
|
1473
|
-
await client.session.connect({
|
|
1474
|
-
Subscribe: ["Message"],
|
|
1475
|
-
Immediate: false,
|
|
1476
|
-
});
|
|
1477
|
-
|
|
1478
|
-
// Wait for connection
|
|
1479
|
-
let connected = false;
|
|
1480
|
-
while (!connected) {
|
|
1481
|
-
const status = await client.session.getStatus();
|
|
1482
|
-
if (!status.LoggedIn) {
|
|
1483
|
-
const qr = await client.session.getQRCode();
|
|
1484
|
-
console.log("Scan this QR code:", qr.QRCode);
|
|
1485
|
-
await new Promise((resolve) => setTimeout(resolve, 5000));
|
|
1486
|
-
} else {
|
|
1487
|
-
connected = true;
|
|
1488
|
-
console.log("Bot connected and ready!");
|
|
1489
|
-
}
|
|
1490
|
-
}
|
|
1491
|
-
|
|
1492
|
-
// Set webhook for receiving messages
|
|
1493
|
-
await client.webhook.setWebhook("https://your-server.com/webhook");
|
|
1494
|
-
}
|
|
1495
|
-
|
|
1496
|
-
// Handle incoming messages in your webhook endpoint
|
|
1497
|
-
function handleIncomingMessage(message: any) {
|
|
1498
|
-
const phone = message.Info.RemoteJid.replace("@s.whatsapp.net", "");
|
|
1499
|
-
const text = message.Message?.conversation || "";
|
|
1500
|
-
|
|
1501
|
-
if (text.toLowerCase() === "hello") {
|
|
1502
|
-
client.chat.sendText({
|
|
1503
|
-
Phone: phone,
|
|
1504
|
-
Body: "Hello! How can I help you today?",
|
|
1505
|
-
});
|
|
1506
|
-
}
|
|
1507
|
-
}
|
|
1508
|
-
|
|
1509
|
-
startBot().catch(console.error);
|
|
1510
|
-
```
|
|
1511
|
-
|
|
1512
|
-
### Group Management Example
|
|
1513
|
-
|
|
1514
|
-
```typescript
|
|
1515
|
-
async function manageGroup() {
|
|
1516
|
-
// Create a new group
|
|
1517
|
-
const group = await client.group.create("Project Team", [
|
|
1518
|
-
"5491155553934",
|
|
1519
|
-
"5491155553935",
|
|
1520
|
-
]);
|
|
1521
|
-
|
|
1522
|
-
console.log("Created group:", group.JID);
|
|
1523
|
-
|
|
1524
|
-
// Set group photo
|
|
1525
|
-
await client.group.setPhoto(group.JID, "data:image/jpeg;base64,...");
|
|
1526
|
-
|
|
1527
|
-
// Configure group settings
|
|
1528
|
-
await client.group.setLocked(group.JID, true); // Only admins can modify
|
|
1529
|
-
await client.group.setEphemeral(group.JID, "7d"); // Messages disappear after 7 days
|
|
1530
|
-
|
|
1531
|
-
// Get and share invite link
|
|
1532
|
-
const invite = await client.group.getInviteLink(group.JID);
|
|
1533
|
-
console.log("Invite link:", invite.InviteLink);
|
|
1534
|
-
}
|
|
1535
|
-
```
|
|
1536
|
-
|
|
1537
|
-
## API Reference
|
|
844
|
+
</details>
|
|
1538
845
|
|
|
1539
|
-
|
|
846
|
+
---
|
|
1540
847
|
|
|
1541
|
-
## Contributing
|
|
848
|
+
## 🤝 Contributing
|
|
1542
849
|
|
|
1543
850
|
1. Fork the repository
|
|
1544
851
|
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
|
|
@@ -1561,43 +868,32 @@ npm run lint
|
|
|
1561
868
|
|
|
1562
869
|
# Build the project
|
|
1563
870
|
npm run build
|
|
1564
|
-
|
|
1565
|
-
# Run development server
|
|
1566
|
-
npm run dev
|
|
1567
871
|
```
|
|
1568
872
|
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
This project uses ESLint and TypeScript. Please ensure your code passes all checks:
|
|
1572
|
-
|
|
1573
|
-
```bash
|
|
1574
|
-
npm run lint
|
|
1575
|
-
npm run lint:fix # Auto-fix issues
|
|
1576
|
-
```
|
|
1577
|
-
|
|
1578
|
-
## License
|
|
873
|
+
## 📄 License
|
|
1579
874
|
|
|
1580
875
|
MIT License - see the [LICENSE](LICENSE) file for details.
|
|
1581
876
|
|
|
1582
|
-
##
|
|
877
|
+
## 🔗 Links
|
|
1583
878
|
|
|
1584
|
-
- 📚 [Documentation](https://github.com/asternic/wuzapi)
|
|
879
|
+
- 📚 [WuzAPI Documentation](https://github.com/asternic/wuzapi)
|
|
1585
880
|
- 🐛 [Issue Tracker](https://github.com/gusnips/wuzapi-node/issues)
|
|
1586
881
|
- 💬 [Discussions](https://github.com/gusnips/wuzapi-node/discussions)
|
|
1587
882
|
|
|
1588
|
-
|
|
883
|
+
---
|
|
884
|
+
|
|
885
|
+
## 📊 Changelog
|
|
1589
886
|
|
|
1590
|
-
###
|
|
887
|
+
### Latest Updates
|
|
1591
888
|
|
|
1592
|
-
-
|
|
1593
|
-
-
|
|
1594
|
-
-
|
|
1595
|
-
-
|
|
1596
|
-
-
|
|
1597
|
-
-
|
|
1598
|
-
-
|
|
1599
|
-
-
|
|
1600
|
-
- Webhook configuration
|
|
889
|
+
- ✅ **Phone Pairing**: Alternative to QR code login
|
|
890
|
+
- ✅ **Interactive Messages**: Buttons, lists, and polls
|
|
891
|
+
- ✅ **Message Management**: Edit and delete messages
|
|
892
|
+
- ✅ **Advanced Groups**: Full participant management
|
|
893
|
+
- ✅ **Newsletter Support**: Business newsletter features
|
|
894
|
+
- ✅ **Enhanced Webhooks**: Update and delete webhook configs
|
|
895
|
+
- ✅ **Proxy Support**: Configure proxy for connections
|
|
896
|
+
- ✅ **History Sync**: Request message history after login
|
|
1601
897
|
|
|
1602
898
|
---
|
|
1603
899
|
|