wuzapi 1.1.0 → 1.2.0
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 +722 -1
- package/dist/index.js +308 -1
- package/dist/index.js.map +1 -1
- package/dist/types/chat.d.ts +10 -10
- package/dist/types/common.d.ts +1 -1
- package/dist/types/events.d.ts +557 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/message.d.ts +799 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -285,7 +285,7 @@ await adminClient.admin.deleteUser(2);
|
|
|
285
285
|
|
|
286
286
|
### Webhook Module
|
|
287
287
|
|
|
288
|
-
Configure webhook settings.
|
|
288
|
+
Configure webhook settings and handle incoming WhatsApp events.
|
|
289
289
|
|
|
290
290
|
```typescript
|
|
291
291
|
// Set webhook URL
|
|
@@ -297,6 +297,721 @@ console.log("Webhook URL:", webhookConfig.webhook);
|
|
|
297
297
|
console.log("Subscribed events:", webhookConfig.subscribe);
|
|
298
298
|
```
|
|
299
299
|
|
|
300
|
+
## Webhook Event Handling
|
|
301
|
+
|
|
302
|
+
WuzAPI sends real-time events to your webhook endpoint. Here's how to handle different types of events:
|
|
303
|
+
|
|
304
|
+
### Basic Webhook Setup
|
|
305
|
+
|
|
306
|
+
```typescript
|
|
307
|
+
import express from "express";
|
|
308
|
+
import WuzapiClient from "wuzapi";
|
|
309
|
+
|
|
310
|
+
const app = express();
|
|
311
|
+
app.use(express.json());
|
|
312
|
+
|
|
313
|
+
const client = new WuzapiClient({
|
|
314
|
+
apiUrl: "http://localhost:8080",
|
|
315
|
+
token: "your-token",
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
// Webhook endpoint
|
|
319
|
+
app.post("/webhook", async (req, res) => {
|
|
320
|
+
try {
|
|
321
|
+
const event = req.body;
|
|
322
|
+
|
|
323
|
+
// Handle different event types
|
|
324
|
+
if (event.Message) {
|
|
325
|
+
await handleMessage(event);
|
|
326
|
+
} else if (event.MessageIDs && event.Type) {
|
|
327
|
+
await handleReceipt(event);
|
|
328
|
+
} else if (event.From && typeof event.Unavailable !== "undefined") {
|
|
329
|
+
await handlePresence(event);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
res.status(200).json({ success: true });
|
|
333
|
+
} catch (error) {
|
|
334
|
+
console.error("Webhook error:", error);
|
|
335
|
+
res.status(500).json({ error: error.message });
|
|
336
|
+
}
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
app.listen(3000, () => {
|
|
340
|
+
console.log("Webhook server running on port 3000");
|
|
341
|
+
});
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
### Message Events
|
|
345
|
+
|
|
346
|
+
Handle incoming messages of all types using the comprehensive Message types:
|
|
347
|
+
|
|
348
|
+
```typescript
|
|
349
|
+
import { getMessageContent } from "wuzapi";
|
|
350
|
+
|
|
351
|
+
async function handleMessage(event) {
|
|
352
|
+
const { Info, Message, IsEphemeral, IsViewOnce } = event;
|
|
353
|
+
const from = Info.RemoteJid.replace("@s.whatsapp.net", "");
|
|
354
|
+
const isGroup = Info.RemoteJid.includes("@g.us");
|
|
355
|
+
|
|
356
|
+
console.log(`New message from ${from}${isGroup ? " (group)" : ""}`);
|
|
357
|
+
|
|
358
|
+
if (IsEphemeral) console.log("⏱️ Ephemeral message");
|
|
359
|
+
if (IsViewOnce) console.log("👁️ View once message");
|
|
360
|
+
|
|
361
|
+
// Use the utility function to get structured message content
|
|
362
|
+
const messageContent = getMessageContent(Message);
|
|
363
|
+
|
|
364
|
+
if (!messageContent) {
|
|
365
|
+
console.log("❓ Unknown message type");
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// Handle different message types with full type safety
|
|
370
|
+
switch (messageContent.type) {
|
|
371
|
+
case "text":
|
|
372
|
+
console.log(`💬 Text: ${messageContent.content}`);
|
|
373
|
+
|
|
374
|
+
// Auto-reply to specific messages
|
|
375
|
+
if (messageContent.content.toLowerCase().includes("hello")) {
|
|
376
|
+
await client.chat.sendText({
|
|
377
|
+
Phone: from,
|
|
378
|
+
Body: "👋 Hello! Thanks for your message.",
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
break;
|
|
382
|
+
|
|
383
|
+
case "extendedText":
|
|
384
|
+
console.log(`📝 Extended text: ${messageContent.content.text}`);
|
|
385
|
+
if (messageContent.content.canonicalUrl) {
|
|
386
|
+
console.log(`🔗 Link preview: ${messageContent.content.canonicalUrl}`);
|
|
387
|
+
console.log(`📰 Title: ${messageContent.content.title}`);
|
|
388
|
+
}
|
|
389
|
+
break;
|
|
390
|
+
|
|
391
|
+
case "image":
|
|
392
|
+
const imageMsg = messageContent.content;
|
|
393
|
+
console.log(`🖼️ Image: ${imageMsg.caption || "No caption"}`);
|
|
394
|
+
console.log(`📏 Dimensions: ${imageMsg.width}x${imageMsg.height}`);
|
|
395
|
+
|
|
396
|
+
// Download the image with proper types
|
|
397
|
+
if (imageMsg.url && imageMsg.mediaKey) {
|
|
398
|
+
try {
|
|
399
|
+
const media = await client.chat.downloadImage({
|
|
400
|
+
Url: imageMsg.url,
|
|
401
|
+
MediaKey: Array.from(imageMsg.mediaKey),
|
|
402
|
+
Mimetype: imageMsg.mimetype,
|
|
403
|
+
FileSHA256: Array.from(imageMsg.fileSha256),
|
|
404
|
+
FileLength: imageMsg.fileLength,
|
|
405
|
+
});
|
|
406
|
+
console.log("✅ Image downloaded successfully");
|
|
407
|
+
} catch (error) {
|
|
408
|
+
console.log("❌ Failed to download image:", error.message);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
break;
|
|
412
|
+
|
|
413
|
+
case "video":
|
|
414
|
+
const videoMsg = messageContent.content;
|
|
415
|
+
console.log(`🎥 Video: ${videoMsg.caption || "No caption"}`);
|
|
416
|
+
console.log(`⏱️ Duration: ${videoMsg.seconds}s`);
|
|
417
|
+
if (videoMsg.gifPlayback) {
|
|
418
|
+
console.log("🎬 GIF playback enabled");
|
|
419
|
+
}
|
|
420
|
+
break;
|
|
421
|
+
|
|
422
|
+
case "audio":
|
|
423
|
+
const audioMsg = messageContent.content;
|
|
424
|
+
console.log(`🎵 Audio: ${audioMsg.seconds}s`);
|
|
425
|
+
if (audioMsg.ptt) {
|
|
426
|
+
console.log("🎤 Voice note (Push-to-talk)");
|
|
427
|
+
}
|
|
428
|
+
break;
|
|
429
|
+
|
|
430
|
+
case "document":
|
|
431
|
+
const docMsg = messageContent.content;
|
|
432
|
+
console.log(`📄 Document: ${docMsg.fileName || docMsg.title}`);
|
|
433
|
+
console.log(`📊 Size: ${docMsg.fileLength} bytes`);
|
|
434
|
+
console.log(`📋 Type: ${docMsg.mimetype}`);
|
|
435
|
+
break;
|
|
436
|
+
|
|
437
|
+
case "location":
|
|
438
|
+
const locMsg = messageContent.content;
|
|
439
|
+
console.log(
|
|
440
|
+
`📍 Location: ${locMsg.degreesLatitude}, ${locMsg.degreesLongitude}`
|
|
441
|
+
);
|
|
442
|
+
if (locMsg.name) console.log(`🏷️ Name: ${locMsg.name}`);
|
|
443
|
+
if (locMsg.isLiveLocation) console.log("📡 Live location sharing");
|
|
444
|
+
break;
|
|
445
|
+
|
|
446
|
+
case "contact":
|
|
447
|
+
console.log(`👤 Contact: ${messageContent.content.displayName}`);
|
|
448
|
+
break;
|
|
449
|
+
|
|
450
|
+
case "sticker":
|
|
451
|
+
const stickerMsg = messageContent.content;
|
|
452
|
+
console.log(`😀 Sticker`);
|
|
453
|
+
if (stickerMsg.isAnimated) console.log("🎬 Animated");
|
|
454
|
+
if (stickerMsg.isLottie) console.log("🎨 Lottie");
|
|
455
|
+
break;
|
|
456
|
+
|
|
457
|
+
case "buttons":
|
|
458
|
+
const btnMsg = messageContent.content;
|
|
459
|
+
console.log(`🔘 Interactive buttons: ${btnMsg.contentText}`);
|
|
460
|
+
console.log(`🔢 ${btnMsg.buttons?.length || 0} buttons`);
|
|
461
|
+
break;
|
|
462
|
+
|
|
463
|
+
case "list":
|
|
464
|
+
const listMsg = messageContent.content;
|
|
465
|
+
console.log(`📋 List: ${listMsg.title}`);
|
|
466
|
+
console.log(`📝 ${listMsg.sections?.length || 0} sections`);
|
|
467
|
+
break;
|
|
468
|
+
|
|
469
|
+
case "buttonsResponse":
|
|
470
|
+
const btnResponse = messageContent.content;
|
|
471
|
+
console.log(`✅ Button clicked: ${btnResponse.selectedDisplayText}`);
|
|
472
|
+
console.log(`🆔 Button ID: ${btnResponse.selectedButtonId}`);
|
|
473
|
+
|
|
474
|
+
// Handle button responses
|
|
475
|
+
switch (btnResponse.selectedButtonId) {
|
|
476
|
+
case "help":
|
|
477
|
+
await client.chat.sendText({
|
|
478
|
+
Phone: from,
|
|
479
|
+
Body: "🆘 How can I help you?",
|
|
480
|
+
});
|
|
481
|
+
break;
|
|
482
|
+
case "info":
|
|
483
|
+
await client.chat.sendText({
|
|
484
|
+
Phone: from,
|
|
485
|
+
Body: "ℹ️ Here's some information...",
|
|
486
|
+
});
|
|
487
|
+
break;
|
|
488
|
+
}
|
|
489
|
+
break;
|
|
490
|
+
|
|
491
|
+
case "listResponse":
|
|
492
|
+
const listResponse = messageContent.content;
|
|
493
|
+
console.log(`✅ List selection: ${listResponse.title}`);
|
|
494
|
+
if (listResponse.singleSelectReply) {
|
|
495
|
+
console.log(
|
|
496
|
+
`🆔 Selected: ${listResponse.singleSelectReply.selectedRowId}`
|
|
497
|
+
);
|
|
498
|
+
}
|
|
499
|
+
break;
|
|
500
|
+
|
|
501
|
+
case "poll":
|
|
502
|
+
const pollMsg = messageContent.content;
|
|
503
|
+
console.log(`📊 Poll: ${pollMsg.name}`);
|
|
504
|
+
console.log(`🔢 Options: ${pollMsg.options?.length || 0}`);
|
|
505
|
+
break;
|
|
506
|
+
|
|
507
|
+
case "reaction":
|
|
508
|
+
const reactionMsg = messageContent.content;
|
|
509
|
+
console.log(`😊 Reaction: ${reactionMsg.text}`);
|
|
510
|
+
console.log(`📝 To message: ${reactionMsg.key?.id}`);
|
|
511
|
+
break;
|
|
512
|
+
|
|
513
|
+
case "groupInvite":
|
|
514
|
+
const inviteMsg = messageContent.content;
|
|
515
|
+
console.log(`👥 Group invite: ${inviteMsg.groupName}`);
|
|
516
|
+
console.log(`🔗 Code: ${inviteMsg.inviteCode}`);
|
|
517
|
+
break;
|
|
518
|
+
|
|
519
|
+
default:
|
|
520
|
+
console.log(`❓ Unhandled message type: ${messageContent.type}`);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
### Read Receipts and Delivery Confirmations
|
|
526
|
+
|
|
527
|
+
Handle message delivery and read confirmations:
|
|
528
|
+
|
|
529
|
+
```typescript
|
|
530
|
+
async function handleReceipt(event) {
|
|
531
|
+
const { MessageSource, MessageIDs, Type, Timestamp } = event;
|
|
532
|
+
const from = MessageSource.Chat.User;
|
|
533
|
+
|
|
534
|
+
console.log(
|
|
535
|
+
`Receipt from ${from}: ${Type} for ${MessageIDs.length} message(s)`
|
|
536
|
+
);
|
|
537
|
+
|
|
538
|
+
switch (Type) {
|
|
539
|
+
case "delivery":
|
|
540
|
+
console.log(`✅ Messages delivered to ${from}`);
|
|
541
|
+
// Update your database to mark messages as delivered
|
|
542
|
+
break;
|
|
543
|
+
|
|
544
|
+
case "read":
|
|
545
|
+
console.log(`👀 Messages read by ${from}`);
|
|
546
|
+
// Update your database to mark messages as read
|
|
547
|
+
break;
|
|
548
|
+
|
|
549
|
+
case "played":
|
|
550
|
+
console.log(`▶️ Voice/video messages played by ${from}`);
|
|
551
|
+
// Update your database to mark media as played
|
|
552
|
+
break;
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
```
|
|
556
|
+
|
|
557
|
+
### Presence and Typing Indicators
|
|
558
|
+
|
|
559
|
+
Handle user online/offline status and typing indicators:
|
|
560
|
+
|
|
561
|
+
```typescript
|
|
562
|
+
// User online/offline status
|
|
563
|
+
async function handlePresence(event) {
|
|
564
|
+
const { From, Unavailable, LastSeen } = event;
|
|
565
|
+
const user = From.User;
|
|
566
|
+
|
|
567
|
+
if (Unavailable) {
|
|
568
|
+
console.log(`🔴 ${user} went offline (last seen: ${LastSeen})`);
|
|
569
|
+
} else {
|
|
570
|
+
console.log(`🟢 ${user} is online`);
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
// Typing indicators
|
|
575
|
+
async function handleChatPresence(event) {
|
|
576
|
+
const { MessageSource, State, Media } = event;
|
|
577
|
+
const from = MessageSource.Sender.User;
|
|
578
|
+
const isGroup = MessageSource.IsGroup;
|
|
579
|
+
|
|
580
|
+
if (State === "composing") {
|
|
581
|
+
if (Media === "text") {
|
|
582
|
+
console.log(`⌨️ ${from} is typing${isGroup ? " in group" : ""}...`);
|
|
583
|
+
} else {
|
|
584
|
+
console.log(
|
|
585
|
+
`📎 ${from} is sending ${Media}${isGroup ? " in group" : ""}...`
|
|
586
|
+
);
|
|
587
|
+
}
|
|
588
|
+
} else if (State === "paused") {
|
|
589
|
+
console.log(`⏸️ ${from} stopped typing`);
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
```
|
|
593
|
+
|
|
594
|
+
### Group Events
|
|
595
|
+
|
|
596
|
+
Handle group-related events:
|
|
597
|
+
|
|
598
|
+
```typescript
|
|
599
|
+
// Group info updates
|
|
600
|
+
async function handleGroupInfo(event) {
|
|
601
|
+
const { JID, GroupName, Participants, Sender } = event;
|
|
602
|
+
console.log(`👥 Group info updated for ${GroupName} (${JID.User})`);
|
|
603
|
+
console.log(`👤 Updated by: ${Sender.User}`);
|
|
604
|
+
console.log(`👥 Participants: ${Participants.length}`);
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
// When you're added to a group
|
|
608
|
+
async function handleJoinedGroup(event) {
|
|
609
|
+
const { Reason, Type, Participants } = event;
|
|
610
|
+
console.log(`🎉 Joined group! Reason: ${Reason}, Type: ${Type}`);
|
|
611
|
+
console.log(`👥 Group has ${Participants.length} participants`);
|
|
612
|
+
|
|
613
|
+
// Send welcome message
|
|
614
|
+
// Note: You'll need the group JID from the event context
|
|
615
|
+
}
|
|
616
|
+
```
|
|
617
|
+
|
|
618
|
+
### Connection Events
|
|
619
|
+
|
|
620
|
+
Handle connection status changes:
|
|
621
|
+
|
|
622
|
+
```typescript
|
|
623
|
+
async function handleConnected(event) {
|
|
624
|
+
console.log("🟢 Connected to WhatsApp!");
|
|
625
|
+
// Your bot is now ready to send messages
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
async function handleDisconnected(event) {
|
|
629
|
+
console.log("🔴 Disconnected from WhatsApp");
|
|
630
|
+
// Attempt to reconnect or notify administrators
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
async function handleLoggedOut(event) {
|
|
634
|
+
const { Reason, OnConnect } = event;
|
|
635
|
+
console.log(`🚪 Logged out from WhatsApp. Reason: ${Reason}`);
|
|
636
|
+
|
|
637
|
+
if (OnConnect) {
|
|
638
|
+
console.log("⚠️ Logout occurred during connection");
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
// You may need to scan QR code again
|
|
642
|
+
}
|
|
643
|
+
```
|
|
644
|
+
|
|
645
|
+
### QR Code Events
|
|
646
|
+
|
|
647
|
+
Handle QR code generation for initial setup:
|
|
648
|
+
|
|
649
|
+
```typescript
|
|
650
|
+
async function handleQR(event) {
|
|
651
|
+
const { Codes } = event;
|
|
652
|
+
console.log("📱 New QR codes received:");
|
|
653
|
+
|
|
654
|
+
Codes.forEach((code, index) => {
|
|
655
|
+
console.log(`📷 QR Code ${index + 1}: ${code}`);
|
|
656
|
+
// Display QR code to user or save to file
|
|
657
|
+
});
|
|
658
|
+
}
|
|
659
|
+
```
|
|
660
|
+
|
|
661
|
+
### Profile and Contact Updates
|
|
662
|
+
|
|
663
|
+
Handle profile picture and contact information changes:
|
|
664
|
+
|
|
665
|
+
```typescript
|
|
666
|
+
// Profile picture changes
|
|
667
|
+
async function handlePicture(event) {
|
|
668
|
+
const { JID, Author, Remove, Timestamp } = event;
|
|
669
|
+
const target = JID.User;
|
|
670
|
+
const changer = Author.User;
|
|
671
|
+
|
|
672
|
+
if (Remove) {
|
|
673
|
+
console.log(`🗑️ ${changer} removed profile picture for ${target}`);
|
|
674
|
+
} else {
|
|
675
|
+
console.log(`🖼️ ${changer} updated profile picture for ${target}`);
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
// Contact info updates
|
|
680
|
+
async function handleContact(event) {
|
|
681
|
+
const { JID, Found, FullName, PushName, BusinessName } = event;
|
|
682
|
+
console.log(`👤 Contact info: ${FullName || PushName} (${JID.User})`);
|
|
683
|
+
|
|
684
|
+
if (BusinessName) {
|
|
685
|
+
console.log(`🏢 Business: ${BusinessName}`);
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
console.log(`✅ Found in WhatsApp: ${Found}`);
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
// Name changes
|
|
692
|
+
async function handlePushName(event) {
|
|
693
|
+
const { JID, OldPushName, NewPushName } = event;
|
|
694
|
+
console.log(
|
|
695
|
+
`📝 ${JID.User} changed name from "${OldPushName}" to "${NewPushName}"`
|
|
696
|
+
);
|
|
697
|
+
}
|
|
698
|
+
```
|
|
699
|
+
|
|
700
|
+
### Error Handling
|
|
701
|
+
|
|
702
|
+
Handle various error events:
|
|
703
|
+
|
|
704
|
+
```typescript
|
|
705
|
+
// Undecryptable messages
|
|
706
|
+
async function handleUndecryptableMessage(event) {
|
|
707
|
+
const { Info, IsUnavailable, UnavailableType, DecryptFailMode } = event;
|
|
708
|
+
console.log(`❌ Failed to decrypt message from ${Info.Source.Sender.User}`);
|
|
709
|
+
console.log(
|
|
710
|
+
`📊 Unavailable: ${IsUnavailable}, Type: ${UnavailableType}, Mode: ${DecryptFailMode}`
|
|
711
|
+
);
|
|
712
|
+
|
|
713
|
+
// Log for debugging or request message retry
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
// Stream errors
|
|
717
|
+
async function handleStreamError(event) {
|
|
718
|
+
const { Code } = event;
|
|
719
|
+
console.log(`🚨 Stream error: ${Code}`);
|
|
720
|
+
|
|
721
|
+
// Handle specific error codes
|
|
722
|
+
switch (Code) {
|
|
723
|
+
case "conflict":
|
|
724
|
+
console.log("Another client connected with same credentials");
|
|
725
|
+
break;
|
|
726
|
+
case "stream:error":
|
|
727
|
+
console.log("General stream error occurred");
|
|
728
|
+
break;
|
|
729
|
+
default:
|
|
730
|
+
console.log("Unknown stream error");
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
```
|
|
734
|
+
|
|
735
|
+
### Complete Webhook Server Example
|
|
736
|
+
|
|
737
|
+
Here's a complete webhook server that handles all event types:
|
|
738
|
+
|
|
739
|
+
```typescript
|
|
740
|
+
import express from "express";
|
|
741
|
+
import WuzapiClient, {
|
|
742
|
+
Message,
|
|
743
|
+
Receipt,
|
|
744
|
+
Presence,
|
|
745
|
+
EventGroupInfo,
|
|
746
|
+
} from "wuzapi";
|
|
747
|
+
|
|
748
|
+
const app = express();
|
|
749
|
+
app.use(express.json());
|
|
750
|
+
|
|
751
|
+
const client = new WuzapiClient({
|
|
752
|
+
apiUrl: process.env.WUZAPI_URL || "http://localhost:8080",
|
|
753
|
+
token: process.env.WUZAPI_TOKEN || "your-token",
|
|
754
|
+
});
|
|
755
|
+
|
|
756
|
+
// Event router
|
|
757
|
+
const eventHandlers = {
|
|
758
|
+
Message: handleMessage,
|
|
759
|
+
Receipt: handleReceipt,
|
|
760
|
+
Presence: handlePresence,
|
|
761
|
+
ChatPresence: handleChatPresence,
|
|
762
|
+
GroupInfo: handleGroupInfo,
|
|
763
|
+
JoinedGroup: handleJoinedGroup,
|
|
764
|
+
Connected: handleConnected,
|
|
765
|
+
Disconnected: handleDisconnected,
|
|
766
|
+
LoggedOut: handleLoggedOut,
|
|
767
|
+
QR: handleQR,
|
|
768
|
+
Picture: handlePicture,
|
|
769
|
+
Contact: handleContact,
|
|
770
|
+
PushName: handlePushName,
|
|
771
|
+
UndecryptableMessage: handleUndecryptableMessage,
|
|
772
|
+
StreamError: handleStreamError,
|
|
773
|
+
};
|
|
774
|
+
|
|
775
|
+
app.post("/webhook", async (req, res) => {
|
|
776
|
+
try {
|
|
777
|
+
const eventData = req.body;
|
|
778
|
+
|
|
779
|
+
// Log all events for debugging
|
|
780
|
+
console.log("📨 Webhook received:", JSON.stringify(eventData, null, 2));
|
|
781
|
+
|
|
782
|
+
// Route to appropriate handler based on event structure
|
|
783
|
+
for (const [eventType, handler] of Object.entries(eventHandlers)) {
|
|
784
|
+
if (isEventType(eventData, eventType)) {
|
|
785
|
+
await handler(eventData);
|
|
786
|
+
break;
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
res.status(200).json({ success: true });
|
|
791
|
+
} catch (error) {
|
|
792
|
+
console.error("❌ Webhook processing error:", error);
|
|
793
|
+
res.status(500).json({ error: error.message });
|
|
794
|
+
}
|
|
795
|
+
});
|
|
796
|
+
|
|
797
|
+
// Helper function to identify event types
|
|
798
|
+
function isEventType(event, type) {
|
|
799
|
+
switch (type) {
|
|
800
|
+
case "Message":
|
|
801
|
+
return event.Message && event.Info;
|
|
802
|
+
case "Receipt":
|
|
803
|
+
return event.MessageIDs && event.Type;
|
|
804
|
+
case "Presence":
|
|
805
|
+
return event.From && typeof event.Unavailable !== "undefined";
|
|
806
|
+
case "ChatPresence":
|
|
807
|
+
return event.State && event.MessageSource;
|
|
808
|
+
case "GroupInfo":
|
|
809
|
+
return event.GroupName && event.Participants;
|
|
810
|
+
case "QR":
|
|
811
|
+
return event.Codes;
|
|
812
|
+
// Add more type checks as needed
|
|
813
|
+
default:
|
|
814
|
+
return false;
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
// Health check
|
|
819
|
+
app.get("/health", (req, res) => {
|
|
820
|
+
res.json({ status: "healthy", timestamp: new Date().toISOString() });
|
|
821
|
+
});
|
|
822
|
+
|
|
823
|
+
// Initialize
|
|
824
|
+
async function initialize() {
|
|
825
|
+
try {
|
|
826
|
+
// Connect to WhatsApp with all events
|
|
827
|
+
await client.session.connect({
|
|
828
|
+
Subscribe: [
|
|
829
|
+
"Message",
|
|
830
|
+
"ReadReceipt",
|
|
831
|
+
"Presence",
|
|
832
|
+
"ChatPresence",
|
|
833
|
+
"GroupInfo",
|
|
834
|
+
"Contact",
|
|
835
|
+
"PushName",
|
|
836
|
+
"Picture",
|
|
837
|
+
"QR",
|
|
838
|
+
"Connected",
|
|
839
|
+
"Disconnected",
|
|
840
|
+
"LoggedOut",
|
|
841
|
+
"UndecryptableMessage",
|
|
842
|
+
"StreamError",
|
|
843
|
+
],
|
|
844
|
+
Immediate: false,
|
|
845
|
+
});
|
|
846
|
+
|
|
847
|
+
// Set webhook
|
|
848
|
+
const webhookUrl =
|
|
849
|
+
process.env.WEBHOOK_URL || "http://localhost:3000/webhook";
|
|
850
|
+
await client.webhook.setWebhook(webhookUrl);
|
|
851
|
+
|
|
852
|
+
app.listen(3000, () => {
|
|
853
|
+
console.log("🚀 Webhook server running on port 3000");
|
|
854
|
+
console.log("🎉 Ready to receive WhatsApp events!");
|
|
855
|
+
});
|
|
856
|
+
} catch (error) {
|
|
857
|
+
console.error("❌ Initialization failed:", error);
|
|
858
|
+
process.exit(1);
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
initialize();
|
|
863
|
+
```
|
|
864
|
+
|
|
865
|
+
### Event Types Reference
|
|
866
|
+
|
|
867
|
+
All webhook events are fully typed. Import the specific event types you need:
|
|
868
|
+
|
|
869
|
+
```typescript
|
|
870
|
+
import {
|
|
871
|
+
// Core message types
|
|
872
|
+
Message,
|
|
873
|
+
MessageEvent,
|
|
874
|
+
MessageContent,
|
|
875
|
+
getMessageContent,
|
|
876
|
+
|
|
877
|
+
// Specific message types
|
|
878
|
+
ImageMessage,
|
|
879
|
+
VideoMessage,
|
|
880
|
+
AudioMessage,
|
|
881
|
+
DocumentMessage,
|
|
882
|
+
LocationMessage,
|
|
883
|
+
ContactMessage,
|
|
884
|
+
StickerMessage,
|
|
885
|
+
ButtonsMessage,
|
|
886
|
+
ListMessage,
|
|
887
|
+
PollCreationMessage,
|
|
888
|
+
ReactionMessage,
|
|
889
|
+
|
|
890
|
+
// Event types
|
|
891
|
+
Receipt,
|
|
892
|
+
Presence,
|
|
893
|
+
ChatPresence,
|
|
894
|
+
EventGroupInfo,
|
|
895
|
+
EventContact,
|
|
896
|
+
QR,
|
|
897
|
+
Picture,
|
|
898
|
+
PushName,
|
|
899
|
+
Connected,
|
|
900
|
+
Disconnected,
|
|
901
|
+
LoggedOut,
|
|
902
|
+
UndecryptableMessage,
|
|
903
|
+
StreamError,
|
|
904
|
+
WhatsAppEvent,
|
|
905
|
+
} from "wuzapi";
|
|
906
|
+
|
|
907
|
+
// Type-safe event handling with full message structure
|
|
908
|
+
async function handleTypedMessage(event: MessageEvent) {
|
|
909
|
+
const messageContent = getMessageContent(event.Message);
|
|
910
|
+
|
|
911
|
+
if (messageContent?.type === "image") {
|
|
912
|
+
// TypeScript knows this is an ImageMessage
|
|
913
|
+
const imageMsg: ImageMessage = messageContent.content;
|
|
914
|
+
console.log(`Image dimensions: ${imageMsg.width}x${imageMsg.height}`);
|
|
915
|
+
console.log(`File size: ${imageMsg.fileLength} bytes`);
|
|
916
|
+
|
|
917
|
+
// Full type safety and autocompletion
|
|
918
|
+
if (imageMsg.caption) {
|
|
919
|
+
console.log(`Caption: ${imageMsg.caption}`);
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
if (messageContent?.type === "buttons") {
|
|
924
|
+
// TypeScript knows this is a ButtonsMessage
|
|
925
|
+
const buttonsMsg: ButtonsMessage = messageContent.content;
|
|
926
|
+
console.log(`Button message: ${buttonsMsg.contentText}`);
|
|
927
|
+
|
|
928
|
+
buttonsMsg.buttons?.forEach((button, index) => {
|
|
929
|
+
console.log(`Button ${index + 1}: ${button.buttonText?.displayText}`);
|
|
930
|
+
});
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
// Helper function to handle all message types generically
|
|
935
|
+
function processMessageContent(content: MessageContent) {
|
|
936
|
+
switch (content.type) {
|
|
937
|
+
case "text":
|
|
938
|
+
// content.content is string
|
|
939
|
+
return content.content.toUpperCase();
|
|
940
|
+
|
|
941
|
+
case "image":
|
|
942
|
+
// content.content is ImageMessage
|
|
943
|
+
return `Image: ${content.content.caption || "No caption"}`;
|
|
944
|
+
|
|
945
|
+
case "video":
|
|
946
|
+
// content.content is VideoMessage
|
|
947
|
+
return `Video (${content.content.seconds}s): ${
|
|
948
|
+
content.content.caption || "No caption"
|
|
949
|
+
}`;
|
|
950
|
+
|
|
951
|
+
// TypeScript ensures you handle all possible types
|
|
952
|
+
default:
|
|
953
|
+
return `Unknown message type: ${content.type}`;
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
```
|
|
957
|
+
|
|
958
|
+
### Advanced Message Type Handling
|
|
959
|
+
|
|
960
|
+
For complex message processing, you can work directly with the typed message structures:
|
|
961
|
+
|
|
962
|
+
```typescript
|
|
963
|
+
import {
|
|
964
|
+
Message,
|
|
965
|
+
ExtendedTextMessage,
|
|
966
|
+
ButtonsMessage,
|
|
967
|
+
ContextInfo,
|
|
968
|
+
} from "wuzapi";
|
|
969
|
+
|
|
970
|
+
// Check for extended text with link preview
|
|
971
|
+
function hasLinkPreview(message: Message): boolean {
|
|
972
|
+
return !!message.extendedTextMessage?.canonicalUrl;
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
// Extract context info from any message
|
|
976
|
+
function getMessageContext(message: Message): ContextInfo | undefined {
|
|
977
|
+
// Check various message types for context info
|
|
978
|
+
return (
|
|
979
|
+
message.extendedTextMessage?.contextInfo ||
|
|
980
|
+
message.imageMessage?.contextInfo ||
|
|
981
|
+
message.videoMessage?.contextInfo ||
|
|
982
|
+
message.audioMessage?.contextInfo
|
|
983
|
+
);
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
// Process interactive messages
|
|
987
|
+
function handleInteractiveMessage(message: Message) {
|
|
988
|
+
if (message.buttonsMessage) {
|
|
989
|
+
const btns = message.buttonsMessage;
|
|
990
|
+
console.log(`Interactive message: ${btns.contentText}`);
|
|
991
|
+
|
|
992
|
+
btns.buttons?.forEach((button) => {
|
|
993
|
+
if (button.type === ButtonType.RESPONSE) {
|
|
994
|
+
console.log(`Response button: ${button.buttonText?.displayText}`);
|
|
995
|
+
} else if (button.type === ButtonType.NATIVE_FLOW) {
|
|
996
|
+
console.log(`Native flow: ${button.nativeFlowInfo?.name}`);
|
|
997
|
+
}
|
|
998
|
+
});
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
if (message.listMessage) {
|
|
1002
|
+
const list = message.listMessage;
|
|
1003
|
+
console.log(`List: ${list.title}`);
|
|
1004
|
+
|
|
1005
|
+
list.sections?.forEach((section) => {
|
|
1006
|
+
console.log(`Section: ${section.title}`);
|
|
1007
|
+
section.rows?.forEach((row) => {
|
|
1008
|
+
console.log(`- ${row.title}: ${row.description}`);
|
|
1009
|
+
});
|
|
1010
|
+
});
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
```
|
|
1014
|
+
|
|
300
1015
|
## Error Handling
|
|
301
1016
|
|
|
302
1017
|
The library provides comprehensive error handling with detailed error information:
|
|
@@ -401,6 +1116,12 @@ if (isConnected) {
|
|
|
401
1116
|
|
|
402
1117
|
## Examples
|
|
403
1118
|
|
|
1119
|
+
For complete working examples, check the `examples/` directory:
|
|
1120
|
+
|
|
1121
|
+
- **`basic-usage.js`** - Basic client setup and usage
|
|
1122
|
+
- **`chatbot-example.js`** - Simple chatbot with command handling
|
|
1123
|
+
- **`webhook-events-example.js`** - Comprehensive webhook event handling
|
|
1124
|
+
|
|
404
1125
|
### Complete Chat Bot Example
|
|
405
1126
|
|
|
406
1127
|
```typescript
|