@elizaos/plugin-whatsapp 2.0.0-alpha.5 → 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.
- package/README.md +278 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/index.js +2298 -0
- package/dist/index.js.map +26 -0
- package/dist/src/accounts.d.ts +217 -0
- package/dist/src/accounts.d.ts.map +1 -0
- package/dist/src/actions/index.d.ts +6 -0
- package/dist/src/actions/index.d.ts.map +1 -0
- package/dist/src/actions/sendMessage.d.ts +4 -0
- package/dist/src/actions/sendMessage.d.ts.map +1 -0
- package/dist/src/actions/sendReaction.d.ts +4 -0
- package/dist/src/actions/sendReaction.d.ts.map +1 -0
- package/dist/src/baileys/auth.d.ts +10 -0
- package/dist/src/baileys/auth.d.ts.map +1 -0
- package/dist/src/baileys/connection.d.ts +19 -0
- package/dist/src/baileys/connection.d.ts.map +1 -0
- package/dist/src/baileys/index.d.ts +5 -0
- package/dist/src/baileys/index.d.ts.map +1 -0
- package/dist/src/baileys/message-adapter.d.ts +14 -0
- package/dist/src/baileys/message-adapter.d.ts.map +1 -0
- package/dist/src/baileys/qr-code.d.ts +6 -0
- package/dist/src/baileys/qr-code.d.ts.map +1 -0
- package/dist/src/client.d.ts +139 -0
- package/dist/src/client.d.ts.map +1 -0
- package/dist/src/clients/baileys-client.d.ts +26 -0
- package/dist/src/clients/baileys-client.d.ts.map +1 -0
- package/dist/src/clients/factory.d.ts +6 -0
- package/dist/src/clients/factory.d.ts.map +1 -0
- package/dist/src/clients/index.d.ts +4 -0
- package/dist/src/clients/index.d.ts.map +1 -0
- package/dist/src/clients/interface.d.ts +10 -0
- package/dist/src/clients/interface.d.ts.map +1 -0
- package/dist/src/config.d.ts +144 -0
- package/dist/src/config.d.ts.map +1 -0
- package/dist/src/handlers/index.d.ts +3 -0
- package/dist/src/handlers/index.d.ts.map +1 -0
- package/dist/src/handlers/message.handler.d.ts +8 -0
- package/dist/src/handlers/message.handler.d.ts.map +1 -0
- package/dist/src/handlers/webhook.handler.d.ts +7 -0
- package/dist/src/handlers/webhook.handler.d.ts.map +1 -0
- package/dist/src/index.d.ts +62 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/normalize.d.ts +72 -0
- package/dist/src/normalize.d.ts.map +1 -0
- package/dist/src/pairing-service.d.ts +54 -0
- package/dist/src/pairing-service.d.ts.map +1 -0
- package/dist/src/runtime-service.d.ts +50 -0
- package/dist/src/runtime-service.d.ts.map +1 -0
- package/dist/src/service.d.ts +157 -0
- package/dist/src/service.d.ts.map +1 -0
- package/dist/src/setup-routes.d.ts +26 -0
- package/dist/src/setup-routes.d.ts.map +1 -0
- package/dist/src/types.d.ts +396 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/utils/config-detector.d.ts +5 -0
- package/dist/src/utils/config-detector.d.ts.map +1 -0
- package/dist/src/utils/index.d.ts +3 -0
- package/dist/src/utils/index.d.ts.map +1 -0
- package/dist/src/utils/validators.d.ts +14 -0
- package/dist/src/utils/validators.d.ts.map +1 -0
- package/package.json +41 -17
- 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
|