my-baileys 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (144) hide show
  1. package/README.md +1145 -0
  2. package/WAProto/GenerateStatics.sh +3 -0
  3. package/WAProto/WAProto.proto +8083 -0
  4. package/WAProto/fix-imports.js +85 -0
  5. package/WAProto/index.d.ts +29095 -0
  6. package/WAProto/index.js +172336 -0
  7. package/engine-requirements.js +10 -0
  8. package/lib/Defaults/index.js +194 -0
  9. package/lib/Signal/Group/ciphertext-message.js +12 -0
  10. package/lib/Signal/Group/group-session-builder.js +30 -0
  11. package/lib/Signal/Group/group_cipher.js +82 -0
  12. package/lib/Signal/Group/index.js +12 -0
  13. package/lib/Signal/Group/keyhelper.js +18 -0
  14. package/lib/Signal/Group/sender-chain-key.js +26 -0
  15. package/lib/Signal/Group/sender-key-distribution-message.js +63 -0
  16. package/lib/Signal/Group/sender-key-message.js +66 -0
  17. package/lib/Signal/Group/sender-key-name.js +48 -0
  18. package/lib/Signal/Group/sender-key-record.js +41 -0
  19. package/lib/Signal/Group/sender-key-state.js +84 -0
  20. package/lib/Signal/Group/sender-message-key.js +26 -0
  21. package/lib/Signal/libsignal.js +431 -0
  22. package/lib/Signal/lid-mapping.js +277 -0
  23. package/lib/Socket/Client/index.js +3 -0
  24. package/lib/Socket/Client/types.js +11 -0
  25. package/lib/Socket/Client/websocket.js +102 -0
  26. package/lib/Socket/aigroups.js +221 -0
  27. package/lib/Socket/business.js +379 -0
  28. package/lib/Socket/chats.js +1193 -0
  29. package/lib/Socket/communities.js +431 -0
  30. package/lib/Socket/graphql.js +524 -0
  31. package/lib/Socket/groups.js +408 -0
  32. package/lib/Socket/index.js +57 -0
  33. package/lib/Socket/interop.js +345 -0
  34. package/lib/Socket/luxu.js +510 -0
  35. package/lib/Socket/managed-account.js +99 -0
  36. package/lib/Socket/message-builder.js +523 -0
  37. package/lib/Socket/messages-recv.js +2009 -0
  38. package/lib/Socket/messages-send.js +1608 -0
  39. package/lib/Socket/mex.js +56 -0
  40. package/lib/Socket/newsletter.js +624 -0
  41. package/lib/Socket/privacy.js +128 -0
  42. package/lib/Socket/registration.js +238 -0
  43. package/lib/Socket/socket.js +1000 -0
  44. package/lib/Socket/text-router.js +67 -0
  45. package/lib/Socket/username.js +234 -0
  46. package/lib/Store/index.js +10 -0
  47. package/lib/Store/keyed-db.js +108 -0
  48. package/lib/Store/make-cache-manager-store.js +85 -0
  49. package/lib/Store/make-in-memory-store.js +198 -0
  50. package/lib/Store/make-ordered-dictionary.js +75 -0
  51. package/lib/Store/object-repository.js +32 -0
  52. package/lib/Types/Auth.js +2 -0
  53. package/lib/Types/Bussines.js +2 -0
  54. package/lib/Types/Call.js +2 -0
  55. package/lib/Types/Chat.js +8 -0
  56. package/lib/Types/Contact.js +2 -0
  57. package/lib/Types/Events.js +2 -0
  58. package/lib/Types/GroupMetadata.js +2 -0
  59. package/lib/Types/Label.js +25 -0
  60. package/lib/Types/LabelAssociation.js +7 -0
  61. package/lib/Types/Message.js +11 -0
  62. package/lib/Types/Mex.js +120 -0
  63. package/lib/Types/Product.js +2 -0
  64. package/lib/Types/Signal.js +2 -0
  65. package/lib/Types/Socket.js +3 -0
  66. package/lib/Types/State.js +56 -0
  67. package/lib/Types/USync.js +2 -0
  68. package/lib/Types/index.js +26 -0
  69. package/lib/Utils/adaptive-healing.js +53 -0
  70. package/lib/Utils/auth-utils.js +302 -0
  71. package/lib/Utils/browser-utils.js +50 -0
  72. package/lib/Utils/business.js +231 -0
  73. package/lib/Utils/chat-utils.js +872 -0
  74. package/lib/Utils/command-loader.js +108 -0
  75. package/lib/Utils/companion-reg-client-utils.js +35 -0
  76. package/lib/Utils/consumer-application.js +106 -0
  77. package/lib/Utils/crypto.js +137 -0
  78. package/lib/Utils/curve25519-js.js +262 -0
  79. package/lib/Utils/decode-wa-message.js +498 -0
  80. package/lib/Utils/event-buffer.js +622 -0
  81. package/lib/Utils/generics.js +403 -0
  82. package/lib/Utils/group-history.js +47 -0
  83. package/lib/Utils/history.js +134 -0
  84. package/lib/Utils/identity-change-handler.js +50 -0
  85. package/lib/Utils/index.js +38 -0
  86. package/lib/Utils/jid-display-normalization.js +198 -0
  87. package/lib/Utils/link-preview.js +85 -0
  88. package/lib/Utils/logger.js +3 -0
  89. package/lib/Utils/lt-hash.js +8 -0
  90. package/lib/Utils/make-mutex.js +33 -0
  91. package/lib/Utils/message-composer.js +273 -0
  92. package/lib/Utils/message-retry-manager.js +267 -0
  93. package/lib/Utils/messages-media.js +885 -0
  94. package/lib/Utils/messages.js +1405 -0
  95. package/lib/Utils/meta-ai-msmsg.js +271 -0
  96. package/lib/Utils/native-bridge.js +77 -0
  97. package/lib/Utils/noise-handler.js +201 -0
  98. package/lib/Utils/offline-node-processor.js +40 -0
  99. package/lib/Utils/optimizer.js +90 -0
  100. package/lib/Utils/pre-key-manager.js +106 -0
  101. package/lib/Utils/process-message.js +630 -0
  102. package/lib/Utils/reporting-utils.js +258 -0
  103. package/lib/Utils/session-pool.js +73 -0
  104. package/lib/Utils/signal.js +207 -0
  105. package/lib/Utils/stanza-ack.js +38 -0
  106. package/lib/Utils/sticker.js +297 -0
  107. package/lib/Utils/sync-action-utils.js +49 -0
  108. package/lib/Utils/tc-token-utils.js +163 -0
  109. package/lib/Utils/use-multi-file-auth-state.js +121 -0
  110. package/lib/Utils/use-sqlite-auth-state.js +168 -0
  111. package/lib/Utils/validate-connection.js +203 -0
  112. package/lib/Utils/view-once-cache.js +79 -0
  113. package/lib/Utils/voip-rekey.js +25 -0
  114. package/lib/Utils/warmup.js +117 -0
  115. package/lib/WABinary/constants.js +1301 -0
  116. package/lib/WABinary/decode.js +262 -0
  117. package/lib/WABinary/encode.js +220 -0
  118. package/lib/WABinary/generic-utils.js +204 -0
  119. package/lib/WABinary/index.js +6 -0
  120. package/lib/WABinary/jid-utils.js +98 -0
  121. package/lib/WABinary/types.js +2 -0
  122. package/lib/WAM/BinaryInfo.js +10 -0
  123. package/lib/WAM/constants.js +22853 -0
  124. package/lib/WAM/encode.js +150 -0
  125. package/lib/WAM/index.js +4 -0
  126. package/lib/WAUSync/Protocols/USyncBusinessProtocol.js +41 -0
  127. package/lib/WAUSync/Protocols/USyncContactProtocol.js +52 -0
  128. package/lib/WAUSync/Protocols/USyncDeviceProtocol.js +54 -0
  129. package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.js +27 -0
  130. package/lib/WAUSync/Protocols/USyncFeatureProtocol.js +52 -0
  131. package/lib/WAUSync/Protocols/USyncPictureProtocol.js +31 -0
  132. package/lib/WAUSync/Protocols/USyncSidelistProtocol.js +26 -0
  133. package/lib/WAUSync/Protocols/USyncStatusProtocol.js +38 -0
  134. package/lib/WAUSync/Protocols/USyncTextStatusProtocol.js +35 -0
  135. package/lib/WAUSync/Protocols/USyncUsernameProtocol.js +25 -0
  136. package/lib/WAUSync/Protocols/UsyncBotProfileProtocol.js +51 -0
  137. package/lib/WAUSync/Protocols/UsyncLIDProtocol.js +29 -0
  138. package/lib/WAUSync/Protocols/index.js +13 -0
  139. package/lib/WAUSync/USyncQuery.js +127 -0
  140. package/lib/WAUSync/USyncUser.js +31 -0
  141. package/lib/WAUSync/index.js +4 -0
  142. package/lib/antiban.js +4087 -0
  143. package/lib/index.js +23 -0
  144. package/package.json +151 -0
@@ -0,0 +1,523 @@
1
+ import crypto from 'crypto';
2
+ import { proto } from '../../WAProto/index.js';
3
+ import { generateWAMessageFromContent, prepareWAMessageMedia } from '../Utils/index.js';
4
+
5
+ export const makeMessageBuilderSocket = (sock) => {
6
+ const { relayMessage, sendMessage, waUploadToServer } = sock;
7
+
8
+ // per-socket-instance poll state: id -> [{ vote, action }]
9
+ const pollActionStore = new Map();
10
+
11
+ /**
12
+ * Relay an arbitrary raw message content object, bypassing the normal
13
+ * content-type detection in sendMessage(). Useful when you've already
14
+ * built a proto-shaped message (e.g. from generateWAMessageFromContent)
15
+ * and just need it sent as-is.
16
+ */
17
+ const sendJsonMessage = async (jid, content = {}, options = {}) => {
18
+ const msg = generateWAMessageFromContent(jid, content, {});
19
+ return relayMessage(jid, msg.message, { messageId: msg.key.id, ...options });
20
+ };
21
+
22
+ /**
23
+ * Sends an HTML GenAI primitive rich response message.
24
+ * html: string template/raw HTML markup to be rendered.
25
+ * (Added from xbailsync@1.0.7 — a plain, explicit send, nothing hidden:
26
+ * only runs when you call it, same as every other method here.)
27
+ */
28
+ const sendHtml = async (jid, html = '', options = {}) => {
29
+ const payloadData = Buffer.from(
30
+ JSON.stringify({
31
+ __typename: "GenAIUnifiedResponse",
32
+ response_id: crypto.randomUUID(),
33
+ sections: [
34
+ {
35
+ __typename: "GenAIUnifiedResponseSection",
36
+ view_model: {
37
+ __typename: "GenAISingleLayoutViewModel",
38
+ primitive: {
39
+ __typename: "FOAHtmlPrimitiveDemoDONOTUSE",
40
+ trusted_sources: [],
41
+ payload: String(html).trim()
42
+ }
43
+ }
44
+ }
45
+ ]
46
+ })
47
+ ).toString("base64");
48
+
49
+ const messageContent = {
50
+ botForwardedMessage: {
51
+ message: {
52
+ richResponseMessage: {
53
+ messageType: 1,
54
+ unifiedResponse: {
55
+ data: payloadData
56
+ },
57
+ contextInfo: {
58
+ isForwarded: true,
59
+ forwardOrigin: 4
60
+ }
61
+ }
62
+ }
63
+ }
64
+ };
65
+
66
+ return sendJsonMessage(jid, messageContent, options);
67
+ };
68
+
69
+ /**
70
+ * Sends a WA poll and remembers which "action" each option maps to, so
71
+ * a caller can look up what the voter selected once the vote comes back
72
+ * through a `messages.update` event (see resolvePollAction below).
73
+ *
74
+ * pollOptions: [{ vote: 'Option label', action: 'anything you want back' }]
75
+ */
76
+ const sendActionPoll = async (jid, name = '', pollOptions = [], options = {}) => {
77
+ const values = pollOptions.map(o => o.vote);
78
+ const pollMsg = await sendMessage(jid, { poll: { name, values, selectableCount: options.selectableCount || 1 } }, options);
79
+ pollActionStore.set(pollMsg.key.id, pollOptions);
80
+ return pollMsg;
81
+ };
82
+
83
+ /**
84
+ * Given a poll creation message id and the selected option label (as
85
+ * resolved via getAggregateVotesInPollMessage), returns the `action`
86
+ * that was registered for it in sendActionPoll, or undefined if this
87
+ * poll wasn't created through sendActionPoll (or already forgotten).
88
+ */
89
+ const resolvePollAction = (pollMessageId, selectedVoteLabel) => {
90
+ const options = pollActionStore.get(pollMessageId);
91
+ return options?.find(o => o.vote === selectedVoteLabel)?.action;
92
+ };
93
+
94
+ /**
95
+ * Sends a set of media files as a single WA album (grouped gallery).
96
+ * media: array of local file paths or URLs; extension picks image/video.
97
+ */
98
+ const sendAlbumMessage = async (jid, media = [], contextInfo = {}) => {
99
+ const albumMsg = generateWAMessageFromContent(jid, proto.Message.fromObject({
100
+ albumMessage: {
101
+ expectedImageCount: media.filter(m => !/\.mp4$/i.test(m)).length,
102
+ expectedVideoCount: media.filter(m => /\.mp4$/i.test(m)).length,
103
+ contextInfo
104
+ }
105
+ }), {});
106
+
107
+ const albumKey = {
108
+ id: await relayMessage(jid, albumMsg.message, { messageId: albumMsg.key.id }),
109
+ remoteJid: jid,
110
+ fromMe: true
111
+ };
112
+
113
+ const keys = { album: albumKey };
114
+ let i = 1;
115
+
116
+ const mimetypes = {
117
+ jpg: 'image/jpeg', jpeg: 'image/jpeg', png: 'image/png',
118
+ gif: 'image/gif', webp: 'image/webp', mp4: 'video/mp4'
119
+ };
120
+ const messagetypes = {
121
+ jpg: 'imageMessage', jpeg: 'imageMessage', png: 'imageMessage',
122
+ gif: 'imageMessage', webp: 'imageMessage', mp4: 'videoMessage'
123
+ };
124
+
125
+ for (const source of media) {
126
+ const ext = source.split('.').pop().toLowerCase();
127
+ const mimetype = mimetypes[ext];
128
+ const type = messagetypes[ext];
129
+
130
+ if (!mimetype || !type) {
131
+ continue;
132
+ }
133
+
134
+ const msg = await prepareWAMessageMedia(
135
+ { [type.startsWith('image') ? 'image' : 'video']: { url: source }, mimetype },
136
+ { upload: waUploadToServer }
137
+ );
138
+
139
+ const mediaMessage = generateWAMessageFromContent(jid, proto.Message.fromObject({
140
+ associatedChildMessage: {
141
+ message: {
142
+ messageContextInfo: {
143
+ messageSecret: crypto.randomBytes(32),
144
+ messageAssociation: {
145
+ associationType: 'MEDIA_ALBUM',
146
+ parentMessageKey: albumKey
147
+ }
148
+ },
149
+ [type]: { ...msg[type] }
150
+ }
151
+ }
152
+ }), {});
153
+
154
+ keys[`media_${i++}`] = {
155
+ id: await relayMessage(jid, mediaMessage.message, { messageId: mediaMessage.key.id }),
156
+ fromMe: true,
157
+ remoteJid: jid
158
+ };
159
+ }
160
+
161
+ return keys;
162
+ };
163
+
164
+ /**
165
+ * Posts `content` to status@broadcast and mentions `jid` in it (i.e. a
166
+ * status update that notifies a specific contact).
167
+ */
168
+ const sendStatusMention = async (jid, content) => {
169
+ const media = generateWAMessageFromContent('status@broadcast', content, {});
170
+
171
+ const additionalNodes = [
172
+ {
173
+ tag: 'meta',
174
+ attrs: {},
175
+ content: [
176
+ {
177
+ tag: 'mentioned_users',
178
+ attrs: {},
179
+ content: [{ tag: 'to', attrs: { jid }, content: undefined }]
180
+ }
181
+ ]
182
+ }
183
+ ];
184
+
185
+ await relayMessage('status@broadcast', media.message, {
186
+ messageId: media.key.id,
187
+ statusJidList: [jid, sock.user?.id],
188
+ additionalNodes
189
+ });
190
+
191
+ return media;
192
+ };
193
+
194
+ /**
195
+ * Builds and sends a "rich response" card (WA's GenAI-style unified
196
+ * response widget: optional header image/title, body buttons or a
197
+ * carousel/row of cards, and an optional footer link).
198
+ *
199
+ * content.header: { title, image: { url, mime_type, inline, width, height }, disclaimer, disclaimerText }
200
+ * content.body: { title, buttons: [labels] } OR { carousel|row: true, cards: [{ title, buttons, toast }] }
201
+ * content.footer: { text, url, image }
202
+ */
203
+ const sendRichResponse = async (jid, content = {}) => {
204
+ const header = content?.header;
205
+ const body = content?.body;
206
+ const footer = content?.footer;
207
+ let messageContextInfo = {};
208
+ const sections = [];
209
+
210
+ if (header) {
211
+ const { disclaimer = false, disclaimerText = ' ', image = { inline: false }, title = '' } = header ?? {};
212
+
213
+ if (disclaimer) {
214
+ messageContextInfo = { messageContextInfo: { botMetadata: { messageDisclaimerText: disclaimerText } } };
215
+ }
216
+
217
+ if (title) {
218
+ sections.push({
219
+ __typename: 'GenAIUnifiedResponseSection',
220
+ view_model: {
221
+ __typename: 'GenAISingleLayoutViewModel',
222
+ primitive: { __typename: 'FOATextPrimitive', text: '# ' + title }
223
+ }
224
+ });
225
+ }
226
+
227
+ if (image?.url) {
228
+ sections.push(image.inline ? {
229
+ __typename: 'GenAIUnifiedResponseSection',
230
+ view_model: {
231
+ __typename: 'GenAISingleLayoutViewModel',
232
+ primitive: {
233
+ __typename: 'GenAIMarkdownTextUXPrimitive',
234
+ text: '{{header}}.{{/header}}',
235
+ inline_entities: [{
236
+ __typename: 'GenAITextInlineEntity',
237
+ key: 'header',
238
+ metadata: {
239
+ __typename: 'GenAILatexItem',
240
+ latex_expression: '.',
241
+ font_height: 24,
242
+ padding: 4,
243
+ latex_image: {
244
+ __typename: 'GenAIMediaItem',
245
+ mime_type: image.mime_type || 'image/png',
246
+ url: image.url,
247
+ url_fallback: image.url,
248
+ width: image.width || 500,
249
+ height: image.height || 500,
250
+ expiration_timestamp_ms: Date.now() + 86400000
251
+ }
252
+ }
253
+ }]
254
+ }
255
+ }
256
+ } : {
257
+ __typename: 'GenAIUnifiedResponseSection',
258
+ view_model: {
259
+ __typename: 'GenAISingleLayoutViewModel',
260
+ primitive: {
261
+ __typename: 'GenAIImagePrimitive',
262
+ preview_image: { __typename: 'GenAIMediaItem', mime_type: image.mime_type || 'image/png', url: image.url },
263
+ full_image: { __typename: 'GenAIMediaItem', mime_type: image.mime_type || 'image/png', url: image.url }
264
+ }
265
+ }
266
+ });
267
+ }
268
+ }
269
+
270
+ if (body) {
271
+ const { cards = null, buttons = null, title = '', toast = '', carousel = false, row = false } = body ?? {};
272
+
273
+ if ((carousel || row) && cards?.length >= 1) {
274
+ sections.push({
275
+ __typename: 'GenAIUnifiedResponseSection',
276
+ view_model: {
277
+ primitives: cards.map((card, cardIndex) => ({
278
+ __typename: 'GenAI3PExtWidgetPrimitive',
279
+ header: { __typename: 'GenAI3PExtWidgetStandardHeader', title: card?.title || '' },
280
+ body: {
281
+ __typename: 'GenAI3PExtCalendarEventList',
282
+ ctas: (card?.buttons || []).map((text, buttonIndex) => ({
283
+ label: text,
284
+ state: 'PENDING',
285
+ kind: 'OTHER',
286
+ tool_call_id: `${cardIndex}${buttonIndex}`,
287
+ toast: { label: card?.toast || '', __typename: 'GenAI3PExtWidgetToast' },
288
+ __typename: 'GenAI3PExtWidgetCTA'
289
+ })),
290
+ sections: []
291
+ }
292
+ })),
293
+ __typename: carousel ? 'GenAIHScrollLayoutViewModel' : 'GenAIActionRowLayoutViewModel'
294
+ }
295
+ });
296
+ } else if (buttons?.length) {
297
+ sections.push({
298
+ __typename: 'GenAIUnifiedResponseSection',
299
+ view_model: {
300
+ primitive: {
301
+ __typename: 'GenAI3PExtWidgetPrimitive',
302
+ header: { __typename: 'GenAI3PExtWidgetStandardHeader', title: title || '' },
303
+ body: {
304
+ __typename: 'GenAI3PExtCalendarEventList',
305
+ ctas: buttons.map((text, buttonIndex) => ({
306
+ label: text,
307
+ state: 'PENDING',
308
+ kind: 'OTHER',
309
+ tool_call_id: `${buttonIndex}`,
310
+ toast: { label: toast, __typename: 'GenAI3PExtWidgetToast' },
311
+ __typename: 'GenAI3PExtWidgetCTA'
312
+ })),
313
+ sections: []
314
+ }
315
+ },
316
+ __typename: 'GenAISingleLayoutViewModel'
317
+ }
318
+ });
319
+ }
320
+ }
321
+
322
+ if (footer) {
323
+ const { text = '', url = '', image = {} } = footer ?? {};
324
+ const img = [];
325
+ if (image?.url) {
326
+ img.push({
327
+ __typename: 'GenAIMarkdownTextUXPrimitive',
328
+ text: '{{header}}.{{/header}}',
329
+ inline_entities: [{
330
+ __typename: 'GenAITextInlineEntity',
331
+ key: 'header',
332
+ metadata: {
333
+ __typename: 'GenAILatexItem',
334
+ latex_expression: '.',
335
+ font_height: 24,
336
+ padding: -5,
337
+ latex_image: {
338
+ __typename: 'GenAIMediaItem',
339
+ mime_type: image.mime_type || 'image/png',
340
+ url: image.url,
341
+ url_fallback: image.url,
342
+ width: image.width || 100,
343
+ height: image.height || 100,
344
+ expiration_timestamp_ms: Date.now() + 86400000
345
+ }
346
+ }
347
+ }]
348
+ });
349
+ }
350
+ sections.push({
351
+ view_model: {
352
+ primitives: [
353
+ { cta_text: text || '', cta_type: 'OPEN_URL', cta_url: url || '', __typename: 'GenAIFooterActionPrimitive' },
354
+ ...img
355
+ ],
356
+ __typename: 'GenAIActionRowLayoutViewModel'
357
+ }
358
+ });
359
+ }
360
+
361
+ return sendJsonMessage(jid, {
362
+ ...messageContextInfo,
363
+ richResponseMessage: {
364
+ unifiedResponse: { data: Buffer.from(JSON.stringify({ sections })).toString('base64') },
365
+ contextInfo: content?.contextInfo ?? {}
366
+ }
367
+ });
368
+ };
369
+
370
+ /**
371
+ * Sends a native quick-reply buttons message.
372
+ * buttons: [{ id: 'unique_id', text: 'Label' }]
373
+ */
374
+ const sendButtonsMessage = async (jid, { text = '', footer = '', buttons = [] } = {}, options = {}) => {
375
+ const interactiveMessage = {
376
+ body: { text },
377
+ ...(footer ? { footer: { text: footer } } : {}),
378
+ nativeFlowMessage: {
379
+ buttons: buttons.map(b => ({
380
+ name: b.name || 'quick_reply',
381
+ buttonParamsJson: JSON.stringify({ display_text: b.text, id: b.id })
382
+ }))
383
+ }
384
+ };
385
+ return sendJsonMessage(jid, { viewOnceMessage: { message: { interactiveMessage } } }, options);
386
+ };
387
+
388
+ /**
389
+ * Sends a native single-select list message.
390
+ * sections: [{ title: 'Section', rows: [{ title, description, id }] }]
391
+ */
392
+ const sendListMessage = async (jid, { text = '', footer = '', buttonText = 'Menu', sections = [] } = {}, options = {}) => {
393
+ const interactiveMessage = {
394
+ body: { text },
395
+ ...(footer ? { footer: { text: footer } } : {}),
396
+ nativeFlowMessage: {
397
+ buttons: [{
398
+ name: 'single_select',
399
+ buttonParamsJson: JSON.stringify({ title: buttonText, sections })
400
+ }]
401
+ }
402
+ };
403
+ return sendJsonMessage(jid, { viewOnceMessage: { message: { interactiveMessage } } }, options);
404
+ };
405
+
406
+ /**
407
+ * Sends a native horizontal-scroll carousel of cards (the correct native
408
+ * mechanism for side-by-side buttons — unlike the GenAI rich-response
409
+ * widget, which is designed as a vertical list).
410
+ * cards: [{ image: 'url or local path', mimetype, title, body, buttons: [{ id, text }] }]
411
+ */
412
+ const sendCarouselMessage = async (jid, { text = '', footer = '', cards = [] } = {}, options = {}) => {
413
+ const cardMessages = [];
414
+
415
+ for (const card of cards) {
416
+ let headerMedia = {};
417
+ if (card.image) {
418
+ const uploaded = await prepareWAMessageMedia(
419
+ { image: { url: card.image }, mimetype: card.mimetype || 'image/jpeg' },
420
+ { upload: waUploadToServer }
421
+ );
422
+ headerMedia = { imageMessage: uploaded.imageMessage };
423
+ }
424
+
425
+ cardMessages.push({
426
+ header: { title: card.title || '', hasMediaAttachment: !!card.image, ...headerMedia },
427
+ body: { text: card.body || '' },
428
+ nativeFlowMessage: {
429
+ buttons: (card.buttons || []).map(b => ({
430
+ name: b.name || 'quick_reply',
431
+ buttonParamsJson: JSON.stringify({ display_text: b.text, id: b.id })
432
+ }))
433
+ }
434
+ });
435
+ }
436
+
437
+ const message = {
438
+ viewOnceMessage: {
439
+ message: {
440
+ interactiveMessage: {
441
+ body: { text },
442
+ ...(footer ? { footer: { text: footer } } : {}),
443
+ carouselMessage: { cards: cardMessages, messageVersion: 1 }
444
+ }
445
+ }
446
+ }
447
+ };
448
+ return sendJsonMessage(jid, message, options);
449
+ };
450
+
451
+ /**
452
+ * Forwards an existing message (e.g. one pulled from the store) to
453
+ * another chat. Delegates to the library's own forward content builder
454
+ * (sendMessage's `forward` content type) so conversation→extendedText
455
+ * conversion, forwardingScore, and isForwarded are handled correctly
456
+ * instead of being reimplemented here.
457
+ */
458
+ const forwardMessage = async (jid, message, options = {}) => {
459
+ return sendMessage(jid, { forward: message, force: options.force }, options);
460
+ };
461
+
462
+ /**
463
+ * Builds a proper vCard string and sends it as a contact message.
464
+ * contact: { name, phone (E.164, no '+'), organization, waid }
465
+ * If multiple contacts are given, sends a contactsArrayMessage.
466
+ */
467
+ const sendVCard = async (jid, contacts, options = {}) => {
468
+ const list = Array.isArray(contacts) ? contacts : [contacts];
469
+ const built = list.map(c => {
470
+ const waid = c.waid || c.phone;
471
+ const vcard = 'BEGIN:VCARD\n'
472
+ + 'VERSION:3.0\n'
473
+ + `FN:${c.name || 'Unknown'}\n`
474
+ + (c.organization ? `ORG:${c.organization};\n` : '')
475
+ + `TEL;type=CELL;type=VOICE;waid=${waid}:+${c.phone}\n`
476
+ + 'END:VCARD';
477
+ return { displayName: c.name || 'Unknown', vcard };
478
+ });
479
+
480
+ if (built.length === 1) {
481
+ return sendMessage(jid, { contacts: { contacts: built } }, options);
482
+ }
483
+ return sendMessage(jid, { contacts: { displayName: `${built.length} contacts`, contacts: built } }, options);
484
+ };
485
+
486
+ /**
487
+ * Sends the same content to a list of jids sequentially, with an
488
+ * optional delay between sends (helps avoid rate limiting). Returns
489
+ * an array of { jid, ok, result|error } for each recipient.
490
+ * options.delayMs is stripped before being passed to sendMessage.
491
+ */
492
+ const broadcastMessage = async (jids, content, options = {}) => {
493
+ const { delayMs = 0, ...sendOptions } = options;
494
+ const results = [];
495
+ for (const jid of jids) {
496
+ try {
497
+ const result = await sendMessage(jid, content, sendOptions);
498
+ results.push({ jid, ok: true, result });
499
+ } catch (error) {
500
+ results.push({ jid, ok: false, error });
501
+ }
502
+ if (delayMs) await new Promise(resolve => setTimeout(resolve, delayMs));
503
+ }
504
+ return results;
505
+ };
506
+
507
+ return {
508
+ ...sock,
509
+ sendJsonMessage,
510
+ sendHtml,
511
+ sendActionPoll,
512
+ resolvePollAction,
513
+ sendAlbumMessage,
514
+ sendStatusMention,
515
+ sendRichResponse,
516
+ sendButtonsMessage,
517
+ sendListMessage,
518
+ sendCarouselMessage,
519
+ forwardMessage,
520
+ sendVCard,
521
+ broadcastMessage
522
+ };
523
+ };