@openclaw/whatsapp 2026.5.7 → 2026.5.10-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{access-control-CTj7P7WP.js → access-control-CdP8S3rJ.js} +66 -111
- package/dist/{action-runtime-BqYZmBX_.js → action-runtime-CMhO8liB.js} +1 -1
- package/dist/action-runtime-api.js +1 -1
- package/dist/action-runtime.runtime.js +1 -1
- package/dist/api.js +120 -12
- package/dist/{auth-store-BNZmNP-s.js → auth-store-CjEimXKX.js} +19 -41
- package/dist/{channel-CTr3YjO8.js → channel-BxntAClG.js} +39 -12
- package/dist/channel-config-api.js +1 -1
- package/dist/channel-plugin-api.js +1 -1
- package/dist/{channel-react-action-BuSTzmDX.js → channel-react-action-DJvzhIPw.js} +1 -1
- package/dist/{channel.runtime-C3i9Svfj.js → channel.runtime-CNdPCK3G.js} +5 -5
- package/dist/{channel.setup-BYtkDwHe.js → channel.setup-BqlcEVzR.js} +4 -4
- package/dist/{connection-controller-BCIuChXD.js → connection-controller-DX2phM7V.js} +6 -197
- package/dist/constants.js +1 -1
- package/dist/contract-api.js +7 -7
- package/dist/{directory-config-BNCbgnmy.js → directory-config-o-muXLgk.js} +1 -1
- package/dist/directory-contract-api.js +1 -1
- package/dist/doctor-contract-api.js +1 -1
- package/dist/{identity-DhaC0MoD.js → identity-KxdJfH7p.js} +1 -1
- package/dist/legacy-session-surface-api.js +1 -1
- package/dist/legacy-state-migrations-api.js +1 -1
- package/dist/light-runtime-api.js +3 -3
- package/dist/{login-DSUTCcJ7.js → login-DV99D0IN.js} +4 -3
- package/dist/{login-qr-dfs8tfGo.js → login-qr-cKndAXTv.js} +3 -2
- package/dist/login-qr-runtime.js +1 -1
- package/dist/{monitor-C5_C_RGJ.js → monitor-mfy3Mmq-.js} +111 -687
- package/dist/{outbound-adapter-CbR17Ehj.js → outbound-adapter-B9ZW8sht.js} +3 -3
- package/dist/{outbound-base-BmzhutvF.js → outbound-base-MVpg2zU2.js} +8 -3
- package/dist/{outbound-media-contract-BPdOq7Hb.js → outbound-media-contract-BzWwBppP.js} +37 -41
- package/dist/outbound-payload-test-api.js +1 -1
- package/dist/{quoted-message-BXs55Teh.js → quoted-message-DQ6hAr34.js} +1 -1
- package/dist/runtime-api.js +11 -10
- package/dist/security-contract-api.js +1 -1
- package/dist/send-api-DsbCqDre.js +697 -0
- package/dist/{send-CxctcFGT.js → send-u88yh8Hw.js} +2 -3
- package/dist/session-DeIU_vjJ.js +198 -0
- package/dist/{session.runtime-C1eSE_KK.js → session.runtime-B9VlYnWQ.js} +1 -1
- package/dist/{setup-surface-CF3l3gEZ.js → setup-finalize-D3YuMUBs.js} +5 -52
- package/dist/setup-plugin-api.js +1 -1
- package/dist/setup-surface-CHjYlKJc.js +45 -0
- package/dist/{shared-CTSTwprn.js → shared-CgYT3IzU.js} +7 -7
- package/dist/{state-migrations-DnCdvoYD.js → state-migrations-CtdIArlK.js} +2 -1
- package/dist/test-api.js +2 -2
- package/dist/{text-runtime-DFFwk5z6.js → text-runtime-DBKC9uEi.js} +27 -2
- package/package.json +6 -6
- package/dist/rolldown-runtime-RkAeH_Qm.js +0 -27
- /package/dist/{active-listener-N7GFKsuN.js → active-listener-D2NHQ5Og.js} +0 -0
- /package/dist/{agent-tools-login-DpRxLNRB.js → agent-tools-login-DYKYW5Nh.js} +0 -0
- /package/dist/{audio-preflight.runtime-Bcdsl_ri.js → audio-preflight.runtime-CIFjeaEN.js} +0 -0
- /package/dist/{command-policy-O8sRnqUV.js → command-policy-BgZRZ31o.js} +0 -0
- /package/dist/{config-schema-Dmo66Ubb.js → config-schema-cSC7M4YA.js} +0 -0
- /package/dist/{constants-DLGCIbfl.js → constants-yKXGCGU9.js} +0 -0
- /package/dist/{doctor-A9tXm9Om.js → doctor-BrgepDUP.js} +0 -0
- /package/dist/{group-session-contract-B8QBn0I2.js → group-session-contract-DL-SLclv.js} +0 -0
- /package/dist/{reply-resolver.runtime-BRCgoO4C.js → reply-resolver.runtime-CROyXXQh.js} +0 -0
- /package/dist/{runtime-6fZ67abR.js → runtime-DdodQB8G.js} +0 -0
- /package/dist/{runtime-group-policy-Gp1lQ0at.js → runtime-group-policy-BH_U23qp.js} +0 -0
- /package/dist/{security-contract-DqH8JDX6.js → security-contract-BzlZFreO.js} +0 -0
- /package/dist/{session-contract-B0efQf9P.js → session-contract-BNUscPf4.js} +0 -0
- /package/dist/{setup-core-DZKvpzbH.js → setup-core-DnhO0PjG.js} +0 -0
|
@@ -0,0 +1,697 @@
|
|
|
1
|
+
import { c as toWhatsappJidWithLid, i as jidToE164, s as toWhatsappJid } from "./text-runtime-DBKC9uEi.js";
|
|
2
|
+
import { n as isWhatsAppNewsletterJid } from "./normalize-target-nXxC_hxG.js";
|
|
3
|
+
import "./normalize-Y3eBdM8a.js";
|
|
4
|
+
import { t as buildQuotedMessageOptions } from "./quoted-message-DQ6hAr34.js";
|
|
5
|
+
import { c as resolveComparableIdentity } from "./identity-KxdJfH7p.js";
|
|
6
|
+
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
|
|
7
|
+
import { logVerbose } from "openclaw/plugin-sdk/runtime-env";
|
|
8
|
+
import { createMessageReceiptFromOutboundResults, listMessageReceiptPlatformIds } from "openclaw/plugin-sdk/channel-message";
|
|
9
|
+
import { extractMessageContent, getContentType, normalizeMessageContent } from "@whiskeysockets/baileys";
|
|
10
|
+
import { formatLocationText } from "openclaw/plugin-sdk/channel-inbound";
|
|
11
|
+
import { recordChannelActivity } from "openclaw/plugin-sdk/channel-activity-runtime";
|
|
12
|
+
//#region extensions/whatsapp/src/vcard.ts
|
|
13
|
+
const ALLOWED_VCARD_KEYS = new Set([
|
|
14
|
+
"FN",
|
|
15
|
+
"N",
|
|
16
|
+
"TEL"
|
|
17
|
+
]);
|
|
18
|
+
function parseVcard(vcard) {
|
|
19
|
+
if (!vcard) return { phones: [] };
|
|
20
|
+
const lines = vcard.split(/\r?\n/);
|
|
21
|
+
let nameFromN;
|
|
22
|
+
let nameFromFn;
|
|
23
|
+
const phones = [];
|
|
24
|
+
for (const rawLine of lines) {
|
|
25
|
+
const line = rawLine.trim();
|
|
26
|
+
if (!line) continue;
|
|
27
|
+
const colonIndex = line.indexOf(":");
|
|
28
|
+
if (colonIndex === -1) continue;
|
|
29
|
+
const key = line.slice(0, colonIndex).toUpperCase();
|
|
30
|
+
const rawValue = line.slice(colonIndex + 1).trim();
|
|
31
|
+
if (!rawValue) continue;
|
|
32
|
+
const baseKey = normalizeVcardKey(key);
|
|
33
|
+
if (!baseKey || !ALLOWED_VCARD_KEYS.has(baseKey)) continue;
|
|
34
|
+
const value = cleanVcardValue(rawValue);
|
|
35
|
+
if (!value) continue;
|
|
36
|
+
if (baseKey === "FN" && !nameFromFn) {
|
|
37
|
+
nameFromFn = normalizeVcardName(value);
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
if (baseKey === "N" && !nameFromN) {
|
|
41
|
+
nameFromN = normalizeVcardName(value);
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
if (baseKey === "TEL") {
|
|
45
|
+
const phone = normalizeVcardPhone(value);
|
|
46
|
+
if (phone) phones.push(phone);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return {
|
|
50
|
+
name: nameFromFn ?? nameFromN,
|
|
51
|
+
phones
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
function normalizeVcardKey(key) {
|
|
55
|
+
const [primary] = key.split(";");
|
|
56
|
+
if (!primary) return;
|
|
57
|
+
const segments = primary.split(".");
|
|
58
|
+
return segments[segments.length - 1] || void 0;
|
|
59
|
+
}
|
|
60
|
+
function cleanVcardValue(value) {
|
|
61
|
+
return value.replace(/\\n/gi, " ").replace(/\\,/g, ",").replace(/\\;/g, ";").trim();
|
|
62
|
+
}
|
|
63
|
+
function normalizeVcardName(value) {
|
|
64
|
+
return value.replace(/;/g, " ").replace(/\s+/g, " ").trim();
|
|
65
|
+
}
|
|
66
|
+
function normalizeVcardPhone(value) {
|
|
67
|
+
const trimmed = value.trim();
|
|
68
|
+
if (!trimmed) return "";
|
|
69
|
+
if (normalizeLowercaseStringOrEmpty(trimmed).startsWith("tel:")) return trimmed.slice(4).trim();
|
|
70
|
+
return trimmed;
|
|
71
|
+
}
|
|
72
|
+
//#endregion
|
|
73
|
+
//#region extensions/whatsapp/src/inbound/extract.ts
|
|
74
|
+
const MESSAGE_WRAPPER_KEYS = [
|
|
75
|
+
"botInvokeMessage",
|
|
76
|
+
"ephemeralMessage",
|
|
77
|
+
"viewOnceMessage",
|
|
78
|
+
"viewOnceMessageV2",
|
|
79
|
+
"viewOnceMessageV2Extension",
|
|
80
|
+
"documentWithCaptionMessage",
|
|
81
|
+
"groupMentionedMessage"
|
|
82
|
+
];
|
|
83
|
+
const MESSAGE_CONTENT_KEYS = [
|
|
84
|
+
"conversation",
|
|
85
|
+
"extendedTextMessage",
|
|
86
|
+
"imageMessage",
|
|
87
|
+
"videoMessage",
|
|
88
|
+
"audioMessage",
|
|
89
|
+
"documentMessage",
|
|
90
|
+
"stickerMessage",
|
|
91
|
+
"locationMessage",
|
|
92
|
+
"liveLocationMessage",
|
|
93
|
+
"contactMessage",
|
|
94
|
+
"contactsArrayMessage",
|
|
95
|
+
"buttonsResponseMessage",
|
|
96
|
+
"listResponseMessage",
|
|
97
|
+
"templateButtonReplyMessage",
|
|
98
|
+
"interactiveResponseMessage",
|
|
99
|
+
"buttonsMessage",
|
|
100
|
+
"listMessage"
|
|
101
|
+
];
|
|
102
|
+
function fallbackNormalizeMessageContent(message) {
|
|
103
|
+
let current = message;
|
|
104
|
+
while (current && typeof current === "object") {
|
|
105
|
+
let unwrapped = false;
|
|
106
|
+
for (const key of MESSAGE_WRAPPER_KEYS) {
|
|
107
|
+
const candidate = current[key];
|
|
108
|
+
if (candidate && typeof candidate === "object" && "message" in candidate && candidate.message) {
|
|
109
|
+
current = candidate.message;
|
|
110
|
+
unwrapped = true;
|
|
111
|
+
break;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
if (!unwrapped) break;
|
|
115
|
+
}
|
|
116
|
+
return current;
|
|
117
|
+
}
|
|
118
|
+
function normalizeMessage(message) {
|
|
119
|
+
if (typeof normalizeMessageContent === "function") return normalizeMessageContent(message);
|
|
120
|
+
return fallbackNormalizeMessageContent(message);
|
|
121
|
+
}
|
|
122
|
+
function fallbackGetContentType(message) {
|
|
123
|
+
const normalized = fallbackNormalizeMessageContent(message);
|
|
124
|
+
if (!normalized || typeof normalized !== "object") return;
|
|
125
|
+
for (const key of MESSAGE_CONTENT_KEYS) if (normalized[key] != null) return key;
|
|
126
|
+
}
|
|
127
|
+
function getMessageContentType(message) {
|
|
128
|
+
if (typeof getContentType === "function") return getContentType(message);
|
|
129
|
+
return fallbackGetContentType(message);
|
|
130
|
+
}
|
|
131
|
+
function extractMessage(message) {
|
|
132
|
+
if (typeof extractMessageContent === "function") return extractMessageContent(message);
|
|
133
|
+
const normalized = fallbackNormalizeMessageContent(message);
|
|
134
|
+
const contentType = fallbackGetContentType(normalized);
|
|
135
|
+
if (!normalized || !contentType || contentType === "conversation") return normalized;
|
|
136
|
+
const candidate = normalized[contentType];
|
|
137
|
+
return candidate && typeof candidate === "object" ? candidate : normalized;
|
|
138
|
+
}
|
|
139
|
+
function getFutureProofInnerMessage(message) {
|
|
140
|
+
const contentType = getMessageContentType(message);
|
|
141
|
+
const candidate = contentType ? message[contentType] : void 0;
|
|
142
|
+
if (candidate && typeof candidate === "object" && "message" in candidate && candidate.message && typeof candidate.message === "object") {
|
|
143
|
+
const inner = normalizeMessage(candidate.message);
|
|
144
|
+
if (inner) {
|
|
145
|
+
const innerType = getMessageContentType(inner);
|
|
146
|
+
if (innerType && innerType !== contentType) return inner;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
function buildMessageChain(message) {
|
|
151
|
+
const chain = [];
|
|
152
|
+
let current = normalizeMessage(message);
|
|
153
|
+
while (current && chain.length < 4) {
|
|
154
|
+
chain.push(current);
|
|
155
|
+
current = getFutureProofInnerMessage(current);
|
|
156
|
+
}
|
|
157
|
+
return chain;
|
|
158
|
+
}
|
|
159
|
+
function unwrapMessage(message) {
|
|
160
|
+
return buildMessageChain(message).at(-1);
|
|
161
|
+
}
|
|
162
|
+
function extractContextInfoFromMessage(message) {
|
|
163
|
+
const contentType = getMessageContentType(message);
|
|
164
|
+
const candidate = contentType ? message[contentType] : void 0;
|
|
165
|
+
const contextInfo = candidate && typeof candidate === "object" && "contextInfo" in candidate ? candidate.contextInfo : void 0;
|
|
166
|
+
if (contextInfo) return contextInfo;
|
|
167
|
+
const fallback = message.extendedTextMessage?.contextInfo ?? message.imageMessage?.contextInfo ?? message.videoMessage?.contextInfo ?? message.documentMessage?.contextInfo ?? message.audioMessage?.contextInfo ?? message.stickerMessage?.contextInfo ?? message.buttonsResponseMessage?.contextInfo ?? message.listResponseMessage?.contextInfo ?? message.templateButtonReplyMessage?.contextInfo ?? message.interactiveResponseMessage?.contextInfo ?? message.buttonsMessage?.contextInfo ?? message.listMessage?.contextInfo;
|
|
168
|
+
if (fallback) return fallback;
|
|
169
|
+
for (const value of Object.values(message)) {
|
|
170
|
+
if (!value || typeof value !== "object") continue;
|
|
171
|
+
if ("contextInfo" in value) {
|
|
172
|
+
const candidateContext = value.contextInfo;
|
|
173
|
+
if (candidateContext) return candidateContext;
|
|
174
|
+
}
|
|
175
|
+
if ("message" in value) {
|
|
176
|
+
const inner = value.message;
|
|
177
|
+
if (inner) {
|
|
178
|
+
const innerCtx = extractContextInfo(inner);
|
|
179
|
+
if (innerCtx) return innerCtx;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
function extractContextInfo(message) {
|
|
185
|
+
for (const candidate of buildMessageChain(message)) {
|
|
186
|
+
const contextInfo = extractContextInfoFromMessage(candidate);
|
|
187
|
+
if (contextInfo) return contextInfo;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
function extractMentionedJids(rawMessage) {
|
|
191
|
+
const message = unwrapMessage(rawMessage);
|
|
192
|
+
if (!message) return;
|
|
193
|
+
const flattened = [
|
|
194
|
+
message.extendedTextMessage?.contextInfo?.mentionedJid,
|
|
195
|
+
message.imageMessage?.contextInfo?.mentionedJid,
|
|
196
|
+
message.videoMessage?.contextInfo?.mentionedJid,
|
|
197
|
+
message.documentMessage?.contextInfo?.mentionedJid,
|
|
198
|
+
message.audioMessage?.contextInfo?.mentionedJid,
|
|
199
|
+
message.stickerMessage?.contextInfo?.mentionedJid,
|
|
200
|
+
message.buttonsResponseMessage?.contextInfo?.mentionedJid,
|
|
201
|
+
message.listResponseMessage?.contextInfo?.mentionedJid
|
|
202
|
+
].flatMap((arr) => arr ?? []).filter(Boolean);
|
|
203
|
+
if (flattened.length === 0) return;
|
|
204
|
+
return Array.from(new Set(flattened));
|
|
205
|
+
}
|
|
206
|
+
function extractText(rawMessage) {
|
|
207
|
+
const message = unwrapMessage(rawMessage);
|
|
208
|
+
if (!message) return;
|
|
209
|
+
const extracted = extractMessage(message);
|
|
210
|
+
const candidates = [message, extracted && extracted !== message ? extracted : void 0];
|
|
211
|
+
for (const candidate of candidates) {
|
|
212
|
+
if (!candidate) continue;
|
|
213
|
+
if (typeof candidate.conversation === "string" && candidate.conversation.trim()) return candidate.conversation.trim();
|
|
214
|
+
const extended = candidate.extendedTextMessage?.text;
|
|
215
|
+
if (extended?.trim()) return extended.trim();
|
|
216
|
+
const caption = candidate.imageMessage?.caption ?? candidate.videoMessage?.caption ?? candidate.documentMessage?.caption;
|
|
217
|
+
if (caption?.trim()) return caption.trim();
|
|
218
|
+
}
|
|
219
|
+
const contactPlaceholder = extractContactPlaceholder(message) ?? (extracted && extracted !== message ? extractContactPlaceholder(extracted) : void 0);
|
|
220
|
+
if (contactPlaceholder) return contactPlaceholder;
|
|
221
|
+
}
|
|
222
|
+
function extractMediaPlaceholder(rawMessage) {
|
|
223
|
+
const message = unwrapMessage(rawMessage);
|
|
224
|
+
if (!message) return;
|
|
225
|
+
if (message.imageMessage) return "<media:image>";
|
|
226
|
+
if (message.videoMessage) return "<media:video>";
|
|
227
|
+
if (message.audioMessage) return "<media:audio>";
|
|
228
|
+
if (message.documentMessage) return "<media:document>";
|
|
229
|
+
if (message.stickerMessage) return "<media:sticker>";
|
|
230
|
+
}
|
|
231
|
+
function extractContactPlaceholder(rawMessage) {
|
|
232
|
+
const contactContext = extractContactContext(rawMessage);
|
|
233
|
+
if (!contactContext) return;
|
|
234
|
+
if (contactContext.kind === "contact") return "<contact>";
|
|
235
|
+
const suffix = contactContext.total === 1 ? "contact" : "contacts";
|
|
236
|
+
return `<contacts: ${contactContext.total} ${suffix}>`;
|
|
237
|
+
}
|
|
238
|
+
function extractContactContext(rawMessage) {
|
|
239
|
+
const message = unwrapMessage(rawMessage);
|
|
240
|
+
if (!message) return;
|
|
241
|
+
const contact = message.contactMessage ?? void 0;
|
|
242
|
+
if (contact) {
|
|
243
|
+
const { name, phones } = describeContact({
|
|
244
|
+
displayName: contact.displayName,
|
|
245
|
+
vcard: contact.vcard
|
|
246
|
+
});
|
|
247
|
+
return {
|
|
248
|
+
kind: "contact",
|
|
249
|
+
total: 1,
|
|
250
|
+
contacts: [{
|
|
251
|
+
name,
|
|
252
|
+
phones
|
|
253
|
+
}]
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
const contactsArray = message.contactsArrayMessage?.contacts ?? void 0;
|
|
257
|
+
if (!contactsArray || contactsArray.length === 0) return;
|
|
258
|
+
return {
|
|
259
|
+
kind: "contacts",
|
|
260
|
+
total: contactsArray.length,
|
|
261
|
+
contacts: contactsArray.map((entry) => describeContact({
|
|
262
|
+
displayName: entry.displayName,
|
|
263
|
+
vcard: entry.vcard
|
|
264
|
+
}))
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
function describeContact(input) {
|
|
268
|
+
const displayName = (input.displayName ?? "").trim();
|
|
269
|
+
const parsed = parseVcard(input.vcard ?? void 0);
|
|
270
|
+
return {
|
|
271
|
+
name: displayName || parsed.name,
|
|
272
|
+
phones: parsed.phones
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
function extractLocationData(rawMessage) {
|
|
276
|
+
const message = unwrapMessage(rawMessage);
|
|
277
|
+
if (!message) return null;
|
|
278
|
+
const live = message.liveLocationMessage ?? void 0;
|
|
279
|
+
if (live) {
|
|
280
|
+
const latitudeRaw = live.degreesLatitude;
|
|
281
|
+
const longitudeRaw = live.degreesLongitude;
|
|
282
|
+
if (latitudeRaw != null && longitudeRaw != null) {
|
|
283
|
+
const latitude = latitudeRaw;
|
|
284
|
+
const longitude = longitudeRaw;
|
|
285
|
+
if (Number.isFinite(latitude) && Number.isFinite(longitude)) return {
|
|
286
|
+
latitude,
|
|
287
|
+
longitude,
|
|
288
|
+
accuracy: live.accuracyInMeters ?? void 0,
|
|
289
|
+
caption: live.caption ?? void 0,
|
|
290
|
+
source: "live",
|
|
291
|
+
isLive: true
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
const location = message.locationMessage ?? void 0;
|
|
296
|
+
if (location) {
|
|
297
|
+
const latitudeRaw = location.degreesLatitude;
|
|
298
|
+
const longitudeRaw = location.degreesLongitude;
|
|
299
|
+
if (latitudeRaw != null && longitudeRaw != null) {
|
|
300
|
+
const latitude = latitudeRaw;
|
|
301
|
+
const longitude = longitudeRaw;
|
|
302
|
+
if (Number.isFinite(latitude) && Number.isFinite(longitude)) {
|
|
303
|
+
const isLive = Boolean(location.isLive);
|
|
304
|
+
return {
|
|
305
|
+
latitude,
|
|
306
|
+
longitude,
|
|
307
|
+
accuracy: location.accuracyInMeters ?? void 0,
|
|
308
|
+
name: location.name ?? void 0,
|
|
309
|
+
address: location.address ?? void 0,
|
|
310
|
+
caption: location.comment ?? void 0,
|
|
311
|
+
source: isLive ? "live" : location.name || location.address ? "place" : "pin",
|
|
312
|
+
isLive
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
return null;
|
|
318
|
+
}
|
|
319
|
+
function describeReplyContext(rawMessage) {
|
|
320
|
+
const message = unwrapMessage(rawMessage);
|
|
321
|
+
if (!message) return null;
|
|
322
|
+
const contextInfo = extractContextInfo(message);
|
|
323
|
+
const quoted = normalizeMessage(contextInfo?.quotedMessage);
|
|
324
|
+
if (!quoted) return null;
|
|
325
|
+
const location = extractLocationData(quoted);
|
|
326
|
+
const locationText = location ? formatLocationText(location) : void 0;
|
|
327
|
+
let body = [extractText(quoted), locationText].filter(Boolean).join("\n").trim();
|
|
328
|
+
if (!body) body = extractMediaPlaceholder(quoted);
|
|
329
|
+
if (!body) {
|
|
330
|
+
const quotedType = quoted ? getMessageContentType(quoted) : void 0;
|
|
331
|
+
logVerbose(`Quoted message missing extractable body${quotedType ? ` (type ${quotedType})` : ""}`);
|
|
332
|
+
return null;
|
|
333
|
+
}
|
|
334
|
+
const senderJid = contextInfo?.participant ?? void 0;
|
|
335
|
+
const sender = resolveComparableIdentity({
|
|
336
|
+
jid: senderJid,
|
|
337
|
+
label: senderJid ? jidToE164(senderJid) ?? senderJid : "unknown sender"
|
|
338
|
+
});
|
|
339
|
+
return {
|
|
340
|
+
id: contextInfo?.stanzaId || void 0,
|
|
341
|
+
body,
|
|
342
|
+
sender
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
function hasInteractiveResponseContent(message) {
|
|
346
|
+
if (!message) return false;
|
|
347
|
+
return Boolean(message.buttonsResponseMessage || message.listResponseMessage || message.templateButtonReplyMessage || message.interactiveResponseMessage);
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* Fast check that a Baileys message carries user-visible inbound content
|
|
351
|
+
* (text, media, contact, location, button/list selection). Returns false for
|
|
352
|
+
* protocol/receipt/typing notifications that arrive on the same
|
|
353
|
+
* `messages.upsert` stream as real messages but should not trigger pairing
|
|
354
|
+
* access-control side effects.
|
|
355
|
+
*/
|
|
356
|
+
function hasInboundUserContent(rawMessage) {
|
|
357
|
+
if (!rawMessage) return false;
|
|
358
|
+
if (extractText(rawMessage)) return true;
|
|
359
|
+
if (extractMediaPlaceholder(rawMessage)) return true;
|
|
360
|
+
if (extractLocationData(rawMessage)) return true;
|
|
361
|
+
for (const candidate of buildMessageChain(rawMessage)) if (hasInteractiveResponseContent(candidate)) return true;
|
|
362
|
+
return false;
|
|
363
|
+
}
|
|
364
|
+
//#endregion
|
|
365
|
+
//#region extensions/whatsapp/src/inbound/outbound-mentions.ts
|
|
366
|
+
const CODE_FENCE_RE = /```[\s\S]*?```/g;
|
|
367
|
+
const INLINE_CODE_RE = /`[^`\n]+`/g;
|
|
368
|
+
const OUTBOUND_MENTION_RE = /@(\+?\d+)/g;
|
|
369
|
+
const KNOWN_USER_JID_RE = /^(\d+)(?::\d+)?@(s\.whatsapp\.net|hosted|lid|hosted\.lid|c\.us)$/i;
|
|
370
|
+
const PHONE_JID_DOMAIN_RE = /^(s\.whatsapp\.net|hosted|c\.us)$/i;
|
|
371
|
+
const LID_JID_DOMAIN_RE = /^(lid|hosted\.lid)$/i;
|
|
372
|
+
function isWhatsAppGroupJid(jid) {
|
|
373
|
+
return jid.endsWith("@g.us");
|
|
374
|
+
}
|
|
375
|
+
function mayContainWhatsAppOutboundMention(text) {
|
|
376
|
+
return /@\+?\d/.test(text);
|
|
377
|
+
}
|
|
378
|
+
function collectCodeRanges(text) {
|
|
379
|
+
const ranges = [];
|
|
380
|
+
for (const match of text.matchAll(CODE_FENCE_RE)) ranges.push({
|
|
381
|
+
start: match.index,
|
|
382
|
+
end: match.index + match[0].length
|
|
383
|
+
});
|
|
384
|
+
for (const match of text.matchAll(INLINE_CODE_RE)) {
|
|
385
|
+
const start = match.index;
|
|
386
|
+
if (ranges.some((range) => start >= range.start && start < range.end)) continue;
|
|
387
|
+
ranges.push({
|
|
388
|
+
start,
|
|
389
|
+
end: start + match[0].length
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
return ranges.toSorted((a, b) => a.start - b.start);
|
|
393
|
+
}
|
|
394
|
+
function isInRange(index, ranges) {
|
|
395
|
+
return ranges.some((range) => index >= range.start && index < range.end);
|
|
396
|
+
}
|
|
397
|
+
function normalizeKnownUserJid(value) {
|
|
398
|
+
const trimmed = value.replace(/^whatsapp:/i, "").trim();
|
|
399
|
+
const jidMatch = trimmed.match(KNOWN_USER_JID_RE);
|
|
400
|
+
if (jidMatch) {
|
|
401
|
+
const domain = jidMatch[2].toLowerCase() === "c.us" ? "s.whatsapp.net" : jidMatch[2].toLowerCase();
|
|
402
|
+
return `${jidMatch[1]}@${domain}`;
|
|
403
|
+
}
|
|
404
|
+
const digits = trimmed.startsWith("+") ? trimmed.replace(/\D/g, "") : /^\d+$/.test(trimmed) ? trimmed : "";
|
|
405
|
+
return digits ? `${digits}@s.whatsapp.net` : null;
|
|
406
|
+
}
|
|
407
|
+
function extractKnownJidParts(value) {
|
|
408
|
+
const normalized = normalizeKnownUserJid(value);
|
|
409
|
+
if (!normalized) return null;
|
|
410
|
+
const match = normalized.match(/^(\d+)@(.+)$/);
|
|
411
|
+
return match ? {
|
|
412
|
+
user: match[1],
|
|
413
|
+
domain: match[2]
|
|
414
|
+
} : null;
|
|
415
|
+
}
|
|
416
|
+
function extractPhoneDigits(value) {
|
|
417
|
+
if (!value) return null;
|
|
418
|
+
const trimmed = value.replace(/^whatsapp:/i, "").trim();
|
|
419
|
+
if (trimmed.startsWith("+") || /^\d+$/.test(trimmed)) return trimmed.replace(/\D/g, "") || null;
|
|
420
|
+
const parts = extractKnownJidParts(trimmed);
|
|
421
|
+
return parts && PHONE_JID_DOMAIN_RE.test(parts.domain) ? parts.user : null;
|
|
422
|
+
}
|
|
423
|
+
function extractLidDigits(value) {
|
|
424
|
+
if (!value) return null;
|
|
425
|
+
const parts = extractKnownJidParts(value);
|
|
426
|
+
return parts && LID_JID_DOMAIN_RE.test(parts.domain) ? parts.user : null;
|
|
427
|
+
}
|
|
428
|
+
function isLidJid(jid) {
|
|
429
|
+
const parts = extractKnownJidParts(jid);
|
|
430
|
+
return Boolean(parts && LID_JID_DOMAIN_RE.test(parts.domain));
|
|
431
|
+
}
|
|
432
|
+
function lidReplacementText(jid) {
|
|
433
|
+
const parts = extractKnownJidParts(jid);
|
|
434
|
+
if (!parts || !LID_JID_DOMAIN_RE.test(parts.domain)) return;
|
|
435
|
+
return `@${parts.user}`;
|
|
436
|
+
}
|
|
437
|
+
function participantValues(participant) {
|
|
438
|
+
return typeof participant === "string" ? { id: participant } : participant;
|
|
439
|
+
}
|
|
440
|
+
function chooseMentionJid(participant) {
|
|
441
|
+
const values = participantValues(participant);
|
|
442
|
+
const idJid = normalizeKnownUserJid(values.id ?? "");
|
|
443
|
+
const lidJid = normalizeKnownUserJid(values.lid ?? "");
|
|
444
|
+
return (idJid && isLidJid(idJid) ? idJid : null) ?? (lidJid && isLidJid(lidJid) ? lidJid : null) ?? idJid ?? lidJid ?? normalizeKnownUserJid(values.phoneNumber ?? "") ?? normalizeKnownUserJid(values.e164 ?? "");
|
|
445
|
+
}
|
|
446
|
+
function buildMentionTargetMaps(participants) {
|
|
447
|
+
const byPhone = /* @__PURE__ */ new Map();
|
|
448
|
+
const byLid = /* @__PURE__ */ new Map();
|
|
449
|
+
for (const participant of participants) {
|
|
450
|
+
const mentionJid = chooseMentionJid(participant);
|
|
451
|
+
if (!mentionJid) continue;
|
|
452
|
+
const target = {
|
|
453
|
+
mentionJid,
|
|
454
|
+
...isLidJid(mentionJid) ? { replacementText: lidReplacementText(mentionJid) } : {}
|
|
455
|
+
};
|
|
456
|
+
const values = participantValues(participant);
|
|
457
|
+
for (const value of [
|
|
458
|
+
values.id,
|
|
459
|
+
values.phoneNumber,
|
|
460
|
+
values.e164
|
|
461
|
+
]) {
|
|
462
|
+
const digits = extractPhoneDigits(value);
|
|
463
|
+
if (digits && !byPhone.has(digits)) byPhone.set(digits, target);
|
|
464
|
+
}
|
|
465
|
+
for (const value of [values.id, values.lid]) {
|
|
466
|
+
const digits = extractLidDigits(value);
|
|
467
|
+
if (digits && !byLid.has(digits)) byLid.set(digits, target);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
return {
|
|
471
|
+
byPhone,
|
|
472
|
+
byLid
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
function shouldSkipMentionAt(text, index, end, codeRanges) {
|
|
476
|
+
if (isInRange(index, codeRanges)) return true;
|
|
477
|
+
const previous = index > 0 ? text[index - 1] : "";
|
|
478
|
+
const next = text[end] ?? "";
|
|
479
|
+
return Boolean(previous && /[\w@]/.test(previous) || next && /[\w@]/.test(next));
|
|
480
|
+
}
|
|
481
|
+
function resolveWhatsAppOutboundMentions(params) {
|
|
482
|
+
if (!isWhatsAppGroupJid(params.chatJid) || !mayContainWhatsAppOutboundMention(params.text) || !params.participants?.length) return {
|
|
483
|
+
text: params.text,
|
|
484
|
+
mentionedJids: []
|
|
485
|
+
};
|
|
486
|
+
const { byPhone, byLid } = buildMentionTargetMaps(params.participants);
|
|
487
|
+
if (byPhone.size === 0 && byLid.size === 0) return {
|
|
488
|
+
text: params.text,
|
|
489
|
+
mentionedJids: []
|
|
490
|
+
};
|
|
491
|
+
const codeRanges = collectCodeRanges(params.text);
|
|
492
|
+
const replacements = [];
|
|
493
|
+
const mentionedJids = [];
|
|
494
|
+
const seenMentionJids = /* @__PURE__ */ new Set();
|
|
495
|
+
for (const match of params.text.matchAll(OUTBOUND_MENTION_RE)) {
|
|
496
|
+
const start = match.index;
|
|
497
|
+
const token = match[0];
|
|
498
|
+
if (shouldSkipMentionAt(params.text, start, start + token.length, codeRanges)) continue;
|
|
499
|
+
const digits = match[1].replace(/\D/g, "");
|
|
500
|
+
const target = token.startsWith("@+") ? byPhone.get(digits) ?? byLid.get(digits) : byLid.get(digits) ?? byPhone.get(digits);
|
|
501
|
+
if (!target) continue;
|
|
502
|
+
if (!seenMentionJids.has(target.mentionJid)) {
|
|
503
|
+
seenMentionJids.add(target.mentionJid);
|
|
504
|
+
mentionedJids.push(target.mentionJid);
|
|
505
|
+
}
|
|
506
|
+
if (target.replacementText && target.replacementText !== token) replacements.push({
|
|
507
|
+
start,
|
|
508
|
+
end: start + token.length,
|
|
509
|
+
text: target.replacementText
|
|
510
|
+
});
|
|
511
|
+
}
|
|
512
|
+
if (replacements.length === 0) return {
|
|
513
|
+
text: params.text,
|
|
514
|
+
mentionedJids
|
|
515
|
+
};
|
|
516
|
+
let text = "";
|
|
517
|
+
let cursor = 0;
|
|
518
|
+
for (const replacement of replacements) {
|
|
519
|
+
text += params.text.slice(cursor, replacement.start);
|
|
520
|
+
text += replacement.text;
|
|
521
|
+
cursor = replacement.end;
|
|
522
|
+
}
|
|
523
|
+
text += params.text.slice(cursor);
|
|
524
|
+
return {
|
|
525
|
+
text,
|
|
526
|
+
mentionedJids
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
function addWhatsAppOutboundMentionsToContent(content, mentionedJids) {
|
|
530
|
+
return mentionedJids.length > 0 ? {
|
|
531
|
+
...content,
|
|
532
|
+
mentions: [...mentionedJids]
|
|
533
|
+
} : content;
|
|
534
|
+
}
|
|
535
|
+
//#endregion
|
|
536
|
+
//#region extensions/whatsapp/src/inbound/send-result.ts
|
|
537
|
+
function resolveWhatsAppReceiptKind(kind) {
|
|
538
|
+
if (kind === "media" || kind === "text") return kind;
|
|
539
|
+
return "unknown";
|
|
540
|
+
}
|
|
541
|
+
function toReceiptSourceResult(key) {
|
|
542
|
+
return {
|
|
543
|
+
channel: "whatsapp",
|
|
544
|
+
messageId: key.id,
|
|
545
|
+
...key.remoteJid ? { toJid: key.remoteJid } : {},
|
|
546
|
+
meta: {
|
|
547
|
+
fromMe: key.fromMe,
|
|
548
|
+
participant: key.participant
|
|
549
|
+
}
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
function createWhatsAppSendReceipt(kind, keys) {
|
|
553
|
+
return createMessageReceiptFromOutboundResults({
|
|
554
|
+
kind: resolveWhatsAppReceiptKind(kind),
|
|
555
|
+
results: keys.map(toReceiptSourceResult)
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
function normalizeKey(key) {
|
|
559
|
+
const id = typeof key?.id === "string" ? key.id.trim() : "";
|
|
560
|
+
if (!id) return;
|
|
561
|
+
return {
|
|
562
|
+
id,
|
|
563
|
+
remoteJid: key?.remoteJid,
|
|
564
|
+
fromMe: key?.fromMe,
|
|
565
|
+
participant: key?.participant
|
|
566
|
+
};
|
|
567
|
+
}
|
|
568
|
+
function normalizeWhatsAppSendResult(result, kind) {
|
|
569
|
+
const key = normalizeKey(result?.key);
|
|
570
|
+
return {
|
|
571
|
+
kind,
|
|
572
|
+
messageId: key?.id ?? "unknown",
|
|
573
|
+
receipt: createWhatsAppSendReceipt(kind, key ? [key] : []),
|
|
574
|
+
keys: key ? [key] : [],
|
|
575
|
+
providerAccepted: Boolean(key)
|
|
576
|
+
};
|
|
577
|
+
}
|
|
578
|
+
function combineWhatsAppSendResults(kind, results) {
|
|
579
|
+
const messageIds = [...new Set(results.flatMap(listWhatsAppSendResultMessageIds))];
|
|
580
|
+
const keys = results.flatMap((result) => result.keys);
|
|
581
|
+
return {
|
|
582
|
+
kind,
|
|
583
|
+
messageId: messageIds[0] ?? "unknown",
|
|
584
|
+
receipt: createWhatsAppSendReceipt(kind, keys),
|
|
585
|
+
keys,
|
|
586
|
+
providerAccepted: results.some((result) => result.providerAccepted)
|
|
587
|
+
};
|
|
588
|
+
}
|
|
589
|
+
function listWhatsAppSendResultMessageIds(result) {
|
|
590
|
+
const receiptIds = result.receipt ? listMessageReceiptPlatformIds(result.receipt) : [];
|
|
591
|
+
if (receiptIds.length > 0) return receiptIds;
|
|
592
|
+
const keyIds = result.keys.map((key) => key.id.trim()).filter(Boolean);
|
|
593
|
+
if (keyIds.length > 0) return [...new Set(keyIds)];
|
|
594
|
+
return [];
|
|
595
|
+
}
|
|
596
|
+
//#endregion
|
|
597
|
+
//#region extensions/whatsapp/src/inbound/send-api.ts
|
|
598
|
+
function recordWhatsAppOutbound(accountId) {
|
|
599
|
+
recordChannelActivity({
|
|
600
|
+
channel: "whatsapp",
|
|
601
|
+
accountId,
|
|
602
|
+
direction: "outbound"
|
|
603
|
+
});
|
|
604
|
+
}
|
|
605
|
+
function createWebSendApi(params) {
|
|
606
|
+
const resolveOutboundJid = (recipient) => params.authDir ? toWhatsappJidWithLid(recipient, { authDir: params.authDir }) : toWhatsappJid(recipient);
|
|
607
|
+
const resolveMentions = async (jid, text) => params.resolveOutboundMentions ? await params.resolveOutboundMentions({
|
|
608
|
+
jid,
|
|
609
|
+
text
|
|
610
|
+
}) : {
|
|
611
|
+
text,
|
|
612
|
+
mentionedJids: []
|
|
613
|
+
};
|
|
614
|
+
return {
|
|
615
|
+
sendMessage: async (to, text, mediaBuffer, mediaType, sendOptions) => {
|
|
616
|
+
const jid = resolveOutboundJid(to);
|
|
617
|
+
let payload;
|
|
618
|
+
if (mediaBuffer) mediaType ??= "application/octet-stream";
|
|
619
|
+
const shouldSendAudioText = Boolean(mediaBuffer && mediaType?.startsWith("audio/") && text.trim());
|
|
620
|
+
const resolvedPayloadText = shouldSendAudioText ? {
|
|
621
|
+
text,
|
|
622
|
+
mentionedJids: []
|
|
623
|
+
} : await resolveMentions(jid, text);
|
|
624
|
+
if (mediaBuffer && mediaType) if (mediaType.startsWith("image/")) payload = {
|
|
625
|
+
image: mediaBuffer,
|
|
626
|
+
caption: resolvedPayloadText.text || void 0,
|
|
627
|
+
mimetype: mediaType
|
|
628
|
+
};
|
|
629
|
+
else if (mediaType.startsWith("audio/")) payload = {
|
|
630
|
+
audio: mediaBuffer,
|
|
631
|
+
ptt: true,
|
|
632
|
+
mimetype: mediaType
|
|
633
|
+
};
|
|
634
|
+
else if (mediaType.startsWith("video/")) {
|
|
635
|
+
const gifPlayback = sendOptions?.gifPlayback;
|
|
636
|
+
payload = {
|
|
637
|
+
video: mediaBuffer,
|
|
638
|
+
caption: resolvedPayloadText.text || void 0,
|
|
639
|
+
mimetype: mediaType,
|
|
640
|
+
...gifPlayback ? { gifPlayback: true } : {}
|
|
641
|
+
};
|
|
642
|
+
} else payload = {
|
|
643
|
+
document: mediaBuffer,
|
|
644
|
+
fileName: sendOptions?.fileName?.trim() || "file",
|
|
645
|
+
caption: resolvedPayloadText.text || void 0,
|
|
646
|
+
mimetype: mediaType
|
|
647
|
+
};
|
|
648
|
+
else payload = { text: resolvedPayloadText.text };
|
|
649
|
+
payload = addWhatsAppOutboundMentionsToContent(payload, resolvedPayloadText.mentionedJids);
|
|
650
|
+
const quotedOpts = buildQuotedMessageOptions({
|
|
651
|
+
messageId: sendOptions?.quotedMessageKey?.id,
|
|
652
|
+
remoteJid: sendOptions?.quotedMessageKey?.remoteJid,
|
|
653
|
+
fromMe: sendOptions?.quotedMessageKey?.fromMe,
|
|
654
|
+
participant: sendOptions?.quotedMessageKey?.participant,
|
|
655
|
+
messageText: sendOptions?.quotedMessageKey?.messageText
|
|
656
|
+
});
|
|
657
|
+
const results = [normalizeWhatsAppSendResult(quotedOpts ? await params.sock.sendMessage(jid, payload, quotedOpts) : await params.sock.sendMessage(jid, payload), mediaBuffer ? "media" : "text")];
|
|
658
|
+
if (shouldSendAudioText) {
|
|
659
|
+
const resolvedAudioText = await resolveMentions(jid, text);
|
|
660
|
+
const textPayload = addWhatsAppOutboundMentionsToContent({ text: resolvedAudioText.text }, resolvedAudioText.mentionedJids);
|
|
661
|
+
const textResult = quotedOpts ? await params.sock.sendMessage(jid, textPayload, quotedOpts) : await params.sock.sendMessage(jid, textPayload);
|
|
662
|
+
results.push(normalizeWhatsAppSendResult(textResult, "text"));
|
|
663
|
+
}
|
|
664
|
+
recordWhatsAppOutbound(sendOptions?.accountId ?? params.defaultAccountId);
|
|
665
|
+
return combineWhatsAppSendResults(mediaBuffer ? "media" : "text", results);
|
|
666
|
+
},
|
|
667
|
+
sendPoll: async (to, poll) => {
|
|
668
|
+
const jid = resolveOutboundJid(to);
|
|
669
|
+
const result = await params.sock.sendMessage(jid, { poll: {
|
|
670
|
+
name: poll.question,
|
|
671
|
+
values: poll.options,
|
|
672
|
+
selectableCount: poll.maxSelections ?? 1
|
|
673
|
+
} });
|
|
674
|
+
recordWhatsAppOutbound(params.defaultAccountId);
|
|
675
|
+
return normalizeWhatsAppSendResult(result, "poll");
|
|
676
|
+
},
|
|
677
|
+
sendReaction: async (chatJid, messageId, emoji, fromMe, participant) => {
|
|
678
|
+
const jid = toWhatsappJid(chatJid);
|
|
679
|
+
return normalizeWhatsAppSendResult(await params.sock.sendMessage(jid, { react: {
|
|
680
|
+
text: emoji,
|
|
681
|
+
key: {
|
|
682
|
+
remoteJid: jid,
|
|
683
|
+
id: messageId,
|
|
684
|
+
fromMe,
|
|
685
|
+
participant: participant ? toWhatsappJid(participant) : void 0
|
|
686
|
+
}
|
|
687
|
+
} }), "reaction");
|
|
688
|
+
},
|
|
689
|
+
sendComposingTo: async (to) => {
|
|
690
|
+
const jid = resolveOutboundJid(to);
|
|
691
|
+
if (isWhatsAppNewsletterJid(jid)) return;
|
|
692
|
+
await params.sock.sendPresenceUpdate("composing", jid);
|
|
693
|
+
}
|
|
694
|
+
};
|
|
695
|
+
}
|
|
696
|
+
//#endregion
|
|
697
|
+
export { mayContainWhatsAppOutboundMention as a, extractContactContext as c, extractMediaPlaceholder as d, extractMentionedJids as f, addWhatsAppOutboundMentionsToContent as i, extractContextInfo as l, hasInboundUserContent as m, listWhatsAppSendResultMessageIds as n, resolveWhatsAppOutboundMentions as o, extractText as p, normalizeWhatsAppSendResult as r, describeReplyContext as s, createWebSendApi as t, extractLocationData as u };
|