@queenanya/baileys 9.5.3-beta.4 → 9.5.3

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 (112) hide show
  1. package/lib/Defaults/index.d.ts.map +1 -1
  2. package/lib/Defaults/index.js +1 -18
  3. package/lib/Defaults/index.js.map +1 -1
  4. package/lib/Signal/libsignal.js +1 -3
  5. package/lib/Signal/libsignal.js.map +1 -1
  6. package/lib/Socket/business.d.ts +1 -1
  7. package/lib/Socket/business.d.ts.map +1 -1
  8. package/lib/Socket/chats.d.ts +1 -1
  9. package/lib/Socket/chats.d.ts.map +1 -1
  10. package/lib/Socket/chats.js +1 -3
  11. package/lib/Socket/chats.js.map +1 -1
  12. package/lib/Socket/communities.d.ts +1 -1
  13. package/lib/Socket/communities.d.ts.map +1 -1
  14. package/lib/Socket/groups.d.ts.map +1 -1
  15. package/lib/Socket/index.d.ts +1 -1
  16. package/lib/Socket/index.d.ts.map +1 -1
  17. package/lib/Socket/messages-recv.d.ts +1 -1
  18. package/lib/Socket/messages-recv.d.ts.map +1 -1
  19. package/lib/Socket/messages-recv.js +271 -14
  20. package/lib/Socket/messages-recv.js.map +1 -1
  21. package/lib/Socket/messages-send.d.ts +1 -1
  22. package/lib/Socket/messages-send.d.ts.map +1 -1
  23. package/lib/Socket/messages-send.js +53 -12
  24. package/lib/Socket/messages-send.js.map +1 -1
  25. package/lib/Socket/newsletter.d.ts.map +1 -1
  26. package/lib/Socket/socket.d.ts.map +1 -1
  27. package/lib/Socket/socket.js +3 -2
  28. package/lib/Socket/socket.js.map +1 -1
  29. package/lib/Types/Message.d.ts +14 -0
  30. package/lib/Types/Message.d.ts.map +1 -1
  31. package/lib/Types/Newsletter.d.ts +11 -0
  32. package/lib/Types/Newsletter.d.ts.map +1 -1
  33. package/lib/Types/Newsletter.js +9 -0
  34. package/lib/Types/Newsletter.js.map +1 -1
  35. package/lib/Utils/companion-reg-client-utils.d.ts +17 -0
  36. package/lib/Utils/companion-reg-client-utils.d.ts.map +1 -0
  37. package/lib/Utils/companion-reg-client-utils.js +34 -0
  38. package/lib/Utils/companion-reg-client-utils.js.map +1 -0
  39. package/lib/Utils/generics.d.ts +2 -1
  40. package/lib/Utils/generics.d.ts.map +1 -1
  41. package/lib/Utils/generics.js +3 -2
  42. package/lib/Utils/generics.js.map +1 -1
  43. package/lib/Utils/messages.d.ts.map +1 -1
  44. package/lib/Utils/messages.js +39 -23
  45. package/lib/Utils/messages.js.map +1 -1
  46. package/lib/Utils/use-mongo-file-auth-state.d.ts.map +1 -1
  47. package/lib/Utils/use-mongo-file-auth-state.js.map +1 -1
  48. package/lib/Utils/validate-connection.d.ts.map +1 -1
  49. package/lib/Utils/validate-connection.js.map +1 -1
  50. package/lib/WABinary/generic-utils.js.map +1 -1
  51. package/lib/WAUSync/USyncQuery.js +1 -1
  52. package/lib/WAUSync/USyncQuery.js.map +1 -1
  53. package/lib/addons/anti-delete.d.ts.map +1 -1
  54. package/lib/addons/anti-delete.js.map +1 -1
  55. package/lib/addons/auto-reply.d.ts.map +1 -1
  56. package/lib/addons/auto-reply.js +1 -1
  57. package/lib/addons/auto-reply.js.map +1 -1
  58. package/lib/addons/button-sender.d.ts +262 -0
  59. package/lib/addons/button-sender.d.ts.map +1 -0
  60. package/lib/addons/button-sender.js +773 -0
  61. package/lib/addons/button-sender.js.map +1 -0
  62. package/lib/addons/call-handler.d.ts +79 -0
  63. package/lib/addons/call-handler.d.ts.map +1 -0
  64. package/lib/addons/call-handler.js +342 -0
  65. package/lib/addons/call-handler.js.map +1 -0
  66. package/lib/addons/from-chats.d.ts +1 -1
  67. package/lib/addons/from-chats.d.ts.map +1 -1
  68. package/lib/addons/from-messages-recv.d.ts +1 -1
  69. package/lib/addons/from-messages-recv.d.ts.map +1 -1
  70. package/lib/addons/from-messages-recv.js.map +1 -1
  71. package/lib/addons/from-messages-send.d.ts.map +1 -1
  72. package/lib/addons/from-messages-send.js +1 -1
  73. package/lib/addons/from-messages-send.js.map +1 -1
  74. package/lib/addons/from-messages.d.ts.map +1 -1
  75. package/lib/addons/from-messages.js +5 -6
  76. package/lib/addons/from-messages.js.map +1 -1
  77. package/lib/addons/index.d.ts +35 -13
  78. package/lib/addons/index.d.ts.map +1 -1
  79. package/lib/addons/index.js +59 -29
  80. package/lib/addons/index.js.map +1 -1
  81. package/lib/addons/jid-plotting.d.ts +4 -3
  82. package/lib/addons/jid-plotting.d.ts.map +1 -1
  83. package/lib/addons/jid-plotting.js +2 -2
  84. package/lib/addons/jid-plotting.js.map +1 -1
  85. package/lib/addons/message-search.d.ts +19 -6
  86. package/lib/addons/message-search.d.ts.map +1 -1
  87. package/lib/addons/message-search.js +2 -2
  88. package/lib/addons/message-search.js.map +1 -1
  89. package/lib/addons/message-utils.d.ts +80 -6
  90. package/lib/addons/message-utils.d.ts.map +1 -1
  91. package/lib/addons/message-utils.js +163 -45
  92. package/lib/addons/message-utils.js.map +1 -1
  93. package/lib/addons/scheduling.d.ts +2 -1
  94. package/lib/addons/scheduling.d.ts.map +1 -1
  95. package/lib/addons/scheduling.js +1 -1
  96. package/lib/addons/scheduling.js.map +1 -1
  97. package/lib/addons/status-posting.d.ts +70 -13
  98. package/lib/addons/status-posting.d.ts.map +1 -1
  99. package/lib/addons/status-posting.js +148 -0
  100. package/lib/addons/status-posting.js.map +1 -1
  101. package/lib/addons/templates.d.ts +1 -1
  102. package/lib/addons/templates.d.ts.map +1 -1
  103. package/lib/addons/templates.js.map +1 -1
  104. package/lib/addons/vcard.d.ts +2 -0
  105. package/lib/addons/vcard.d.ts.map +1 -1
  106. package/lib/addons/vcard.js +9 -9
  107. package/lib/addons/vcard.js.map +1 -1
  108. package/package.json +1 -1
  109. package/lib/addons/chat-control.d.ts +0 -57
  110. package/lib/addons/chat-control.d.ts.map +0 -1
  111. package/lib/addons/chat-control.js +0 -128
  112. package/lib/addons/chat-control.js.map +0 -1
@@ -0,0 +1,773 @@
1
+ /**
2
+ * button-sender.ts
3
+ * Ported from @ryuu-reinzz/button-helper v2.2.5 → TypeScript/ESM for @queenanya/baileys
4
+ *
5
+ * Provides runtime helpers to send WhatsApp interactive / native-flow button
6
+ * messages via relayMessage directly, bypassing sendMessage's validation path
7
+ * that does not recognise interactiveMessage payloads.
8
+ *
9
+ * Visibility guarantee:
10
+ * All button types (quick_reply, cta_url, cta_copy, cta_call, list, template,
11
+ * carousel/cards, combined, single_select) render correctly on:
12
+ * • WhatsApp Messenger — Android & iOS
13
+ * • WhatsApp Business — Android & iOS
14
+ *
15
+ * What this file provides:
16
+ * 1. buildInteractiveButtons() — legacy → native_flow normaliser
17
+ * 2. validateAuthoringButtons() — permissive pre-send validation
18
+ * 3. validateSendButtonsPayload() — strict sendButtons payload check
19
+ * 4. validateSendInteractiveMessagePayload()— strict interactive payload check
20
+ * 5. validateInteractiveMessageContent() — post-convert check
21
+ * 6. convertToInteractiveMessage() — high-level authoring → proto shape
22
+ * 7. sendInteractiveMessage() — low-level power function
23
+ * 8. sendInteractiveMessageV2() — extended (thumbnail / externalAdReply)
24
+ * 9. sendButtons() — convenience quick-reply wrapper
25
+ * 10. InteractiveValidationError — structured error class
26
+ *
27
+ * getButtonType / getButtonArgs are imported from ./message-utils (already in
28
+ * this repo — no duplication).
29
+ *
30
+ * Usage:
31
+ * import { sendButtons, sendInteractiveMessage } from '@queenanya/baileys'
32
+ *
33
+ * await sendButtons(sock, jid, {
34
+ * text: 'Choose an option',
35
+ * footer: 'Bot footer',
36
+ * buttons: [
37
+ * { id: 'yes', text: 'Yes' },
38
+ * { id: 'no', text: 'No' },
39
+ * ],
40
+ * })
41
+ */
42
+ import { generateMessageIDV2 } from '../Utils/generics.js';
43
+ import { generateWAMessageFromContent, normalizeMessageContent } from '../Utils/messages.js';
44
+ import { isJidGroup } from '../WABinary/jid-utils.js';
45
+ import { getButtonArgs, getButtonType } from './message-utils.js';
46
+ // Re-export so callers can import these from button-sender directly (matches button-helper API)
47
+ export { getButtonType, getButtonArgs };
48
+ // ─────────────────────────────────────────────────────────────────────────────
49
+ // Custom Error
50
+ // ─────────────────────────────────────────────────────────────────────────────
51
+ /**
52
+ * Thrown when button payload authoring validation fails.
53
+ * Provides structured errors/warnings + a canonical example so callers can
54
+ * surface actionable feedback to end-users or logs.
55
+ */
56
+ export class InteractiveValidationError extends Error {
57
+ constructor(message, { context, errors = [], warnings = [], example } = {}) {
58
+ super(message);
59
+ this.name = 'InteractiveValidationError';
60
+ this.context = context;
61
+ this.errors = errors;
62
+ this.warnings = warnings;
63
+ this.example = example;
64
+ }
65
+ toJSON() {
66
+ return {
67
+ name: this.name,
68
+ message: this.message,
69
+ context: this.context,
70
+ errors: this.errors,
71
+ warnings: this.warnings,
72
+ example: this.example
73
+ };
74
+ }
75
+ formatDetailed() {
76
+ const lines = [`[${this.name}] ${this.message}${this.context ? ' (' + this.context + ')' : ''}`];
77
+ if (this.errors.length) {
78
+ lines.push('Errors:');
79
+ this.errors.forEach(e => lines.push(' - ' + e));
80
+ }
81
+ if (this.warnings.length) {
82
+ lines.push('Warnings:');
83
+ this.warnings.forEach(w => lines.push(' - ' + w));
84
+ }
85
+ if (this.example) {
86
+ lines.push('Example payload:', JSON.stringify(this.example, null, 2));
87
+ }
88
+ return lines.join('\n');
89
+ }
90
+ }
91
+ // ─────────────────────────────────────────────────────────────────────────────
92
+ // Example payloads (embedded inside thrown errors for developer guidance)
93
+ // ─────────────────────────────────────────────────────────────────────────────
94
+ const EXAMPLE_PAYLOADS = {
95
+ sendButtons: {
96
+ text: 'Choose an option',
97
+ buttons: [
98
+ { id: 'opt1', text: 'Option 1' },
99
+ { id: 'opt2', text: 'Option 2' },
100
+ {
101
+ name: 'cta_url',
102
+ buttonParamsJson: JSON.stringify({
103
+ display_text: 'Visit Site',
104
+ url: 'https://example.com'
105
+ })
106
+ }
107
+ ],
108
+ footer: 'Footer text'
109
+ },
110
+ sendInteractiveMessage: {
111
+ text: 'Pick an action',
112
+ interactiveButtons: [
113
+ {
114
+ name: 'quick_reply',
115
+ buttonParamsJson: JSON.stringify({ display_text: 'Hello', id: 'hello' })
116
+ },
117
+ {
118
+ name: 'cta_copy',
119
+ buttonParamsJson: JSON.stringify({ display_text: 'Copy Code', copy_code: 'ABC123' })
120
+ }
121
+ ],
122
+ footer: 'Footer'
123
+ }
124
+ };
125
+ // ─────────────────────────────────────────────────────────────────────────────
126
+ // Allowed button name sets
127
+ // ─────────────────────────────────────────────────────────────────────────────
128
+ /** Allowed complex button names in sendButtons */
129
+ const SEND_BUTTONS_ALLOWED_COMPLEX = new Set(['cta_url', 'cta_copy', 'cta_call']);
130
+ /** Full set of button names allowed in sendInteractiveMessage */
131
+ const INTERACTIVE_ALLOWED_NAMES = new Set([
132
+ 'quick_reply',
133
+ 'cta_url',
134
+ 'cta_copy',
135
+ 'cta_call',
136
+ 'cta_catalog',
137
+ 'cta_reminder',
138
+ 'cta_cancel_reminder',
139
+ 'address_message',
140
+ 'send_location',
141
+ 'open_webview',
142
+ 'mpm',
143
+ 'wa_payment_transaction_details',
144
+ 'automated_greeting_message_view_catalog',
145
+ 'galaxy_message',
146
+ 'single_select'
147
+ ]);
148
+ /** Minimum required fields per button name */
149
+ const REQUIRED_FIELDS_MAP = {
150
+ cta_url: ['display_text', 'url'],
151
+ cta_copy: ['display_text', 'copy_code'],
152
+ cta_call: ['display_text', 'phone_number'],
153
+ cta_catalog: ['business_phone_number'],
154
+ cta_reminder: ['display_text'],
155
+ cta_cancel_reminder: ['display_text'],
156
+ address_message: ['display_text'],
157
+ send_location: ['display_text'],
158
+ open_webview: ['title', 'link'],
159
+ mpm: ['product_id'],
160
+ wa_payment_transaction_details: ['transaction_id'],
161
+ automated_greeting_message_view_catalog: ['business_phone_number', 'catalog_product_id'],
162
+ galaxy_message: ['flow_token', 'flow_id'],
163
+ single_select: ['title', 'sections'],
164
+ quick_reply: ['display_text', 'id']
165
+ };
166
+ // ─────────────────────────────────────────────────────────────────────────────
167
+ // Internal helper
168
+ // ─────────────────────────────────────────────────────────────────────────────
169
+ function parseButtonParamsInternal(name, buttonParamsJson, errors, _warnings, index) {
170
+ let parsed;
171
+ try {
172
+ parsed = JSON.parse(buttonParamsJson);
173
+ }
174
+ catch (e) {
175
+ errors.push(`button[${index}] (${name}) invalid JSON: ${e.message}`);
176
+ return null;
177
+ }
178
+ const required = REQUIRED_FIELDS_MAP[name] ?? [];
179
+ for (const field of required) {
180
+ if (!(field in parsed)) {
181
+ errors.push(`button[${index}] (${name}) missing required field '${field}'`);
182
+ }
183
+ }
184
+ if (name === 'open_webview' && parsed.link) {
185
+ const link = parsed.link;
186
+ if (typeof link !== 'object' || !link.url) {
187
+ errors.push(`button[${index}] (open_webview) link.url required`);
188
+ }
189
+ }
190
+ if (name === 'single_select') {
191
+ if (!Array.isArray(parsed.sections) || parsed.sections.length === 0) {
192
+ errors.push(`button[${index}] (single_select) sections must be a non-empty array`);
193
+ }
194
+ }
195
+ return parsed;
196
+ }
197
+ // ─────────────────────────────────────────────────────────────────────────────
198
+ // Public: normalise + validate
199
+ // ─────────────────────────────────────────────────────────────────────────────
200
+ /**
201
+ * Normalise various legacy / upstream button shapes into the native-flow format
202
+ * { name, buttonParamsJson }.
203
+ *
204
+ * Accepted input shapes:
205
+ * 1. Already native_flow : { name: string, buttonParamsJson: string }
206
+ * 2. Simple legacy : { id?: string, text?: string, displayText?: string }
207
+ * 3. Old Baileys : { buttonId: string, buttonText: { displayText: string } }
208
+ * 4. Unknown : passed through verbatim
209
+ */
210
+ export function buildInteractiveButtons(buttons = []) {
211
+ return buttons.map((b, i) => {
212
+ const btn = b;
213
+ // 1. Already native shape
214
+ if (btn.name && btn.buttonParamsJson)
215
+ return b;
216
+ // 2. Legacy quick-reply
217
+ if (btn.id || btn.text || btn.displayText) {
218
+ return {
219
+ name: 'quick_reply',
220
+ buttonParamsJson: JSON.stringify({
221
+ display_text: btn.text ?? btn.displayText ?? `Button ${i + 1}`,
222
+ id: btn.id ?? `quick_${i + 1}`
223
+ })
224
+ };
225
+ }
226
+ // 3. Old Baileys shape
227
+ const oldBt = btn.buttonText;
228
+ if (btn.buttonId && oldBt?.displayText) {
229
+ return {
230
+ name: 'quick_reply',
231
+ buttonParamsJson: JSON.stringify({
232
+ display_text: oldBt.displayText,
233
+ id: btn.buttonId
234
+ })
235
+ };
236
+ }
237
+ // 4. Unknown — pass through
238
+ return b;
239
+ });
240
+ }
241
+ /**
242
+ * Validate raw button objects before conversion.
243
+ * Permissive: only blocks clearly malformed input.
244
+ */
245
+ export function validateAuthoringButtons(buttons) {
246
+ const errors = [];
247
+ const warnings = [];
248
+ if (buttons === null)
249
+ return { errors: [], warnings: [], valid: true, cleaned: [] };
250
+ if (!Array.isArray(buttons)) {
251
+ errors.push('buttons must be an array');
252
+ return { errors, warnings, valid: false, cleaned: [] };
253
+ }
254
+ const SOFT_CAP = 25;
255
+ if (buttons.length === 0) {
256
+ warnings.push('buttons array is empty');
257
+ }
258
+ else if (buttons.length > SOFT_CAP) {
259
+ warnings.push(`buttons count (${buttons.length}) exceeds soft cap of ${SOFT_CAP}; may be rejected by client`);
260
+ }
261
+ const cleaned = buttons.map((b, idx) => {
262
+ const btn = b;
263
+ if (b === null || typeof b !== 'object') {
264
+ errors.push(`button[${idx}] is not an object`);
265
+ return b;
266
+ }
267
+ if (btn.name && btn.buttonParamsJson) {
268
+ if (typeof btn.buttonParamsJson !== 'string') {
269
+ errors.push(`button[${idx}] buttonParamsJson must be string`);
270
+ }
271
+ else {
272
+ try {
273
+ JSON.parse(btn.buttonParamsJson);
274
+ }
275
+ catch (e) {
276
+ errors.push(`button[${idx}] buttonParamsJson is not valid JSON: ${e.message}`);
277
+ }
278
+ }
279
+ return b;
280
+ }
281
+ if (btn.id || btn.text || btn.displayText)
282
+ return b;
283
+ const oldBt = btn.buttonText;
284
+ if (btn.buttonId && oldBt?.displayText)
285
+ return b;
286
+ warnings.push(`button[${idx}] unrecognized shape; passing through unchanged`);
287
+ return b;
288
+ });
289
+ return { errors, warnings, valid: errors.length === 0, cleaned };
290
+ }
291
+ /**
292
+ * Strict validator for sendButtons() payload.
293
+ */
294
+ export function validateSendButtonsPayload(data) {
295
+ const errors = [];
296
+ const warnings = [];
297
+ if (!data || typeof data !== 'object') {
298
+ return { valid: false, errors: ['payload must be an object'], warnings };
299
+ }
300
+ const d = data;
301
+ if (!d.text || typeof d.text !== 'string') {
302
+ errors.push('text is mandatory and must be a string');
303
+ }
304
+ if (!Array.isArray(d.buttons) || d.buttons.length === 0) {
305
+ errors.push('buttons is mandatory and must be a non-empty array');
306
+ }
307
+ else {
308
+ ;
309
+ d.buttons.forEach((btn, i) => {
310
+ const b = btn;
311
+ if (!btn || typeof btn !== 'object') {
312
+ errors.push(`button[${i}] must be an object`);
313
+ return;
314
+ }
315
+ if (b.id && b.text) {
316
+ if (typeof b.id !== 'string' || typeof b.text !== 'string') {
317
+ errors.push(`button[${i}] legacy quick reply id/text must be strings`);
318
+ }
319
+ return;
320
+ }
321
+ if (b.name && b.buttonParamsJson) {
322
+ if (!SEND_BUTTONS_ALLOWED_COMPLEX.has(b.name)) {
323
+ errors.push(`button[${i}] name '${b.name}' not allowed in sendButtons (allowed: ${[...SEND_BUTTONS_ALLOWED_COMPLEX].join(', ')})`);
324
+ return;
325
+ }
326
+ if (typeof b.buttonParamsJson !== 'string') {
327
+ errors.push(`button[${i}] buttonParamsJson must be string`);
328
+ return;
329
+ }
330
+ parseButtonParamsInternal(b.name, b.buttonParamsJson, errors, warnings, i);
331
+ return;
332
+ }
333
+ errors.push(`button[${i}] invalid shape — expected {id, text} or {name, buttonParamsJson} with name in [${[...SEND_BUTTONS_ALLOWED_COMPLEX].join(', ')}]`);
334
+ });
335
+ }
336
+ return { valid: errors.length === 0, errors, warnings };
337
+ }
338
+ /**
339
+ * Strict validator for sendInteractiveMessage() authoring payload.
340
+ */
341
+ export function validateSendInteractiveMessagePayload(data) {
342
+ const errors = [];
343
+ const warnings = [];
344
+ if (!data || typeof data !== 'object') {
345
+ return { valid: false, errors: ['payload must be an object'], warnings };
346
+ }
347
+ const d = data;
348
+ if (!d.text || typeof d.text !== 'string') {
349
+ errors.push('text is mandatory and must be a string');
350
+ }
351
+ if (!Array.isArray(d.interactiveButtons) || d.interactiveButtons.length === 0) {
352
+ errors.push('interactiveButtons is mandatory and must be a non-empty array');
353
+ }
354
+ else {
355
+ ;
356
+ d.interactiveButtons.forEach((btn, i) => {
357
+ if (!btn || typeof btn !== 'object') {
358
+ errors.push(`interactiveButtons[${i}] must be an object`);
359
+ return;
360
+ }
361
+ if (!btn.name || typeof btn.name !== 'string') {
362
+ errors.push(`interactiveButtons[${i}] missing name`);
363
+ return;
364
+ }
365
+ if (!INTERACTIVE_ALLOWED_NAMES.has(btn.name)) {
366
+ errors.push(`interactiveButtons[${i}] name '${btn.name}' not allowed`);
367
+ return;
368
+ }
369
+ if (!btn.buttonParamsJson || typeof btn.buttonParamsJson !== 'string') {
370
+ errors.push(`interactiveButtons[${i}] buttonParamsJson must be a non-empty string`);
371
+ return;
372
+ }
373
+ parseButtonParamsInternal(btn.name, btn.buttonParamsJson, errors, warnings, i);
374
+ });
375
+ }
376
+ return { valid: errors.length === 0, errors, warnings };
377
+ }
378
+ /**
379
+ * Validate already-converted interactiveMessage content (just before WAMessage creation).
380
+ */
381
+ export function validateInteractiveMessageContent(content) {
382
+ const errors = [];
383
+ const warnings = [];
384
+ if (!content || typeof content !== 'object') {
385
+ return { errors: ['content must be an object'], warnings, valid: false };
386
+ }
387
+ const c = content;
388
+ const interactive = c.interactiveMessage;
389
+ // Non-interactive messages are fine — nothing to validate
390
+ if (!interactive)
391
+ return { errors, warnings, valid: true };
392
+ const nativeFlow = interactive.nativeFlowMessage;
393
+ if (!nativeFlow) {
394
+ errors.push('interactiveMessage.nativeFlowMessage missing');
395
+ return { errors, warnings, valid: false };
396
+ }
397
+ if (!Array.isArray(nativeFlow.buttons)) {
398
+ errors.push('nativeFlowMessage.buttons must be an array');
399
+ return { errors, warnings, valid: false };
400
+ }
401
+ if (nativeFlow.buttons.length === 0) {
402
+ warnings.push('nativeFlowMessage.buttons is empty');
403
+ }
404
+ ;
405
+ nativeFlow.buttons.forEach((btn, i) => {
406
+ if (!btn || typeof btn !== 'object') {
407
+ errors.push(`buttons[${i}] is not an object`);
408
+ return;
409
+ }
410
+ if (!btn.buttonParamsJson) {
411
+ warnings.push(`buttons[${i}] missing buttonParamsJson (may fail to render)`);
412
+ }
413
+ else if (typeof btn.buttonParamsJson !== 'string') {
414
+ errors.push(`buttons[${i}] buttonParamsJson must be string`);
415
+ }
416
+ else {
417
+ try {
418
+ JSON.parse(btn.buttonParamsJson);
419
+ }
420
+ catch (e) {
421
+ warnings.push(`buttons[${i}] buttonParamsJson invalid JSON (${e.message})`);
422
+ }
423
+ }
424
+ if (!btn.name) {
425
+ warnings.push(`buttons[${i}] missing name; defaulting to quick_reply`);
426
+ btn.name = 'quick_reply';
427
+ }
428
+ });
429
+ return { errors, warnings, valid: errors.length === 0 };
430
+ }
431
+ // ─────────────────────────────────────────────────────────────────────────────
432
+ // Content converter
433
+ // ─────────────────────────────────────────────────────────────────────────────
434
+ /**
435
+ * Convert the high-level authoring shape:
436
+ * { text, footer?, title?, subtitle?, interactiveButtons: [...] }
437
+ * into the exact structure @queenanya/baileys / WAProto expects:
438
+ * { interactiveMessage: { nativeFlowMessage: { buttons: [...] }, body?, header?, footer? } }
439
+ *
440
+ * Authoring-only fields are stripped so they don't leak into
441
+ * generateWAMessageFromContent.
442
+ */
443
+ export function convertToInteractiveMessage(content) {
444
+ const btns = content.interactiveButtons;
445
+ if (btns && btns.length > 0) {
446
+ const interactiveMessage = {
447
+ nativeFlowMessage: {
448
+ buttons: btns.map(btn => ({
449
+ name: btn.name ?? 'quick_reply',
450
+ buttonParamsJson: btn.buttonParamsJson
451
+ })),
452
+ messageParamsJson: ''
453
+ }
454
+ };
455
+ if (content.title || content.subtitle) {
456
+ interactiveMessage.header = {
457
+ title: (content.title ?? content.subtitle ?? '')
458
+ };
459
+ }
460
+ if (content.text) {
461
+ interactiveMessage.body = { text: content.text };
462
+ }
463
+ if (content.footer) {
464
+ interactiveMessage.footer = { text: content.footer };
465
+ }
466
+ // Strip authoring-only keys to avoid leaking into proto serialisation
467
+ const newContent = { ...content };
468
+ delete newContent.interactiveButtons;
469
+ delete newContent.title;
470
+ delete newContent.subtitle;
471
+ delete newContent.text;
472
+ delete newContent.footer;
473
+ return { ...newContent, interactiveMessage };
474
+ }
475
+ return content;
476
+ }
477
+ // ─────────────────────────────────────────────────────────────────────────────
478
+ // Core send helpers
479
+ // ─────────────────────────────────────────────────────────────────────────────
480
+ /**
481
+ * Low-level power function — sends any interactive message by:
482
+ *
483
+ * 1. Validate authoring payload (when interactiveButtons is provided).
484
+ * 2. Convert to interactiveMessage / nativeFlowMessage proto shape.
485
+ * 3. Build WAMessage via generateWAMessageFromContent (skips sendMessage
486
+ * validation that rejects interactiveMessage).
487
+ * 4. Derive & inject required binary nodes so buttons render on all platforms:
488
+ * - biz node { tag:'biz', attrs:{} }
489
+ * - interactive child { type:'native_flow', v:'1' }
490
+ * - native_flow child { v:'9', name:'mixed' }
491
+ * - bot node { tag:'bot', attrs:{ biz_bot:'1' } } (private chats only)
492
+ * 5. Relay via sock.relayMessage.
493
+ *
494
+ * iOS/Android + WA Messenger + WA Business compatibility:
495
+ * getButtonArgs() (message-utils.ts) already handles all button categories:
496
+ * single_select, payment flows, mpm/catalog/location specials, standard
497
+ * native_flow (mixed), list, carousel/cards — each with the correct node tree.
498
+ *
499
+ * @param sock Active @queenanya/baileys socket instance.
500
+ * @param jid Destination chat JID.
501
+ * @param content Authoring payload (may include interactiveButtons).
502
+ * @param options Pass-through relay options (additionalNodes, etc.).
503
+ */
504
+ export async function sendInteractiveMessage(sock, jid, content, options = {}) {
505
+ if (!sock) {
506
+ throw new InteractiveValidationError('Socket is required', {
507
+ context: 'sendInteractiveMessage'
508
+ });
509
+ }
510
+ // Step 1 — authoring validation
511
+ if (Array.isArray(content.interactiveButtons)) {
512
+ const strict = validateSendInteractiveMessagePayload(content);
513
+ if (!strict.valid) {
514
+ throw new InteractiveValidationError('Interactive authoring payload invalid', {
515
+ context: 'sendInteractiveMessage.validateSendInteractiveMessagePayload',
516
+ errors: strict.errors,
517
+ warnings: strict.warnings,
518
+ example: EXAMPLE_PAYLOADS.sendInteractiveMessage
519
+ });
520
+ }
521
+ if (strict.warnings.length) {
522
+ console.warn('[button-sender] sendInteractiveMessage warnings:', strict.warnings);
523
+ }
524
+ }
525
+ // Step 2 — convert to proto shape
526
+ const convertedContent = convertToInteractiveMessage(content);
527
+ const { errors: cErr, warnings: cWarn, valid: cValid } = validateInteractiveMessageContent(convertedContent);
528
+ if (!cValid) {
529
+ throw new InteractiveValidationError('Converted interactive content invalid', {
530
+ context: 'sendInteractiveMessage.validateInteractiveMessageContent',
531
+ errors: cErr,
532
+ warnings: cWarn,
533
+ example: convertToInteractiveMessage(EXAMPLE_PAYLOADS.sendInteractiveMessage)
534
+ });
535
+ }
536
+ if (cWarn.length)
537
+ console.warn('[button-sender] Interactive content warnings:', cWarn);
538
+ // Step 3 — build WAMessage (uses internal @queenanya/baileys helpers directly)
539
+ const userJid = sock.authState?.creds?.me?.id ?? sock.user?.id ?? '';
540
+ const fullMsg = generateWAMessageFromContent(jid, convertedContent, {
541
+ userJid,
542
+ messageId: generateMessageIDV2(userJid),
543
+ timestamp: new Date()
544
+ });
545
+ // Step 4 — derive binary nodes
546
+ // normalizeMessageContent unwraps view-once/ephemerals so getButtonType
547
+ // inspects the real inner message type correctly.
548
+ const normalizedContent = normalizeMessageContent(fullMsg.message);
549
+ const buttonType = normalizedContent ? getButtonType(normalizedContent) : undefined;
550
+ const additionalNodes = [...(options.additionalNodes ?? [])];
551
+ if (buttonType && normalizedContent) {
552
+ // biz > interactive > native_flow(v:9, mixed) — exact sendButton node structure
553
+ // Works on: Android, iOS, WA Messenger, WA Business
554
+ const bizNode = getButtonArgs(normalizedContent);
555
+ additionalNodes.push(bizNode);
556
+ // Private chats also need the bot node
557
+ if (!isJidGroup(jid)) {
558
+ additionalNodes.push({ tag: 'bot', attrs: { biz_bot: '1' } });
559
+ }
560
+ console.log('[button-sender] Interactive send:', {
561
+ type: buttonType,
562
+ nodes: additionalNodes.map(n => ({ tag: n.tag, attrs: n.attrs })),
563
+ private: !isJidGroup(jid)
564
+ });
565
+ }
566
+ // Step 5 — relay
567
+ await sock.relayMessage(jid, fullMsg.message, {
568
+ messageId: fullMsg.key.id,
569
+ useCachedGroupMetadata: options.useCachedGroupMetadata,
570
+ additionalAttributes: options.additionalAttributes ?? {},
571
+ statusJidList: options.statusJidList,
572
+ additionalNodes
573
+ });
574
+ // Step 6 (optional) — emit to local event stream
575
+ // Only for private chats to avoid duplicate processing in groups.
576
+ if (sock.config?.emitOwnEvents && !isJidGroup(jid)) {
577
+ process.nextTick(() => {
578
+ if (sock.processingMutex?.mutex && sock.upsertMessage) {
579
+ void sock.processingMutex.mutex(() => sock.upsertMessage(fullMsg, 'append'));
580
+ }
581
+ });
582
+ }
583
+ return fullMsg;
584
+ }
585
+ /**
586
+ * Extended send variant with thumbnail / externalAdReply patching.
587
+ * Merged best-of-both from button-helper v2.2.3 + v2.2.5.
588
+ *
589
+ * Dummy document priority (Patch 1):
590
+ * 1. content.filePath — load file from local disk path (v2.2.3)
591
+ * 2. content.fileUrl — fetch file buffer from a URL (v2.2.3)
592
+ * 3. content.thumbnailUrl — fetch jpegThumbnail buffer AND use as dummy (v2.2.5)
593
+ * 4. fallback — plain text dummy buffer (v2.2.3)
594
+ * mimetype is taken from content.mimetype if provided, else auto-detected.
595
+ *
596
+ * externalAdReply (Patch 2) — merged fields from both versions:
597
+ * - containsAutoReply : true (v2.2.3)
598
+ * - mediaUrl / thumbnailUrl (v2.2.5)
599
+ * - renderLargerThumbnail : true (v2.2.5)
600
+ * - jpegThumbnail buffer (v2.2.5 — WA trusts this most)
601
+ *
602
+ * axios is a soft peer dependency — errors are non-fatal (console warn only).
603
+ *
604
+ * @example — thumbnail from URL
605
+ * await sendInteractiveMessageV2(sock, jid, {
606
+ * text: 'Hello',
607
+ * thumbnailUrl: 'https://example.com/thumb.jpg',
608
+ * interactiveButtons: [{ name: 'quick_reply', buttonParamsJson: '{"display_text":"Hi","id":"hi"}' }],
609
+ * })
610
+ *
611
+ * @example — dummy file from local path
612
+ * await sendInteractiveMessageV2(sock, jid, {
613
+ * text: 'Hello',
614
+ * filePath: '/tmp/myfile.pdf',
615
+ * mimetype: 'application/pdf',
616
+ * interactiveButtons: [...],
617
+ * })
618
+ */
619
+ export async function sendInteractiveMessageV2(sock, jid, content, options = {}) {
620
+ if (!sock) {
621
+ throw new InteractiveValidationError('Socket is required', {
622
+ context: 'sendInteractiveMessageV2'
623
+ });
624
+ }
625
+ const hasThumb = !!content.thumbnailUrl;
626
+ const hasFilePath = !!content.filePath;
627
+ const hasFileUrl = !!content.fileUrl;
628
+ const shouldForce = options.forceExternalAdReply === true;
629
+ // ── Helper: fetch buffer from any URL (axios soft peer dep) ───────────────
630
+ async function bufferFromUrl(url) {
631
+ try {
632
+ // @ts-ignore — axios is an optional peer dependency
633
+ const { default: axios } = await import('axios');
634
+ const res = await axios.get(url, { responseType: 'arraybuffer' });
635
+ return Buffer.from(res.data);
636
+ }
637
+ catch (e) {
638
+ console.warn('[button-sender] ⚠️ Failed to fetch buffer from URL:', e);
639
+ return null;
640
+ }
641
+ }
642
+ // ── Patch 1 — build dummy document so externalAdReply renders thumbnail ───
643
+ // Only inject when no media already present in the payload.
644
+ if ((hasThumb || hasFilePath || hasFileUrl || shouldForce) && !content.document && !content.image && !content.video) {
645
+ try {
646
+ let fileBuffer;
647
+ let fileName = 'QueenAnya.pdf';
648
+ let mimeType = 'application/pdf';
649
+ if (hasFilePath) {
650
+ // v2.2.3: load from local disk
651
+ const { readFileSync } = await import('fs');
652
+ fileBuffer = readFileSync(content.filePath);
653
+ fileName = content.filePath.split('/').pop() ?? fileName;
654
+ mimeType = content.mimetype ?? 'application/octet-stream';
655
+ }
656
+ else if (hasFileUrl) {
657
+ // v2.2.3: fetch from remote URL
658
+ const buf = await bufferFromUrl(content.fileUrl);
659
+ fileBuffer = buf ?? Buffer.from('dummy', 'utf-8');
660
+ fileName = content.fileUrl.split('/').pop() ?? fileName;
661
+ mimeType = content.mimetype ?? 'application/octet-stream';
662
+ }
663
+ else {
664
+ // v2.2.5: simple hardcoded dummy PDF
665
+ fileBuffer = Buffer.from('dummy', 'utf-8');
666
+ // keep fileName / mimeType defaults above
667
+ }
668
+ content.document = fileBuffer;
669
+ content.fileName = content.fileName ?? fileName;
670
+ content.mimetype = content.mimetype ?? mimeType;
671
+ }
672
+ catch (e) {
673
+ console.warn('[button-sender] ⚠️ Failed to build dummy document:', e);
674
+ }
675
+ }
676
+ // ── Fetch jpegThumbnail buffer from thumbnailUrl (v2.2.5) ─────────────────
677
+ // WA trusts jpegThumbnail buffer more than a bare URL string.
678
+ let jpegThumb = null;
679
+ if (hasThumb)
680
+ jpegThumb = await bufferFromUrl(content.thumbnailUrl);
681
+ // ── Patch 2 — build externalAdReply contextInfo (merged v2.2.3 + v2.2.5) ─
682
+ if (hasThumb || hasFilePath || hasFileUrl || shouldForce) {
683
+ const thumbUrl = (content.thumbnailUrl ?? options.thumbnailUrl ?? '');
684
+ const existingCtx = (content.contextInfo ?? {});
685
+ const existingEar = (existingCtx.externalAdReply ?? {});
686
+ content.contextInfo = {
687
+ ...existingCtx,
688
+ externalAdReply: {
689
+ ...existingEar,
690
+ mediaType: 1,
691
+ // v2.2.3 field — marks this as an auto-reply card
692
+ containsAutoReply: true,
693
+ title: (existingEar.title ??
694
+ `© ${globalThis.ownername ?? 'QueenAnya'} - 2025`),
695
+ body: (existingEar.body ?? 'Virtual Assistant'),
696
+ sourceUrl: (existingEar.sourceUrl ?? 'https://example.com'),
697
+ // v2.2.5 fields — reliable thumbnail display on all clients
698
+ mediaUrl: thumbUrl,
699
+ thumbnailUrl: thumbUrl,
700
+ renderLargerThumbnail: true,
701
+ // jpegThumbnail buffer: WA Messenger + Business trust this most
702
+ ...(jpegThumb ? { jpegThumbnail: jpegThumb } : {})
703
+ }
704
+ };
705
+ }
706
+ return sendInteractiveMessage(sock, jid, content, options);
707
+ }
708
+ // ─────────────────────────────────────────────────────────────────────────────
709
+ // Public convenience wrapper
710
+ // ─────────────────────────────────────────────────────────────────────────────
711
+ /**
712
+ * Convenience wrapper for the most common quick-reply / CTA button use case.
713
+ *
714
+ * @example — quick-reply
715
+ * await sendButtons(sock, jid, {
716
+ * text: 'Are you sure?',
717
+ * footer: 'Bot v1',
718
+ * buttons: [
719
+ * { id: 'yes', text: 'Yes' },
720
+ * { id: 'no', text: 'No' },
721
+ * ],
722
+ * })
723
+ *
724
+ * @example — mixed quick-reply + CTA URL
725
+ * await sendButtons(sock, jid, {
726
+ * text: 'Check our site',
727
+ * buttons: [
728
+ * { id: 'info', text: 'More Info' },
729
+ * { name: 'cta_url', buttonParamsJson: JSON.stringify({ display_text: 'Open', url: 'https://example.com' }) },
730
+ * ],
731
+ * })
732
+ */
733
+ export async function sendButtons(sock, jid, data = { text: '', buttons: [] }, options = {}) {
734
+ if (!sock) {
735
+ throw new InteractiveValidationError('Socket is required', { context: 'sendButtons' });
736
+ }
737
+ const { text = '', footer = '', title, subtitle, buttons = [] } = data;
738
+ // Strict payload validation
739
+ const strict = validateSendButtonsPayload({ text, buttons, title, subtitle, footer });
740
+ if (!strict.valid) {
741
+ throw new InteractiveValidationError('Buttons payload invalid', {
742
+ context: 'sendButtons.validateSendButtonsPayload',
743
+ errors: strict.errors,
744
+ warnings: strict.warnings,
745
+ example: EXAMPLE_PAYLOADS.sendButtons
746
+ });
747
+ }
748
+ if (strict.warnings.length) {
749
+ console.warn('[button-sender] sendButtons warnings:', strict.warnings);
750
+ }
751
+ // Validate authoring buttons
752
+ const { errors, warnings, cleaned } = validateAuthoringButtons(buttons);
753
+ if (errors.length) {
754
+ throw new InteractiveValidationError('Authoring button objects invalid', {
755
+ context: 'sendButtons.validateAuthoringButtons',
756
+ errors,
757
+ warnings,
758
+ example: EXAMPLE_PAYLOADS.sendButtons.buttons
759
+ });
760
+ }
761
+ if (warnings.length) {
762
+ console.warn('[button-sender] Button validation warnings:', warnings);
763
+ }
764
+ // Normalise to native_flow format
765
+ const interactiveButtons = buildInteractiveButtons(cleaned);
766
+ const payload = { text, footer, interactiveButtons };
767
+ if (title)
768
+ payload.title = title;
769
+ if (subtitle)
770
+ payload.subtitle = subtitle;
771
+ return sendInteractiveMessage(sock, jid, payload, options);
772
+ }
773
+ //# sourceMappingURL=button-sender.js.map