@elizaos/plugin-whatsapp 2.0.0-alpha.1 → 2.0.0-alpha.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/dist/index.js +1510 -27
  2. package/dist/index.js.map +25 -1
  3. package/dist/src/accounts.d.ts +199 -0
  4. package/dist/src/accounts.d.ts.map +1 -0
  5. package/dist/src/actions/index.d.ts +3 -0
  6. package/dist/src/actions/index.d.ts.map +1 -0
  7. package/dist/src/actions/sendMessage.d.ts +4 -0
  8. package/dist/src/actions/sendMessage.d.ts.map +1 -0
  9. package/dist/src/actions/sendReaction.d.ts +4 -0
  10. package/dist/src/actions/sendReaction.d.ts.map +1 -0
  11. package/dist/src/baileys/auth.d.ts +10 -0
  12. package/dist/src/baileys/auth.d.ts.map +1 -0
  13. package/dist/src/baileys/connection.d.ts +19 -0
  14. package/dist/src/baileys/connection.d.ts.map +1 -0
  15. package/dist/src/baileys/index.d.ts +5 -0
  16. package/dist/src/baileys/index.d.ts.map +1 -0
  17. package/dist/src/baileys/message-adapter.d.ts +13 -0
  18. package/dist/src/baileys/message-adapter.d.ts.map +1 -0
  19. package/dist/src/baileys/qr-code.d.ts +6 -0
  20. package/dist/src/baileys/qr-code.d.ts.map +1 -0
  21. package/dist/src/client.d.ts +88 -0
  22. package/dist/src/client.d.ts.map +1 -0
  23. package/dist/src/clients/baileys-client.d.ts +17 -0
  24. package/dist/src/clients/baileys-client.d.ts.map +1 -0
  25. package/dist/src/clients/factory.d.ts +6 -0
  26. package/dist/src/clients/factory.d.ts.map +1 -0
  27. package/dist/src/clients/index.d.ts +4 -0
  28. package/dist/src/clients/index.d.ts.map +1 -0
  29. package/dist/src/clients/interface.d.ts +10 -0
  30. package/dist/src/clients/interface.d.ts.map +1 -0
  31. package/dist/src/config.d.ts +135 -0
  32. package/dist/src/config.d.ts.map +1 -0
  33. package/dist/src/handlers/index.d.ts +3 -0
  34. package/dist/src/handlers/index.d.ts.map +1 -0
  35. package/dist/src/handlers/message.handler.d.ts +8 -0
  36. package/dist/src/handlers/message.handler.d.ts.map +1 -0
  37. package/dist/src/handlers/webhook.handler.d.ts +7 -0
  38. package/dist/src/handlers/webhook.handler.d.ts.map +1 -0
  39. package/dist/src/index.d.ts +27 -0
  40. package/dist/src/index.d.ts.map +1 -0
  41. package/dist/src/normalize.d.ts +69 -0
  42. package/dist/src/normalize.d.ts.map +1 -0
  43. package/dist/src/service.d.ts +117 -0
  44. package/dist/src/service.d.ts.map +1 -0
  45. package/dist/src/types.d.ts +367 -0
  46. package/dist/src/types.d.ts.map +1 -0
  47. package/dist/src/utils/config-detector.d.ts +3 -0
  48. package/dist/src/utils/config-detector.d.ts.map +1 -0
  49. package/dist/src/utils/index.d.ts +3 -0
  50. package/dist/src/utils/index.d.ts.map +1 -0
  51. package/dist/src/utils/validators.d.ts +10 -0
  52. package/dist/src/utils/validators.d.ts.map +1 -0
  53. package/package.json +129 -74
  54. package/LICENSE +0 -21
  55. package/README.md +0 -226
  56. package/dist/index.d.ts +0 -83
package/dist/index.js CHANGED
@@ -1,43 +1,1005 @@
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
+ };
1
350
  // src/client.ts
2
351
  import axios from "axios";
3
- var WhatsAppClient = class {
352
+ import { EventEmitter } from "node:events";
353
+ var DEFAULT_API_VERSION = "v24.0";
354
+
355
+ class WhatsAppClient extends EventEmitter {
4
356
  client;
5
357
  config;
358
+ connectionStatus = "close";
6
359
  constructor(config) {
360
+ super();
7
361
  this.config = config;
362
+ const apiVersion = config.apiVersion || DEFAULT_API_VERSION;
8
363
  this.client = axios.create({
9
- baseURL: "https://graph.facebook.com/v17.0",
364
+ baseURL: `https://graph.facebook.com/${apiVersion}`,
10
365
  headers: {
11
366
  Authorization: `Bearer ${config.accessToken}`,
12
367
  "Content-Type": "application/json"
13
368
  }
14
369
  });
15
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
+ }
16
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) {
17
399
  const endpoint = `/${this.config.phoneNumberId}/messages`;
18
400
  const payload = {
19
401
  messaging_product: "whatsapp",
20
402
  recipient_type: "individual",
21
- to: message.to,
22
- type: message.type,
23
- ...message.type === "text" ? { text: { body: message.content } } : { template: message.content }
403
+ to: params.to,
404
+ type: "reaction",
405
+ reaction: {
406
+ message_id: params.messageId,
407
+ emoji: params.emoji
408
+ }
24
409
  };
25
- return this.client.post(endpoint, payload);
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
+ }
26
548
  }
27
549
  async verifyWebhook(token) {
28
550
  return token === this.config.webhookVerifyToken;
29
551
  }
30
- };
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
+ }
31
989
 
32
990
  // src/handlers/message.handler.ts
33
- var MessageHandler = class {
991
+ class MessageHandler {
992
+ client;
34
993
  constructor(client) {
35
994
  this.client = client;
36
995
  }
37
996
  async send(message) {
38
997
  try {
39
998
  const response = await this.client.sendMessage(message);
40
- return response.data;
999
+ if (response && typeof response === "object" && "data" in response) {
1000
+ return response.data;
1001
+ }
1002
+ return response;
41
1003
  } catch (error) {
42
1004
  if (error instanceof Error) {
43
1005
  throw new Error(`Failed to send WhatsApp message: ${error.message}`);
@@ -45,23 +1007,18 @@ var MessageHandler = class {
45
1007
  throw new Error("Failed to send WhatsApp message");
46
1008
  }
47
1009
  }
48
- };
49
-
1010
+ }
50
1011
  // src/handlers/webhook.handler.ts
51
- var WebhookHandler = class {
52
- constructor(client) {
53
- this.client = client;
54
- }
1012
+ class WebhookHandler {
55
1013
  async handle(event) {
56
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j;
57
1014
  try {
58
- if ((_e = (_d = (_c = (_b = (_a = event.entry) == null ? void 0 : _a[0]) == null ? void 0 : _b.changes) == null ? void 0 : _c[0]) == null ? void 0 : _d.value) == null ? void 0 : _e.messages) {
1015
+ if (event.entry?.[0]?.changes?.[0]?.value?.messages) {
59
1016
  const messages = event.entry[0].changes[0].value.messages;
60
1017
  for (const message of messages) {
61
1018
  await this.handleMessage(message);
62
1019
  }
63
1020
  }
64
- if ((_j = (_i = (_h = (_g = (_f = event.entry) == null ? void 0 : _f[0]) == null ? void 0 : _g.changes) == null ? void 0 : _h[0]) == null ? void 0 : _i.value) == null ? void 0 : _j.statuses) {
1021
+ if (event.entry?.[0]?.changes?.[0]?.value?.statuses) {
65
1022
  const statuses = event.entry[0].changes[0].value.statuses;
66
1023
  for (const status of statuses) {
67
1024
  await this.handleStatus(status);
@@ -80,33 +1037,559 @@ var WebhookHandler = class {
80
1037
  async handleStatus(status) {
81
1038
  console.log("Received status update:", status);
82
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"
83
1506
  };
84
1507
 
85
1508
  // src/index.ts
86
- var WhatsAppPlugin = class {
1509
+ class WhatsAppPlugin extends EventEmitter4 {
87
1510
  client;
88
1511
  messageHandler;
89
1512
  webhookHandler;
90
1513
  name;
91
1514
  description;
1515
+ actions = [sendMessageAction, sendReactionAction];
92
1516
  constructor(config) {
93
- this.name = "WhatsApp Cloud API Plugin";
94
- this.description = "A plugin for integrating WhatsApp Cloud API with your application.";
95
- this.client = new WhatsAppClient(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);
96
1521
  this.messageHandler = new MessageHandler(this.client);
97
- this.webhookHandler = new WebhookHandler(this.client);
1522
+ this.webhookHandler = new WebhookHandler;
1523
+ this.setupEventForwarding();
98
1524
  }
99
- async sendMessage(message) {
100
- return this.messageHandler.send(message);
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);
101
1543
  }
102
1544
  async handleWebhook(event) {
103
1545
  return this.webhookHandler.handle(event);
104
1546
  }
105
1547
  async verifyWebhook(token) {
1548
+ if (!this.client.verifyWebhook) {
1549
+ throw new Error("verifyWebhook is only supported by Cloud API authentication");
1550
+ }
106
1551
  return this.client.verifyWebhook(token);
107
1552
  }
1553
+ }
1554
+ var whatsappPlugin = {
1555
+ name: "whatsapp",
1556
+ description: "WhatsApp integration for ElizaOS (Cloud API + Baileys)",
1557
+ actions: [sendMessageAction, sendReactionAction]
108
1558
  };
1559
+ var src_default = whatsappPlugin;
109
1560
  export {
110
- WhatsAppPlugin
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
111
1592
  };
112
- //# sourceMappingURL=index.js.map
1593
+
1594
+ //# debugId=A662ECA311FECE2F64756E2164756E21
1595
+ //# sourceMappingURL=index.js.map