@elizaos/plugin-whatsapp 2.0.0-alpha.5 → 2.0.0-alpha.7

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/dist/index.js ADDED
@@ -0,0 +1,1595 @@
1
+ // src/index.ts
2
+ import { EventEmitter as EventEmitter4 } from "node:events";
3
+
4
+ // src/actions/sendMessage.ts
5
+ import { composePromptFromState, ModelType, parseJSONObjectFromText } from "@elizaos/core";
6
+ var WHATSAPP_SEND_MESSAGE_ACTION = "WHATSAPP_SEND_MESSAGE";
7
+ var SEND_MESSAGE_TEMPLATE = `
8
+ You are extracting WhatsApp message parameters from a conversation.
9
+
10
+ The user wants to send a WhatsApp message. Extract the following:
11
+ 1. to: The phone number to send to (E.164 format, e.g., +14155552671)
12
+ 2. text: The message text to send
13
+
14
+ {{recentMessages}}
15
+
16
+ Based on the conversation, extract the message parameters.
17
+
18
+ Respond with a JSON object:
19
+ {
20
+ "to": "+14155552671",
21
+ "text": "Hello from WhatsApp!"
22
+ }
23
+ `;
24
+ var sendMessageAction = {
25
+ name: WHATSAPP_SEND_MESSAGE_ACTION,
26
+ similes: ["SEND_WHATSAPP", "WHATSAPP_MESSAGE", "TEXT_WHATSAPP", "SEND_WHATSAPP_MESSAGE"],
27
+ description: "Send a text message via WhatsApp",
28
+ validate: async (runtime, message, state, options) => {
29
+ const __avTextRaw = typeof message?.content?.text === "string" ? message.content.text : "";
30
+ const __avText = __avTextRaw.toLowerCase();
31
+ const __avKeywords = ["whatsapp", "send", "message"];
32
+ const __avKeywordOk = __avKeywords.length > 0 && __avKeywords.some((kw) => kw.length > 0 && __avText.includes(kw));
33
+ const __avRegex = new RegExp("\\b(?:whatsapp|send|message)\\b", "i");
34
+ const __avRegexOk = __avRegex.test(__avText);
35
+ const __avSource = String(message?.content?.source ?? message?.source ?? "");
36
+ const __avExpectedSource = "whatsapp";
37
+ const __avSourceOk = __avExpectedSource ? __avSource === __avExpectedSource : Boolean(__avSource || state || runtime?.agentId || runtime?.getService);
38
+ const __avOptions = options && typeof options === "object" ? options : {};
39
+ const __avInputOk = __avText.trim().length > 0 || Object.keys(__avOptions).length > 0 || Boolean(message?.content && typeof message.content === "object");
40
+ if (!(__avKeywordOk && __avRegexOk && __avSourceOk && __avInputOk)) {
41
+ return false;
42
+ }
43
+ const __avLegacyValidate = async (_runtime, message2) => {
44
+ const source = message2.content?.source;
45
+ return source === "whatsapp";
46
+ };
47
+ try {
48
+ return Boolean(await __avLegacyValidate(runtime, message, state, options));
49
+ } catch {
50
+ return false;
51
+ }
52
+ },
53
+ handler: async (runtime, message, state, _options, callback) => {
54
+ const accessToken = runtime.getSetting("WHATSAPP_ACCESS_TOKEN");
55
+ const phoneNumberId = runtime.getSetting("WHATSAPP_PHONE_NUMBER_ID");
56
+ const apiVersion = runtime.getSetting("WHATSAPP_API_VERSION") || "v24.0";
57
+ if (!accessToken || !phoneNumberId) {
58
+ if (callback) {
59
+ await callback({
60
+ text: "WhatsApp is not configured. Missing access token or phone number ID."
61
+ });
62
+ }
63
+ return { success: false, error: "WhatsApp not configured" };
64
+ }
65
+ const currentState = state ?? await runtime.composeState(message);
66
+ const prompt = composePromptFromState({
67
+ state: currentState,
68
+ template: SEND_MESSAGE_TEMPLATE
69
+ });
70
+ let params;
71
+ try {
72
+ const response = await runtime.useModel(ModelType.TEXT_SMALL, {
73
+ prompt
74
+ });
75
+ const parsed = parseJSONObjectFromText(response);
76
+ if (!parsed || !parsed.to || !parsed.text) {
77
+ const to = message.content?.from;
78
+ const text = currentState.values?.response?.toString() || "";
79
+ if (!to) {
80
+ if (callback) {
81
+ await callback({
82
+ text: "Could not determine who to send the message to"
83
+ });
84
+ }
85
+ return { success: false, error: "Missing recipient" };
86
+ }
87
+ if (!text || text.trim() === "") {
88
+ if (callback) {
89
+ await callback({
90
+ text: "Cannot send an empty message. Please provide message content."
91
+ });
92
+ }
93
+ return { success: false, error: "Empty message text" };
94
+ }
95
+ params = { to, text };
96
+ } else {
97
+ if (!parsed.text.trim()) {
98
+ if (callback) {
99
+ await callback({
100
+ text: "Cannot send an empty message. Please provide message content."
101
+ });
102
+ }
103
+ return { success: false, error: "Empty message text" };
104
+ }
105
+ params = parsed;
106
+ }
107
+ } catch {
108
+ if (callback) {
109
+ await callback({
110
+ text: "Failed to parse message parameters"
111
+ });
112
+ }
113
+ return { success: false, error: "Failed to parse message parameters" };
114
+ }
115
+ try {
116
+ const url = `https://graph.facebook.com/${apiVersion}/${phoneNumberId}/messages`;
117
+ const response = await fetch(url, {
118
+ method: "POST",
119
+ headers: {
120
+ Authorization: `Bearer ${accessToken}`,
121
+ "Content-Type": "application/json"
122
+ },
123
+ body: JSON.stringify({
124
+ messaging_product: "whatsapp",
125
+ recipient_type: "individual",
126
+ to: params.to,
127
+ type: "text",
128
+ text: {
129
+ preview_url: false,
130
+ body: params.text
131
+ }
132
+ })
133
+ });
134
+ if (!response.ok) {
135
+ const errorData = await response.json();
136
+ throw new Error(errorData.error?.message || `HTTP ${response.status}`);
137
+ }
138
+ const data = await response.json();
139
+ const messageId = data.messages?.[0]?.id;
140
+ if (callback) {
141
+ await callback({
142
+ text: `Message sent to ${params.to}`,
143
+ action: WHATSAPP_SEND_MESSAGE_ACTION
144
+ });
145
+ }
146
+ return {
147
+ success: true,
148
+ data: {
149
+ action: WHATSAPP_SEND_MESSAGE_ACTION,
150
+ to: params.to,
151
+ messageId
152
+ }
153
+ };
154
+ } catch (error) {
155
+ const errorMessage = error instanceof Error ? error.message : String(error);
156
+ if (callback) {
157
+ await callback({
158
+ text: `Failed to send WhatsApp message: ${errorMessage}`
159
+ });
160
+ }
161
+ return { success: false, error: errorMessage };
162
+ }
163
+ },
164
+ examples: [
165
+ [
166
+ {
167
+ name: "{{name1}}",
168
+ content: {
169
+ text: "Send a WhatsApp message to +14155552671 saying hello"
170
+ }
171
+ },
172
+ {
173
+ name: "{{agentName}}",
174
+ content: {
175
+ text: "I'll send that WhatsApp message now.",
176
+ actions: [WHATSAPP_SEND_MESSAGE_ACTION]
177
+ }
178
+ }
179
+ ]
180
+ ]
181
+ };
182
+ // src/actions/sendReaction.ts
183
+ import { composePromptFromState as composePromptFromState2, ModelType as ModelType2, parseJSONObjectFromText as parseJSONObjectFromText2 } from "@elizaos/core";
184
+ var WHATSAPP_SEND_REACTION_ACTION = "WHATSAPP_SEND_REACTION";
185
+ var REACTION_TEMPLATE = `
186
+ You are extracting WhatsApp reaction parameters from a conversation.
187
+
188
+ The user wants to react to a WhatsApp message. Extract the following:
189
+ 1. messageId: The ID of the message to react to
190
+ 2. emoji: The emoji to use as a reaction
191
+
192
+ {{recentMessages}}
193
+
194
+ Based on the conversation, extract the reaction parameters.
195
+
196
+ Respond with a JSON object:
197
+ {
198
+ "messageId": "wamid.xxx",
199
+ "emoji": "\uD83D\uDC4D"
200
+ }
201
+ `;
202
+ var sendReactionAction = {
203
+ name: WHATSAPP_SEND_REACTION_ACTION,
204
+ similes: ["WHATSAPP_REACT", "REACT_WHATSAPP", "WHATSAPP_EMOJI"],
205
+ description: "Send a reaction emoji to a WhatsApp message",
206
+ validate: async (runtime, message, state, options) => {
207
+ const __avTextRaw = typeof message?.content?.text === "string" ? message.content.text : "";
208
+ const __avText = __avTextRaw.toLowerCase();
209
+ const __avKeywords = ["whatsapp", "send", "reaction"];
210
+ const __avKeywordOk = __avKeywords.length > 0 && __avKeywords.some((kw) => kw.length > 0 && __avText.includes(kw));
211
+ const __avRegex = new RegExp("\\b(?:whatsapp|send|reaction)\\b", "i");
212
+ const __avRegexOk = __avRegex.test(__avText);
213
+ const __avSource = String(message?.content?.source ?? message?.source ?? "");
214
+ const __avExpectedSource = "whatsapp";
215
+ const __avSourceOk = __avExpectedSource ? __avSource === __avExpectedSource : Boolean(__avSource || state || runtime?.agentId || runtime?.getService);
216
+ const __avOptions = options && typeof options === "object" ? options : {};
217
+ const __avInputOk = __avText.trim().length > 0 || Object.keys(__avOptions).length > 0 || Boolean(message?.content && typeof message.content === "object");
218
+ if (!(__avKeywordOk && __avRegexOk && __avSourceOk && __avInputOk)) {
219
+ return false;
220
+ }
221
+ const __avLegacyValidate = async (_runtime, message2) => {
222
+ const source = message2.content?.source;
223
+ return source === "whatsapp";
224
+ };
225
+ try {
226
+ return Boolean(await __avLegacyValidate(runtime, message, state, options));
227
+ } catch {
228
+ return false;
229
+ }
230
+ },
231
+ handler: async (runtime, message, state, _options, callback) => {
232
+ const accessToken = runtime.getSetting("WHATSAPP_ACCESS_TOKEN");
233
+ const phoneNumberId = runtime.getSetting("WHATSAPP_PHONE_NUMBER_ID");
234
+ const apiVersion = runtime.getSetting("WHATSAPP_API_VERSION") || "v24.0";
235
+ if (!accessToken || !phoneNumberId) {
236
+ if (callback) {
237
+ await callback({
238
+ text: "WhatsApp is not configured. Missing access token or phone number ID."
239
+ });
240
+ }
241
+ return { success: false, error: "WhatsApp not configured" };
242
+ }
243
+ const currentState = state ?? await runtime.composeState(message);
244
+ const prompt = composePromptFromState2({
245
+ state: currentState,
246
+ template: REACTION_TEMPLATE
247
+ });
248
+ let params;
249
+ try {
250
+ const response = await runtime.useModel(ModelType2.TEXT_SMALL, {
251
+ prompt
252
+ });
253
+ const parsed = parseJSONObjectFromText2(response);
254
+ if (!parsed || !parsed.messageId || !parsed.emoji) {
255
+ const messageId = message.content?.messageId;
256
+ if (!messageId) {
257
+ if (callback) {
258
+ await callback({
259
+ text: "Could not determine which message to react to"
260
+ });
261
+ }
262
+ return { success: false, error: "Missing message ID" };
263
+ }
264
+ params = { messageId, emoji: "\uD83D\uDC4D" };
265
+ } else {
266
+ params = parsed;
267
+ }
268
+ } catch {
269
+ if (callback) {
270
+ await callback({
271
+ text: "Failed to parse reaction parameters"
272
+ });
273
+ }
274
+ return { success: false, error: "Failed to parse reaction parameters" };
275
+ }
276
+ const to = message.content?.from;
277
+ if (!to) {
278
+ if (callback) {
279
+ await callback({
280
+ text: "Could not determine the recipient for the reaction"
281
+ });
282
+ }
283
+ return { success: false, error: "Missing recipient" };
284
+ }
285
+ try {
286
+ const url = `https://graph.facebook.com/${apiVersion}/${phoneNumberId}/messages`;
287
+ const response = await fetch(url, {
288
+ method: "POST",
289
+ headers: {
290
+ Authorization: `Bearer ${accessToken}`,
291
+ "Content-Type": "application/json"
292
+ },
293
+ body: JSON.stringify({
294
+ messaging_product: "whatsapp",
295
+ recipient_type: "individual",
296
+ to,
297
+ type: "reaction",
298
+ reaction: {
299
+ message_id: params.messageId,
300
+ emoji: params.emoji
301
+ }
302
+ })
303
+ });
304
+ if (!response.ok) {
305
+ const errorData = await response.json();
306
+ throw new Error(errorData.error?.message || `HTTP ${response.status}`);
307
+ }
308
+ if (callback) {
309
+ await callback({
310
+ text: `Reacted with ${params.emoji}`,
311
+ action: WHATSAPP_SEND_REACTION_ACTION
312
+ });
313
+ }
314
+ return {
315
+ success: true,
316
+ data: {
317
+ action: WHATSAPP_SEND_REACTION_ACTION,
318
+ messageId: params.messageId,
319
+ emoji: params.emoji
320
+ }
321
+ };
322
+ } catch (error) {
323
+ const errorMessage = error instanceof Error ? error.message : String(error);
324
+ if (callback) {
325
+ await callback({
326
+ text: `Failed to send reaction: ${errorMessage}`
327
+ });
328
+ }
329
+ return { success: false, error: errorMessage };
330
+ }
331
+ },
332
+ examples: [
333
+ [
334
+ {
335
+ name: "{{name1}}",
336
+ content: {
337
+ text: "React with a thumbs up"
338
+ }
339
+ },
340
+ {
341
+ name: "{{agentName}}",
342
+ content: {
343
+ text: "I'll add that reaction.",
344
+ actions: [WHATSAPP_SEND_REACTION_ACTION]
345
+ }
346
+ }
347
+ ]
348
+ ]
349
+ };
350
+ // src/client.ts
351
+ import axios from "axios";
352
+ import { EventEmitter } from "node:events";
353
+ var DEFAULT_API_VERSION = "v24.0";
354
+
355
+ class WhatsAppClient extends EventEmitter {
356
+ client;
357
+ config;
358
+ connectionStatus = "close";
359
+ constructor(config) {
360
+ super();
361
+ this.config = config;
362
+ const apiVersion = config.apiVersion || DEFAULT_API_VERSION;
363
+ this.client = axios.create({
364
+ baseURL: `https://graph.facebook.com/${apiVersion}`,
365
+ headers: {
366
+ Authorization: `Bearer ${config.accessToken}`,
367
+ "Content-Type": "application/json"
368
+ }
369
+ });
370
+ }
371
+ async start() {
372
+ this.connectionStatus = "open";
373
+ this.emit("connection", "open");
374
+ this.emit("ready");
375
+ }
376
+ async stop() {
377
+ this.connectionStatus = "close";
378
+ this.emit("connection", "close");
379
+ }
380
+ getConnectionStatus() {
381
+ return this.connectionStatus;
382
+ }
383
+ getPhoneNumberId() {
384
+ return this.config.phoneNumberId;
385
+ }
386
+ async sendMessage(message) {
387
+ const endpoint = `/${this.config.phoneNumberId}/messages`;
388
+ const payload = this.buildMessagePayload(message);
389
+ return this.client.post(endpoint, payload);
390
+ }
391
+ async sendTextMessage(to, text, _previewUrl = false) {
392
+ return this.sendMessage({
393
+ type: "text",
394
+ to,
395
+ content: text
396
+ });
397
+ }
398
+ async sendReaction(params) {
399
+ const endpoint = `/${this.config.phoneNumberId}/messages`;
400
+ const payload = {
401
+ messaging_product: "whatsapp",
402
+ recipient_type: "individual",
403
+ to: params.to,
404
+ type: "reaction",
405
+ reaction: {
406
+ message_id: params.messageId,
407
+ emoji: params.emoji
408
+ }
409
+ };
410
+ try {
411
+ const response = await this.client.post(endpoint, payload);
412
+ return {
413
+ success: true,
414
+ messageId: response.data.messages?.[0]?.id
415
+ };
416
+ } catch (error) {
417
+ const errorMessage = error instanceof Error ? error.message : String(error);
418
+ return {
419
+ success: false,
420
+ error: errorMessage
421
+ };
422
+ }
423
+ }
424
+ async removeReaction(to, messageId) {
425
+ return this.sendReaction({
426
+ to,
427
+ messageId,
428
+ emoji: ""
429
+ });
430
+ }
431
+ async sendImage(to, imageUrl, caption) {
432
+ return this.sendMessage({
433
+ type: "image",
434
+ to,
435
+ content: {
436
+ link: imageUrl,
437
+ caption
438
+ }
439
+ });
440
+ }
441
+ async sendVideo(to, videoUrl, caption) {
442
+ return this.sendMessage({
443
+ type: "video",
444
+ to,
445
+ content: {
446
+ link: videoUrl,
447
+ caption
448
+ }
449
+ });
450
+ }
451
+ async sendAudio(to, audioUrl) {
452
+ return this.sendMessage({
453
+ type: "audio",
454
+ to,
455
+ content: {
456
+ link: audioUrl
457
+ }
458
+ });
459
+ }
460
+ async sendDocument(to, documentUrl, filename, caption) {
461
+ return this.sendMessage({
462
+ type: "document",
463
+ to,
464
+ content: {
465
+ link: documentUrl,
466
+ filename,
467
+ caption
468
+ }
469
+ });
470
+ }
471
+ async sendLocation(to, latitude, longitude, name, address) {
472
+ return this.sendMessage({
473
+ type: "location",
474
+ to,
475
+ content: {
476
+ latitude,
477
+ longitude,
478
+ name,
479
+ address
480
+ }
481
+ });
482
+ }
483
+ async sendButtonMessage(to, bodyText, buttons, headerText, footerText) {
484
+ const interactive = {
485
+ type: "button",
486
+ body: { text: bodyText },
487
+ action: {
488
+ buttons: buttons.map((btn) => ({
489
+ type: "reply",
490
+ reply: { id: btn.id, title: btn.title }
491
+ }))
492
+ }
493
+ };
494
+ if (headerText) {
495
+ interactive.header = { type: "text", text: headerText };
496
+ }
497
+ if (footerText) {
498
+ interactive.footer = { text: footerText };
499
+ }
500
+ return this.sendMessage({
501
+ type: "interactive",
502
+ to,
503
+ content: interactive
504
+ });
505
+ }
506
+ async sendListMessage(to, bodyText, buttonText, sections, headerText, footerText) {
507
+ const interactive = {
508
+ type: "list",
509
+ body: { text: bodyText },
510
+ action: {
511
+ button: buttonText,
512
+ sections
513
+ }
514
+ };
515
+ if (headerText) {
516
+ interactive.header = { type: "text", text: headerText };
517
+ }
518
+ if (footerText) {
519
+ interactive.footer = { text: footerText };
520
+ }
521
+ return this.sendMessage({
522
+ type: "interactive",
523
+ to,
524
+ content: interactive
525
+ });
526
+ }
527
+ async markMessageAsRead(messageId) {
528
+ const endpoint = `/${this.config.phoneNumberId}/messages`;
529
+ const payload = {
530
+ messaging_product: "whatsapp",
531
+ status: "read",
532
+ message_id: messageId
533
+ };
534
+ try {
535
+ await this.client.post(endpoint, payload);
536
+ return true;
537
+ } catch {
538
+ return false;
539
+ }
540
+ }
541
+ async getMediaUrl(mediaId) {
542
+ try {
543
+ const response = await this.client.get(`/${mediaId}`);
544
+ return response.data.url || null;
545
+ } catch {
546
+ return null;
547
+ }
548
+ }
549
+ async verifyWebhook(token) {
550
+ return token === this.config.webhookVerifyToken;
551
+ }
552
+ buildMessagePayload(message) {
553
+ const basePayload = {
554
+ messaging_product: "whatsapp",
555
+ recipient_type: "individual",
556
+ to: message.to,
557
+ type: message.type
558
+ };
559
+ const contextPayload = message.replyToMessageId ? { context: { message_id: message.replyToMessageId } } : {};
560
+ switch (message.type) {
561
+ case "text":
562
+ return {
563
+ ...basePayload,
564
+ ...contextPayload,
565
+ text: {
566
+ body: message.content
567
+ }
568
+ };
569
+ case "template":
570
+ return {
571
+ ...basePayload,
572
+ ...contextPayload,
573
+ template: message.content
574
+ };
575
+ case "image": {
576
+ const imageContent = message.content;
577
+ return {
578
+ ...basePayload,
579
+ ...contextPayload,
580
+ image: {
581
+ link: imageContent.link,
582
+ caption: imageContent.caption
583
+ }
584
+ };
585
+ }
586
+ case "video": {
587
+ const videoContent = message.content;
588
+ return {
589
+ ...basePayload,
590
+ ...contextPayload,
591
+ video: {
592
+ link: videoContent.link,
593
+ caption: videoContent.caption
594
+ }
595
+ };
596
+ }
597
+ case "audio": {
598
+ const audioContent = message.content;
599
+ return {
600
+ ...basePayload,
601
+ ...contextPayload,
602
+ audio: {
603
+ link: audioContent.link
604
+ }
605
+ };
606
+ }
607
+ case "document": {
608
+ const docContent = message.content;
609
+ return {
610
+ ...basePayload,
611
+ ...contextPayload,
612
+ document: {
613
+ link: docContent.link,
614
+ filename: docContent.filename,
615
+ caption: docContent.caption
616
+ }
617
+ };
618
+ }
619
+ case "location": {
620
+ const locContent = message.content;
621
+ return {
622
+ ...basePayload,
623
+ ...contextPayload,
624
+ location: {
625
+ latitude: locContent.latitude,
626
+ longitude: locContent.longitude,
627
+ name: locContent.name,
628
+ address: locContent.address
629
+ }
630
+ };
631
+ }
632
+ case "reaction": {
633
+ const reactionContent = message.content;
634
+ return {
635
+ ...basePayload,
636
+ reaction: {
637
+ message_id: reactionContent.messageId,
638
+ emoji: reactionContent.emoji
639
+ }
640
+ };
641
+ }
642
+ case "interactive": {
643
+ const interactiveContent = message.content;
644
+ return {
645
+ ...basePayload,
646
+ ...contextPayload,
647
+ interactive: interactiveContent
648
+ };
649
+ }
650
+ default:
651
+ return basePayload;
652
+ }
653
+ }
654
+ }
655
+
656
+ // src/utils/config-detector.ts
657
+ function detectAuthMethod(config) {
658
+ const explicitMethod = config.authMethod;
659
+ if (explicitMethod !== undefined) {
660
+ if (explicitMethod === "baileys" || explicitMethod === "cloudapi") {
661
+ return explicitMethod;
662
+ }
663
+ throw new Error(`Invalid authMethod: "${String(explicitMethod)}". Must be either "baileys" or "cloudapi".`);
664
+ }
665
+ if ("authDir" in config && config.authDir) {
666
+ return "baileys";
667
+ }
668
+ if ("accessToken" in config && "phoneNumberId" in config) {
669
+ return "cloudapi";
670
+ }
671
+ throw new Error("Cannot detect auth method. Provide either authDir (Baileys) or accessToken + phoneNumberId (Cloud API).");
672
+ }
673
+
674
+ // src/clients/baileys-client.ts
675
+ import { EventEmitter as EventEmitter3 } from "node:events";
676
+
677
+ // src/baileys/auth.ts
678
+ import { useMultiFileAuthState } from "@whiskeysockets/baileys";
679
+
680
+ class BaileysAuthManager {
681
+ authDir;
682
+ state;
683
+ saveCreds;
684
+ constructor(authDir) {
685
+ this.authDir = authDir;
686
+ }
687
+ async initialize() {
688
+ const result = await useMultiFileAuthState(this.authDir);
689
+ this.state = result.state;
690
+ this.saveCreds = result.saveCreds;
691
+ return this.state;
692
+ }
693
+ async save() {
694
+ if (this.saveCreds) {
695
+ await this.saveCreds();
696
+ }
697
+ }
698
+ }
699
+
700
+ // src/baileys/connection.ts
701
+ import makeWASocket, { DisconnectReason } from "@whiskeysockets/baileys";
702
+ import { EventEmitter as EventEmitter2 } from "node:events";
703
+ import pino from "pino";
704
+
705
+ class BaileysConnection extends EventEmitter2 {
706
+ socket;
707
+ authManager;
708
+ connectionStatus = "close";
709
+ reconnecting = false;
710
+ reconnectAttempts = 0;
711
+ maxReconnectAttempts = 10;
712
+ constructor(authManager) {
713
+ super();
714
+ this.authManager = authManager;
715
+ }
716
+ async connect() {
717
+ this.connectionStatus = "connecting";
718
+ this.emit("connection", "connecting");
719
+ const state = await this.authManager.initialize();
720
+ this.socket = makeWASocket({
721
+ auth: state,
722
+ printQRInTerminal: false,
723
+ logger: pino({ level: "silent" }),
724
+ browser: ["Chrome (Linux)", "", ""]
725
+ });
726
+ this.setupEventHandlers();
727
+ return this.socket;
728
+ }
729
+ setupEventHandlers() {
730
+ if (!this.socket) {
731
+ return;
732
+ }
733
+ this.socket.ev.on("connection.update", async (update) => {
734
+ const { connection, qr, lastDisconnect } = update;
735
+ if (qr) {
736
+ this.emit("qr", qr);
737
+ }
738
+ if (connection) {
739
+ this.connectionStatus = connection;
740
+ this.emit("connection", connection);
741
+ }
742
+ if (connection === "open") {
743
+ this.reconnectAttempts = 0;
744
+ return;
745
+ }
746
+ if (connection !== "close") {
747
+ return;
748
+ }
749
+ const statusCode = lastDisconnect?.error?.output?.statusCode;
750
+ const isQRTimeout = statusCode === 515;
751
+ const shouldReconnect = statusCode !== DisconnectReason.loggedOut && statusCode !== 405;
752
+ if (lastDisconnect?.error && !isQRTimeout) {
753
+ this.emit("error", lastDisconnect.error);
754
+ }
755
+ if (!shouldReconnect) {
756
+ return;
757
+ }
758
+ if (this.reconnecting) {
759
+ return;
760
+ }
761
+ if (this.reconnectAttempts >= this.maxReconnectAttempts) {
762
+ this.emit("error", new Error("Max reconnection attempts reached"));
763
+ return;
764
+ }
765
+ this.reconnecting = true;
766
+ try {
767
+ this.reconnectAttempts += 1;
768
+ const baseDelayMs = isQRTimeout ? 1000 : 3000;
769
+ const backoffMs = Math.min(baseDelayMs * Math.pow(2, this.reconnectAttempts - 1), 30000);
770
+ await new Promise((resolve) => setTimeout(resolve, backoffMs));
771
+ await this.connect();
772
+ } catch (error) {
773
+ this.emit("error", error);
774
+ } finally {
775
+ this.reconnecting = false;
776
+ }
777
+ });
778
+ this.socket.ev.on("creds.update", async () => {
779
+ await this.authManager.save();
780
+ });
781
+ this.socket.ev.on("messages.upsert", ({ messages }) => {
782
+ this.emit("messages", messages);
783
+ });
784
+ }
785
+ getSocket() {
786
+ return this.socket;
787
+ }
788
+ getStatus() {
789
+ return this.connectionStatus;
790
+ }
791
+ async disconnect() {
792
+ if (!this.socket) {
793
+ return;
794
+ }
795
+ this.socket.ev.removeAllListeners();
796
+ this.socket.ws?.close?.();
797
+ this.socket = undefined;
798
+ this.connectionStatus = "close";
799
+ this.emit("connection", "close");
800
+ }
801
+ }
802
+
803
+ // src/baileys/message-adapter.ts
804
+ class MessageAdapter {
805
+ toUnified(msg) {
806
+ return {
807
+ id: msg.key?.id ?? "",
808
+ from: msg.key?.remoteJid ?? "",
809
+ timestamp: Number(msg.messageTimestamp ?? 0),
810
+ type: this.detectType(msg),
811
+ content: this.extractContent(msg)
812
+ };
813
+ }
814
+ toBaileys(msg) {
815
+ switch (msg.type) {
816
+ case "text":
817
+ return { text: msg.content };
818
+ case "image":
819
+ return this.mediaWithCaption("image", msg.content);
820
+ case "video":
821
+ return this.mediaWithCaption("video", msg.content);
822
+ case "audio":
823
+ return this.mediaNoCaption("audio", msg.content);
824
+ case "document":
825
+ return this.mediaWithFilename(msg.content);
826
+ case "template":
827
+ return { text: this.renderTemplate(msg.content) };
828
+ default:
829
+ throw new Error(`Message type ${msg.type} is not yet supported for Baileys`);
830
+ }
831
+ }
832
+ mediaWithCaption(key, media) {
833
+ if (!media?.link) {
834
+ throw new Error(`${key} message requires a media link`);
835
+ }
836
+ return {
837
+ [key]: { url: media.link },
838
+ ...media.caption ? { caption: media.caption } : {}
839
+ };
840
+ }
841
+ mediaNoCaption(key, media) {
842
+ if (!media?.link) {
843
+ throw new Error(`${key} message requires a media link`);
844
+ }
845
+ return { [key]: { url: media.link } };
846
+ }
847
+ mediaWithFilename(media) {
848
+ if (!media?.link) {
849
+ throw new Error("document message requires a media link");
850
+ }
851
+ return {
852
+ document: { url: media.link },
853
+ ...media.filename ? { fileName: media.filename } : {},
854
+ ...media.caption ? { caption: media.caption } : {}
855
+ };
856
+ }
857
+ detectType(msg) {
858
+ if (msg.message?.conversation || msg.message?.extendedTextMessage) {
859
+ return "text";
860
+ }
861
+ if (msg.message?.imageMessage) {
862
+ return "image";
863
+ }
864
+ if (msg.message?.audioMessage) {
865
+ return "audio";
866
+ }
867
+ if (msg.message?.videoMessage) {
868
+ return "video";
869
+ }
870
+ if (msg.message?.documentMessage) {
871
+ return "document";
872
+ }
873
+ return "text";
874
+ }
875
+ extractContent(msg) {
876
+ return msg.message?.conversation ?? msg.message?.extendedTextMessage?.text ?? "";
877
+ }
878
+ renderTemplate(template) {
879
+ const params = template.components?.flatMap((component) => component.parameters.map((parameter) => parameter.text).filter(Boolean));
880
+ return params && params.length > 0 ? `${template.name}: ${params.join(", ")}` : template.name;
881
+ }
882
+ }
883
+
884
+ // src/baileys/qr-code.ts
885
+ import QRCode from "qrcode";
886
+ import QRCodeTerminal from "qrcode-terminal";
887
+
888
+ class QRCodeGenerator {
889
+ async generate(qrString) {
890
+ return {
891
+ terminal: await this.generateTerminal(qrString),
892
+ dataURL: await QRCode.toDataURL(qrString),
893
+ raw: qrString
894
+ };
895
+ }
896
+ async generateTerminal(qr) {
897
+ return new Promise((resolve) => {
898
+ QRCodeTerminal.generate(qr, { small: true }, (output) => {
899
+ resolve(output);
900
+ });
901
+ });
902
+ }
903
+ }
904
+
905
+ // src/clients/baileys-client.ts
906
+ class BaileysClient extends EventEmitter3 {
907
+ config;
908
+ authManager;
909
+ connection;
910
+ qrGenerator;
911
+ adapter;
912
+ constructor(config) {
913
+ super();
914
+ this.config = config;
915
+ this.authManager = new BaileysAuthManager(config.authDir);
916
+ this.connection = new BaileysConnection(this.authManager);
917
+ this.qrGenerator = new QRCodeGenerator;
918
+ this.adapter = new MessageAdapter;
919
+ this.setupEventForwarding();
920
+ }
921
+ setupEventForwarding() {
922
+ this.connection.on("qr", async (qr) => {
923
+ try {
924
+ const qrData = await this.qrGenerator.generate(qr);
925
+ if (this.config.printQRInTerminal !== false) {
926
+ console.log(`
927
+ === Scan QR Code ===
928
+ `);
929
+ console.log(qrData.terminal);
930
+ }
931
+ this.emit("qr", qrData);
932
+ } catch (error) {
933
+ this.emit("error", error);
934
+ }
935
+ });
936
+ this.connection.on("connection", (status) => {
937
+ this.emit("connection", status);
938
+ if (status === "open") {
939
+ this.emit("ready");
940
+ }
941
+ });
942
+ this.connection.on("messages", (messages) => {
943
+ for (const message of messages) {
944
+ const maybe = message;
945
+ if (!maybe.key?.fromMe && maybe.message) {
946
+ this.emit("message", this.adapter.toUnified(message));
947
+ }
948
+ }
949
+ });
950
+ this.connection.on("error", (error) => {
951
+ this.emit("error", error);
952
+ });
953
+ }
954
+ async start() {
955
+ await this.connection.connect();
956
+ }
957
+ async stop() {
958
+ await this.connection.disconnect();
959
+ }
960
+ async sendMessage(message) {
961
+ const socket = this.connection.getSocket();
962
+ if (!socket) {
963
+ throw new Error("Not connected to WhatsApp via Baileys");
964
+ }
965
+ const payload = this.adapter.toBaileys(message);
966
+ const result = await socket.sendMessage(message.to, payload);
967
+ const id = result?.key?.id ?? "";
968
+ return {
969
+ messaging_product: "whatsapp",
970
+ contacts: [{ input: message.to, wa_id: message.to }],
971
+ messages: [{ id }]
972
+ };
973
+ }
974
+ getConnectionStatus() {
975
+ return this.connection.getStatus();
976
+ }
977
+ }
978
+
979
+ // src/clients/factory.ts
980
+ class ClientFactory {
981
+ static create(config) {
982
+ const authMethod = detectAuthMethod(config);
983
+ if (authMethod === "baileys") {
984
+ return new BaileysClient(config);
985
+ }
986
+ return new WhatsAppClient(config);
987
+ }
988
+ }
989
+
990
+ // src/handlers/message.handler.ts
991
+ class MessageHandler {
992
+ client;
993
+ constructor(client) {
994
+ this.client = client;
995
+ }
996
+ async send(message) {
997
+ try {
998
+ const response = await this.client.sendMessage(message);
999
+ if (response && typeof response === "object" && "data" in response) {
1000
+ return response.data;
1001
+ }
1002
+ return response;
1003
+ } catch (error) {
1004
+ if (error instanceof Error) {
1005
+ throw new Error(`Failed to send WhatsApp message: ${error.message}`);
1006
+ }
1007
+ throw new Error("Failed to send WhatsApp message");
1008
+ }
1009
+ }
1010
+ }
1011
+ // src/handlers/webhook.handler.ts
1012
+ class WebhookHandler {
1013
+ async handle(event) {
1014
+ try {
1015
+ if (event.entry?.[0]?.changes?.[0]?.value?.messages) {
1016
+ const messages = event.entry[0].changes[0].value.messages;
1017
+ for (const message of messages) {
1018
+ await this.handleMessage(message);
1019
+ }
1020
+ }
1021
+ if (event.entry?.[0]?.changes?.[0]?.value?.statuses) {
1022
+ const statuses = event.entry[0].changes[0].value.statuses;
1023
+ for (const status of statuses) {
1024
+ await this.handleStatus(status);
1025
+ }
1026
+ }
1027
+ } catch (error) {
1028
+ if (error instanceof Error) {
1029
+ throw new Error(`Failed to send WhatsApp message: ${error.message}`);
1030
+ }
1031
+ throw new Error("Failed to send WhatsApp message");
1032
+ }
1033
+ }
1034
+ async handleMessage(message) {
1035
+ console.log("Received message:", message);
1036
+ }
1037
+ async handleStatus(status) {
1038
+ console.log("Received status update:", status);
1039
+ }
1040
+ }
1041
+ // src/accounts.ts
1042
+ import { checkPairingAllowed, isInAllowlist } from "@elizaos/core";
1043
+ var DEFAULT_ACCOUNT_ID = "default";
1044
+ function normalizeAccountId(accountId) {
1045
+ if (!accountId || typeof accountId !== "string") {
1046
+ return DEFAULT_ACCOUNT_ID;
1047
+ }
1048
+ const trimmed = accountId.trim().toLowerCase();
1049
+ if (!trimmed || trimmed === "default") {
1050
+ return DEFAULT_ACCOUNT_ID;
1051
+ }
1052
+ return trimmed;
1053
+ }
1054
+ function getMultiAccountConfig(runtime) {
1055
+ const characterWhatsApp = runtime.character?.settings?.whatsapp;
1056
+ return {
1057
+ enabled: characterWhatsApp?.enabled,
1058
+ accessToken: characterWhatsApp?.accessToken,
1059
+ phoneNumberId: characterWhatsApp?.phoneNumberId,
1060
+ businessAccountId: characterWhatsApp?.businessAccountId,
1061
+ webhookVerifyToken: characterWhatsApp?.webhookVerifyToken,
1062
+ apiVersion: characterWhatsApp?.apiVersion,
1063
+ dmPolicy: characterWhatsApp?.dmPolicy,
1064
+ groupPolicy: characterWhatsApp?.groupPolicy,
1065
+ mediaMaxMb: characterWhatsApp?.mediaMaxMb,
1066
+ textChunkLimit: characterWhatsApp?.textChunkLimit,
1067
+ accounts: characterWhatsApp?.accounts,
1068
+ groups: characterWhatsApp?.groups
1069
+ };
1070
+ }
1071
+ function listWhatsAppAccountIds(runtime) {
1072
+ const config = getMultiAccountConfig(runtime);
1073
+ const accounts = config.accounts;
1074
+ const ids = new Set;
1075
+ const envToken = runtime.getSetting("WHATSAPP_ACCESS_TOKEN");
1076
+ const envPhoneId = runtime.getSetting("WHATSAPP_PHONE_NUMBER_ID");
1077
+ const baseConfigured = Boolean(config.accessToken?.trim() && config.phoneNumberId?.trim());
1078
+ const envConfigured = Boolean(envToken?.trim() && envPhoneId?.trim());
1079
+ if (baseConfigured || envConfigured) {
1080
+ ids.add(DEFAULT_ACCOUNT_ID);
1081
+ }
1082
+ if (accounts && typeof accounts === "object") {
1083
+ for (const id of Object.keys(accounts)) {
1084
+ if (id) {
1085
+ ids.add(normalizeAccountId(id));
1086
+ }
1087
+ }
1088
+ }
1089
+ const result = Array.from(ids);
1090
+ if (result.length === 0) {
1091
+ return [DEFAULT_ACCOUNT_ID];
1092
+ }
1093
+ return result.slice().sort((a, b) => a.localeCompare(b));
1094
+ }
1095
+ function resolveDefaultWhatsAppAccountId(runtime) {
1096
+ const ids = listWhatsAppAccountIds(runtime);
1097
+ if (ids.includes(DEFAULT_ACCOUNT_ID)) {
1098
+ return DEFAULT_ACCOUNT_ID;
1099
+ }
1100
+ return ids[0] ?? DEFAULT_ACCOUNT_ID;
1101
+ }
1102
+ function getAccountConfig(runtime, accountId) {
1103
+ const config = getMultiAccountConfig(runtime);
1104
+ const accounts = config.accounts;
1105
+ if (!accounts || typeof accounts !== "object") {
1106
+ return;
1107
+ }
1108
+ const direct = accounts[accountId];
1109
+ if (direct) {
1110
+ return direct;
1111
+ }
1112
+ const normalized = normalizeAccountId(accountId);
1113
+ const matchKey = Object.keys(accounts).find((key) => normalizeAccountId(key) === normalized);
1114
+ return matchKey ? accounts[matchKey] : undefined;
1115
+ }
1116
+ function resolveWhatsAppToken(runtime, accountId) {
1117
+ const multiConfig = getMultiAccountConfig(runtime);
1118
+ const accountConfig = getAccountConfig(runtime, accountId);
1119
+ if (accountConfig?.accessToken?.trim()) {
1120
+ return { token: accountConfig.accessToken.trim(), source: "config" };
1121
+ }
1122
+ if (accountId === DEFAULT_ACCOUNT_ID) {
1123
+ if (multiConfig.accessToken?.trim()) {
1124
+ return { token: multiConfig.accessToken.trim(), source: "config" };
1125
+ }
1126
+ const envToken = runtime.getSetting("WHATSAPP_ACCESS_TOKEN");
1127
+ if (envToken?.trim()) {
1128
+ return { token: envToken.trim(), source: "env" };
1129
+ }
1130
+ }
1131
+ return { token: "", source: "none" };
1132
+ }
1133
+ function filterDefined(obj) {
1134
+ return Object.fromEntries(Object.entries(obj).filter(([, v]) => v !== undefined));
1135
+ }
1136
+ function mergeWhatsAppAccountConfig(runtime, accountId) {
1137
+ const multiConfig = getMultiAccountConfig(runtime);
1138
+ const { accounts: _ignored, ...baseConfig } = multiConfig;
1139
+ const accountConfig = getAccountConfig(runtime, accountId) ?? {};
1140
+ const envToken = runtime.getSetting("WHATSAPP_ACCESS_TOKEN");
1141
+ const envPhoneId = runtime.getSetting("WHATSAPP_PHONE_NUMBER_ID");
1142
+ const envBusinessId = runtime.getSetting("WHATSAPP_BUSINESS_ACCOUNT_ID");
1143
+ const envWebhookToken = runtime.getSetting("WHATSAPP_WEBHOOK_VERIFY_TOKEN");
1144
+ const envDmPolicy = runtime.getSetting("WHATSAPP_DM_POLICY");
1145
+ const envGroupPolicy = runtime.getSetting("WHATSAPP_GROUP_POLICY");
1146
+ const envConfig = {
1147
+ accessToken: envToken || undefined,
1148
+ phoneNumberId: envPhoneId || undefined,
1149
+ businessAccountId: envBusinessId || undefined,
1150
+ webhookVerifyToken: envWebhookToken || undefined,
1151
+ dmPolicy: envDmPolicy,
1152
+ groupPolicy: envGroupPolicy
1153
+ };
1154
+ return {
1155
+ ...filterDefined(envConfig),
1156
+ ...filterDefined(baseConfig),
1157
+ ...filterDefined(accountConfig)
1158
+ };
1159
+ }
1160
+ function resolveWhatsAppAccount(runtime, accountId) {
1161
+ const normalizedAccountId = normalizeAccountId(accountId);
1162
+ const multiConfig = getMultiAccountConfig(runtime);
1163
+ const baseEnabled = multiConfig.enabled !== false;
1164
+ const merged = mergeWhatsAppAccountConfig(runtime, normalizedAccountId);
1165
+ const accountEnabled = merged.enabled !== false;
1166
+ const enabled = baseEnabled && accountEnabled;
1167
+ const { token, source: tokenSource } = resolveWhatsAppToken(runtime, normalizedAccountId);
1168
+ const phoneNumberId = merged.phoneNumberId?.trim() || "";
1169
+ const configured = Boolean(token && phoneNumberId);
1170
+ return {
1171
+ accountId: normalizedAccountId,
1172
+ enabled,
1173
+ name: merged.name?.trim() || undefined,
1174
+ accessToken: token,
1175
+ phoneNumberId,
1176
+ businessAccountId: merged.businessAccountId?.trim() || undefined,
1177
+ tokenSource,
1178
+ configured,
1179
+ config: merged
1180
+ };
1181
+ }
1182
+ function listEnabledWhatsAppAccounts(runtime) {
1183
+ return listWhatsAppAccountIds(runtime).map((accountId) => resolveWhatsAppAccount(runtime, accountId)).filter((account) => account.enabled && account.configured);
1184
+ }
1185
+ function isMultiAccountEnabled(runtime) {
1186
+ const accounts = listEnabledWhatsAppAccounts(runtime);
1187
+ return accounts.length > 1;
1188
+ }
1189
+ function resolveWhatsAppGroupConfig(runtime, accountId, groupId) {
1190
+ const multiConfig = getMultiAccountConfig(runtime);
1191
+ const accountConfig = getAccountConfig(runtime, accountId);
1192
+ const accountGroup = accountConfig?.groups?.[groupId];
1193
+ if (accountGroup) {
1194
+ return accountGroup;
1195
+ }
1196
+ return multiConfig.groups?.[groupId];
1197
+ }
1198
+ function isWhatsAppUserAllowed(params) {
1199
+ const { identifier, accountConfig, isGroup, groupConfig } = params;
1200
+ if (isGroup) {
1201
+ const policy2 = accountConfig.groupPolicy ?? "allowlist";
1202
+ if (policy2 === "disabled") {
1203
+ return false;
1204
+ }
1205
+ if (policy2 === "open") {
1206
+ return true;
1207
+ }
1208
+ if (groupConfig?.allowFrom?.length) {
1209
+ return groupConfig.allowFrom.some((allowed) => String(allowed) === identifier);
1210
+ }
1211
+ if (accountConfig.groupAllowFrom?.length) {
1212
+ return accountConfig.groupAllowFrom.some((allowed) => String(allowed) === identifier);
1213
+ }
1214
+ return policy2 !== "allowlist";
1215
+ }
1216
+ const policy = accountConfig.dmPolicy ?? "pairing";
1217
+ if (policy === "disabled") {
1218
+ return false;
1219
+ }
1220
+ if (policy === "open") {
1221
+ return true;
1222
+ }
1223
+ if (policy === "pairing") {
1224
+ return true;
1225
+ }
1226
+ if (accountConfig.allowFrom?.length) {
1227
+ return accountConfig.allowFrom.some((allowed) => String(allowed) === identifier);
1228
+ }
1229
+ return false;
1230
+ }
1231
+ function isWhatsAppMentionRequired(params) {
1232
+ const { groupConfig } = params;
1233
+ return groupConfig?.requireMention ?? false;
1234
+ }
1235
+ async function checkWhatsAppUserAccess(params) {
1236
+ const { runtime, identifier, accountConfig, isGroup, groupConfig, metadata } = params;
1237
+ if (isGroup) {
1238
+ const policy2 = accountConfig.groupPolicy ?? "allowlist";
1239
+ if (policy2 === "disabled") {
1240
+ return { allowed: false };
1241
+ }
1242
+ if (policy2 === "open") {
1243
+ return { allowed: true };
1244
+ }
1245
+ if (groupConfig?.allowFrom?.length) {
1246
+ const allowed = groupConfig.allowFrom.some((a) => String(a) === identifier);
1247
+ return { allowed };
1248
+ }
1249
+ if (accountConfig.groupAllowFrom?.length) {
1250
+ const allowed = accountConfig.groupAllowFrom.some((a) => String(a) === identifier);
1251
+ return { allowed };
1252
+ }
1253
+ return { allowed: policy2 !== "allowlist" };
1254
+ }
1255
+ const policy = accountConfig.dmPolicy ?? "pairing";
1256
+ if (policy === "disabled") {
1257
+ return { allowed: false };
1258
+ }
1259
+ if (policy === "open") {
1260
+ return { allowed: true };
1261
+ }
1262
+ if (policy === "pairing") {
1263
+ const result = await checkPairingAllowed(runtime, {
1264
+ channel: "whatsapp",
1265
+ senderId: identifier,
1266
+ metadata
1267
+ });
1268
+ return {
1269
+ allowed: result.allowed,
1270
+ pairingCode: result.pairingCode,
1271
+ newPairingRequest: result.newRequest,
1272
+ replyMessage: result.replyMessage
1273
+ };
1274
+ }
1275
+ if (accountConfig.allowFrom?.length) {
1276
+ const allowed = accountConfig.allowFrom.some((a) => String(a) === identifier);
1277
+ if (allowed) {
1278
+ return { allowed: true };
1279
+ }
1280
+ }
1281
+ const inDynamicAllowlist = await isInAllowlist(runtime, "whatsapp", identifier);
1282
+ return { allowed: inDynamicAllowlist };
1283
+ }
1284
+ // src/normalize.ts
1285
+ var WHATSAPP_TEXT_CHUNK_LIMIT = 4096;
1286
+ var WHATSAPP_USER_JID_RE = /^(\d+)(?::\d+)?@s\.whatsapp\.net$/i;
1287
+ var WHATSAPP_LID_RE = /^(\d+)@lid$/i;
1288
+ function stripWhatsAppTargetPrefixes(value) {
1289
+ let candidate = value.trim();
1290
+ for (;; ) {
1291
+ const before = candidate;
1292
+ candidate = candidate.replace(/^whatsapp:/i, "").trim();
1293
+ if (candidate === before) {
1294
+ return candidate;
1295
+ }
1296
+ }
1297
+ }
1298
+ function normalizeE164(input) {
1299
+ const stripped = input.replace(/[\s\-().]+/g, "");
1300
+ const digitsOnly = stripped.replace(/[^\d+]/g, "");
1301
+ if (!digitsOnly) {
1302
+ return "";
1303
+ }
1304
+ if (digitsOnly.startsWith("+")) {
1305
+ return digitsOnly;
1306
+ }
1307
+ if (digitsOnly.startsWith("00")) {
1308
+ return `+${digitsOnly.slice(2)}`;
1309
+ }
1310
+ if (digitsOnly.length >= 10) {
1311
+ return `+${digitsOnly}`;
1312
+ }
1313
+ return digitsOnly;
1314
+ }
1315
+ function isWhatsAppGroupJid(value) {
1316
+ const candidate = stripWhatsAppTargetPrefixes(value);
1317
+ const lower = candidate.toLowerCase();
1318
+ if (!lower.endsWith("@g.us")) {
1319
+ return false;
1320
+ }
1321
+ const localPart = candidate.slice(0, candidate.length - "@g.us".length);
1322
+ if (!localPart || localPart.includes("@")) {
1323
+ return false;
1324
+ }
1325
+ return /^[0-9]+(-[0-9]+)*$/.test(localPart);
1326
+ }
1327
+ function isWhatsAppUserTarget(value) {
1328
+ const candidate = stripWhatsAppTargetPrefixes(value);
1329
+ return WHATSAPP_USER_JID_RE.test(candidate) || WHATSAPP_LID_RE.test(candidate);
1330
+ }
1331
+ function extractUserJidPhone(jid) {
1332
+ const userMatch = jid.match(WHATSAPP_USER_JID_RE);
1333
+ if (userMatch) {
1334
+ return userMatch[1];
1335
+ }
1336
+ const lidMatch = jid.match(WHATSAPP_LID_RE);
1337
+ if (lidMatch) {
1338
+ return lidMatch[1];
1339
+ }
1340
+ return null;
1341
+ }
1342
+ function normalizeWhatsAppTarget(value) {
1343
+ const candidate = stripWhatsAppTargetPrefixes(value);
1344
+ if (!candidate) {
1345
+ return null;
1346
+ }
1347
+ if (isWhatsAppGroupJid(candidate)) {
1348
+ const localPart = candidate.slice(0, candidate.length - "@g.us".length);
1349
+ return `${localPart}@g.us`;
1350
+ }
1351
+ if (isWhatsAppUserTarget(candidate)) {
1352
+ const phone = extractUserJidPhone(candidate);
1353
+ if (!phone) {
1354
+ return null;
1355
+ }
1356
+ const normalized2 = normalizeE164(phone);
1357
+ return normalized2.length > 1 ? normalized2 : null;
1358
+ }
1359
+ if (candidate.includes("@")) {
1360
+ return null;
1361
+ }
1362
+ const normalized = normalizeE164(candidate);
1363
+ return normalized.length > 1 ? normalized : null;
1364
+ }
1365
+ function formatWhatsAppId(id) {
1366
+ if (isWhatsAppGroupJid(id)) {
1367
+ return `group:${id}`;
1368
+ }
1369
+ const normalized = normalizeWhatsAppTarget(id);
1370
+ return normalized || id;
1371
+ }
1372
+ function isWhatsAppGroup(id) {
1373
+ return isWhatsAppGroupJid(id);
1374
+ }
1375
+ function getWhatsAppChatType(id) {
1376
+ return isWhatsAppGroupJid(id) ? "group" : "user";
1377
+ }
1378
+ function buildWhatsAppUserJid(phoneNumber) {
1379
+ const normalized = normalizeE164(phoneNumber);
1380
+ const digits = normalized.replace(/^\+/, "");
1381
+ return `${digits}@s.whatsapp.net`;
1382
+ }
1383
+ function splitAtBreakPoint(text, limit) {
1384
+ if (text.length <= limit) {
1385
+ return { chunk: text, remainder: "" };
1386
+ }
1387
+ const searchArea = text.slice(0, limit);
1388
+ const doubleNewline = searchArea.lastIndexOf(`
1389
+
1390
+ `);
1391
+ if (doubleNewline > limit * 0.5) {
1392
+ return {
1393
+ chunk: text.slice(0, doubleNewline).trimEnd(),
1394
+ remainder: text.slice(doubleNewline + 2).trimStart()
1395
+ };
1396
+ }
1397
+ const singleNewline = searchArea.lastIndexOf(`
1398
+ `);
1399
+ if (singleNewline > limit * 0.5) {
1400
+ return {
1401
+ chunk: text.slice(0, singleNewline).trimEnd(),
1402
+ remainder: text.slice(singleNewline + 1).trimStart()
1403
+ };
1404
+ }
1405
+ const sentenceEnd = Math.max(searchArea.lastIndexOf(". "), searchArea.lastIndexOf("! "), searchArea.lastIndexOf("? "));
1406
+ if (sentenceEnd > limit * 0.5) {
1407
+ return {
1408
+ chunk: text.slice(0, sentenceEnd + 1).trimEnd(),
1409
+ remainder: text.slice(sentenceEnd + 2).trimStart()
1410
+ };
1411
+ }
1412
+ const space = searchArea.lastIndexOf(" ");
1413
+ if (space > limit * 0.5) {
1414
+ return {
1415
+ chunk: text.slice(0, space).trimEnd(),
1416
+ remainder: text.slice(space + 1).trimStart()
1417
+ };
1418
+ }
1419
+ return {
1420
+ chunk: text.slice(0, limit),
1421
+ remainder: text.slice(limit)
1422
+ };
1423
+ }
1424
+ function chunkWhatsAppText(text, opts = {}) {
1425
+ const limit = opts.limit ?? WHATSAPP_TEXT_CHUNK_LIMIT;
1426
+ if (!text?.trim()) {
1427
+ return [];
1428
+ }
1429
+ const normalizedText = text.trim();
1430
+ if (normalizedText.length <= limit) {
1431
+ return [normalizedText];
1432
+ }
1433
+ const chunks = [];
1434
+ let remaining = normalizedText;
1435
+ while (remaining.length > 0) {
1436
+ const { chunk, remainder } = splitAtBreakPoint(remaining, limit);
1437
+ if (chunk) {
1438
+ chunks.push(chunk);
1439
+ }
1440
+ remaining = remainder;
1441
+ }
1442
+ return chunks.filter((c) => c.length > 0);
1443
+ }
1444
+ function truncateText(text, maxLength) {
1445
+ if (text.length <= maxLength) {
1446
+ return text;
1447
+ }
1448
+ if (maxLength <= 3) {
1449
+ return "...".slice(0, maxLength);
1450
+ }
1451
+ return `${text.slice(0, maxLength - 3)}...`;
1452
+ }
1453
+ function resolveWhatsAppSystemLocation(params) {
1454
+ const { chatType, chatId, chatName } = params;
1455
+ const name = chatName || chatId.slice(0, 8);
1456
+ return `WhatsApp ${chatType}:${name}`;
1457
+ }
1458
+ function isValidWhatsAppNumber(value) {
1459
+ const normalized = normalizeWhatsAppTarget(value);
1460
+ if (!normalized) {
1461
+ return false;
1462
+ }
1463
+ if (!normalized.startsWith("+")) {
1464
+ return false;
1465
+ }
1466
+ const digits = normalized.replace(/^\+/, "");
1467
+ return /^\d{10,15}$/.test(digits);
1468
+ }
1469
+ function formatWhatsAppPhoneNumber(phoneNumber) {
1470
+ const normalized = normalizeE164(phoneNumber);
1471
+ if (!normalized) {
1472
+ return phoneNumber;
1473
+ }
1474
+ const digits = normalized.replace(/^\+/, "");
1475
+ if (digits.length <= 10) {
1476
+ return normalized;
1477
+ }
1478
+ const countryCode = digits.slice(0, digits.length - 10);
1479
+ const rest = digits.slice(-10);
1480
+ return `+${countryCode} ${rest.slice(0, 3)} ${rest.slice(3, 6)} ${rest.slice(6)}`;
1481
+ }
1482
+ // src/types.ts
1483
+ var WhatsAppEventType;
1484
+ ((WhatsAppEventType2) => {
1485
+ WhatsAppEventType2["MESSAGE_RECEIVED"] = "WHATSAPP_MESSAGE_RECEIVED";
1486
+ WhatsAppEventType2["MESSAGE_SENT"] = "WHATSAPP_MESSAGE_SENT";
1487
+ WhatsAppEventType2["MESSAGE_DELIVERED"] = "WHATSAPP_MESSAGE_DELIVERED";
1488
+ WhatsAppEventType2["MESSAGE_READ"] = "WHATSAPP_MESSAGE_READ";
1489
+ WhatsAppEventType2["MESSAGE_FAILED"] = "WHATSAPP_MESSAGE_FAILED";
1490
+ WhatsAppEventType2["REACTION_RECEIVED"] = "WHATSAPP_REACTION_RECEIVED";
1491
+ WhatsAppEventType2["REACTION_SENT"] = "WHATSAPP_REACTION_SENT";
1492
+ WhatsAppEventType2["INTERACTIVE_REPLY"] = "WHATSAPP_INTERACTIVE_REPLY";
1493
+ WhatsAppEventType2["WEBHOOK_VERIFIED"] = "WHATSAPP_WEBHOOK_VERIFIED";
1494
+ })(WhatsAppEventType ||= {});
1495
+ var WHATSAPP_REACTIONS = {
1496
+ THUMBS_UP: "\uD83D\uDC4D",
1497
+ THUMBS_DOWN: "\uD83D\uDC4E",
1498
+ HEART: "❤️",
1499
+ LAUGHING: "\uD83D\uDE02",
1500
+ SURPRISED: "\uD83D\uDE2E",
1501
+ SAD: "\uD83D\uDE22",
1502
+ PRAYING: "\uD83D\uDE4F",
1503
+ CLAPPING: "\uD83D\uDC4F",
1504
+ FIRE: "\uD83D\uDD25",
1505
+ CELEBRATION: "\uD83C\uDF89"
1506
+ };
1507
+
1508
+ // src/index.ts
1509
+ class WhatsAppPlugin extends EventEmitter4 {
1510
+ client;
1511
+ messageHandler;
1512
+ webhookHandler;
1513
+ name;
1514
+ description;
1515
+ actions = [sendMessageAction, sendReactionAction];
1516
+ constructor(config) {
1517
+ super();
1518
+ this.name = "WhatsApp Plugin";
1519
+ this.description = "WhatsApp integration supporting Cloud API and Baileys (QR auth)";
1520
+ this.client = ClientFactory.create(config);
1521
+ this.messageHandler = new MessageHandler(this.client);
1522
+ this.webhookHandler = new WebhookHandler;
1523
+ this.setupEventForwarding();
1524
+ }
1525
+ setupEventForwarding() {
1526
+ this.client.on("message", (payload) => this.emit("message", payload));
1527
+ this.client.on("qr", (payload) => this.emit("qr", payload));
1528
+ this.client.on("ready", () => this.emit("ready"));
1529
+ this.client.on("connection", (status) => this.emit("connection", status));
1530
+ this.client.on("error", (error) => this.emit("error", error));
1531
+ }
1532
+ async start() {
1533
+ await this.client.start();
1534
+ }
1535
+ async stop() {
1536
+ await this.client.stop();
1537
+ }
1538
+ getConnectionStatus() {
1539
+ return this.client.getConnectionStatus();
1540
+ }
1541
+ async sendMessage(message2) {
1542
+ return this.messageHandler.send(message2);
1543
+ }
1544
+ async handleWebhook(event) {
1545
+ return this.webhookHandler.handle(event);
1546
+ }
1547
+ async verifyWebhook(token) {
1548
+ if (!this.client.verifyWebhook) {
1549
+ throw new Error("verifyWebhook is only supported by Cloud API authentication");
1550
+ }
1551
+ return this.client.verifyWebhook(token);
1552
+ }
1553
+ }
1554
+ var whatsappPlugin = {
1555
+ name: "whatsapp",
1556
+ description: "WhatsApp integration for ElizaOS (Cloud API + Baileys)",
1557
+ actions: [sendMessageAction, sendReactionAction]
1558
+ };
1559
+ var src_default = whatsappPlugin;
1560
+ export {
1561
+ truncateText,
1562
+ resolveWhatsAppToken,
1563
+ resolveWhatsAppSystemLocation,
1564
+ resolveWhatsAppGroupConfig,
1565
+ resolveWhatsAppAccount,
1566
+ resolveDefaultWhatsAppAccountId,
1567
+ normalizeWhatsAppTarget,
1568
+ normalizeE164,
1569
+ normalizeAccountId,
1570
+ listWhatsAppAccountIds,
1571
+ listEnabledWhatsAppAccounts,
1572
+ isWhatsAppUserTarget,
1573
+ isWhatsAppUserAllowed,
1574
+ isWhatsAppMentionRequired,
1575
+ isWhatsAppGroupJid,
1576
+ isWhatsAppGroup,
1577
+ isValidWhatsAppNumber,
1578
+ isMultiAccountEnabled,
1579
+ getWhatsAppChatType,
1580
+ formatWhatsAppPhoneNumber,
1581
+ formatWhatsAppId,
1582
+ src_default as default,
1583
+ chunkWhatsAppText,
1584
+ checkWhatsAppUserAccess,
1585
+ buildWhatsAppUserJid,
1586
+ WhatsAppPlugin,
1587
+ WhatsAppEventType,
1588
+ WHATSAPP_TEXT_CHUNK_LIMIT,
1589
+ WHATSAPP_REACTIONS,
1590
+ DEFAULT_ACCOUNT_ID,
1591
+ ClientFactory
1592
+ };
1593
+
1594
+ //# debugId=3ABF74EE0B1ACBEC64756E2164756E21
1595
+ //# sourceMappingURL=index.js.map