@elizaos/plugin-whatsapp 2.0.0-alpha.4 → 2.0.0-alpha.537

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 (62) hide show
  1. package/README.md +278 -0
  2. package/dist/.tsbuildinfo +1 -0
  3. package/dist/index.js +2298 -0
  4. package/dist/index.js.map +26 -0
  5. package/dist/src/accounts.d.ts +217 -0
  6. package/dist/src/accounts.d.ts.map +1 -0
  7. package/dist/src/actions/index.d.ts +6 -0
  8. package/dist/src/actions/index.d.ts.map +1 -0
  9. package/dist/src/actions/sendMessage.d.ts +4 -0
  10. package/dist/src/actions/sendMessage.d.ts.map +1 -0
  11. package/dist/src/actions/sendReaction.d.ts +4 -0
  12. package/dist/src/actions/sendReaction.d.ts.map +1 -0
  13. package/dist/src/baileys/auth.d.ts +10 -0
  14. package/dist/src/baileys/auth.d.ts.map +1 -0
  15. package/dist/src/baileys/connection.d.ts +19 -0
  16. package/dist/src/baileys/connection.d.ts.map +1 -0
  17. package/dist/src/baileys/index.d.ts +5 -0
  18. package/dist/src/baileys/index.d.ts.map +1 -0
  19. package/dist/src/baileys/message-adapter.d.ts +14 -0
  20. package/dist/src/baileys/message-adapter.d.ts.map +1 -0
  21. package/dist/src/baileys/qr-code.d.ts +6 -0
  22. package/dist/src/baileys/qr-code.d.ts.map +1 -0
  23. package/dist/src/client.d.ts +139 -0
  24. package/dist/src/client.d.ts.map +1 -0
  25. package/dist/src/clients/baileys-client.d.ts +26 -0
  26. package/dist/src/clients/baileys-client.d.ts.map +1 -0
  27. package/dist/src/clients/factory.d.ts +6 -0
  28. package/dist/src/clients/factory.d.ts.map +1 -0
  29. package/dist/src/clients/index.d.ts +4 -0
  30. package/dist/src/clients/index.d.ts.map +1 -0
  31. package/dist/src/clients/interface.d.ts +10 -0
  32. package/dist/src/clients/interface.d.ts.map +1 -0
  33. package/dist/src/config.d.ts +144 -0
  34. package/dist/src/config.d.ts.map +1 -0
  35. package/dist/src/handlers/index.d.ts +3 -0
  36. package/dist/src/handlers/index.d.ts.map +1 -0
  37. package/dist/src/handlers/message.handler.d.ts +8 -0
  38. package/dist/src/handlers/message.handler.d.ts.map +1 -0
  39. package/dist/src/handlers/webhook.handler.d.ts +7 -0
  40. package/dist/src/handlers/webhook.handler.d.ts.map +1 -0
  41. package/dist/src/index.d.ts +62 -0
  42. package/dist/src/index.d.ts.map +1 -0
  43. package/dist/src/normalize.d.ts +72 -0
  44. package/dist/src/normalize.d.ts.map +1 -0
  45. package/dist/src/pairing-service.d.ts +54 -0
  46. package/dist/src/pairing-service.d.ts.map +1 -0
  47. package/dist/src/runtime-service.d.ts +50 -0
  48. package/dist/src/runtime-service.d.ts.map +1 -0
  49. package/dist/src/service.d.ts +157 -0
  50. package/dist/src/service.d.ts.map +1 -0
  51. package/dist/src/setup-routes.d.ts +26 -0
  52. package/dist/src/setup-routes.d.ts.map +1 -0
  53. package/dist/src/types.d.ts +396 -0
  54. package/dist/src/types.d.ts.map +1 -0
  55. package/dist/src/utils/config-detector.d.ts +5 -0
  56. package/dist/src/utils/config-detector.d.ts.map +1 -0
  57. package/dist/src/utils/index.d.ts +3 -0
  58. package/dist/src/utils/index.d.ts.map +1 -0
  59. package/dist/src/utils/validators.d.ts +14 -0
  60. package/dist/src/utils/validators.d.ts.map +1 -0
  61. package/package.json +84 -14
  62. package/dist/index.d.ts +0 -2
package/dist/index.js ADDED
@@ -0,0 +1,2298 @@
1
+ import { createRequire } from "node:module";
2
+ var __require = /* @__PURE__ */ createRequire(import.meta.url);
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
+ descriptionCompressed: "Send WhatsApp text message.",
29
+ suppressPostActionContinuation: true,
30
+ validate: async (_runtime, message, _state, _options) => {
31
+ const text = message.content?.text?.toLowerCase() ?? "";
32
+ const hasIntent = ["whatsapp", "send", "message"].some((keyword) => text.includes(keyword)) && /\b(?:whatsapp|send|message)\b/i.test(text);
33
+ return hasIntent && message.content?.source === "whatsapp";
34
+ },
35
+ handler: async (runtime, message, state, _options, callback) => {
36
+ const accessToken = runtime.getSetting("WHATSAPP_ACCESS_TOKEN");
37
+ const phoneNumberId = runtime.getSetting("WHATSAPP_PHONE_NUMBER_ID");
38
+ const apiVersion = runtime.getSetting("WHATSAPP_API_VERSION") || "v24.0";
39
+ if (!accessToken || !phoneNumberId) {
40
+ if (callback) {
41
+ await callback({
42
+ text: "WhatsApp is not configured. Missing access token or phone number ID."
43
+ });
44
+ }
45
+ return { success: false, error: "WhatsApp not configured" };
46
+ }
47
+ const currentState = state ?? await runtime.composeState(message);
48
+ const prompt = composePromptFromState({
49
+ state: currentState,
50
+ template: SEND_MESSAGE_TEMPLATE
51
+ });
52
+ let params;
53
+ try {
54
+ const response = await runtime.useModel(ModelType.TEXT_SMALL, {
55
+ prompt
56
+ });
57
+ const parsed = parseJSONObjectFromText(response);
58
+ if (!parsed?.to || !parsed.text) {
59
+ const to = message.content?.from;
60
+ const text = currentState.values?.response?.toString() || "";
61
+ if (!to) {
62
+ if (callback) {
63
+ await callback({
64
+ text: "Could not determine who to send the message to"
65
+ });
66
+ }
67
+ return { success: false, error: "Missing recipient" };
68
+ }
69
+ if (!text || text.trim() === "") {
70
+ if (callback) {
71
+ await callback({
72
+ text: "Cannot send an empty message. Please provide message content."
73
+ });
74
+ }
75
+ return { success: false, error: "Empty message text" };
76
+ }
77
+ params = { to, text };
78
+ } else {
79
+ if (!parsed.text.trim()) {
80
+ if (callback) {
81
+ await callback({
82
+ text: "Cannot send an empty message. Please provide message content."
83
+ });
84
+ }
85
+ return { success: false, error: "Empty message text" };
86
+ }
87
+ params = parsed;
88
+ }
89
+ } catch {
90
+ if (callback) {
91
+ await callback({
92
+ text: "Failed to parse message parameters"
93
+ });
94
+ }
95
+ return { success: false, error: "Failed to parse message parameters" };
96
+ }
97
+ try {
98
+ const url = `https://graph.facebook.com/${apiVersion}/${phoneNumberId}/messages`;
99
+ const response = await fetch(url, {
100
+ method: "POST",
101
+ headers: {
102
+ Authorization: `Bearer ${accessToken}`,
103
+ "Content-Type": "application/json"
104
+ },
105
+ body: JSON.stringify({
106
+ messaging_product: "whatsapp",
107
+ recipient_type: "individual",
108
+ to: params.to,
109
+ type: "text",
110
+ text: {
111
+ preview_url: false,
112
+ body: params.text
113
+ }
114
+ })
115
+ });
116
+ if (!response.ok) {
117
+ const errorData = await response.json();
118
+ throw new Error(errorData.error?.message || `HTTP ${response.status}`);
119
+ }
120
+ const data = await response.json();
121
+ const messageId = data.messages?.[0]?.id;
122
+ return {
123
+ success: true,
124
+ data: {
125
+ action: WHATSAPP_SEND_MESSAGE_ACTION,
126
+ to: params.to,
127
+ messageId,
128
+ suppressVisibleCallback: true,
129
+ suppressActionResultClipboard: true
130
+ }
131
+ };
132
+ } catch (error) {
133
+ const errorMessage = error instanceof Error ? error.message : String(error);
134
+ if (callback) {
135
+ await callback({
136
+ text: `Failed to send WhatsApp message: ${errorMessage}`
137
+ });
138
+ }
139
+ return { success: false, error: errorMessage };
140
+ }
141
+ },
142
+ examples: [
143
+ [
144
+ {
145
+ name: "{{name1}}",
146
+ content: {
147
+ text: "Send a WhatsApp message to +14155552671 saying hello"
148
+ }
149
+ },
150
+ {
151
+ name: "{{agentName}}",
152
+ content: {
153
+ text: "I'll send that WhatsApp message now.",
154
+ actions: [WHATSAPP_SEND_MESSAGE_ACTION]
155
+ }
156
+ }
157
+ ]
158
+ ]
159
+ };
160
+ // src/actions/sendReaction.ts
161
+ import { composePromptFromState as composePromptFromState2, ModelType as ModelType2, parseJSONObjectFromText as parseJSONObjectFromText2 } from "@elizaos/core";
162
+ var WHATSAPP_SEND_REACTION_ACTION = "WHATSAPP_SEND_REACTION";
163
+ var REACTION_TEMPLATE = `
164
+ You are extracting WhatsApp reaction parameters from a conversation.
165
+
166
+ The user wants to react to a WhatsApp message. Extract the following:
167
+ 1. messageId: The ID of the message to react to
168
+ 2. emoji: The emoji to use as a reaction
169
+
170
+ {{recentMessages}}
171
+
172
+ Based on the conversation, extract the reaction parameters.
173
+
174
+ Respond with a JSON object:
175
+ {
176
+ "messageId": "wamid.xxx",
177
+ "emoji": "\uD83D\uDC4D"
178
+ }
179
+ `;
180
+ var sendReactionAction = {
181
+ name: WHATSAPP_SEND_REACTION_ACTION,
182
+ similes: ["WHATSAPP_REACT", "REACT_WHATSAPP", "WHATSAPP_EMOJI"],
183
+ description: "Send a reaction emoji to a WhatsApp message",
184
+ descriptionCompressed: "React to WhatsApp message with emoji.",
185
+ suppressPostActionContinuation: true,
186
+ validate: async (_runtime, message, _state, _options) => {
187
+ const text = message.content?.text?.toLowerCase() ?? "";
188
+ const hasIntent = ["whatsapp", "send", "reaction"].some((keyword) => text.includes(keyword)) && /\b(?:whatsapp|send|reaction)\b/i.test(text);
189
+ return hasIntent && message.content?.source === "whatsapp";
190
+ },
191
+ handler: async (runtime, message, state, _options, callback) => {
192
+ const accessToken = runtime.getSetting("WHATSAPP_ACCESS_TOKEN");
193
+ const phoneNumberId = runtime.getSetting("WHATSAPP_PHONE_NUMBER_ID");
194
+ const apiVersion = runtime.getSetting("WHATSAPP_API_VERSION") || "v24.0";
195
+ if (!accessToken || !phoneNumberId) {
196
+ if (callback) {
197
+ await callback({
198
+ text: "WhatsApp is not configured. Missing access token or phone number ID."
199
+ });
200
+ }
201
+ return { success: false, error: "WhatsApp not configured" };
202
+ }
203
+ const currentState = state ?? await runtime.composeState(message);
204
+ const prompt = composePromptFromState2({
205
+ state: currentState,
206
+ template: REACTION_TEMPLATE
207
+ });
208
+ let params;
209
+ try {
210
+ const response = await runtime.useModel(ModelType2.TEXT_SMALL, {
211
+ prompt
212
+ });
213
+ const parsed = parseJSONObjectFromText2(response);
214
+ if (!parsed?.messageId || !parsed.emoji) {
215
+ const messageId = message.content?.messageId;
216
+ if (!messageId) {
217
+ if (callback) {
218
+ await callback({
219
+ text: "Could not determine which message to react to"
220
+ });
221
+ }
222
+ return { success: false, error: "Missing message ID" };
223
+ }
224
+ params = { messageId, emoji: "\uD83D\uDC4D" };
225
+ } else {
226
+ params = parsed;
227
+ }
228
+ } catch {
229
+ if (callback) {
230
+ await callback({
231
+ text: "Failed to parse reaction parameters"
232
+ });
233
+ }
234
+ return { success: false, error: "Failed to parse reaction parameters" };
235
+ }
236
+ const to = message.content?.from;
237
+ if (!to) {
238
+ if (callback) {
239
+ await callback({
240
+ text: "Could not determine the recipient for the reaction"
241
+ });
242
+ }
243
+ return { success: false, error: "Missing recipient" };
244
+ }
245
+ try {
246
+ const url = `https://graph.facebook.com/${apiVersion}/${phoneNumberId}/messages`;
247
+ const response = await fetch(url, {
248
+ method: "POST",
249
+ headers: {
250
+ Authorization: `Bearer ${accessToken}`,
251
+ "Content-Type": "application/json"
252
+ },
253
+ body: JSON.stringify({
254
+ messaging_product: "whatsapp",
255
+ recipient_type: "individual",
256
+ to,
257
+ type: "reaction",
258
+ reaction: {
259
+ message_id: params.messageId,
260
+ emoji: params.emoji
261
+ }
262
+ })
263
+ });
264
+ if (!response.ok) {
265
+ const errorData = await response.json();
266
+ throw new Error(errorData.error?.message || `HTTP ${response.status}`);
267
+ }
268
+ return {
269
+ success: true,
270
+ data: {
271
+ action: WHATSAPP_SEND_REACTION_ACTION,
272
+ messageId: params.messageId,
273
+ emoji: params.emoji,
274
+ suppressVisibleCallback: true,
275
+ suppressActionResultClipboard: true
276
+ }
277
+ };
278
+ } catch (error) {
279
+ const errorMessage = error instanceof Error ? error.message : String(error);
280
+ if (callback) {
281
+ await callback({
282
+ text: `Failed to send reaction: ${errorMessage}`
283
+ });
284
+ }
285
+ return { success: false, error: errorMessage };
286
+ }
287
+ },
288
+ examples: [
289
+ [
290
+ {
291
+ name: "{{name1}}",
292
+ content: {
293
+ text: "React with a thumbs up"
294
+ }
295
+ },
296
+ {
297
+ name: "{{agentName}}",
298
+ content: {
299
+ text: "I'll add that reaction.",
300
+ actions: [WHATSAPP_SEND_REACTION_ACTION]
301
+ }
302
+ }
303
+ ]
304
+ ]
305
+ };
306
+ // src/runtime-service.ts
307
+ import {
308
+ ChannelType,
309
+ createUniqueUuid,
310
+ lifeOpsPassiveConnectorsEnabled,
311
+ Service
312
+ } from "@elizaos/core";
313
+
314
+ // src/accounts.ts
315
+ import { checkPairingAllowed, isInAllowlist } from "@elizaos/core";
316
+ var DEFAULT_ACCOUNT_ID = "default";
317
+ function normalizeAccountId(accountId) {
318
+ if (!accountId || typeof accountId !== "string") {
319
+ return DEFAULT_ACCOUNT_ID;
320
+ }
321
+ const trimmed = accountId.trim().toLowerCase();
322
+ if (!trimmed || trimmed === "default") {
323
+ return DEFAULT_ACCOUNT_ID;
324
+ }
325
+ return trimmed;
326
+ }
327
+ function getMultiAccountConfig(runtime) {
328
+ const characterWhatsApp = runtime.character?.settings?.whatsapp;
329
+ return {
330
+ enabled: characterWhatsApp?.enabled,
331
+ accessToken: characterWhatsApp?.accessToken,
332
+ phoneNumberId: characterWhatsApp?.phoneNumberId,
333
+ businessAccountId: characterWhatsApp?.businessAccountId,
334
+ webhookVerifyToken: characterWhatsApp?.webhookVerifyToken,
335
+ apiVersion: characterWhatsApp?.apiVersion,
336
+ dmPolicy: characterWhatsApp?.dmPolicy,
337
+ groupPolicy: characterWhatsApp?.groupPolicy,
338
+ mediaMaxMb: characterWhatsApp?.mediaMaxMb,
339
+ textChunkLimit: characterWhatsApp?.textChunkLimit,
340
+ accounts: characterWhatsApp?.accounts,
341
+ groups: characterWhatsApp?.groups
342
+ };
343
+ }
344
+ function listWhatsAppAccountIds(runtime) {
345
+ const config = getMultiAccountConfig(runtime);
346
+ const accounts = config.accounts;
347
+ const ids = new Set;
348
+ const envToken = runtime.getSetting("WHATSAPP_ACCESS_TOKEN");
349
+ const envPhoneId = runtime.getSetting("WHATSAPP_PHONE_NUMBER_ID");
350
+ const baseConfigured = Boolean(config.accessToken?.trim() && config.phoneNumberId?.trim());
351
+ const envConfigured = Boolean(envToken?.trim() && envPhoneId?.trim());
352
+ if (baseConfigured || envConfigured) {
353
+ ids.add(DEFAULT_ACCOUNT_ID);
354
+ }
355
+ if (accounts && typeof accounts === "object") {
356
+ for (const id of Object.keys(accounts)) {
357
+ if (id) {
358
+ ids.add(normalizeAccountId(id));
359
+ }
360
+ }
361
+ }
362
+ const result = Array.from(ids);
363
+ if (result.length === 0) {
364
+ return [DEFAULT_ACCOUNT_ID];
365
+ }
366
+ return result.slice().sort((a, b) => a.localeCompare(b));
367
+ }
368
+ function resolveDefaultWhatsAppAccountId(runtime) {
369
+ const ids = listWhatsAppAccountIds(runtime);
370
+ if (ids.includes(DEFAULT_ACCOUNT_ID)) {
371
+ return DEFAULT_ACCOUNT_ID;
372
+ }
373
+ return ids[0] ?? DEFAULT_ACCOUNT_ID;
374
+ }
375
+ function getAccountConfig(runtime, accountId) {
376
+ const config = getMultiAccountConfig(runtime);
377
+ const accounts = config.accounts;
378
+ if (!accounts || typeof accounts !== "object") {
379
+ return;
380
+ }
381
+ const direct = accounts[accountId];
382
+ if (direct) {
383
+ return direct;
384
+ }
385
+ const normalized = normalizeAccountId(accountId);
386
+ const matchKey = Object.keys(accounts).find((key) => normalizeAccountId(key) === normalized);
387
+ return matchKey ? accounts[matchKey] : undefined;
388
+ }
389
+ function resolveWhatsAppToken(runtime, accountId) {
390
+ const multiConfig = getMultiAccountConfig(runtime);
391
+ const accountConfig = getAccountConfig(runtime, accountId);
392
+ if (accountConfig?.accessToken?.trim()) {
393
+ return { token: accountConfig.accessToken.trim(), source: "config" };
394
+ }
395
+ if (accountId === DEFAULT_ACCOUNT_ID) {
396
+ if (multiConfig.accessToken?.trim()) {
397
+ return { token: multiConfig.accessToken.trim(), source: "config" };
398
+ }
399
+ const envToken = runtime.getSetting("WHATSAPP_ACCESS_TOKEN");
400
+ if (envToken?.trim()) {
401
+ return { token: envToken.trim(), source: "env" };
402
+ }
403
+ }
404
+ return { token: "", source: "none" };
405
+ }
406
+ function filterDefined(obj) {
407
+ return Object.fromEntries(Object.entries(obj).filter(([, v]) => v !== undefined));
408
+ }
409
+ function mergeWhatsAppAccountConfig(runtime, accountId) {
410
+ const multiConfig = getMultiAccountConfig(runtime);
411
+ const { accounts: _ignored, ...baseConfig } = multiConfig;
412
+ const accountConfig = getAccountConfig(runtime, accountId) ?? {};
413
+ const envToken = runtime.getSetting("WHATSAPP_ACCESS_TOKEN");
414
+ const envPhoneId = runtime.getSetting("WHATSAPP_PHONE_NUMBER_ID");
415
+ const envBusinessId = runtime.getSetting("WHATSAPP_BUSINESS_ACCOUNT_ID");
416
+ const envWebhookToken = runtime.getSetting("WHATSAPP_WEBHOOK_VERIFY_TOKEN");
417
+ const envDmPolicy = runtime.getSetting("WHATSAPP_DM_POLICY");
418
+ const envGroupPolicy = runtime.getSetting("WHATSAPP_GROUP_POLICY");
419
+ const envConfig = {
420
+ accessToken: envToken || undefined,
421
+ phoneNumberId: envPhoneId || undefined,
422
+ businessAccountId: envBusinessId || undefined,
423
+ webhookVerifyToken: envWebhookToken || undefined,
424
+ dmPolicy: envDmPolicy,
425
+ groupPolicy: envGroupPolicy
426
+ };
427
+ return {
428
+ ...filterDefined(envConfig),
429
+ ...filterDefined(baseConfig),
430
+ ...filterDefined(accountConfig)
431
+ };
432
+ }
433
+ function resolveWhatsAppAccount(runtime, accountId) {
434
+ const normalizedAccountId = normalizeAccountId(accountId);
435
+ const multiConfig = getMultiAccountConfig(runtime);
436
+ const baseEnabled = multiConfig.enabled !== false;
437
+ const merged = mergeWhatsAppAccountConfig(runtime, normalizedAccountId);
438
+ const accountEnabled = merged.enabled !== false;
439
+ const enabled = baseEnabled && accountEnabled;
440
+ const { token, source: tokenSource } = resolveWhatsAppToken(runtime, normalizedAccountId);
441
+ const phoneNumberId = merged.phoneNumberId?.trim() || "";
442
+ const configured = Boolean(token && phoneNumberId);
443
+ return {
444
+ accountId: normalizedAccountId,
445
+ enabled,
446
+ name: merged.name?.trim() || undefined,
447
+ accessToken: token,
448
+ phoneNumberId,
449
+ businessAccountId: merged.businessAccountId?.trim() || undefined,
450
+ tokenSource,
451
+ configured,
452
+ config: merged
453
+ };
454
+ }
455
+ function listEnabledWhatsAppAccounts(runtime) {
456
+ return listWhatsAppAccountIds(runtime).map((accountId) => resolveWhatsAppAccount(runtime, accountId)).filter((account) => account.enabled && account.configured);
457
+ }
458
+ function isMultiAccountEnabled(runtime) {
459
+ const accounts = listEnabledWhatsAppAccounts(runtime);
460
+ return accounts.length > 1;
461
+ }
462
+ function resolveWhatsAppGroupConfig(runtime, accountId, groupId) {
463
+ const multiConfig = getMultiAccountConfig(runtime);
464
+ const accountConfig = getAccountConfig(runtime, accountId);
465
+ const accountGroup = accountConfig?.groups?.[groupId];
466
+ if (accountGroup) {
467
+ return accountGroup;
468
+ }
469
+ return multiConfig.groups?.[groupId];
470
+ }
471
+ function isWhatsAppUserAllowed(params) {
472
+ const { identifier, accountConfig, isGroup, groupConfig } = params;
473
+ if (isGroup) {
474
+ const policy2 = accountConfig.groupPolicy ?? "allowlist";
475
+ if (policy2 === "disabled") {
476
+ return false;
477
+ }
478
+ if (policy2 === "open") {
479
+ return true;
480
+ }
481
+ if (groupConfig?.allowFrom?.length) {
482
+ return groupConfig.allowFrom.some((allowed) => String(allowed) === identifier);
483
+ }
484
+ if (accountConfig.groupAllowFrom?.length) {
485
+ return accountConfig.groupAllowFrom.some((allowed) => String(allowed) === identifier);
486
+ }
487
+ return policy2 !== "allowlist";
488
+ }
489
+ const policy = accountConfig.dmPolicy ?? "pairing";
490
+ if (policy === "disabled") {
491
+ return false;
492
+ }
493
+ if (policy === "open") {
494
+ return true;
495
+ }
496
+ if (policy === "pairing") {
497
+ return true;
498
+ }
499
+ if (accountConfig.allowFrom?.length) {
500
+ return accountConfig.allowFrom.some((allowed) => String(allowed) === identifier);
501
+ }
502
+ return false;
503
+ }
504
+ function isWhatsAppMentionRequired(params) {
505
+ const { groupConfig } = params;
506
+ return groupConfig?.requireMention ?? false;
507
+ }
508
+ async function checkWhatsAppUserAccess(params) {
509
+ const { runtime, identifier, accountConfig, isGroup, groupConfig, metadata } = params;
510
+ if (isGroup) {
511
+ const policy2 = accountConfig.groupPolicy ?? "allowlist";
512
+ if (policy2 === "disabled") {
513
+ return { allowed: false };
514
+ }
515
+ if (policy2 === "open") {
516
+ return { allowed: true };
517
+ }
518
+ if (groupConfig?.allowFrom?.length) {
519
+ const allowed = groupConfig.allowFrom.some((a) => String(a) === identifier);
520
+ return { allowed };
521
+ }
522
+ if (accountConfig.groupAllowFrom?.length) {
523
+ const allowed = accountConfig.groupAllowFrom.some((a) => String(a) === identifier);
524
+ return { allowed };
525
+ }
526
+ return { allowed: policy2 !== "allowlist" };
527
+ }
528
+ const policy = accountConfig.dmPolicy ?? "pairing";
529
+ if (policy === "disabled") {
530
+ return { allowed: false };
531
+ }
532
+ if (policy === "open") {
533
+ return { allowed: true };
534
+ }
535
+ if (policy === "pairing") {
536
+ const result = await checkPairingAllowed(runtime, {
537
+ channel: "whatsapp",
538
+ senderId: identifier,
539
+ metadata
540
+ });
541
+ return {
542
+ allowed: result.allowed,
543
+ pairingCode: result.pairingCode,
544
+ newPairingRequest: result.newRequest,
545
+ replyMessage: result.replyMessage
546
+ };
547
+ }
548
+ if (accountConfig.allowFrom?.length) {
549
+ const allowed = accountConfig.allowFrom.some((a) => String(a) === identifier);
550
+ if (allowed) {
551
+ return { allowed: true };
552
+ }
553
+ }
554
+ const inDynamicAllowlist = await isInAllowlist(runtime, "whatsapp", identifier);
555
+ return { allowed: inDynamicAllowlist };
556
+ }
557
+
558
+ // src/client.ts
559
+ import { EventEmitter } from "node:events";
560
+ import axios from "axios";
561
+ var DEFAULT_API_VERSION = "v24.0";
562
+
563
+ class WhatsAppClient extends EventEmitter {
564
+ client;
565
+ config;
566
+ connectionStatus = "close";
567
+ constructor(config) {
568
+ super();
569
+ this.config = config;
570
+ const apiVersion = config.apiVersion || DEFAULT_API_VERSION;
571
+ this.client = axios.create({
572
+ baseURL: `https://graph.facebook.com/${apiVersion}`,
573
+ headers: {
574
+ Authorization: `Bearer ${config.accessToken}`,
575
+ "Content-Type": "application/json"
576
+ }
577
+ });
578
+ }
579
+ async start() {
580
+ this.connectionStatus = "open";
581
+ this.emit("connection", "open");
582
+ this.emit("ready");
583
+ }
584
+ async stop() {
585
+ this.connectionStatus = "close";
586
+ this.emit("connection", "close");
587
+ }
588
+ getConnectionStatus() {
589
+ return this.connectionStatus;
590
+ }
591
+ getPhoneNumberId() {
592
+ return this.config.phoneNumberId;
593
+ }
594
+ async sendMessage(message) {
595
+ const endpoint = `/${this.config.phoneNumberId}/messages`;
596
+ const payload = this.buildMessagePayload(message);
597
+ return this.client.post(endpoint, payload);
598
+ }
599
+ async sendTextMessage(to, text, _previewUrl = false) {
600
+ return this.sendMessage({
601
+ type: "text",
602
+ to,
603
+ content: text
604
+ });
605
+ }
606
+ async sendReaction(params) {
607
+ const endpoint = `/${this.config.phoneNumberId}/messages`;
608
+ const payload = {
609
+ messaging_product: "whatsapp",
610
+ recipient_type: "individual",
611
+ to: params.to,
612
+ type: "reaction",
613
+ reaction: {
614
+ message_id: params.messageId,
615
+ emoji: params.emoji
616
+ }
617
+ };
618
+ try {
619
+ const response = await this.client.post(endpoint, payload);
620
+ return {
621
+ success: true,
622
+ messageId: response.data.messages?.[0]?.id
623
+ };
624
+ } catch (error) {
625
+ const errorMessage = error instanceof Error ? error.message : String(error);
626
+ return {
627
+ success: false,
628
+ error: errorMessage
629
+ };
630
+ }
631
+ }
632
+ async removeReaction(to, messageId) {
633
+ return this.sendReaction({
634
+ to,
635
+ messageId,
636
+ emoji: ""
637
+ });
638
+ }
639
+ async sendImage(to, imageUrl, caption) {
640
+ return this.sendMessage({
641
+ type: "image",
642
+ to,
643
+ content: {
644
+ link: imageUrl,
645
+ caption
646
+ }
647
+ });
648
+ }
649
+ async sendVideo(to, videoUrl, caption) {
650
+ return this.sendMessage({
651
+ type: "video",
652
+ to,
653
+ content: {
654
+ link: videoUrl,
655
+ caption
656
+ }
657
+ });
658
+ }
659
+ async sendAudio(to, audioUrl) {
660
+ return this.sendMessage({
661
+ type: "audio",
662
+ to,
663
+ content: {
664
+ link: audioUrl
665
+ }
666
+ });
667
+ }
668
+ async sendDocument(to, documentUrl, filename, caption) {
669
+ return this.sendMessage({
670
+ type: "document",
671
+ to,
672
+ content: {
673
+ link: documentUrl,
674
+ filename,
675
+ caption
676
+ }
677
+ });
678
+ }
679
+ async sendLocation(to, latitude, longitude, name, address) {
680
+ return this.sendMessage({
681
+ type: "location",
682
+ to,
683
+ content: {
684
+ latitude,
685
+ longitude,
686
+ name,
687
+ address
688
+ }
689
+ });
690
+ }
691
+ async sendButtonMessage(to, bodyText, buttons, headerText, footerText) {
692
+ const interactive = {
693
+ type: "button",
694
+ body: { text: bodyText },
695
+ action: {
696
+ buttons: buttons.map((btn) => ({
697
+ type: "reply",
698
+ reply: { id: btn.id, title: btn.title }
699
+ }))
700
+ }
701
+ };
702
+ if (headerText) {
703
+ interactive.header = { type: "text", text: headerText };
704
+ }
705
+ if (footerText) {
706
+ interactive.footer = { text: footerText };
707
+ }
708
+ return this.sendMessage({
709
+ type: "interactive",
710
+ to,
711
+ content: interactive
712
+ });
713
+ }
714
+ async sendListMessage(to, bodyText, buttonText, sections, headerText, footerText) {
715
+ const interactive = {
716
+ type: "list",
717
+ body: { text: bodyText },
718
+ action: {
719
+ button: buttonText,
720
+ sections
721
+ }
722
+ };
723
+ if (headerText) {
724
+ interactive.header = { type: "text", text: headerText };
725
+ }
726
+ if (footerText) {
727
+ interactive.footer = { text: footerText };
728
+ }
729
+ return this.sendMessage({
730
+ type: "interactive",
731
+ to,
732
+ content: interactive
733
+ });
734
+ }
735
+ async markMessageAsRead(messageId) {
736
+ const endpoint = `/${this.config.phoneNumberId}/messages`;
737
+ const payload = {
738
+ messaging_product: "whatsapp",
739
+ status: "read",
740
+ message_id: messageId
741
+ };
742
+ try {
743
+ await this.client.post(endpoint, payload);
744
+ return true;
745
+ } catch {
746
+ return false;
747
+ }
748
+ }
749
+ async getMediaUrl(mediaId) {
750
+ try {
751
+ const response = await this.client.get(`/${mediaId}`);
752
+ return response.data.url || null;
753
+ } catch {
754
+ return null;
755
+ }
756
+ }
757
+ async verifyWebhook(token) {
758
+ return token === this.config.webhookVerifyToken;
759
+ }
760
+ buildMessagePayload(message) {
761
+ const basePayload = {
762
+ messaging_product: "whatsapp",
763
+ recipient_type: "individual",
764
+ to: message.to,
765
+ type: message.type
766
+ };
767
+ const contextPayload = message.replyToMessageId ? { context: { message_id: message.replyToMessageId } } : {};
768
+ switch (message.type) {
769
+ case "text":
770
+ return {
771
+ ...basePayload,
772
+ ...contextPayload,
773
+ text: {
774
+ body: message.content
775
+ }
776
+ };
777
+ case "template":
778
+ return {
779
+ ...basePayload,
780
+ ...contextPayload,
781
+ template: message.content
782
+ };
783
+ case "image": {
784
+ const imageContent = message.content;
785
+ return {
786
+ ...basePayload,
787
+ ...contextPayload,
788
+ image: {
789
+ link: imageContent.link,
790
+ caption: imageContent.caption
791
+ }
792
+ };
793
+ }
794
+ case "video": {
795
+ const videoContent = message.content;
796
+ return {
797
+ ...basePayload,
798
+ ...contextPayload,
799
+ video: {
800
+ link: videoContent.link,
801
+ caption: videoContent.caption
802
+ }
803
+ };
804
+ }
805
+ case "audio": {
806
+ const audioContent = message.content;
807
+ return {
808
+ ...basePayload,
809
+ ...contextPayload,
810
+ audio: {
811
+ link: audioContent.link
812
+ }
813
+ };
814
+ }
815
+ case "document": {
816
+ const docContent = message.content;
817
+ return {
818
+ ...basePayload,
819
+ ...contextPayload,
820
+ document: {
821
+ link: docContent.link,
822
+ filename: docContent.filename,
823
+ caption: docContent.caption
824
+ }
825
+ };
826
+ }
827
+ case "location": {
828
+ const locContent = message.content;
829
+ return {
830
+ ...basePayload,
831
+ ...contextPayload,
832
+ location: {
833
+ latitude: locContent.latitude,
834
+ longitude: locContent.longitude,
835
+ name: locContent.name,
836
+ address: locContent.address
837
+ }
838
+ };
839
+ }
840
+ case "reaction": {
841
+ const reactionContent = message.content;
842
+ return {
843
+ ...basePayload,
844
+ reaction: {
845
+ message_id: reactionContent.messageId,
846
+ emoji: reactionContent.emoji
847
+ }
848
+ };
849
+ }
850
+ case "interactive": {
851
+ const interactiveContent = message.content;
852
+ return {
853
+ ...basePayload,
854
+ ...contextPayload,
855
+ interactive: interactiveContent
856
+ };
857
+ }
858
+ default:
859
+ return basePayload;
860
+ }
861
+ }
862
+ }
863
+
864
+ // src/clients/baileys-client.ts
865
+ import { EventEmitter as EventEmitter3 } from "node:events";
866
+
867
+ // src/baileys/auth.ts
868
+ import { useMultiFileAuthState } from "@whiskeysockets/baileys";
869
+
870
+ class BaileysAuthManager {
871
+ authDir;
872
+ state;
873
+ saveCreds;
874
+ constructor(authDir) {
875
+ this.authDir = authDir;
876
+ }
877
+ async initialize() {
878
+ const result = await useMultiFileAuthState(this.authDir);
879
+ this.state = result.state;
880
+ this.saveCreds = result.saveCreds;
881
+ return this.state;
882
+ }
883
+ async save() {
884
+ if (this.saveCreds) {
885
+ await this.saveCreds();
886
+ }
887
+ }
888
+ }
889
+
890
+ // src/baileys/connection.ts
891
+ import { EventEmitter as EventEmitter2 } from "node:events";
892
+ import makeWASocket, { DisconnectReason } from "@whiskeysockets/baileys";
893
+ import pino from "pino";
894
+
895
+ class BaileysConnection extends EventEmitter2 {
896
+ socket;
897
+ authManager;
898
+ connectionStatus = "close";
899
+ reconnecting = false;
900
+ reconnectAttempts = 0;
901
+ maxReconnectAttempts = 10;
902
+ constructor(authManager) {
903
+ super();
904
+ this.authManager = authManager;
905
+ }
906
+ async connect() {
907
+ this.connectionStatus = "connecting";
908
+ this.emit("connection", "connecting");
909
+ const state = await this.authManager.initialize();
910
+ this.socket = makeWASocket({
911
+ auth: state,
912
+ printQRInTerminal: false,
913
+ logger: pino({ level: "silent" }),
914
+ browser: ["Chrome (Linux)", "", ""]
915
+ });
916
+ this.setupEventHandlers();
917
+ return this.socket;
918
+ }
919
+ setupEventHandlers() {
920
+ if (!this.socket) {
921
+ return;
922
+ }
923
+ this.socket.ev.on("connection.update", async (update) => {
924
+ const { connection, qr, lastDisconnect } = update;
925
+ if (qr) {
926
+ this.emit("qr", qr);
927
+ }
928
+ if (connection) {
929
+ this.connectionStatus = connection;
930
+ this.emit("connection", connection);
931
+ }
932
+ if (connection === "open") {
933
+ this.reconnectAttempts = 0;
934
+ return;
935
+ }
936
+ if (connection !== "close") {
937
+ return;
938
+ }
939
+ const statusCode = lastDisconnect?.error?.output?.statusCode;
940
+ const isQRTimeout = statusCode === 515;
941
+ const shouldReconnect = statusCode !== DisconnectReason.loggedOut && statusCode !== 405;
942
+ if (lastDisconnect?.error && !isQRTimeout) {
943
+ this.emit("error", lastDisconnect.error);
944
+ }
945
+ if (!shouldReconnect) {
946
+ return;
947
+ }
948
+ if (this.reconnecting) {
949
+ return;
950
+ }
951
+ if (this.reconnectAttempts >= this.maxReconnectAttempts) {
952
+ this.emit("error", new Error("Max reconnection attempts reached"));
953
+ return;
954
+ }
955
+ this.reconnecting = true;
956
+ try {
957
+ this.reconnectAttempts += 1;
958
+ const baseDelayMs = isQRTimeout ? 1000 : 3000;
959
+ const backoffMs = Math.min(baseDelayMs * 2 ** (this.reconnectAttempts - 1), 30000);
960
+ await new Promise((resolve) => setTimeout(resolve, backoffMs));
961
+ await this.connect();
962
+ } catch (error) {
963
+ this.emit("error", error);
964
+ } finally {
965
+ this.reconnecting = false;
966
+ }
967
+ });
968
+ this.socket.ev.on("creds.update", async () => {
969
+ await this.authManager.save();
970
+ });
971
+ this.socket.ev.on("messages.upsert", ({ messages }) => {
972
+ this.emit("messages", messages);
973
+ });
974
+ }
975
+ getSocket() {
976
+ return this.socket;
977
+ }
978
+ getStatus() {
979
+ return this.connectionStatus;
980
+ }
981
+ async disconnect() {
982
+ if (!this.socket) {
983
+ return;
984
+ }
985
+ this.socket.ev.removeAllListeners();
986
+ this.socket.ws?.close?.();
987
+ this.socket = undefined;
988
+ this.connectionStatus = "close";
989
+ this.emit("connection", "close");
990
+ }
991
+ }
992
+
993
+ // src/baileys/message-adapter.ts
994
+ class MessageAdapter {
995
+ toNormalized(msg) {
996
+ const chatId = msg.key?.remoteJid ?? "";
997
+ const senderId = msg.key?.participant ?? chatId;
998
+ return {
999
+ id: msg.key?.id ?? "",
1000
+ from: chatId,
1001
+ timestamp: Number(msg.messageTimestamp ?? 0),
1002
+ type: this.detectType(msg),
1003
+ content: this.extractContent(msg),
1004
+ chatId,
1005
+ senderId,
1006
+ replyToId: this.extractReplyToId(msg)
1007
+ };
1008
+ }
1009
+ toBaileys(msg) {
1010
+ switch (msg.type) {
1011
+ case "text":
1012
+ return { text: msg.content };
1013
+ case "image":
1014
+ return this.mediaWithCaption("image", msg.content);
1015
+ case "video":
1016
+ return this.mediaWithCaption("video", msg.content);
1017
+ case "audio":
1018
+ return this.mediaNoCaption("audio", msg.content);
1019
+ case "document":
1020
+ return this.mediaWithFilename(msg.content);
1021
+ case "template":
1022
+ return { text: this.renderTemplate(msg.content) };
1023
+ default:
1024
+ throw new Error(`Message type ${msg.type} is not yet supported for Baileys`);
1025
+ }
1026
+ }
1027
+ mediaWithCaption(key, media) {
1028
+ if (!media?.link) {
1029
+ throw new Error(`${key} message requires a media link`);
1030
+ }
1031
+ return {
1032
+ [key]: { url: media.link },
1033
+ ...media.caption ? { caption: media.caption } : {}
1034
+ };
1035
+ }
1036
+ mediaNoCaption(key, media) {
1037
+ if (!media?.link) {
1038
+ throw new Error(`${key} message requires a media link`);
1039
+ }
1040
+ return { [key]: { url: media.link } };
1041
+ }
1042
+ mediaWithFilename(media) {
1043
+ if (!media?.link) {
1044
+ throw new Error("document message requires a media link");
1045
+ }
1046
+ return {
1047
+ document: { url: media.link },
1048
+ ...media.filename ? { fileName: media.filename } : {},
1049
+ ...media.caption ? { caption: media.caption } : {}
1050
+ };
1051
+ }
1052
+ detectType(msg) {
1053
+ if (msg.message?.conversation || msg.message?.extendedTextMessage) {
1054
+ return "text";
1055
+ }
1056
+ if (msg.message?.imageMessage) {
1057
+ return "image";
1058
+ }
1059
+ if (msg.message?.audioMessage) {
1060
+ return "audio";
1061
+ }
1062
+ if (msg.message?.videoMessage) {
1063
+ return "video";
1064
+ }
1065
+ if (msg.message?.documentMessage) {
1066
+ return "document";
1067
+ }
1068
+ return "text";
1069
+ }
1070
+ extractContent(msg) {
1071
+ return msg.message?.conversation ?? msg.message?.extendedTextMessage?.text ?? msg.message?.imageMessage?.caption ?? msg.message?.videoMessage?.caption ?? msg.message?.documentMessage?.caption ?? "";
1072
+ }
1073
+ extractReplyToId(msg) {
1074
+ const contextInfo = msg.message?.extendedTextMessage?.contextInfo ?? msg.message?.imageMessage?.contextInfo ?? msg.message?.videoMessage?.contextInfo ?? msg.message?.documentMessage?.contextInfo;
1075
+ return typeof contextInfo?.stanzaId === "string" ? contextInfo.stanzaId : undefined;
1076
+ }
1077
+ renderTemplate(template) {
1078
+ const params = template.components?.flatMap((component) => component.parameters.map((parameter) => parameter.text).filter(Boolean));
1079
+ return params && params.length > 0 ? `${template.name}: ${params.join(", ")}` : template.name;
1080
+ }
1081
+ }
1082
+
1083
+ // src/baileys/qr-code.ts
1084
+ import QRCode from "qrcode";
1085
+ import QRCodeTerminal from "qrcode-terminal";
1086
+
1087
+ class QRCodeGenerator {
1088
+ async generate(qrString) {
1089
+ return {
1090
+ terminal: await this.generateTerminal(qrString),
1091
+ dataURL: await QRCode.toDataURL(qrString),
1092
+ raw: qrString
1093
+ };
1094
+ }
1095
+ async generateTerminal(qr) {
1096
+ return new Promise((resolve) => {
1097
+ QRCodeTerminal.generate(qr, { small: true }, (output) => {
1098
+ resolve(output);
1099
+ });
1100
+ });
1101
+ }
1102
+ }
1103
+
1104
+ // src/clients/baileys-client.ts
1105
+ class BaileysClient extends EventEmitter3 {
1106
+ config;
1107
+ authManager;
1108
+ connection;
1109
+ qrGenerator;
1110
+ adapter;
1111
+ constructor(config) {
1112
+ super();
1113
+ this.config = config;
1114
+ this.authManager = new BaileysAuthManager(config.authDir);
1115
+ this.connection = new BaileysConnection(this.authManager);
1116
+ this.qrGenerator = new QRCodeGenerator;
1117
+ this.adapter = new MessageAdapter;
1118
+ this.setupEventForwarding();
1119
+ }
1120
+ setupEventForwarding() {
1121
+ this.connection.on("qr", async (qr) => {
1122
+ try {
1123
+ const qrData = await this.qrGenerator.generate(qr);
1124
+ if (this.config.printQRInTerminal !== false) {
1125
+ console.log(`
1126
+ === Scan QR Code ===
1127
+ `);
1128
+ console.log(qrData.terminal);
1129
+ }
1130
+ this.emit("qr", qrData);
1131
+ } catch (error) {
1132
+ this.emit("error", error);
1133
+ }
1134
+ });
1135
+ this.connection.on("connection", (status) => {
1136
+ this.emit("connection", status);
1137
+ if (status === "open") {
1138
+ this.emit("ready");
1139
+ }
1140
+ });
1141
+ this.connection.on("messages", (messages) => {
1142
+ for (const message of messages) {
1143
+ const maybe = message;
1144
+ if (!maybe.key?.fromMe && maybe.message) {
1145
+ this.emit("message", this.adapter.toNormalized(message));
1146
+ }
1147
+ }
1148
+ });
1149
+ this.connection.on("error", (error) => {
1150
+ this.emit("error", error);
1151
+ });
1152
+ }
1153
+ async start() {
1154
+ await this.connection.connect();
1155
+ }
1156
+ async stop() {
1157
+ await this.connection.disconnect();
1158
+ }
1159
+ async sendMessage(message) {
1160
+ const socket = this.connection.getSocket();
1161
+ if (!socket) {
1162
+ throw new Error("Not connected to WhatsApp via Baileys");
1163
+ }
1164
+ const payload = this.adapter.toBaileys(message);
1165
+ const result = await socket.sendMessage(message.to, payload);
1166
+ const id = result?.key?.id ?? "";
1167
+ return {
1168
+ messaging_product: "whatsapp",
1169
+ contacts: [{ input: message.to, wa_id: message.to }],
1170
+ messages: [{ id }]
1171
+ };
1172
+ }
1173
+ getConnectionStatus() {
1174
+ return this.connection.getStatus();
1175
+ }
1176
+ getPhoneNumber() {
1177
+ return this.connection.getSocket()?.user?.id?.split(":")[0] ?? null;
1178
+ }
1179
+ }
1180
+
1181
+ // src/normalize.ts
1182
+ var WHATSAPP_TEXT_CHUNK_LIMIT = 4096;
1183
+ var WHATSAPP_USER_JID_RE = /^(\d+)(?::\d+)?@s\.whatsapp\.net$/i;
1184
+ var WHATSAPP_LID_RE = /^(\d+)@lid$/i;
1185
+ function stripWhatsAppTargetPrefixes(value) {
1186
+ let candidate = value.trim();
1187
+ for (;; ) {
1188
+ const before = candidate;
1189
+ candidate = candidate.replace(/^whatsapp:/i, "").trim();
1190
+ if (candidate === before) {
1191
+ return candidate;
1192
+ }
1193
+ }
1194
+ }
1195
+ function normalizeE164(input) {
1196
+ const stripped = input.replace(/[\s\-().]+/g, "");
1197
+ const digitsOnly = stripped.replace(/[^\d+]/g, "");
1198
+ if (!digitsOnly) {
1199
+ return "";
1200
+ }
1201
+ if (digitsOnly.startsWith("+")) {
1202
+ return digitsOnly;
1203
+ }
1204
+ if (digitsOnly.startsWith("00")) {
1205
+ return `+${digitsOnly.slice(2)}`;
1206
+ }
1207
+ if (digitsOnly.length >= 10) {
1208
+ return `+${digitsOnly}`;
1209
+ }
1210
+ return digitsOnly;
1211
+ }
1212
+ function isWhatsAppGroupJid(value) {
1213
+ const candidate = stripWhatsAppTargetPrefixes(value);
1214
+ const lower = candidate.toLowerCase();
1215
+ if (!lower.endsWith("@g.us")) {
1216
+ return false;
1217
+ }
1218
+ const localPart = candidate.slice(0, candidate.length - "@g.us".length);
1219
+ if (!localPart || localPart.includes("@")) {
1220
+ return false;
1221
+ }
1222
+ return /^[0-9]+(-[0-9]+)*$/.test(localPart);
1223
+ }
1224
+ function isWhatsAppUserTarget(value) {
1225
+ const candidate = stripWhatsAppTargetPrefixes(value);
1226
+ return WHATSAPP_USER_JID_RE.test(candidate) || WHATSAPP_LID_RE.test(candidate);
1227
+ }
1228
+ function extractUserJidPhone(jid) {
1229
+ const userMatch = jid.match(WHATSAPP_USER_JID_RE);
1230
+ if (userMatch) {
1231
+ return userMatch[1];
1232
+ }
1233
+ const lidMatch = jid.match(WHATSAPP_LID_RE);
1234
+ if (lidMatch) {
1235
+ return lidMatch[1];
1236
+ }
1237
+ return null;
1238
+ }
1239
+ function normalizeWhatsAppTarget(value) {
1240
+ const candidate = stripWhatsAppTargetPrefixes(value);
1241
+ if (!candidate) {
1242
+ return null;
1243
+ }
1244
+ if (isWhatsAppGroupJid(candidate)) {
1245
+ const localPart = candidate.slice(0, candidate.length - "@g.us".length);
1246
+ return `${localPart}@g.us`;
1247
+ }
1248
+ if (isWhatsAppUserTarget(candidate)) {
1249
+ const phone = extractUserJidPhone(candidate);
1250
+ if (!phone) {
1251
+ return null;
1252
+ }
1253
+ const normalized2 = normalizeE164(phone);
1254
+ return normalized2.length > 1 ? normalized2 : null;
1255
+ }
1256
+ if (candidate.includes("@")) {
1257
+ return null;
1258
+ }
1259
+ const normalized = normalizeE164(candidate);
1260
+ return normalized.length > 1 ? normalized : null;
1261
+ }
1262
+ function formatWhatsAppId(id) {
1263
+ if (isWhatsAppGroupJid(id)) {
1264
+ return `group:${id}`;
1265
+ }
1266
+ const normalized = normalizeWhatsAppTarget(id);
1267
+ return normalized || id;
1268
+ }
1269
+ function isWhatsAppGroup(id) {
1270
+ return isWhatsAppGroupJid(id);
1271
+ }
1272
+ function getWhatsAppChatType(id) {
1273
+ return isWhatsAppGroupJid(id) ? "group" : "user";
1274
+ }
1275
+ function buildWhatsAppUserJid(phoneNumber) {
1276
+ const normalized = normalizeE164(phoneNumber);
1277
+ const digits = normalized.replace(/^\+/, "");
1278
+ return `${digits}@s.whatsapp.net`;
1279
+ }
1280
+ function splitAtBreakPoint(text, limit) {
1281
+ if (text.length <= limit) {
1282
+ return { chunk: text, remainder: "" };
1283
+ }
1284
+ const searchArea = text.slice(0, limit);
1285
+ const doubleNewline = searchArea.lastIndexOf(`
1286
+
1287
+ `);
1288
+ if (doubleNewline > limit * 0.5) {
1289
+ return {
1290
+ chunk: text.slice(0, doubleNewline).trimEnd(),
1291
+ remainder: text.slice(doubleNewline + 2).trimStart()
1292
+ };
1293
+ }
1294
+ const singleNewline = searchArea.lastIndexOf(`
1295
+ `);
1296
+ if (singleNewline > limit * 0.5) {
1297
+ return {
1298
+ chunk: text.slice(0, singleNewline).trimEnd(),
1299
+ remainder: text.slice(singleNewline + 1).trimStart()
1300
+ };
1301
+ }
1302
+ const sentenceEnd = Math.max(searchArea.lastIndexOf(". "), searchArea.lastIndexOf("! "), searchArea.lastIndexOf("? "));
1303
+ if (sentenceEnd > limit * 0.5) {
1304
+ return {
1305
+ chunk: text.slice(0, sentenceEnd + 1).trimEnd(),
1306
+ remainder: text.slice(sentenceEnd + 2).trimStart()
1307
+ };
1308
+ }
1309
+ const space = searchArea.lastIndexOf(" ");
1310
+ if (space > limit * 0.5) {
1311
+ return {
1312
+ chunk: text.slice(0, space).trimEnd(),
1313
+ remainder: text.slice(space + 1).trimStart()
1314
+ };
1315
+ }
1316
+ return {
1317
+ chunk: text.slice(0, limit),
1318
+ remainder: text.slice(limit)
1319
+ };
1320
+ }
1321
+ function chunkWhatsAppText(text, opts = {}) {
1322
+ const limit = opts.limit ?? WHATSAPP_TEXT_CHUNK_LIMIT;
1323
+ if (!text?.trim()) {
1324
+ return [];
1325
+ }
1326
+ const normalizedText = text.trim();
1327
+ if (normalizedText.length <= limit) {
1328
+ return [normalizedText];
1329
+ }
1330
+ const chunks = [];
1331
+ let remaining = normalizedText;
1332
+ while (remaining.length > 0) {
1333
+ const { chunk, remainder } = splitAtBreakPoint(remaining, limit);
1334
+ if (chunk) {
1335
+ chunks.push(chunk);
1336
+ }
1337
+ remaining = remainder;
1338
+ }
1339
+ return chunks.filter((c) => c.length > 0);
1340
+ }
1341
+ function truncateText(text, maxLength) {
1342
+ if (text.length <= maxLength) {
1343
+ return text;
1344
+ }
1345
+ if (maxLength <= 3) {
1346
+ return "...".slice(0, maxLength);
1347
+ }
1348
+ return `${text.slice(0, maxLength - 3)}...`;
1349
+ }
1350
+ function resolveWhatsAppSystemLocation(params) {
1351
+ const { chatType, chatId, chatName } = params;
1352
+ const name = chatName || chatId.slice(0, 8);
1353
+ return `WhatsApp ${chatType}:${name}`;
1354
+ }
1355
+ function isValidWhatsAppNumber(value) {
1356
+ const normalized = normalizeWhatsAppTarget(value);
1357
+ if (!normalized) {
1358
+ return false;
1359
+ }
1360
+ if (!normalized.startsWith("+")) {
1361
+ return false;
1362
+ }
1363
+ const digits = normalized.replace(/^\+/, "");
1364
+ return /^\d{10,15}$/.test(digits);
1365
+ }
1366
+ function formatWhatsAppPhoneNumber(phoneNumber) {
1367
+ const normalized = normalizeE164(phoneNumber);
1368
+ if (!normalized) {
1369
+ return phoneNumber;
1370
+ }
1371
+ const digits = normalized.replace(/^\+/, "");
1372
+ if (digits.length <= 10) {
1373
+ return normalized;
1374
+ }
1375
+ const countryCode = digits.slice(0, digits.length - 10);
1376
+ const rest = digits.slice(-10);
1377
+ return `+${countryCode} ${rest.slice(0, 3)} ${rest.slice(3, 6)} ${rest.slice(6)}`;
1378
+ }
1379
+
1380
+ // src/runtime-service.ts
1381
+ function readStringSetting(runtime, key) {
1382
+ const value = runtime.getSetting(key);
1383
+ if (typeof value === "string" && value.trim().length > 0) {
1384
+ return value.trim();
1385
+ }
1386
+ const envValue = process.env[key];
1387
+ if (typeof envValue === "string" && envValue.trim().length > 0) {
1388
+ return envValue.trim();
1389
+ }
1390
+ return;
1391
+ }
1392
+ function readCsvSetting(runtime, key) {
1393
+ const value = readStringSetting(runtime, key);
1394
+ if (!value) {
1395
+ return [];
1396
+ }
1397
+ return value.split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0);
1398
+ }
1399
+ function resolveRuntimeConfig(runtime) {
1400
+ const dmPolicy = readStringSetting(runtime, "WHATSAPP_DM_POLICY");
1401
+ const groupPolicy = readStringSetting(runtime, "WHATSAPP_GROUP_POLICY");
1402
+ const allowFrom = readCsvSetting(runtime, "WHATSAPP_ALLOW_FROM");
1403
+ const groupAllowFrom = readCsvSetting(runtime, "WHATSAPP_GROUP_ALLOW_FROM");
1404
+ const authDir = readStringSetting(runtime, "WHATSAPP_AUTH_DIR") ?? readStringSetting(runtime, "WHATSAPP_SESSION_PATH");
1405
+ if (authDir) {
1406
+ return {
1407
+ transport: "baileys",
1408
+ authDir,
1409
+ dmPolicy,
1410
+ groupPolicy,
1411
+ allowFrom,
1412
+ groupAllowFrom
1413
+ };
1414
+ }
1415
+ const accessToken = readStringSetting(runtime, "WHATSAPP_ACCESS_TOKEN");
1416
+ const phoneNumberId = readStringSetting(runtime, "WHATSAPP_PHONE_NUMBER_ID");
1417
+ if (accessToken && phoneNumberId) {
1418
+ return {
1419
+ transport: "cloudapi",
1420
+ accessToken,
1421
+ phoneNumberId,
1422
+ webhookVerifyToken: readStringSetting(runtime, "WHATSAPP_WEBHOOK_VERIFY_TOKEN"),
1423
+ apiVersion: readStringSetting(runtime, "WHATSAPP_API_VERSION"),
1424
+ dmPolicy,
1425
+ groupPolicy,
1426
+ allowFrom,
1427
+ groupAllowFrom
1428
+ };
1429
+ }
1430
+ return null;
1431
+ }
1432
+ function toTimestampMs(value) {
1433
+ const parsed = Number(value);
1434
+ if (!Number.isFinite(parsed) || parsed <= 0) {
1435
+ return Date.now();
1436
+ }
1437
+ return parsed >= 1000000000000 ? parsed : parsed * 1000;
1438
+ }
1439
+ function toMemoryId(runtime, chatId, messageId) {
1440
+ return createUniqueUuid(runtime, `whatsapp:${chatId}:${messageId}`);
1441
+ }
1442
+ function normalizeBaileysSendTarget(target) {
1443
+ if (isWhatsAppGroupJid(target) || isWhatsAppUserTarget(target)) {
1444
+ return target;
1445
+ }
1446
+ const normalized = normalizeWhatsAppTarget(target);
1447
+ return normalized ? buildWhatsAppUserJid(normalized) : target;
1448
+ }
1449
+ function extractWebhookText(message) {
1450
+ if (typeof message.text?.body === "string" && message.text.body.trim()) {
1451
+ return message.text.body.trim();
1452
+ }
1453
+ if (typeof message.interactive?.button_reply?.title === "string" && message.interactive.button_reply.title.trim()) {
1454
+ return message.interactive.button_reply.title.trim();
1455
+ }
1456
+ if (typeof message.interactive?.list_reply?.title === "string" && message.interactive.list_reply.title.trim()) {
1457
+ return message.interactive.list_reply.title.trim();
1458
+ }
1459
+ if (typeof message.interactive?.nfm_reply?.body === "string" && message.interactive.nfm_reply.body.trim()) {
1460
+ return message.interactive.nfm_reply.body.trim();
1461
+ }
1462
+ if (typeof message.image?.caption === "string" && message.image.caption.trim()) {
1463
+ return message.image.caption.trim();
1464
+ }
1465
+ if (typeof message.video?.caption === "string" && message.video.caption.trim()) {
1466
+ return message.video.caption.trim();
1467
+ }
1468
+ if (typeof message.document?.caption === "string" && message.document.caption.trim()) {
1469
+ return message.document.caption.trim();
1470
+ }
1471
+ if (message.reaction?.emoji) {
1472
+ return `Reaction: ${message.reaction.emoji}`;
1473
+ }
1474
+ if (message.location) {
1475
+ const { latitude, longitude } = message.location;
1476
+ return `Location: ${latitude}, ${longitude}`;
1477
+ }
1478
+ return "";
1479
+ }
1480
+
1481
+ class WhatsAppConnectorService extends Service {
1482
+ static serviceType = "whatsapp";
1483
+ capabilityDescription = "The agent is able to send and receive messages on whatsapp";
1484
+ connected = false;
1485
+ phoneNumber = null;
1486
+ client = null;
1487
+ config = null;
1488
+ constructor(runtime) {
1489
+ super(runtime);
1490
+ if (runtime) {
1491
+ this.runtime = runtime;
1492
+ }
1493
+ }
1494
+ static async start(runtime) {
1495
+ const service = new WhatsAppConnectorService(runtime);
1496
+ await service.initialize();
1497
+ return service;
1498
+ }
1499
+ async initialize() {
1500
+ this.config = resolveRuntimeConfig(this.runtime);
1501
+ if (!this.config) {
1502
+ this.runtime.logger.warn({ src: "plugin:whatsapp", agentId: this.runtime.agentId }, "WhatsApp connector is not configured");
1503
+ return;
1504
+ }
1505
+ this.client = this.config.transport === "baileys" ? new BaileysClient({
1506
+ authMethod: "baileys",
1507
+ authDir: this.config.authDir,
1508
+ printQRInTerminal: false
1509
+ }) : new WhatsAppClient({
1510
+ accessToken: this.config.accessToken,
1511
+ phoneNumberId: this.config.phoneNumberId,
1512
+ webhookVerifyToken: this.config.webhookVerifyToken,
1513
+ apiVersion: this.config.apiVersion
1514
+ });
1515
+ this.bindClientEvents(this.client);
1516
+ await this.client.start();
1517
+ if (this.config.transport === "cloudapi") {
1518
+ this.connected = true;
1519
+ }
1520
+ }
1521
+ async stop() {
1522
+ if (this.client) {
1523
+ await this.client.stop();
1524
+ }
1525
+ this.connected = false;
1526
+ this.phoneNumber = null;
1527
+ }
1528
+ async handleWebhook(event) {
1529
+ for (const entry of event.entry ?? []) {
1530
+ for (const change of entry.changes ?? []) {
1531
+ const value = change.value;
1532
+ if (typeof value?.metadata?.display_phone_number === "string") {
1533
+ this.phoneNumber = value.metadata.display_phone_number;
1534
+ }
1535
+ for (const message of value?.messages ?? []) {
1536
+ await this.handleIncomingWebhookMessage(message);
1537
+ }
1538
+ }
1539
+ }
1540
+ }
1541
+ verifyWebhook(mode, token, challenge) {
1542
+ const expectedToken = this.config?.transport === "cloudapi" ? this.config.webhookVerifyToken : readStringSetting(this.runtime, "WHATSAPP_WEBHOOK_VERIFY_TOKEN");
1543
+ if (mode === "subscribe" && expectedToken && token === expectedToken && challenge) {
1544
+ return challenge;
1545
+ }
1546
+ return null;
1547
+ }
1548
+ bindClientEvents(client) {
1549
+ client.on("connection", (status) => {
1550
+ this.connected = status === "open";
1551
+ if (status === "open" && client instanceof BaileysClient) {
1552
+ const nextPhone = client.getPhoneNumber();
1553
+ this.phoneNumber = (nextPhone && normalizeWhatsAppTarget(nextPhone)) ?? nextPhone;
1554
+ }
1555
+ if (status === "close") {
1556
+ this.phoneNumber = null;
1557
+ }
1558
+ });
1559
+ client.on("ready", () => {
1560
+ this.connected = true;
1561
+ if (client instanceof BaileysClient) {
1562
+ const nextPhone = client.getPhoneNumber();
1563
+ this.phoneNumber = (nextPhone && normalizeWhatsAppTarget(nextPhone)) ?? nextPhone;
1564
+ }
1565
+ });
1566
+ client.on("message", (message) => {
1567
+ this.handleNormalizedMessage(message).catch((error) => {
1568
+ this.runtime.logger.error({
1569
+ src: "plugin:whatsapp",
1570
+ agentId: this.runtime.agentId,
1571
+ error: error instanceof Error ? error.message : String(error)
1572
+ }, "Failed to process inbound WhatsApp message");
1573
+ });
1574
+ });
1575
+ client.on("error", (error) => {
1576
+ this.runtime.logger.error({
1577
+ src: "plugin:whatsapp",
1578
+ agentId: this.runtime.agentId,
1579
+ error: error instanceof Error ? error.message : String(error)
1580
+ }, "WhatsApp client error");
1581
+ });
1582
+ }
1583
+ async handleNormalizedMessage(message) {
1584
+ const chatId = message.chatId ?? message.from;
1585
+ const senderId = message.senderId ?? message.from;
1586
+ const text = typeof message.content === "string" ? message.content.trim() : "";
1587
+ if (!chatId || !senderId || !text) {
1588
+ return;
1589
+ }
1590
+ await this.processIncomingMessage({
1591
+ chatId,
1592
+ senderId,
1593
+ text,
1594
+ externalMessageId: message.id,
1595
+ replyToExternalMessageId: message.replyToId,
1596
+ createdAt: toTimestampMs(message.timestamp)
1597
+ });
1598
+ }
1599
+ async handleIncomingWebhookMessage(message) {
1600
+ const text = extractWebhookText(message);
1601
+ if (!text) {
1602
+ return;
1603
+ }
1604
+ const normalizedSender = normalizeWhatsAppTarget(message.from) ?? message.from;
1605
+ await this.processIncomingMessage({
1606
+ chatId: normalizedSender,
1607
+ senderId: normalizedSender,
1608
+ text,
1609
+ externalMessageId: message.id,
1610
+ replyToExternalMessageId: message.context?.id,
1611
+ createdAt: toTimestampMs(message.timestamp)
1612
+ });
1613
+ }
1614
+ async processIncomingMessage(params) {
1615
+ if (!this.runtime.messageService) {
1616
+ throw new Error("WhatsApp connector requires runtime.messageService");
1617
+ }
1618
+ const isGroup = isWhatsAppGroupJid(params.chatId);
1619
+ const normalizedSender = normalizeWhatsAppTarget(params.senderId) ?? params.senderId;
1620
+ const accountConfig = {
1621
+ dmPolicy: this.config?.dmPolicy,
1622
+ groupPolicy: this.config?.groupPolicy,
1623
+ allowFrom: this.config?.allowFrom,
1624
+ groupAllowFrom: this.config?.groupAllowFrom
1625
+ };
1626
+ const access = await checkWhatsAppUserAccess({
1627
+ runtime: this.runtime,
1628
+ identifier: normalizedSender,
1629
+ accountConfig,
1630
+ isGroup,
1631
+ ...isGroup ? { groupId: params.chatId } : {},
1632
+ metadata: { senderId: normalizedSender }
1633
+ });
1634
+ if (!access.allowed) {
1635
+ if (access.replyMessage) {
1636
+ await this.sendTextMessage(params.chatId, access.replyMessage);
1637
+ }
1638
+ return;
1639
+ }
1640
+ const channelType = isGroup ? ChannelType.GROUP : ChannelType.DM;
1641
+ const roomId = createUniqueUuid(this.runtime, `whatsapp-room:${params.chatId}`);
1642
+ const worldId = createUniqueUuid(this.runtime, `whatsapp-world:${params.chatId}`);
1643
+ const entityId = createUniqueUuid(this.runtime, `whatsapp-entity:${normalizedSender}`);
1644
+ const inboundMemoryId = toMemoryId(this.runtime, params.chatId, params.externalMessageId);
1645
+ await this.runtime.ensureConnection({
1646
+ entityId,
1647
+ roomId,
1648
+ userId: normalizedSender,
1649
+ userName: normalizedSender,
1650
+ name: normalizedSender,
1651
+ source: "whatsapp",
1652
+ channelId: params.chatId,
1653
+ type: channelType,
1654
+ worldId,
1655
+ worldName: resolveWhatsAppSystemLocation({
1656
+ chatType: isGroup ? "group" : "user",
1657
+ chatId: params.chatId
1658
+ })
1659
+ });
1660
+ const inboundMemory = {
1661
+ id: inboundMemoryId,
1662
+ entityId,
1663
+ agentId: this.runtime.agentId,
1664
+ roomId,
1665
+ content: {
1666
+ text: params.text,
1667
+ source: "whatsapp",
1668
+ channelType,
1669
+ from: normalizedSender,
1670
+ messageId: params.externalMessageId,
1671
+ ...params.replyToExternalMessageId ? {
1672
+ inReplyTo: toMemoryId(this.runtime, params.chatId, params.replyToExternalMessageId)
1673
+ } : {}
1674
+ },
1675
+ metadata: {
1676
+ type: "message",
1677
+ source: "whatsapp",
1678
+ provider: "whatsapp",
1679
+ timestamp: params.createdAt,
1680
+ entityName: normalizedSender,
1681
+ entityUserName: normalizedSender,
1682
+ fromBot: false,
1683
+ fromId: normalizedSender,
1684
+ sourceId: entityId,
1685
+ chatType: channelType,
1686
+ messageIdFull: params.externalMessageId,
1687
+ sender: {
1688
+ id: normalizedSender,
1689
+ name: normalizedSender,
1690
+ username: normalizedSender
1691
+ },
1692
+ whatsapp: {
1693
+ id: normalizedSender,
1694
+ userId: normalizedSender,
1695
+ username: normalizedSender,
1696
+ userName: normalizedSender,
1697
+ name: normalizedSender,
1698
+ chatId: params.chatId,
1699
+ messageId: params.externalMessageId
1700
+ },
1701
+ rawChatId: params.chatId,
1702
+ rawSenderId: params.senderId
1703
+ },
1704
+ createdAt: params.createdAt
1705
+ };
1706
+ const callback = async (content) => {
1707
+ const text = typeof content.text === "string" ? content.text.trim() : "";
1708
+ if (!text) {
1709
+ return [];
1710
+ }
1711
+ const chunks = chunkWhatsAppText(text);
1712
+ const responseMemories = [];
1713
+ for (const [index, chunk] of chunks.entries()) {
1714
+ const response = await this.sendTextMessage(params.chatId, chunk, params.externalMessageId);
1715
+ const externalResponseId = response.messages?.[0]?.id ?? `${params.externalMessageId}:response:${index}:${Date.now()}`;
1716
+ responseMemories.push({
1717
+ id: toMemoryId(this.runtime, params.chatId, externalResponseId),
1718
+ entityId: this.runtime.agentId,
1719
+ agentId: this.runtime.agentId,
1720
+ roomId,
1721
+ content: {
1722
+ ...content,
1723
+ text: chunk,
1724
+ source: "whatsapp",
1725
+ channelType,
1726
+ inReplyTo: inboundMemoryId
1727
+ },
1728
+ metadata: {
1729
+ type: "message",
1730
+ source: "whatsapp",
1731
+ provider: "whatsapp",
1732
+ timestamp: Date.now(),
1733
+ fromBot: true,
1734
+ fromId: this.runtime.agentId,
1735
+ sourceId: this.runtime.agentId,
1736
+ chatType: channelType,
1737
+ messageIdFull: externalResponseId,
1738
+ whatsapp: {
1739
+ chatId: params.chatId,
1740
+ messageId: externalResponseId
1741
+ },
1742
+ rawChatId: params.chatId,
1743
+ externalMessageId: externalResponseId
1744
+ },
1745
+ createdAt: Date.now()
1746
+ });
1747
+ }
1748
+ return responseMemories;
1749
+ };
1750
+ const autoReplyRaw = this.runtime.getSetting("WHATSAPP_AUTO_REPLY");
1751
+ const autoReply = !lifeOpsPassiveConnectorsEnabled(this.runtime) && (autoReplyRaw === true || autoReplyRaw === "true");
1752
+ if (!autoReply) {
1753
+ await this.runtime.createMemory(inboundMemory, "messages");
1754
+ return;
1755
+ }
1756
+ await this.runtime.messageService.handleMessage(this.runtime, inboundMemory, callback);
1757
+ }
1758
+ async sendTextMessage(chatId, text, replyToMessageId) {
1759
+ if (!this.client || !this.config) {
1760
+ throw new Error("WhatsApp client is not initialized");
1761
+ }
1762
+ const response = await this.client.sendMessage({
1763
+ type: "text",
1764
+ to: this.config.transport === "baileys" ? normalizeBaileysSendTarget(chatId) : normalizeWhatsAppTarget(chatId) ?? chatId,
1765
+ content: text,
1766
+ replyToMessageId
1767
+ });
1768
+ return "data" in response ? response.data : response;
1769
+ }
1770
+ async sendMessage(message) {
1771
+ return this.sendTextMessage(message.to, message.content, message.replyToMessageId);
1772
+ }
1773
+ }
1774
+
1775
+ // src/setup-routes.ts
1776
+ import fs2 from "node:fs";
1777
+ import path2 from "node:path";
1778
+
1779
+ // src/pairing-service.ts
1780
+ import fs from "node:fs";
1781
+ import path from "node:path";
1782
+ var LOG_PREFIX = "[whatsapp-pairing]";
1783
+ function sanitizeAccountId(raw) {
1784
+ const cleaned = raw.replace(/[^a-zA-Z0-9_-]/g, "");
1785
+ if (!cleaned || cleaned !== raw) {
1786
+ throw new Error(`Invalid accountId: must only contain alphanumeric characters, dashes, and underscores`);
1787
+ }
1788
+ return cleaned;
1789
+ }
1790
+
1791
+ class WhatsAppPairingSession {
1792
+ socket = null;
1793
+ status = "idle";
1794
+ options;
1795
+ qrAttempts = 0;
1796
+ MAX_QR_ATTEMPTS = 5;
1797
+ restartTimer = null;
1798
+ constructor(options) {
1799
+ this.options = options;
1800
+ }
1801
+ async start() {
1802
+ this.setStatus("initializing");
1803
+ const baileys = await import("@whiskeysockets/baileys");
1804
+ const makeWASocket2 = baileys.default;
1805
+ const { useMultiFileAuthState: useMultiFileAuthState2, fetchLatestBaileysVersion, DisconnectReason: DisconnectReason2 } = baileys;
1806
+ const QRCode2 = (await import("qrcode")).default;
1807
+ const { Boom } = await import("@hapi/boom");
1808
+ fs.mkdirSync(this.options.authDir, { recursive: true });
1809
+ const { state, saveCreds } = await useMultiFileAuthState2(this.options.authDir);
1810
+ const { version } = await fetchLatestBaileysVersion();
1811
+ const pino2 = (await import("pino")).default;
1812
+ const baileysLogger = pino2({ level: "silent" });
1813
+ this.socket = makeWASocket2({
1814
+ version,
1815
+ auth: state,
1816
+ logger: baileysLogger,
1817
+ printQRInTerminal: false,
1818
+ browser: ["Eliza AI", "Desktop", "1.0.0"]
1819
+ });
1820
+ this.socket.ev.on("creds.update", saveCreds);
1821
+ this.socket.ev.on("connection.update", async (update) => {
1822
+ const { connection, lastDisconnect, qr } = update;
1823
+ if (qr) {
1824
+ this.qrAttempts++;
1825
+ console.info(`${LOG_PREFIX} QR code received (attempt ${this.qrAttempts}/${this.MAX_QR_ATTEMPTS})`);
1826
+ if (this.qrAttempts > this.MAX_QR_ATTEMPTS) {
1827
+ this.setStatus("timeout");
1828
+ this.stop();
1829
+ return;
1830
+ }
1831
+ try {
1832
+ const qrDataUrl = await QRCode2.toDataURL(qr, {
1833
+ width: 256,
1834
+ margin: 2,
1835
+ color: { dark: "#000000", light: "#ffffff" }
1836
+ });
1837
+ this.setStatus("waiting_for_qr");
1838
+ this.options.onEvent({
1839
+ type: "whatsapp-qr",
1840
+ accountId: this.options.accountId,
1841
+ qrDataUrl,
1842
+ expiresInMs: 20000
1843
+ });
1844
+ } catch {}
1845
+ }
1846
+ if (connection === "close") {
1847
+ const statusCode = lastDisconnect?.error?.output?.statusCode;
1848
+ console.info(`${LOG_PREFIX} Connection closed, statusCode=${statusCode}, status=${this.status}`);
1849
+ if (statusCode === DisconnectReason2.loggedOut) {
1850
+ this.setStatus("disconnected");
1851
+ } else if (statusCode === DisconnectReason2.restartRequired || statusCode === DisconnectReason2.timedOut || statusCode === DisconnectReason2.connectionClosed || statusCode === DisconnectReason2.connectionReplaced) {
1852
+ console.info(`${LOG_PREFIX} Restarting pairing after transient close...`);
1853
+ this.socket = null;
1854
+ this.qrAttempts = 0;
1855
+ this.restartTimer = setTimeout(() => {
1856
+ this.restartTimer = null;
1857
+ this.start().catch((err) => {
1858
+ console.error(`${LOG_PREFIX} Restart failed:`, err);
1859
+ this.setStatus("error");
1860
+ this.options.onEvent({
1861
+ type: "whatsapp-status",
1862
+ accountId: this.options.accountId,
1863
+ status: "error",
1864
+ error: String(err)
1865
+ });
1866
+ });
1867
+ }, 3000);
1868
+ }
1869
+ } else if (connection === "open") {
1870
+ const phoneNumber = this.socket?.user?.id?.split(":")[0] ?? "";
1871
+ this.setStatus("connected");
1872
+ this.options.onEvent({
1873
+ type: "whatsapp-status",
1874
+ accountId: this.options.accountId,
1875
+ status: "connected",
1876
+ phoneNumber
1877
+ });
1878
+ }
1879
+ });
1880
+ }
1881
+ stop() {
1882
+ if (this.restartTimer) {
1883
+ clearTimeout(this.restartTimer);
1884
+ this.restartTimer = null;
1885
+ }
1886
+ try {
1887
+ this.socket?.end(undefined);
1888
+ } catch {}
1889
+ this.socket = null;
1890
+ }
1891
+ getStatus() {
1892
+ return this.status;
1893
+ }
1894
+ setStatus(status) {
1895
+ this.status = status;
1896
+ this.options.onEvent({
1897
+ type: "whatsapp-status",
1898
+ accountId: this.options.accountId,
1899
+ status
1900
+ });
1901
+ }
1902
+ }
1903
+ function whatsappAuthExists(workspaceDir, accountId = "default") {
1904
+ const credsPath = path.join(workspaceDir, "whatsapp-auth", accountId, "creds.json");
1905
+ return fs.existsSync(credsPath);
1906
+ }
1907
+ async function whatsappLogout(workspaceDir, accountId = "default") {
1908
+ const authDir = path.join(workspaceDir, "whatsapp-auth", accountId);
1909
+ const credsPath = path.join(authDir, "creds.json");
1910
+ if (fs.existsSync(credsPath)) {
1911
+ try {
1912
+ const baileys = await import("@whiskeysockets/baileys");
1913
+ const makeWASocket2 = baileys.default;
1914
+ const { useMultiFileAuthState: useMultiFileAuthState2, fetchLatestBaileysVersion } = baileys;
1915
+ const pino2 = (await import("pino")).default;
1916
+ const logger = pino2({ level: "silent" });
1917
+ const { state } = await useMultiFileAuthState2(authDir);
1918
+ const { version } = await fetchLatestBaileysVersion();
1919
+ const sock = makeWASocket2({
1920
+ version,
1921
+ auth: state,
1922
+ logger,
1923
+ printQRInTerminal: false
1924
+ });
1925
+ await new Promise((resolve) => {
1926
+ let settled = false;
1927
+ const finish = () => {
1928
+ if (settled)
1929
+ return;
1930
+ settled = true;
1931
+ clearTimeout(timeout);
1932
+ try {
1933
+ sock.ev.removeAllListeners("connection.update");
1934
+ } catch {}
1935
+ try {
1936
+ sock.end(undefined);
1937
+ } catch {}
1938
+ resolve();
1939
+ };
1940
+ const timeout = setTimeout(finish, 1e4);
1941
+ sock.ev.on("connection.update", async (update) => {
1942
+ if (update.connection === "open") {
1943
+ try {
1944
+ await sock.logout();
1945
+ } catch {}
1946
+ finish();
1947
+ } else if (update.connection === "close") {
1948
+ finish();
1949
+ }
1950
+ });
1951
+ });
1952
+ } catch {}
1953
+ }
1954
+ fs.rmSync(authDir, { recursive: true, force: true });
1955
+ }
1956
+
1957
+ // src/setup-routes.ts
1958
+ var whatsappPairingSessions = new Map;
1959
+ var MAX_PAIRING_SESSIONS = 10;
1960
+ function isConnectorSetupService(service) {
1961
+ return typeof service === "object" && service !== null && typeof service.getConfig === "function" && typeof service.persistConfig === "function" && typeof service.updateConfig === "function" && typeof service.registerEscalationChannel === "function" && typeof service.setOwnerContact === "function" && typeof service.getWorkspaceDir === "function" && typeof service.broadcastWs === "function";
1962
+ }
1963
+ function getSetupService(runtime) {
1964
+ const service = runtime.getService("connector-setup");
1965
+ return isConnectorSetupService(service) ? service : null;
1966
+ }
1967
+ function cleanupStaleSessions() {
1968
+ for (const [id, session] of whatsappPairingSessions) {
1969
+ const status = session.getStatus();
1970
+ if (status === "disconnected" || status === "timeout" || status === "error") {
1971
+ session.stop();
1972
+ whatsappPairingSessions.delete(id);
1973
+ }
1974
+ }
1975
+ }
1976
+ async function handleWebhookVerify(req, res, runtime) {
1977
+ const url = new URL(req.url ?? "/", `http://${req.headers?.host ?? "localhost"}`);
1978
+ const mode = url.searchParams.get("hub.mode") ?? "";
1979
+ const token = url.searchParams.get("hub.verify_token") ?? "";
1980
+ const challenge = url.searchParams.get("hub.challenge") ?? "";
1981
+ const service = runtime.getService("whatsapp");
1982
+ if (!service || typeof service.verifyWebhook !== "function") {
1983
+ res.status(503).json({ error: "WhatsApp service unavailable" });
1984
+ return;
1985
+ }
1986
+ const verifiedChallenge = service.verifyWebhook(mode, token, challenge);
1987
+ if (!verifiedChallenge) {
1988
+ res.status(403).json({ error: "Webhook verification failed" });
1989
+ return;
1990
+ }
1991
+ res.status(200).json(verifiedChallenge);
1992
+ }
1993
+ async function handleWebhookEvent(req, res, runtime) {
1994
+ const service = runtime.getService("whatsapp");
1995
+ if (!service || typeof service.handleWebhook !== "function") {
1996
+ res.status(503).json({ error: "WhatsApp service unavailable" });
1997
+ return;
1998
+ }
1999
+ const body = req.body;
2000
+ if (!body) {
2001
+ res.status(400).json({ error: "Missing request body" });
2002
+ return;
2003
+ }
2004
+ await service.handleWebhook(body);
2005
+ res.status(200).json("EVENT_RECEIVED");
2006
+ }
2007
+ async function handlePair(req, res, runtime) {
2008
+ cleanupStaleSessions();
2009
+ const setupService = getSetupService(runtime);
2010
+ const body = req.body;
2011
+ let accountId;
2012
+ try {
2013
+ accountId = sanitizeAccountId(body && typeof body.accountId === "string" && body.accountId.trim() ? body.accountId.trim() : "default");
2014
+ } catch (err) {
2015
+ res.status(400).json({ error: err.message });
2016
+ return;
2017
+ }
2018
+ const isReplacing = whatsappPairingSessions.has(accountId);
2019
+ if (!isReplacing && whatsappPairingSessions.size >= MAX_PAIRING_SESSIONS) {
2020
+ res.status(429).json({
2021
+ error: `Too many concurrent pairing sessions (max ${MAX_PAIRING_SESSIONS})`
2022
+ });
2023
+ return;
2024
+ }
2025
+ const workspaceDir = setupService?.getWorkspaceDir() ?? ".";
2026
+ const authDir = path2.join(workspaceDir, "whatsapp-auth", accountId);
2027
+ whatsappPairingSessions.get(accountId)?.stop();
2028
+ const session = new WhatsAppPairingSession({
2029
+ authDir,
2030
+ accountId,
2031
+ onEvent: (event) => {
2032
+ setupService?.broadcastWs(event);
2033
+ if (event.status === "connected") {
2034
+ if (setupService) {
2035
+ setupService.updateConfig((config) => {
2036
+ if (!config.connectors)
2037
+ config.connectors = {};
2038
+ const connectors = config.connectors;
2039
+ connectors.whatsapp = {
2040
+ ...connectors.whatsapp ?? {},
2041
+ authDir,
2042
+ enabled: true
2043
+ };
2044
+ });
2045
+ const phoneNumber = event.phoneNumber;
2046
+ setupService.setOwnerContact({
2047
+ source: "whatsapp",
2048
+ channelId: phoneNumber ?? undefined
2049
+ });
2050
+ }
2051
+ }
2052
+ }
2053
+ });
2054
+ whatsappPairingSessions.set(accountId, session);
2055
+ try {
2056
+ await session.start();
2057
+ res.status(200).json({ ok: true, accountId, status: session.getStatus() });
2058
+ } catch (err) {
2059
+ res.status(500).json({ ok: false, error: String(err) });
2060
+ }
2061
+ }
2062
+ async function handleStatus(req, res, runtime) {
2063
+ cleanupStaleSessions();
2064
+ const setupService = getSetupService(runtime);
2065
+ const url = new URL(req.url ?? "/", `http://${req.headers?.host ?? "localhost"}`);
2066
+ let accountId;
2067
+ try {
2068
+ accountId = sanitizeAccountId(url.searchParams.get("accountId") || "default");
2069
+ } catch (err) {
2070
+ res.status(400).json({ error: err.message });
2071
+ return;
2072
+ }
2073
+ const session = whatsappPairingSessions.get(accountId);
2074
+ const workspaceDir = setupService?.getWorkspaceDir() ?? ".";
2075
+ let serviceConnected = false;
2076
+ let servicePhone = null;
2077
+ try {
2078
+ const waService = runtime.getService("whatsapp");
2079
+ if (waService && typeof waService === "object") {
2080
+ const waState = waService;
2081
+ serviceConnected = Boolean(waState.connected);
2082
+ servicePhone = typeof waState.phoneNumber === "string" ? waState.phoneNumber : null;
2083
+ }
2084
+ } catch {}
2085
+ res.status(200).json({
2086
+ accountId,
2087
+ status: session?.getStatus() ?? "idle",
2088
+ authExists: whatsappAuthExists(workspaceDir, accountId),
2089
+ serviceConnected,
2090
+ servicePhone
2091
+ });
2092
+ }
2093
+ async function handlePairStop(req, res, _runtime) {
2094
+ const body = req.body;
2095
+ let accountId;
2096
+ try {
2097
+ accountId = sanitizeAccountId(body && typeof body.accountId === "string" && body.accountId.trim() ? body.accountId.trim() : "default");
2098
+ } catch (err) {
2099
+ res.status(400).json({ error: err.message });
2100
+ return;
2101
+ }
2102
+ const session = whatsappPairingSessions.get(accountId);
2103
+ if (session) {
2104
+ session.stop();
2105
+ whatsappPairingSessions.delete(accountId);
2106
+ }
2107
+ res.status(200).json({ ok: true, accountId, status: "idle" });
2108
+ }
2109
+ async function handleDisconnect(req, res, runtime) {
2110
+ const setupService = getSetupService(runtime);
2111
+ const body = req.body;
2112
+ let accountId;
2113
+ try {
2114
+ accountId = sanitizeAccountId(body && typeof body.accountId === "string" && body.accountId.trim() ? body.accountId.trim() : "default");
2115
+ } catch (err) {
2116
+ res.status(400).json({ error: err.message });
2117
+ return;
2118
+ }
2119
+ const session = whatsappPairingSessions.get(accountId);
2120
+ if (session) {
2121
+ session.stop();
2122
+ whatsappPairingSessions.delete(accountId);
2123
+ }
2124
+ const workspaceDir = setupService?.getWorkspaceDir() ?? ".";
2125
+ try {
2126
+ await whatsappLogout(workspaceDir, accountId);
2127
+ } catch (logoutErr) {
2128
+ console.warn(`[whatsapp] Logout failed for ${accountId}, deleting auth files directly:`, String(logoutErr));
2129
+ const authDir = path2.join(workspaceDir, "whatsapp-auth", accountId);
2130
+ try {
2131
+ fs2.rmSync(authDir, { recursive: true, force: true });
2132
+ } catch {}
2133
+ }
2134
+ if (setupService) {
2135
+ setupService.updateConfig((config) => {
2136
+ const connectors = config.connectors;
2137
+ if (connectors) {
2138
+ delete connectors.whatsapp;
2139
+ }
2140
+ });
2141
+ }
2142
+ res.status(200).json({ ok: true, accountId });
2143
+ }
2144
+ var whatsappSetupRoutes = [
2145
+ {
2146
+ name: "whatsapp-webhook-verify",
2147
+ type: "GET",
2148
+ path: "/api/whatsapp/webhook",
2149
+ handler: handleWebhookVerify,
2150
+ rawPath: true,
2151
+ public: true
2152
+ },
2153
+ {
2154
+ name: "whatsapp-webhook-event",
2155
+ type: "POST",
2156
+ path: "/api/whatsapp/webhook",
2157
+ handler: handleWebhookEvent,
2158
+ rawPath: true,
2159
+ public: true
2160
+ },
2161
+ {
2162
+ type: "POST",
2163
+ path: "/api/whatsapp/pair",
2164
+ handler: handlePair,
2165
+ rawPath: true
2166
+ },
2167
+ {
2168
+ type: "GET",
2169
+ path: "/api/whatsapp/status",
2170
+ handler: handleStatus,
2171
+ rawPath: true
2172
+ },
2173
+ {
2174
+ type: "POST",
2175
+ path: "/api/whatsapp/pair/stop",
2176
+ handler: handlePairStop,
2177
+ rawPath: true
2178
+ },
2179
+ {
2180
+ type: "POST",
2181
+ path: "/api/whatsapp/disconnect",
2182
+ handler: handleDisconnect,
2183
+ rawPath: true
2184
+ }
2185
+ ];
2186
+ function stopAllPairingSessions() {
2187
+ for (const session of whatsappPairingSessions.values()) {
2188
+ try {
2189
+ session.stop();
2190
+ } catch {}
2191
+ }
2192
+ whatsappPairingSessions.clear();
2193
+ }
2194
+ // src/utils/config-detector.ts
2195
+ function detectAuthMethod(config) {
2196
+ const explicitMethod = config.authMethod;
2197
+ if (explicitMethod !== undefined) {
2198
+ if (explicitMethod === "baileys" || explicitMethod === "cloudapi") {
2199
+ return explicitMethod;
2200
+ }
2201
+ throw new Error(`Invalid authMethod: "${String(explicitMethod)}". Must be either "baileys" or "cloudapi".`);
2202
+ }
2203
+ if ("authDir" in config && config.authDir) {
2204
+ return "baileys";
2205
+ }
2206
+ if ("accessToken" in config && "phoneNumberId" in config) {
2207
+ return "cloudapi";
2208
+ }
2209
+ throw new Error("Cannot detect auth method. Provide either authDir (Baileys) or accessToken + phoneNumberId (Cloud API).");
2210
+ }
2211
+
2212
+ // src/clients/factory.ts
2213
+ var ClientFactory = {
2214
+ create(config) {
2215
+ const authMethod = detectAuthMethod(config);
2216
+ if (authMethod === "baileys") {
2217
+ return new BaileysClient(config);
2218
+ }
2219
+ return new WhatsAppClient(config);
2220
+ }
2221
+ };
2222
+ // src/types.ts
2223
+ var WhatsAppEventType;
2224
+ ((WhatsAppEventType2) => {
2225
+ WhatsAppEventType2["MESSAGE_RECEIVED"] = "WHATSAPP_MESSAGE_RECEIVED";
2226
+ WhatsAppEventType2["MESSAGE_SENT"] = "WHATSAPP_MESSAGE_SENT";
2227
+ WhatsAppEventType2["MESSAGE_DELIVERED"] = "WHATSAPP_MESSAGE_DELIVERED";
2228
+ WhatsAppEventType2["MESSAGE_READ"] = "WHATSAPP_MESSAGE_READ";
2229
+ WhatsAppEventType2["MESSAGE_FAILED"] = "WHATSAPP_MESSAGE_FAILED";
2230
+ WhatsAppEventType2["REACTION_RECEIVED"] = "WHATSAPP_REACTION_RECEIVED";
2231
+ WhatsAppEventType2["REACTION_SENT"] = "WHATSAPP_REACTION_SENT";
2232
+ WhatsAppEventType2["INTERACTIVE_REPLY"] = "WHATSAPP_INTERACTIVE_REPLY";
2233
+ WhatsAppEventType2["WEBHOOK_VERIFIED"] = "WHATSAPP_WEBHOOK_VERIFIED";
2234
+ })(WhatsAppEventType ||= {});
2235
+ var WHATSAPP_REACTIONS = {
2236
+ THUMBS_UP: "\uD83D\uDC4D",
2237
+ THUMBS_DOWN: "\uD83D\uDC4E",
2238
+ HEART: "❤️",
2239
+ LAUGHING: "\uD83D\uDE02",
2240
+ SURPRISED: "\uD83D\uDE2E",
2241
+ SAD: "\uD83D\uDE22",
2242
+ PRAYING: "\uD83D\uDE4F",
2243
+ CLAPPING: "\uD83D\uDC4F",
2244
+ FIRE: "\uD83D\uDD25",
2245
+ CELEBRATION: "\uD83C\uDF89"
2246
+ };
2247
+
2248
+ // src/index.ts
2249
+ var whatsappPlugin = {
2250
+ name: "whatsapp",
2251
+ description: "WhatsApp integration for ElizaOS (Cloud API + Baileys)",
2252
+ actions: [sendMessageAction, sendReactionAction],
2253
+ services: [WhatsAppConnectorService],
2254
+ routes: whatsappSetupRoutes
2255
+ };
2256
+ var src_default = whatsappPlugin;
2257
+ export {
2258
+ whatsappSetupRoutes,
2259
+ whatsappLogout,
2260
+ whatsappAuthExists,
2261
+ truncateText,
2262
+ stopAllPairingSessions,
2263
+ sanitizeAccountId as sanitizeWhatsAppAccountId,
2264
+ resolveWhatsAppToken,
2265
+ resolveWhatsAppSystemLocation,
2266
+ resolveWhatsAppGroupConfig,
2267
+ resolveWhatsAppAccount,
2268
+ resolveDefaultWhatsAppAccountId,
2269
+ normalizeWhatsAppTarget,
2270
+ normalizeE164,
2271
+ normalizeAccountId,
2272
+ listWhatsAppAccountIds,
2273
+ listEnabledWhatsAppAccounts,
2274
+ isWhatsAppUserTarget,
2275
+ isWhatsAppUserAllowed,
2276
+ isWhatsAppMentionRequired,
2277
+ isWhatsAppGroupJid,
2278
+ isWhatsAppGroup,
2279
+ isValidWhatsAppNumber,
2280
+ isMultiAccountEnabled,
2281
+ getWhatsAppChatType,
2282
+ formatWhatsAppPhoneNumber,
2283
+ formatWhatsAppId,
2284
+ src_default as default,
2285
+ chunkWhatsAppText,
2286
+ checkWhatsAppUserAccess,
2287
+ buildWhatsAppUserJid,
2288
+ WhatsAppPairingSession,
2289
+ WhatsAppEventType,
2290
+ WhatsAppConnectorService,
2291
+ WHATSAPP_TEXT_CHUNK_LIMIT,
2292
+ WHATSAPP_REACTIONS,
2293
+ DEFAULT_ACCOUNT_ID,
2294
+ ClientFactory
2295
+ };
2296
+
2297
+ //# debugId=60D99E2A9F2E72FB64756E2164756E21
2298
+ //# sourceMappingURL=index.js.map