@zaiinlin/bails 0.0.0-stage → 1.0.2

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 (128) hide show
  1. package/README.md +1 -3
  2. package/WAProto/GenerateStatics.sh +3 -0
  3. package/WAProto/WAProto.proto +7772 -0
  4. package/WAProto/fix-imports.js +85 -0
  5. package/WAProto/index.d.ts +79257 -0
  6. package/WAProto/index.js +242946 -0
  7. package/engine-requirements.js +10 -0
  8. package/lib/Defaults/index.js +131 -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 +54 -0
  26. package/lib/Socket/MessageBuilder.js +3729 -0
  27. package/lib/Socket/business.js +379 -0
  28. package/lib/Socket/chats.js +1198 -0
  29. package/lib/Socket/communities.js +431 -0
  30. package/lib/Socket/graphql.js +716 -0
  31. package/lib/Socket/groups.js +374 -0
  32. package/lib/Socket/index.js +29 -0
  33. package/lib/Socket/interop.js +463 -0
  34. package/lib/Socket/luxu.js +387 -0
  35. package/lib/Socket/message-builder.js +556 -0
  36. package/lib/Socket/messages-recv.js +1916 -0
  37. package/lib/Socket/messages-send.js +1516 -0
  38. package/lib/Socket/mex.js +54 -0
  39. package/lib/Socket/newsletter.js +699 -0
  40. package/lib/Socket/privacy.js +318 -0
  41. package/lib/Socket/socket.js +1115 -0
  42. package/lib/Socket/username.js +236 -0
  43. package/lib/Store/index.js +10 -0
  44. package/lib/Store/keyed-db.js +108 -0
  45. package/lib/Store/make-cache-manager-store.js +85 -0
  46. package/lib/Store/make-in-memory-store.js +244 -0
  47. package/lib/Store/make-ordered-dictionary.js +75 -0
  48. package/lib/Store/object-repository.js +32 -0
  49. package/lib/Types/Auth.js +2 -0
  50. package/lib/Types/Bussines.js +2 -0
  51. package/lib/Types/Call.js +2 -0
  52. package/lib/Types/Chat.js +8 -0
  53. package/lib/Types/Contact.js +2 -0
  54. package/lib/Types/Events.js +2 -0
  55. package/lib/Types/GroupMetadata.js +2 -0
  56. package/lib/Types/Label.js +25 -0
  57. package/lib/Types/LabelAssociation.js +7 -0
  58. package/lib/Types/Message.js +11 -0
  59. package/lib/Types/Mex.js +111 -0
  60. package/lib/Types/Product.js +2 -0
  61. package/lib/Types/Signal.js +2 -0
  62. package/lib/Types/Socket.js +3 -0
  63. package/lib/Types/State.js +56 -0
  64. package/lib/Types/USync.js +2 -0
  65. package/lib/Types/index.js +26 -0
  66. package/lib/Utils/auth-utils.js +302 -0
  67. package/lib/Utils/browser-utils.js +48 -0
  68. package/lib/Utils/business.js +231 -0
  69. package/lib/Utils/chat-utils.js +872 -0
  70. package/lib/Utils/companion-reg-client-utils.js +35 -0
  71. package/lib/Utils/crypto.js +118 -0
  72. package/lib/Utils/decode-wa-message.js +350 -0
  73. package/lib/Utils/event-buffer.js +622 -0
  74. package/lib/Utils/generics.js +403 -0
  75. package/lib/Utils/history.js +134 -0
  76. package/lib/Utils/identity-change-handler.js +50 -0
  77. package/lib/Utils/index.js +24 -0
  78. package/lib/Utils/link-preview.js +85 -0
  79. package/lib/Utils/logger.js +3 -0
  80. package/lib/Utils/lt-hash.js +8 -0
  81. package/lib/Utils/make-mutex.js +33 -0
  82. package/lib/Utils/message-composer.js +273 -0
  83. package/lib/Utils/message-retry-manager.js +265 -0
  84. package/lib/Utils/messages-media.js +870 -0
  85. package/lib/Utils/messages.js +1394 -0
  86. package/lib/Utils/noise-handler.js +201 -0
  87. package/lib/Utils/offline-node-processor.js +40 -0
  88. package/lib/Utils/pre-key-manager.js +106 -0
  89. package/lib/Utils/process-message.js +630 -0
  90. package/lib/Utils/reporting-utils.js +258 -0
  91. package/lib/Utils/signal.js +201 -0
  92. package/lib/Utils/stanza-ack.js +38 -0
  93. package/lib/Utils/sticker.js +133 -0
  94. package/lib/Utils/sync-action-utils.js +49 -0
  95. package/lib/Utils/tc-token-utils.js +163 -0
  96. package/lib/Utils/use-multi-file-auth-state.js +121 -0
  97. package/lib/Utils/use-sqlite-auth-state.js +162 -0
  98. package/lib/Utils/validate-connection.js +203 -0
  99. package/lib/WABinary/constants.js +1301 -0
  100. package/lib/WABinary/decode.js +262 -0
  101. package/lib/WABinary/encode.js +220 -0
  102. package/lib/WABinary/generic-utils.js +204 -0
  103. package/lib/WABinary/index.js +6 -0
  104. package/lib/WABinary/jid-utils.js +98 -0
  105. package/lib/WABinary/types.js +2 -0
  106. package/lib/WAM/BinaryInfo.js +10 -0
  107. package/lib/WAM/constants.js +22853 -0
  108. package/lib/WAM/encode.js +150 -0
  109. package/lib/WAM/index.js +4 -0
  110. package/lib/WAUSync/Protocols/USyncBotProfileProtocol.js +53 -0
  111. package/lib/WAUSync/Protocols/USyncBusinessProtocol.js +43 -0
  112. package/lib/WAUSync/Protocols/USyncContactProtocol.js +54 -0
  113. package/lib/WAUSync/Protocols/USyncDeviceProtocol.js +57 -0
  114. package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.js +30 -0
  115. package/lib/WAUSync/Protocols/USyncFeatureProtocol.js +58 -0
  116. package/lib/WAUSync/Protocols/USyncLIDProtocol.js +30 -0
  117. package/lib/WAUSync/Protocols/USyncPictureProtocol.js +32 -0
  118. package/lib/WAUSync/Protocols/USyncSidelistProtocol.js +28 -0
  119. package/lib/WAUSync/Protocols/USyncStatusProtocol.js +39 -0
  120. package/lib/WAUSync/Protocols/USyncTextStatusProtocol.js +38 -0
  121. package/lib/WAUSync/Protocols/USyncUsernameProtocol.js +27 -0
  122. package/lib/WAUSync/Protocols/index.js +12 -0
  123. package/lib/WAUSync/USyncQuery.js +151 -0
  124. package/lib/WAUSync/USyncUser.js +56 -0
  125. package/lib/WAUSync/index.js +3 -0
  126. package/lib/index.js +92 -0
  127. package/package.json +143 -4
  128. package/readme.md +456 -0
@@ -0,0 +1,556 @@
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
+ */
26
+ const sendHtml = async (jid, html = '', options = {}) => {
27
+ const payloadData = Buffer.from(
28
+ JSON.stringify({
29
+ __typename: "GenAIUnifiedResponse",
30
+ response_id: crypto.randomUUID(),
31
+ sections: [
32
+ {
33
+ __typename: "GenAIUnifiedResponseSection",
34
+ view_model: {
35
+ __typename: "GenAISingleLayoutViewModel",
36
+ primitive: {
37
+ __typename: "FOAHtmlPrimitiveDemoDONOTUSE",
38
+ trusted_sources: [],
39
+ payload: String(html).trim()
40
+ }
41
+ }
42
+ }
43
+ ]
44
+ })
45
+ ).toString("base64");
46
+
47
+ const messageContent = {
48
+ botForwardedMessage: {
49
+ message: {
50
+ richResponseMessage: {
51
+ messageType: 1,
52
+ unifiedResponse: {
53
+ data: payloadData
54
+ },
55
+ contextInfo: {
56
+ isForwarded: true,
57
+ forwardOrigin: 4
58
+ }
59
+ }
60
+ }
61
+ }
62
+ };
63
+
64
+ return sendJsonMessage(jid, messageContent, options);
65
+ };
66
+
67
+ /**
68
+ * Sends a WA poll and remembers which "action" each option maps to.
69
+ */
70
+ const sendActionPoll = async (jid, name = '', pollOptions = [], options = {}) => {
71
+ const values = pollOptions.map(o => o.vote);
72
+ const pollMsg = await sendMessage(jid, { poll: { name, values, selectableCount: options.selectableCount || 1 } }, options);
73
+ pollActionStore.set(pollMsg.key.id, pollOptions);
74
+ return pollMsg;
75
+ };
76
+
77
+ /**
78
+ * Resolves selected poll option action.
79
+ */
80
+ const resolvePollAction = (pollMessageId, selectedVoteLabel) => {
81
+ const options = pollActionStore.get(pollMessageId);
82
+ return options?.find(o => o.vote === selectedVoteLabel)?.action;
83
+ };
84
+
85
+ /**
86
+ * Sends a set of media files as a single WA album.
87
+ */
88
+ const sendAlbumMessage = async (jid, media = [], contextInfo = {}) => {
89
+ const albumMsg = generateWAMessageFromContent(jid, proto.Message.fromObject({
90
+ albumMessage: {
91
+ expectedImageCount: media.filter(m => !/\.mp4$/i.test(m)).length,
92
+ expectedVideoCount: media.filter(m => /\.mp4$/i.test(m)).length,
93
+ contextInfo
94
+ }
95
+ }), {});
96
+
97
+ const albumKey = {
98
+ id: await relayMessage(jid, albumMsg.message, { messageId: albumMsg.key.id }),
99
+ remoteJid: jid,
100
+ fromMe: true
101
+ };
102
+
103
+ const keys = { album: albumKey };
104
+ let i = 1;
105
+
106
+ const mimetypes = {
107
+ jpg: 'image/jpeg', jpeg: 'image/jpeg', png: 'image/png',
108
+ gif: 'image/gif', webp: 'image/webp', mp4: 'video/mp4'
109
+ };
110
+ const messagetypes = {
111
+ jpg: 'imageMessage', jpeg: 'imageMessage', png: 'imageMessage',
112
+ gif: 'imageMessage', webp: 'imageMessage', mp4: 'videoMessage'
113
+ };
114
+
115
+ for (const source of media) {
116
+ const ext = source.split('.').pop().toLowerCase();
117
+ const mimetype = mimetypes[ext];
118
+ const type = messagetypes[ext];
119
+
120
+ if (!mimetype || !type) {
121
+ continue;
122
+ }
123
+
124
+ const msg = await prepareWAMessageMedia(
125
+ { [type.startsWith('image') ? 'image' : 'video']: { url: source }, mimetype },
126
+ { upload: waUploadToServer }
127
+ );
128
+
129
+ const mediaMessage = generateWAMessageFromContent(jid, proto.Message.fromObject({
130
+ associatedChildMessage: {
131
+ message: {
132
+ messageContextInfo: {
133
+ messageSecret: crypto.randomBytes(32),
134
+ messageAssociation: {
135
+ associationType: 'MEDIA_ALBUM',
136
+ parentMessageKey: albumKey
137
+ }
138
+ },
139
+ [type]: { ...msg[type] }
140
+ }
141
+ }
142
+ }), {});
143
+
144
+ keys[`media_${i++}`] = {
145
+ id: await relayMessage(jid, mediaMessage.message, { messageId: mediaMessage.key.id }),
146
+ fromMe: true,
147
+ remoteJid: jid
148
+ };
149
+ }
150
+
151
+ return keys;
152
+ };
153
+
154
+ /**
155
+ * Posts status mention.
156
+ */
157
+ const sendStatusMention = async (jid, content) => {
158
+ const media = generateWAMessageFromContent('status@broadcast', content, {});
159
+
160
+ const additionalNodes = [
161
+ {
162
+ tag: 'meta',
163
+ attrs: {},
164
+ content: [
165
+ {
166
+ tag: 'mentioned_users',
167
+ attrs: {},
168
+ content: [{ tag: 'to', attrs: { jid }, content: undefined }]
169
+ }
170
+ ]
171
+ }
172
+ ];
173
+
174
+ await relayMessage('status@broadcast', media.message, {
175
+ messageId: media.key.id,
176
+ statusJidList: [jid, sock.user?.id],
177
+ additionalNodes
178
+ });
179
+
180
+ return media;
181
+ };
182
+
183
+ /**
184
+ * Builds and sends a "rich response" menu card (WA GenAI style).
185
+ * Supports both structured content (header/body/footer) AND raw custom sections array.
186
+ */
187
+ const sendRich = async (jid, content = {}) => {
188
+ let contextInfoExtra = {};
189
+ let sections = [];
190
+
191
+ // 1. Jika pengguna mengirimkan array 'sections' secara langsung
192
+ if (content?.sections && Array.isArray(content.sections)) {
193
+ sections = content.sections;
194
+ } else {
195
+ // 2. Logic pembentukan sections dari header, body, dan footer
196
+ let header = content?.header;
197
+ let body = content?.body;
198
+ let footer = content?.footer;
199
+
200
+ const randomToolId = () => crypto.randomBytes(8).toString('hex');
201
+
202
+ if (header) {
203
+ const { disclaimer = false, disclaimerText = ' ', image = { inline: false }, title = '' } = header ?? {};
204
+
205
+ if (disclaimer) {
206
+ contextInfoExtra = {
207
+ messageContextInfo: {
208
+ botMetadata: {
209
+ messageDisclaimerText: disclaimerText
210
+ }
211
+ }
212
+ };
213
+ }
214
+
215
+ if (title) {
216
+ sections.push({
217
+ __typename: 'GenAIUnifiedResponseSection',
218
+ view_model: {
219
+ __typename: 'GenAISingleLayoutViewModel',
220
+ primitive: {
221
+ __typename: 'FOATextPrimitive',
222
+ text: '# ' + title
223
+ }
224
+ }
225
+ });
226
+ }
227
+
228
+ if (image?.url) {
229
+ if (image?.inline) {
230
+ sections.push({
231
+ __typename: 'GenAIUnifiedResponseSection',
232
+ view_model: {
233
+ __typename: 'GenAISingleLayoutViewModel',
234
+ primitive: {
235
+ __typename: 'GenAIMarkdownTextUXPrimitive',
236
+ text: '{{header}}.{{/header}}',
237
+ inline_entities: [{
238
+ __typename: 'GenAITextInlineEntity',
239
+ key: 'header',
240
+ metadata: {
241
+ __typename: 'GenAILatexItem',
242
+ latex_expression: '.',
243
+ font_height: 24,
244
+ padding: 4,
245
+ latex_image: {
246
+ __typename: 'GenAIMediaItem',
247
+ mime_type: image.mime_type || 'image/png',
248
+ url: image.url,
249
+ url_fallback: image.url,
250
+ width: image.width || 500,
251
+ height: image.height || 500,
252
+ expiration_timestamp_ms: Date.now() + 86400000
253
+ }
254
+ }
255
+ }]
256
+ }
257
+ }
258
+ });
259
+ } else {
260
+ sections.push({
261
+ __typename: 'GenAIUnifiedResponseSection',
262
+ view_model: {
263
+ __typename: 'GenAISingleLayoutViewModel',
264
+ primitive: {
265
+ __typename: 'GenAIImagePrimitive',
266
+ preview_image: {
267
+ __typename: 'GenAIMediaItem',
268
+ mime_type: image.mime_type || 'image/png',
269
+ url: image.url
270
+ },
271
+ full_image: {
272
+ __typename: 'GenAIMediaItem',
273
+ mime_type: image.mime_type || 'image/png',
274
+ url: image.url
275
+ }
276
+ }
277
+ }
278
+ });
279
+ }
280
+ }
281
+ }
282
+
283
+ if (body) {
284
+ const { cards = null, buttons = null, title = '', toast = '', carousel = false, row = false } = body ?? {};
285
+
286
+ if ((carousel || row) && cards?.length >= 1) {
287
+ sections.push({
288
+ __typename: 'GenAIUnifiedResponseSection',
289
+ view_model: {
290
+ primitives: cards.map((card) => ({
291
+ __typename: 'GenAI3PExtWidgetPrimitive',
292
+ header: {
293
+ __typename: 'GenAI3PExtWidgetStandardHeader',
294
+ title: card?.title || ''
295
+ },
296
+ body: {
297
+ __typename: 'GenAI3PExtCalendarEventList',
298
+ ctas: (card?.buttons || []).map((text) => ({
299
+ label: text,
300
+ state: 'PENDING',
301
+ kind: 'OTHER',
302
+ tool_call_id: randomToolId(),
303
+ toast: {
304
+ label: card?.toast || '',
305
+ __typename: 'GenAI3PExtWidgetToast'
306
+ },
307
+ __typename: 'GenAI3PExtWidgetCTA'
308
+ })),
309
+ sections: []
310
+ }
311
+ })),
312
+ __typename: carousel ? 'GenAIHScrollLayoutViewModel' : 'GenAIActionRowLayoutViewModel'
313
+ }
314
+ });
315
+ } else if (buttons?.length) {
316
+ sections.push({
317
+ __typename: 'GenAIUnifiedResponseSection',
318
+ view_model: {
319
+ primitive: {
320
+ __typename: 'GenAI3PExtWidgetPrimitive',
321
+ header: {
322
+ __typename: 'GenAI3PExtWidgetStandardHeader',
323
+ title: title || ''
324
+ },
325
+ body: {
326
+ __typename: 'GenAI3PExtCalendarEventList',
327
+ ctas: buttons.map((text) => ({
328
+ label: text,
329
+ state: 'PENDING',
330
+ kind: 'OTHER',
331
+ tool_call_id: randomToolId(),
332
+ toast: {
333
+ label: toast,
334
+ __typename: 'GenAI3PExtWidgetToast'
335
+ },
336
+ __typename: 'GenAI3PExtWidgetCTA'
337
+ })),
338
+ sections: []
339
+ }
340
+ },
341
+ __typename: 'GenAISingleLayoutViewModel'
342
+ }
343
+ });
344
+ }
345
+ }
346
+
347
+ if (footer) {
348
+ const { text = '', url = '', image = {} } = footer ?? {};
349
+ let img = [];
350
+ if (image?.url) {
351
+ img.push({
352
+ __typename: 'GenAIMarkdownTextUXPrimitive',
353
+ text: '{{header}}.{{/header}}',
354
+ inline_entities: [{
355
+ __typename: 'GenAITextInlineEntity',
356
+ key: 'header',
357
+ metadata: {
358
+ __typename: 'GenAILatexItem',
359
+ latex_expression: '.',
360
+ font_height: 24,
361
+ padding: -5,
362
+ latex_image: {
363
+ __typename: 'GenAIMediaItem',
364
+ mime_type: image.mime_type || 'image/png',
365
+ url: image.url,
366
+ url_fallback: image.url,
367
+ width: image.width || 100,
368
+ height: image.height || 100,
369
+ expiration_timestamp_ms: Date.now() + 86400000
370
+ }
371
+ }
372
+ }]
373
+ });
374
+ }
375
+ sections.push({
376
+ view_model: {
377
+ primitives: [
378
+ {
379
+ cta_text: text || 'Link',
380
+ cta_type: 'OPEN_URL',
381
+ cta_url: url || '',
382
+ __typename: 'GenAIFooterActionPrimitive'
383
+ },
384
+ ...img
385
+ ],
386
+ __typename: 'GenAIActionRowLayoutViewModel'
387
+ }
388
+ });
389
+ }
390
+ }
391
+
392
+ const waMsg = generateWAMessageFromContent(jid, {
393
+ ...contextInfoExtra,
394
+ botForwardedMessage: {
395
+ message: {
396
+ richResponseMessage: {
397
+ messageType: 1,
398
+ submessages: [],
399
+ unifiedResponse: {
400
+ data: Buffer.from(JSON.stringify({ sections })).toString('base64')
401
+ },
402
+ contextInfo: {
403
+ isForwarded: true,
404
+ forwardOrigin: 4,
405
+ ...(content?.contextInfo ?? {})
406
+ }
407
+ }
408
+ }
409
+ }
410
+ }, {});
411
+
412
+ await relayMessage(jid, waMsg.message, { messageId: waMsg.key.id });
413
+ return waMsg;
414
+ };
415
+
416
+ /**
417
+ * Native quick-reply buttons.
418
+ */
419
+ const sendButtonsMessage = async (jid, { text = '', footer = '', buttons = [] } = {}, options = {}) => {
420
+ const interactiveMessage = {
421
+ body: { text },
422
+ ...(footer ? { footer: { text: footer } } : {}),
423
+ nativeFlowMessage: {
424
+ buttons: buttons.map(b => ({
425
+ name: b.name || 'quick_reply',
426
+ buttonParamsJson: JSON.stringify({ display_text: b.text, id: b.id })
427
+ }))
428
+ }
429
+ };
430
+ return sendJsonMessage(jid, { viewOnceMessage: { message: { interactiveMessage } } }, options);
431
+ };
432
+
433
+ /**
434
+ * Native single-select list.
435
+ */
436
+ const sendListMessage = async (jid, { text = '', footer = '', buttonText = 'Menu', sections = [] } = {}, options = {}) => {
437
+ const interactiveMessage = {
438
+ body: { text },
439
+ ...(footer ? { footer: { text: footer } } : {}),
440
+ nativeFlowMessage: {
441
+ buttons: [{
442
+ name: 'single_select',
443
+ buttonParamsJson: JSON.stringify({ title: buttonText, sections })
444
+ }]
445
+ }
446
+ };
447
+ return sendJsonMessage(jid, { viewOnceMessage: { message: { interactiveMessage } } }, options);
448
+ };
449
+
450
+ /**
451
+ * Native carousel.
452
+ */
453
+ const sendCarouselMessage = async (jid, { text = '', footer = '', cards = [] } = {}, options = {}) => {
454
+ const cardMessages = [];
455
+
456
+ for (const card of cards) {
457
+ let headerMedia = {};
458
+ if (card.image) {
459
+ const uploaded = await prepareWAMessageMedia(
460
+ { image: { url: card.image }, mimetype: card.mimetype || 'image/jpeg' },
461
+ { upload: waUploadToServer }
462
+ );
463
+ headerMedia = { imageMessage: uploaded.imageMessage };
464
+ }
465
+
466
+ cardMessages.push({
467
+ header: { title: card.title || '', hasMediaAttachment: !!card.image, ...headerMedia },
468
+ body: { text: card.body || '' },
469
+ nativeFlowMessage: {
470
+ buttons: (card.buttons || []).map(b => ({
471
+ name: b.name || 'quick_reply',
472
+ buttonParamsJson: JSON.stringify({ display_text: b.text, id: b.id })
473
+ }))
474
+ }
475
+ });
476
+ }
477
+
478
+ const message = {
479
+ viewOnceMessage: {
480
+ message: {
481
+ interactiveMessage: {
482
+ body: { text },
483
+ ...(footer ? { footer: { text: footer } } : {}),
484
+ carouselMessage: { cards: cardMessages, messageVersion: 1 }
485
+ }
486
+ }
487
+ }
488
+ };
489
+ return sendJsonMessage(jid, message, options);
490
+ };
491
+
492
+ /**
493
+ * Forward message.
494
+ */
495
+ const forwardMessage = async (jid, message, options = {}) => {
496
+ return sendMessage(jid, { forward: message, force: options.force }, options);
497
+ };
498
+
499
+ /**
500
+ * Send vCard contact.
501
+ */
502
+ const sendVCard = async (jid, contacts, options = {}) => {
503
+ const list = Array.isArray(contacts) ? contacts : [contacts];
504
+ const built = list.map(c => {
505
+ const waid = c.waid || c.phone;
506
+ const vcard = 'BEGIN:VCARD\n'
507
+ + 'VERSION:3.0\n'
508
+ + `FN:${c.name || 'Unknown'}\n`
509
+ + (c.organization ? `ORG:${c.organization};\n` : '')
510
+ + `TEL;type=CELL;type=VOICE;waid=${waid}:+${c.phone}\n`
511
+ + 'END:VCARD';
512
+ return { displayName: c.name || 'Unknown', vcard };
513
+ });
514
+
515
+ if (built.length === 1) {
516
+ return sendMessage(jid, { contacts: { contacts: built } }, options);
517
+ }
518
+ return sendMessage(jid, { contacts: { displayName: `${built.length} contacts`, contacts: built } }, options);
519
+ };
520
+
521
+ /**
522
+ * Broadcast message.
523
+ */
524
+ const broadcastMessage = async (jids, content, options = {}) => {
525
+ const { delayMs = 0, ...sendOptions } = options;
526
+ const results = [];
527
+ for (const jid of jids) {
528
+ try {
529
+ const result = await sendMessage(jid, content, sendOptions);
530
+ results.push({ jid, ok: true, result });
531
+ } catch (error) {
532
+ results.push({ jid, ok: false, error });
533
+ }
534
+ if (delayMs) await new Promise(resolve => setTimeout(resolve, delayMs));
535
+ }
536
+ return results;
537
+ };
538
+
539
+ return {
540
+ ...sock,
541
+ sendJsonMessage,
542
+ sendActionPoll,
543
+ resolvePollAction,
544
+ sendAlbumMessage,
545
+ sendStatusMention,
546
+ sendRich,
547
+ richMenu: sendRich,
548
+ sendButtonsMessage,
549
+ sendListMessage,
550
+ sendCarouselMessage,
551
+ sendHtml,
552
+ forwardMessage,
553
+ sendVCard,
554
+ broadcastMessage
555
+ };
556
+ };