baileys-mbuilder 4.5.0-beta

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 (4) hide show
  1. package/MessageBuilder.js +1387 -0
  2. package/README.md +204 -0
  3. package/index.js +1663 -0
  4. package/package.json +39 -0
@@ -0,0 +1,1387 @@
1
+ /**
2
+ * Do not remove this watermark.
3
+ *
4
+ * NIXCODE - Advanced WhatsApp Interactive Message Builder
5
+ * Built for creating buttons, carousels, native flows,
6
+ * and AI rich response payloads using Baileys with
7
+ * fluent chaining, flexible payload customization,
8
+ * and scalable architecture for modern bot development.
9
+ *
10
+ * Runtime:
11
+ * - Baileys: @whiskeysockets/baileys (latest)
12
+ *
13
+ * Created by Nixel
14
+ * Contributors: ~ Ahmad tumbuh kembang
15
+ *
16
+ * WhatsApp: wa.me/6282139672290
17
+ * Channel: https://whatsapp.com/channel/0029VbCV1ck8fewpdNb2TY2k
18
+ *
19
+ * Copyright (c) 2026 Nixel
20
+ *
21
+ * Permission is granted to use and modify this library
22
+ * for personal or commercial projects.
23
+ *
24
+ * Reuploading, reselling, relicensing, or redistributing
25
+ * this library as a standalone product is prohibited.
26
+ *
27
+ * Do not claim this project as your own original work.
28
+ */
29
+
30
+ const VERSION = '4.5';
31
+
32
+ import { generateWAMessageFromContent, prepareWAMessageMedia } from 'baileys';
33
+ import crypto from 'crypto';
34
+ import sharp from 'sharp';
35
+
36
+ function extractIE(text, { extract = true, hyperlink = true, citation = true, latex = true } = {}) {
37
+ if (!extract) {
38
+ return {
39
+ text,
40
+ ie: [],
41
+ };
42
+ }
43
+ let ie = [],
44
+ result = '',
45
+ last = 0,
46
+ citation_index = 1,
47
+ hyperlink_index = 0,
48
+ latex_index = 0,
49
+ stack = [];
50
+ for (let i = 0; i < text.length; i++) {
51
+ if (text[i] == '[' && text[i - 1] != '\\') {
52
+ stack.push(i);
53
+ } else if (text[i] == ']' && (text[i + 1] == '(' || text[i + 1] == '<')) {
54
+ let start = stack.pop();
55
+ if (start == null) continue;
56
+ let open = text[i + 1],
57
+ close = open == '(' ? ')' : '>',
58
+ type = open == '(' ? 'link' : 'latex',
59
+ end = i + 2,
60
+ depth = 1;
61
+ while (end < text.length && depth) {
62
+ if (text[end] == open && text[end - 1] != '\\') depth++;
63
+ else if (text[end] == close && text[end - 1] != '\\') depth--;
64
+ end++;
65
+ }
66
+ if (depth) continue;
67
+ let raw = text.slice(start + 1, i).trim(),
68
+ url = text.slice(i + 2, end - 1).trim(),
69
+ key,
70
+ tag,
71
+ data;
72
+ if (type == 'latex') {
73
+ if (!latex) continue;
74
+ let [txt = '', width = null, height = null, font_height = null, padding = null] = raw.split('|');
75
+ key = `\u004E\u0049\u0058\u0045\u004C_LATEX_${latex_index++}`;
76
+ tag = `{{${key}}}${txt || 'image'}{{/${key}}}`;
77
+ data = {
78
+ type: 'latex',
79
+ ie: {
80
+ key,
81
+ text: txt,
82
+ url,
83
+ width,
84
+ height,
85
+ font_height,
86
+ padding,
87
+ },
88
+ };
89
+ } else if (raw) {
90
+ if (!hyperlink) continue;
91
+ key = `\u004E\u0049\u0058\u0045\u004C_HYPERLINK_${hyperlink_index++}`;
92
+ tag = `{{${key}}}${url}{{/${key}}}`;
93
+ data = {
94
+ type: 'hyperlink',
95
+ ie: {
96
+ key,
97
+ text: raw,
98
+ url,
99
+ },
100
+ };
101
+ } else {
102
+ if (!citation) continue;
103
+ key = `\u004E\u0049\u0058\u0045\u004C_CITATION_${citation_index - 1}`;
104
+ tag = `{{${key}}}${url}{{/${key}}}`;
105
+ data = {
106
+ type: 'citation',
107
+ ie: {
108
+ reference_id: citation_index++,
109
+ key,
110
+ text: '',
111
+ url,
112
+ },
113
+ };
114
+ }
115
+ result += text.slice(last, start) + tag;
116
+ last = end;
117
+ ie.push(data);
118
+ i = end - 1;
119
+ }
120
+ }
121
+ result += text.slice(last);
122
+ return {
123
+ text: result,
124
+ ie,
125
+ };
126
+ }
127
+
128
+ class BaseBuilder {
129
+ constructor() {
130
+ this._title = '';
131
+ this._subtitle = '';
132
+ this._body = '';
133
+ this._footer = '';
134
+ this._contextInfo = {};
135
+ this._extraPayload = {};
136
+ }
137
+
138
+ setTitle(title) {
139
+ if (typeof title !== 'string') {
140
+ throw new TypeError('Title must be a string');
141
+ }
142
+ this._title = title;
143
+ return this;
144
+ }
145
+
146
+ setSubtitle(subtitle) {
147
+ if (typeof subtitle !== 'string') {
148
+ throw new TypeError('Subtitle must be a string');
149
+ }
150
+ this._subtitle = subtitle;
151
+ return this;
152
+ }
153
+
154
+ setBody(body) {
155
+ if (typeof body !== 'string') {
156
+ throw new TypeError('Body must be a string');
157
+ }
158
+ this._body = body;
159
+ return this;
160
+ }
161
+
162
+ setFooter(footer) {
163
+ if (typeof footer !== 'string') {
164
+ throw new TypeError('Footer must be a string');
165
+ }
166
+ this._footer = footer;
167
+ return this;
168
+ }
169
+
170
+ setContextInfo(obj) {
171
+ if (typeof obj !== 'object' || obj === null || Array.isArray(obj)) {
172
+ throw new TypeError('ContextInfo must be a plain object');
173
+ }
174
+
175
+ this._contextInfo = obj;
176
+ return this;
177
+ }
178
+
179
+ addPayload(obj) {
180
+ if (typeof obj !== 'object' || obj === null || Array.isArray(obj)) {
181
+ throw new TypeError('Payload must be a plain object');
182
+ }
183
+
184
+ Object.assign(this._extraPayload, obj);
185
+
186
+ return this;
187
+ }
188
+
189
+ static async resize(buffer, x, y, fit = 'cover') {
190
+ return await sharp(buffer)
191
+ .resize(x, y, {
192
+ fit,
193
+ position: 'center',
194
+ background: { r: 0, g: 0, b: 0, alpha: 0 },
195
+ })
196
+ .png()
197
+ .toBuffer();
198
+ }
199
+
200
+ static async fetchBuffer(url, options = {}, config = {}) {
201
+ try {
202
+ let response = await fetch(url, options);
203
+ if (!response.ok) throw Error(`HTTP ${response.status}`);
204
+ return Buffer.from(await response.arrayBuffer());
205
+ } catch (error) {
206
+ if (config.silent) return Buffer.alloc(0);
207
+ throw error;
208
+ }
209
+ }
210
+ }
211
+
212
+ class Button extends BaseBuilder {
213
+ #client;
214
+
215
+ constructor(client) {
216
+ super();
217
+ if (!client) {
218
+ throw new Error('Socket is required');
219
+ }
220
+ this.#client = client;
221
+
222
+ this._buttons = [];
223
+ this._data;
224
+ this._currentSelectionIndex = -1;
225
+ this._currentSectionIndex = -1;
226
+ this._params = {};
227
+ }
228
+
229
+ setVideo(path, options = {}) {
230
+ if (!path) throw new Error('Url or buffer needed');
231
+ Buffer.isBuffer(path) ? (this._data = { video: path, ...options }) : (this._data = { video: { url: path }, ...options });
232
+ return this;
233
+ }
234
+
235
+ setImage(path, options = {}) {
236
+ if (!path) throw new Error('Url or buffer needed');
237
+ Buffer.isBuffer(path) ? (this._data = { image: path, ...options }) : (this._data = { image: { url: path }, ...options });
238
+ return this;
239
+ }
240
+
241
+ setDocument(path, options = {}) {
242
+ if (!path) throw new Error('Url or buffer needed');
243
+ Buffer.isBuffer(path) ? (this._data = { document: path, ...options }) : (this._data = { document: { url: path }, ...options });
244
+ return this;
245
+ }
246
+
247
+ setMedia(obj) {
248
+ if (typeof obj !== 'object' || obj === null || Array.isArray(obj)) {
249
+ throw new TypeError('Media must be a plain object');
250
+ }
251
+
252
+ this._data = obj;
253
+ return this;
254
+ }
255
+
256
+ clearButtons() {
257
+ this._buttons = [];
258
+ return this;
259
+ }
260
+
261
+ setParams(obj) {
262
+ this._params = obj;
263
+ return this;
264
+ }
265
+
266
+ addButton(name, params) {
267
+ this._buttons.push({
268
+ name,
269
+ buttonParamsJson: typeof params === 'string' ? params : JSON.stringify(params),
270
+ });
271
+
272
+ return this;
273
+ }
274
+
275
+ makeRow(header = '', title = '', description = '', id = '') {
276
+ if (this._currentSelectionIndex === -1 || this._currentSectionIndex === -1) {
277
+ throw new Error('You need to create a selection and a section first');
278
+ }
279
+ const buttonParams = JSON.parse(this._buttons[this._currentSelectionIndex].buttonParamsJson);
280
+ buttonParams.sections[this._currentSectionIndex].rows.push({ header, title, description, id });
281
+ this._buttons[this._currentSelectionIndex].buttonParamsJson = JSON.stringify(buttonParams);
282
+ return this;
283
+ }
284
+
285
+ makeSection(title = '', highlight_label = '') {
286
+ if (this._currentSelectionIndex === -1) {
287
+ throw new Error('You need to create a selection first');
288
+ }
289
+ const buttonParams = JSON.parse(this._buttons[this._currentSelectionIndex].buttonParamsJson);
290
+ buttonParams.sections.push({ title, highlight_label, rows: [] });
291
+ this._currentSectionIndex = buttonParams.sections.length - 1;
292
+ this._buttons[this._currentSelectionIndex].buttonParamsJson = JSON.stringify(buttonParams);
293
+ return this;
294
+ }
295
+
296
+ addSelection(title, options = {}) {
297
+ this._buttons.push({ ...options, name: 'single_select', buttonParamsJson: JSON.stringify({ title, sections: [] }) });
298
+ this._currentSelectionIndex = this._buttons.length - 1;
299
+ this._currentSectionIndex = -1;
300
+ return this;
301
+ }
302
+
303
+ addReply(display_text = '', id = '', options = {}) {
304
+ this._buttons.push({
305
+ name: 'quick_reply',
306
+ buttonParamsJson: JSON.stringify({
307
+ display_text,
308
+ id,
309
+ ...options,
310
+ }),
311
+ });
312
+ return this;
313
+ }
314
+
315
+ addCall(display_text = '', id = '', options = {}) {
316
+ this._buttons.push({
317
+ name: 'cta_call',
318
+ buttonParamsJson: JSON.stringify({
319
+ display_text,
320
+ id,
321
+ ...options,
322
+ }),
323
+ });
324
+ return this;
325
+ }
326
+
327
+ addReminder(display_text = '', id = '', options = {}) {
328
+ this._buttons.push({
329
+ name: 'cta_reminder',
330
+ buttonParamsJson: JSON.stringify({
331
+ display_text,
332
+ id,
333
+ ...options,
334
+ }),
335
+ });
336
+ return this;
337
+ }
338
+
339
+ addCancelReminder(display_text = '', id = '', options = {}) {
340
+ this._buttons.push({
341
+ name: 'cta_cancel_reminder',
342
+ buttonParamsJson: JSON.stringify({
343
+ display_text,
344
+ id,
345
+ ...options,
346
+ }),
347
+ });
348
+ return this;
349
+ }
350
+
351
+ addAddress(display_text = '', id = '', options = {}) {
352
+ this._buttons.push({
353
+ name: 'address_message',
354
+ buttonParamsJson: JSON.stringify({
355
+ display_text,
356
+ id,
357
+ ...options,
358
+ }),
359
+ });
360
+ return this;
361
+ }
362
+
363
+ addLocation(options = {}) {
364
+ this._buttons.push({
365
+ name: 'send_location',
366
+ buttonParamsJson: JSON.stringify(options),
367
+ });
368
+ return this;
369
+ }
370
+
371
+ addUrl(display_text = '', url = '', webview_interaction = false, options = {}) {
372
+ this._buttons.push({
373
+ ...options,
374
+ name: 'cta_url',
375
+ buttonParamsJson: JSON.stringify({
376
+ display_text,
377
+ url,
378
+ webview_interaction,
379
+ ...options,
380
+ }),
381
+ });
382
+ return this;
383
+ }
384
+
385
+ addCopy(display_text = '', copy_code = '', options = {}) {
386
+ this._buttons.push({
387
+ name: 'cta_copy',
388
+ buttonParamsJson: JSON.stringify({
389
+ display_text,
390
+ copy_code,
391
+ ...options,
392
+ }),
393
+ });
394
+ return this;
395
+ }
396
+
397
+ static paramsList = {
398
+ limited_time_offer: {
399
+ text: 'string',
400
+ url: 'string',
401
+ copy_code: 'string',
402
+ expiration_time: 'number',
403
+ },
404
+ bottom_sheet: {
405
+ in_thread_buttons_limit: 'number',
406
+ divider_indices: ['number'],
407
+ list_title: 'string',
408
+ button_title: 'string',
409
+ },
410
+ tap_target_configuration: {
411
+ title: 'string',
412
+ description: 'string',
413
+ canonical_url: 'string',
414
+ domain: 'string',
415
+ buttonIndex: 'number',
416
+ },
417
+ };
418
+
419
+ async toCard() {
420
+ return {
421
+ body: {
422
+ text: this._body,
423
+ },
424
+ footer: {
425
+ text: this._footer,
426
+ },
427
+ header: {
428
+ title: this._title,
429
+ subtitle: this._subtitle,
430
+ hasMediaAttachment: !!this._data,
431
+ ...(this._data
432
+ ? await prepareWAMessageMedia(this._data, { upload: this.#client.waUploadToServer }).catch((e) => {
433
+ if (String(e).includes('Invalid media type')) return this._data;
434
+ throw e;
435
+ })
436
+ : {}),
437
+ },
438
+ nativeFlowMessage: {
439
+ messageParamsJson: JSON.stringify(this._params),
440
+ buttons: this._buttons,
441
+ },
442
+ };
443
+ }
444
+
445
+ async build(jid, { ...options } = {}) {
446
+ const message = await this.toCard();
447
+
448
+ return generateWAMessageFromContent(
449
+ jid,
450
+ {
451
+ ...this._extraPayload,
452
+ interactiveMessage: {
453
+ ...message,
454
+ contextInfo: this._contextInfo,
455
+ },
456
+ },
457
+ { ...options }
458
+ );
459
+ }
460
+
461
+ async send(jid, { ...options } = {}) {
462
+ const msg = await this.build(jid, options);
463
+
464
+ await this.#client.relayMessage(msg.key.remoteJid, msg.message, {
465
+ messageId: msg.key.id,
466
+ additionalNodes: [
467
+ {
468
+ tag: 'biz',
469
+ attrs: {},
470
+ content: [
471
+ {
472
+ tag: 'interactive',
473
+ attrs: { type: 'native_flow', v: '1' },
474
+ content: [{ tag: 'native_flow', attrs: { v: '9', name: 'mixed' } }],
475
+ },
476
+ ],
477
+ },
478
+ ],
479
+ ...options,
480
+ });
481
+ return msg;
482
+ }
483
+ }
484
+
485
+ class ButtonV2 extends BaseBuilder {
486
+ #client;
487
+
488
+ constructor(client) {
489
+ super();
490
+ if (!client) {
491
+ throw new Error('Socket is required');
492
+ }
493
+
494
+ this.#client = client;
495
+ this._image;
496
+ this._data;
497
+ this._buttons = [];
498
+ }
499
+
500
+ addButton(displayText = '', buttonId = crypto.randomUUID()) {
501
+ this._buttons.push({
502
+ buttonId,
503
+ buttonText: { displayText },
504
+ type: 1,
505
+ });
506
+ return this;
507
+ }
508
+
509
+ addRawButton(obj) {
510
+ if (typeof obj !== 'object' || obj === null || Array.isArray(obj)) {
511
+ throw new TypeError('Buttons must be a plain object');
512
+ }
513
+
514
+ this._buttons.push(obj);
515
+ return this;
516
+ }
517
+
518
+ setThumbnail(path) {
519
+ if (!path) throw new Error('Url or buffer needed');
520
+ this._image = path;
521
+ return this;
522
+ }
523
+
524
+ setMedia(obj) {
525
+ if (typeof obj !== 'object' || obj === null || Array.isArray(obj)) {
526
+ throw new TypeError('Media must be a plain object');
527
+ }
528
+
529
+ this._data = obj;
530
+ return this;
531
+ }
532
+
533
+ async build(jid, { ...options } = {}) {
534
+ let _thumbnail = this._image ? await BaseBuilder.resize(Buffer.isBuffer(this._image) ? this._image : await BaseBuilder.fetchBuffer(this._image, {}, { silent: true }), 300, 300) : null;
535
+ const msg = generateWAMessageFromContent(
536
+ jid,
537
+ {
538
+ ...this._extraPayload,
539
+ buttonsMessage: {
540
+ contentText: this._body,
541
+ footerText: this._footer,
542
+ ...(this._data
543
+ ? this._data
544
+ : {
545
+ headerType: 6,
546
+ locationMessage: {
547
+ degreesLatitude: 0,
548
+ degreesLongitude: 0,
549
+ name: this._title,
550
+ address: this._subtitle,
551
+ jpegThumbnail: _thumbnail,
552
+ },
553
+ }),
554
+ viewOnce: true,
555
+ contextInfo: this._contextInfo,
556
+ buttons: [...this._buttons],
557
+ },
558
+ },
559
+ { ...options }
560
+ );
561
+ return msg;
562
+ }
563
+
564
+ async send(jid, { ...options } = {}) {
565
+ if (this._buttons.length < 1) throw new Error('ButtonV2 requires at least one button');
566
+ const msg = await this.build(jid, options);
567
+
568
+ await this.#client.relayMessage(msg.key.remoteJid, msg.message, {
569
+ messageId: msg.key.id,
570
+ additionalNodes: [
571
+ {
572
+ tag: 'biz',
573
+ attrs: {},
574
+ content: [
575
+ {
576
+ tag: 'interactive',
577
+ attrs: { type: 'native_flow', v: '1' },
578
+ content: [{ tag: 'native_flow', attrs: { v: '9', name: 'mixed' } }],
579
+ },
580
+ ],
581
+ },
582
+ ],
583
+ ...options,
584
+ });
585
+ return msg;
586
+ }
587
+ }
588
+
589
+ class Carousel extends BaseBuilder {
590
+ #client;
591
+
592
+ constructor(client) {
593
+ super();
594
+ if (!client) {
595
+ throw new Error('Socket is required');
596
+ }
597
+
598
+ this.#client = client;
599
+ this._cards = [];
600
+ }
601
+
602
+ addCard(card) {
603
+ const cards = Array.isArray(card) ? card : [card];
604
+ const baseIndex = this._cards.length;
605
+
606
+ for (const [index, c] of cards.entries()) {
607
+ if (!c?.header?.hasMediaAttachment) {
608
+ throw new Error(`Card [${baseIndex + index}] must include an image or video in header`);
609
+ }
610
+ }
611
+
612
+ this._cards.push(...cards);
613
+ return this;
614
+ }
615
+
616
+ build(jid, { ...options } = {}) {
617
+ return generateWAMessageFromContent(
618
+ jid,
619
+ {
620
+ ...this._extraPayload,
621
+ interactiveMessage: {
622
+ header: {
623
+ hasMediaAttachment: false,
624
+ },
625
+ body: { text: this._body },
626
+ footer: { text: this._footer },
627
+ contextInfo: this._contextInfo,
628
+ carouselMessage: {
629
+ cards: this._cards,
630
+ },
631
+ },
632
+ },
633
+ { ...options }
634
+ );
635
+ }
636
+
637
+ async send(jid, { ...options } = {}) {
638
+ const msg = this.build(jid, options);
639
+
640
+ await this.#client.relayMessage(msg.key.remoteJid, msg.message, {
641
+ messageId: msg.key.id,
642
+ additionalNodes: [
643
+ {
644
+ tag: 'biz',
645
+ attrs: {},
646
+ content: [
647
+ {
648
+ tag: 'interactive',
649
+ attrs: { type: 'native_flow', v: '1' },
650
+ content: [{ tag: 'native_flow', attrs: { v: '9', name: 'mixed' } }],
651
+ },
652
+ ],
653
+ },
654
+ ],
655
+ ...options,
656
+ });
657
+ return msg;
658
+ }
659
+ }
660
+
661
+ class AIRich extends BaseBuilder {
662
+ #client;
663
+
664
+ constructor(client) {
665
+ if (!client) {
666
+ throw new Error('Socket is required');
667
+ }
668
+
669
+ super();
670
+ this.#client = client;
671
+ this._contextInfo = {};
672
+ this._submessages = [];
673
+ this._sections = [];
674
+ this._richResponseSources = [];
675
+ }
676
+
677
+ static newLayout(name, data) {
678
+ return {
679
+ view_model: {
680
+ [Array.isArray(data) ? 'primitives' : 'primitive']: data,
681
+ __typename: `GenAI${name}LayoutViewModel`,
682
+ },
683
+ };
684
+ }
685
+
686
+ addSubmessage(submessage) {
687
+ const items = Array.isArray(submessage) ? submessage : [submessage];
688
+
689
+ for (const item of items) {
690
+ if (typeof item !== 'object' || item === null || Array.isArray(item)) {
691
+ throw new TypeError('Submessage must be a plain object or array of plain objects');
692
+ }
693
+
694
+ this._submessages.push(item);
695
+ }
696
+
697
+ return this;
698
+ }
699
+
700
+ addSection(section) {
701
+ const items = Array.isArray(section) ? section : [section];
702
+
703
+ for (const item of items) {
704
+ if (typeof item !== 'object' || item === null || Array.isArray(item)) {
705
+ throw new TypeError('Section must be a plain object or array of plain objects');
706
+ }
707
+
708
+ this._sections.push(item);
709
+ }
710
+
711
+ return this;
712
+ }
713
+
714
+ addText(text, { hyperlink = true, citation = true, latex = true } = {}) {
715
+ if (typeof text != 'string') {
716
+ throw new TypeError('Text must be a string');
717
+ }
718
+
719
+ const extractedIE = extractIE(text, {
720
+ hyperlink,
721
+ citation,
722
+ latex,
723
+ });
724
+
725
+ const inline_entities = extractedIE.ie.map(({ type, ie }) => {
726
+ if (type == 'hyperlink') {
727
+ return {
728
+ key: ie.key,
729
+ metadata: {
730
+ display_name: ie.text,
731
+ is_trusted: true,
732
+ url: ie.url,
733
+ __typename: 'GenAIInlineLinkItem',
734
+ },
735
+ };
736
+ }
737
+ if (type == 'citation') {
738
+ return {
739
+ key: ie.key,
740
+ metadata: {
741
+ reference_id: ie.reference_id,
742
+ reference_url: ie.url,
743
+ reference_title: ie.url,
744
+ reference_display_name: ie.url,
745
+ sources: [],
746
+ __typename: 'GenAISearchCitationItem',
747
+ },
748
+ };
749
+ }
750
+ if (type == 'latex') {
751
+ return {
752
+ key: ie.key,
753
+ metadata: {
754
+ latex_expression: ie.text,
755
+ latex_image: {
756
+ url: ie.url,
757
+ width: Number(ie.width) || 100,
758
+ height: Number(ie.height) || 100,
759
+ },
760
+ font_height: Number(ie.font_height) || 83.333333333333,
761
+ padding: Number(ie.padding) || 15,
762
+ __typename: 'GenAILatexItem',
763
+ },
764
+ };
765
+ }
766
+
767
+ return {
768
+ key: ie.key,
769
+ metadata: {
770
+ latex_expression: ie.text,
771
+ latex_image: {
772
+ url: ie.url,
773
+ width,
774
+ height,
775
+ },
776
+ font_height: Number(ie.font_height) || 83.333333333333,
777
+ padding: Number(ie.padding) || 15,
778
+ __typename: 'GenAILatexItem',
779
+ },
780
+ };
781
+ });
782
+
783
+ this._submessages.push({
784
+ messageType: 2,
785
+ messageText: extractedIE.text,
786
+ });
787
+
788
+ this._sections.push(
789
+ AIRich.newLayout('Single', {
790
+ text: extractedIE.text,
791
+ ...(inline_entities.length && {
792
+ inline_entities,
793
+ }),
794
+ __typename: 'GenAIMarkdownTextUXPrimitive',
795
+ })
796
+ );
797
+
798
+ return this;
799
+ }
800
+
801
+ addCode(language, code) {
802
+ if (typeof language !== 'string' || typeof code !== 'string') {
803
+ throw new TypeError('Language and code must be a string');
804
+ }
805
+
806
+ const meta = AIRich.tokenizer(code, language);
807
+
808
+ this._submessages.push({
809
+ messageType: 5,
810
+ codeMetadata: {
811
+ codeLanguage: language,
812
+ codeBlocks: meta.codeBlock,
813
+ },
814
+ });
815
+
816
+ this._sections.push(
817
+ AIRich.newLayout('Single', {
818
+ language,
819
+ code_blocks: meta.unified_codeBlock,
820
+ __typename: 'GenAICodeUXPrimitive',
821
+ })
822
+ );
823
+
824
+ return this;
825
+ }
826
+
827
+ addTable(table) {
828
+ if (!Array.isArray(table)) {
829
+ throw new TypeError('Table must be an array');
830
+ }
831
+
832
+ const meta = AIRich.toTableMetadata(table);
833
+
834
+ this._submessages.push({
835
+ messageType: 4,
836
+ tableMetadata: {
837
+ title: meta.title,
838
+ rows: meta.rows,
839
+ },
840
+ });
841
+
842
+ this._sections.push(
843
+ AIRich.newLayout('Single', {
844
+ rows: meta.unified_rows,
845
+ __typename: 'GenATableUXPrimitive',
846
+ })
847
+ );
848
+
849
+ return this;
850
+ }
851
+
852
+ addSource(sources = []) {
853
+ if (!(Array.isArray(sources) && (sources.every((item) => typeof item === 'string') || sources.every((item) => Array.isArray(item) && item.every((v) => typeof v === 'string'))))) {
854
+ throw new TypeError('Sources must be a string array or an array of string arrays');
855
+ }
856
+
857
+ if (sources.every((item) => typeof item === 'string')) {
858
+ sources = [sources];
859
+ }
860
+
861
+ const source = sources.map(([profile_url, url, text]) => ({
862
+ source_type: 'THIRD_PARTY',
863
+ source_display_name: text ?? '',
864
+ source_subtitle: 'AI',
865
+ source_url: url ?? '',
866
+ favicon: {
867
+ url: profile_url ?? '',
868
+ mime_type: 'image/jpeg',
869
+ width: 16,
870
+ height: 16,
871
+ },
872
+ }));
873
+
874
+ this._sections.push(
875
+ AIRich.newLayout('Single', {
876
+ sources: source,
877
+ __typename: 'GenAISearchResultPrimitive',
878
+ })
879
+ );
880
+
881
+ return this;
882
+ }
883
+
884
+ addReels(reelsItems = []) {
885
+ if (
886
+ !(
887
+ (reelsItems && typeof reelsItems === 'object' && !Array.isArray(reelsItems)) ||
888
+ (Array.isArray(reelsItems) && reelsItems.every((item) => item && typeof item === 'object' && !Array.isArray(item)))
889
+ )
890
+ ) {
891
+ throw new TypeError('Reels items must be an object or an array of objects');
892
+ }
893
+
894
+ if (!Array.isArray(reelsItems)) {
895
+ reelsItems = [reelsItems];
896
+ }
897
+
898
+ this._submessages.push({
899
+ messageType: 9,
900
+ contentItemsMetadata: {
901
+ contentType: 1,
902
+ itemsMetadata: reelsItems.map((item) => ({
903
+ reelItem: {
904
+ title: item.username ?? '',
905
+ profileIconUrl: item.profileIconUrl ?? item.profile_url ?? '',
906
+ thumbnailUrl: item.thumbnailUrl ?? item.thumbnail ?? '',
907
+ videoUrl: item.videoUrl ?? item.url ?? '',
908
+ },
909
+ })),
910
+ },
911
+ });
912
+
913
+ reelsItems.forEach((item, idx) => {
914
+ this._richResponseSources.push({
915
+ provider: '\u004E\u0049\u0058\u0045\u004C',
916
+ thumbnailCDNURL: item.thumbnailUrl ?? item.thumbnail ?? '',
917
+ sourceProviderURL: item.videoUrl ?? item.url ?? '',
918
+ sourceQuery: '',
919
+ faviconCDNURL: item.profileIconUrl ?? item.profile_url ?? '',
920
+ citationNumber: idx + 1,
921
+ sourceTitle: item.username ?? '',
922
+ });
923
+ });
924
+
925
+ this._sections.push(
926
+ AIRich.newLayout(
927
+ 'HScroll',
928
+ reelsItems.map((item) => ({
929
+ reels_url: item.videoUrl ?? item.url ?? '',
930
+ thumbnail_url: item.thumbnailUrl ?? item.thumbnail ?? '',
931
+ creator: item.username ?? item.title ?? '',
932
+ avatar_url: item.profileIconUrl ?? item.profile_url ?? '',
933
+ reels_title: item.reels_title ?? item.title ?? '',
934
+ likes_count: item.likes_count ?? item.like ?? 0,
935
+ shares_count: item.shares_count ?? item.share ?? 0,
936
+ view_count: item.view_count ?? item.view ?? 0,
937
+ reel_source: item.reel_source ?? item.source ?? 'IG',
938
+ is_verified: !!(item.is_verified || item.verified),
939
+ __typename: 'GenAIReelPrimitive',
940
+ }))
941
+ )
942
+ );
943
+
944
+ return this;
945
+ }
946
+
947
+ addImage(imageUrl) {
948
+ if (!(typeof imageUrl === 'string' || (Array.isArray(imageUrl) && imageUrl.every((v) => typeof v === 'string')))) {
949
+ throw new TypeError('imageUrl must be a string or array of strings');
950
+ }
951
+ const imageUrls = Array.isArray(imageUrl)
952
+ ? imageUrl.map((url) => ({
953
+ imagePreviewUrl: url,
954
+ imageHighResUrl: url,
955
+ sourceUrl: String.fromCharCode(104, 116, 116, 112, 115, 58, 47, 47, 102, 105, 111, 114, 97, 46, 110, 105, 120, 101, 108, 46, 109, 121, 46, 105, 100, 47),
956
+ }))
957
+ : [
958
+ {
959
+ imagePreviewUrl: imageUrl,
960
+ imageHighResUrl: imageUrl,
961
+ sourceUrl: String.fromCharCode(104, 116, 116, 112, 115, 58, 47, 47, 102, 105, 111, 114, 97, 46, 110, 105, 120, 101, 108, 46, 109, 121, 46, 105, 100, 47),
962
+ },
963
+ ];
964
+
965
+ this._submessages.push({
966
+ messageType: 1,
967
+ gridImageMetadata: {
968
+ gridImageUrl: {
969
+ imagePreviewUrl: Array.isArray(imageUrl) ? imageUrl[0] : imageUrl,
970
+ },
971
+ imageUrls,
972
+ },
973
+ });
974
+
975
+ imageUrls.forEach(({ imagePreviewUrl }) => {
976
+ this._sections.push(
977
+ AIRich.newLayout('Single', {
978
+ media: {
979
+ url: imagePreviewUrl,
980
+ mime_type: 'image/png',
981
+ },
982
+ imagine_type: 'IMAGE',
983
+ status: {
984
+ status: 'READY',
985
+ },
986
+ __typename: 'GenAIImaginePrimitive',
987
+ })
988
+ );
989
+ });
990
+
991
+ return this;
992
+ }
993
+
994
+ addVideo(videoUrl) {
995
+ if (!(typeof videoUrl === 'string' || (Array.isArray(videoUrl) && videoUrl.every((v) => typeof v === 'string')))) {
996
+ throw new TypeError('videoUrl must be a string or array of strings');
997
+ }
998
+
999
+ const videoUrls = (Array.isArray(videoUrl) ? videoUrl : [videoUrl]).map((item) => {
1000
+ const [url, duration = 0] = item.split('|');
1001
+
1002
+ return {
1003
+ videoPreviewUrl: url,
1004
+ videoHighResUrl: url,
1005
+ duration: Number(duration) || 0,
1006
+ sourceUrl: String.fromCharCode(104, 116, 116, 112, 115, 58, 47, 47, 102, 105, 111, 114, 97, 46, 110, 105, 120, 101, 108, 46, 109, 121, 46, 105, 100, 47),
1007
+ };
1008
+ });
1009
+
1010
+ this._submessages.push({
1011
+ messageType: 2,
1012
+ messageText: '[ CANNOT_LOAD_VIDEO - \u004E\u0049\u0058\u0045\u004C ]',
1013
+ });
1014
+
1015
+ videoUrls.forEach(({ videoPreviewUrl, duration = 0 }) => {
1016
+ this._sections.push(
1017
+ AIRich.newLayout('Single', {
1018
+ media: {
1019
+ url: videoPreviewUrl,
1020
+ mime_type: 'video/mp4',
1021
+ duration,
1022
+ },
1023
+ imagine_type: 'ANIMATE',
1024
+ status: {
1025
+ status: 'READY',
1026
+ },
1027
+ __typename: 'GenAIImaginePrimitive',
1028
+ })
1029
+ );
1030
+ });
1031
+
1032
+ return this;
1033
+ }
1034
+
1035
+ addProduct(data = {}) {
1036
+ if (!((data && typeof data === 'object' && !Array.isArray(data)) || (Array.isArray(data) && data.every((item) => item && typeof item === 'object' && !Array.isArray(item))))) {
1037
+ throw new TypeError('Product items must be an object or an array of objects');
1038
+ }
1039
+
1040
+ this._submessages.push({
1041
+ messageType: 2,
1042
+ messageText: '[ CANNOT_LOAD_PRODUCT - NIXEL ]',
1043
+ });
1044
+
1045
+ const items = Array.isArray(data) ? data : [data];
1046
+
1047
+ const product = items.map((item) => ({
1048
+ title: item.title,
1049
+ brand: item.brand,
1050
+ price: item.price,
1051
+ sale_price: item.sale_price,
1052
+ product_url: item.product_url ?? item.url,
1053
+ image: {
1054
+ url: item.image_url ?? item.image,
1055
+ },
1056
+ additional_images: [
1057
+ {
1058
+ url: item.icon_url ?? item.icon,
1059
+ },
1060
+ ],
1061
+ __typename: 'GenAIProductItemCardPrimitive',
1062
+ }));
1063
+
1064
+ this._sections.push(AIRich.newLayout(Array.isArray(data) ? 'HScroll' : 'Single', Array.isArray(data) ? product : product[0]));
1065
+
1066
+ return this;
1067
+ }
1068
+
1069
+ addPost(data = {}) {
1070
+ if (!((data && typeof data === 'object' && !Array.isArray(data)) || (Array.isArray(data) && data.every((item) => item && typeof item === 'object' && !Array.isArray(item))))) {
1071
+ throw new TypeError('Post items must be an object or an array of objects');
1072
+ }
1073
+
1074
+ const posts = Array.isArray(data) ? data : [data];
1075
+
1076
+ this._submessages.push({
1077
+ messageType: 2,
1078
+ messageText: '[ CANNOT_LOAD_POST - NIXEL ]',
1079
+ });
1080
+
1081
+ const primitives = posts.map((p) => ({
1082
+ title: p.title ?? '',
1083
+ subtitle: p.subtitle ?? '',
1084
+ username: p.username ?? '',
1085
+ profile_picture_url: p.profile_picture_url ?? p.profile_url ?? '',
1086
+ is_verified: !!(p.is_verified || p.verified),
1087
+ thumbnail_url: p.thumbnail_url ?? p.thumbnail ?? '',
1088
+ post_caption: p.post_caption ?? p.caption ?? '',
1089
+ likes_count: p.likes_count ?? p.like ?? 0,
1090
+ comments_count: p.comments_count ?? p.comment ?? 0,
1091
+ shares_count: p.shares_count ?? p.share ?? 0,
1092
+ post_url: p.post_url ?? p.url ?? '',
1093
+ post_deeplink: p.post_deeplink ?? p.deeplink ?? '',
1094
+ source_app: p.source_app || p.source || 'INSTAGRAM',
1095
+ footer_label: p.footer_label ?? p.footer ?? '',
1096
+ footer_icon: p.footer_icon ?? p.icon ?? '',
1097
+ is_carousel: posts.length > 1,
1098
+ orientation: p.orientation ?? 'LANDSCAPE',
1099
+ post_type: p.post_type ?? 'VIDEO',
1100
+ __typename: 'GenAIPostPrimitive',
1101
+ }));
1102
+
1103
+ this._sections.push(AIRich.newLayout('HScroll', primitives));
1104
+
1105
+ return this;
1106
+ }
1107
+
1108
+ addTip(text) {
1109
+ this._submessages.push({
1110
+ messageType: 2,
1111
+ messageText: text,
1112
+ });
1113
+
1114
+ this._sections.push(
1115
+ AIRich.newLayout('Single', {
1116
+ text,
1117
+ __typename: 'GenAIMetadataTextPrimitive',
1118
+ })
1119
+ );
1120
+
1121
+ return this;
1122
+ }
1123
+
1124
+ addSuggest(suggestion) {
1125
+ if (!(typeof suggestion === 'string' || (Array.isArray(suggestion) && suggestion.every((v) => typeof v === 'string')))) {
1126
+ throw new TypeError('Suggestion must be a string or array of strings');
1127
+ }
1128
+
1129
+ const suggest = Array.isArray(suggestion)
1130
+ ? suggestion.map((text) => ({
1131
+ prompt_text: text,
1132
+ prompt_type: 'SUGGESTED_PROMPT',
1133
+ __typename: 'GenAIFollowUpSuggestionPillPrimitive',
1134
+ }))
1135
+ : [
1136
+ {
1137
+ prompt_text: suggestion,
1138
+ prompt_type: 'SUGGESTED_PROMPT',
1139
+ __typename: 'GenAIFollowUpSuggestionPillPrimitive',
1140
+ },
1141
+ ];
1142
+
1143
+ this._sections.push(AIRich.newLayout('ActionRow', suggest));
1144
+
1145
+ return this;
1146
+ }
1147
+
1148
+ build({ forwarded = true, includesUnifiedResponse = true, includesSubmessages = true, quoted, quotedParticipant, ...options } = {}) {
1149
+ const forward = forwarded
1150
+ ? {
1151
+ forwardingScore: 1,
1152
+ isForwarded: true,
1153
+ forwardedAiBotMessageInfo: { botJid: '0@bot' },
1154
+ forwardOrigin: 4,
1155
+ }
1156
+ : {};
1157
+
1158
+ const qObj = quoted
1159
+ ? {
1160
+ stanzaId: quoted?.key?.id || quoted?.id,
1161
+ participant: quotedParticipant || quoted?.key?.participant || quoted?.key?.remoteJid,
1162
+ quotedType: 0,
1163
+ quotedMessage: typeof quoted === 'object' && quoted !== null ? (quoted.message ?? quoted) : undefined,
1164
+ }
1165
+ : {};
1166
+
1167
+ const sections = this._footer
1168
+ ? [
1169
+ ...this._sections,
1170
+ AIRich.newLayout('Single', {
1171
+ text: this._footer,
1172
+ __typename: 'GenAIMetadataTextPrimitive',
1173
+ }),
1174
+ ]
1175
+ : [...this._sections];
1176
+
1177
+ return {
1178
+ messageContextInfo: {
1179
+ deviceListMetadata: {},
1180
+ deviceListMetadataVersion: 2,
1181
+ botMetadata: {
1182
+ messageDisclaimerText: this._title,
1183
+ richResponseSourcesMetadata: { sources: this._richResponseSources },
1184
+ },
1185
+ },
1186
+ ...this._extraPayload,
1187
+ botForwardedMessage: {
1188
+ message: {
1189
+ richResponseMessage: {
1190
+ messageType: 1,
1191
+ submessages: includesSubmessages ? this._submessages : [],
1192
+ unifiedResponse: {
1193
+ data: includesUnifiedResponse ? Buffer.from(JSON.stringify({ response_id: crypto.randomUUID(), sections })).toString('base64') : '',
1194
+ },
1195
+ contextInfo: {
1196
+ ...forward,
1197
+ ...qObj,
1198
+ ...this._contextInfo,
1199
+ },
1200
+ },
1201
+ },
1202
+ },
1203
+ };
1204
+ }
1205
+
1206
+ async send(jid, { forwarded, includesUnifiedResponse, includesSubmessages, ...options } = {}) {
1207
+ const msg = this.build({ forwarded, includesUnifiedResponse, ...options });
1208
+
1209
+ return await this.#client.relayMessage(jid, msg, { ...options });
1210
+ }
1211
+
1212
+ static tokenizer(code, lang = 'javascript') {
1213
+ const keywordsMap = {
1214
+ javascript: new Set([
1215
+ 'break',
1216
+ 'case',
1217
+ 'catch',
1218
+ 'continue',
1219
+ 'debugger',
1220
+ 'delete',
1221
+ 'do',
1222
+ 'else',
1223
+ 'finally',
1224
+ 'for',
1225
+ 'function',
1226
+ 'if',
1227
+ 'in',
1228
+ 'instanceof',
1229
+ 'new',
1230
+ 'return',
1231
+ 'switch',
1232
+ 'this',
1233
+ 'throw',
1234
+ 'try',
1235
+ 'typeof',
1236
+ 'var',
1237
+ 'void',
1238
+ 'while',
1239
+ 'with',
1240
+ 'true',
1241
+ 'false',
1242
+ 'null',
1243
+ 'undefined',
1244
+ 'class',
1245
+ 'const',
1246
+ 'let',
1247
+ 'super',
1248
+ 'extends',
1249
+ 'export',
1250
+ 'import',
1251
+ 'yield',
1252
+ 'static',
1253
+ 'constructor',
1254
+ 'async',
1255
+ 'await',
1256
+ 'get',
1257
+ 'set',
1258
+ ]),
1259
+ };
1260
+
1261
+ const TYPE_MAP = {
1262
+ 0: 'DEFAULT',
1263
+ 1: 'KEYWORD',
1264
+ 2: 'METHOD',
1265
+ 3: 'STR',
1266
+ 4: 'NUMBER',
1267
+ 5: 'COMMENT',
1268
+ };
1269
+
1270
+ const keywords = keywordsMap[lang] || new Set();
1271
+ const tokens = [];
1272
+
1273
+ let i = 0;
1274
+
1275
+ const push = (content, type) => {
1276
+ if (!content) return;
1277
+ const last = tokens[tokens.length - 1];
1278
+ if (last && last.highlightType === type) last.codeContent += content;
1279
+ else tokens.push({ codeContent: content, highlightType: type });
1280
+ };
1281
+
1282
+ while (i < code.length) {
1283
+ const c = code[i];
1284
+
1285
+ if (/\s/.test(c)) {
1286
+ let s = i;
1287
+ while (i < code.length && /\s/.test(code[i])) i++;
1288
+ push(code.slice(s, i), 0);
1289
+ continue;
1290
+ }
1291
+
1292
+ if (c === '/' && code[i + 1] === '/') {
1293
+ let s = i;
1294
+ i += 2;
1295
+ while (i < code.length && code[i] !== '\n') i++;
1296
+ push(code.slice(s, i), 5);
1297
+ continue;
1298
+ }
1299
+
1300
+ if (c === '"' || c === "'" || c === '`') {
1301
+ let s = i;
1302
+ const q = c;
1303
+ i++;
1304
+ while (i < code.length) {
1305
+ if (code[i] === '\\' && i + 1 < code.length) i += 2;
1306
+ else if (code[i] === q) {
1307
+ i++;
1308
+ break;
1309
+ } else i++;
1310
+ }
1311
+ push(code.slice(s, i), 3);
1312
+ continue;
1313
+ }
1314
+
1315
+ if (/[0-9]/.test(c)) {
1316
+ let s = i;
1317
+ while (i < code.length && /[0-9.]/.test(code[i])) i++;
1318
+ push(code.slice(s, i), 4);
1319
+ continue;
1320
+ }
1321
+
1322
+ if (/[a-zA-Z_$]/.test(c)) {
1323
+ let s = i;
1324
+ while (i < code.length && /[a-zA-Z0-9_$]/.test(code[i])) i++;
1325
+ const word = code.slice(s, i);
1326
+
1327
+ let type = 0;
1328
+ if (keywords.has(word)) type = 1;
1329
+ else {
1330
+ let j = i;
1331
+ while (j < code.length && /\s/.test(code[j])) j++;
1332
+ if (code[j] === '(') type = 2;
1333
+ }
1334
+
1335
+ push(word, type);
1336
+ continue;
1337
+ }
1338
+
1339
+ push(c, 0);
1340
+ i++;
1341
+ }
1342
+
1343
+ return {
1344
+ codeBlock: tokens,
1345
+ unified_codeBlock: tokens.map((t) => ({
1346
+ content: t.codeContent,
1347
+ type: TYPE_MAP[t.highlightType],
1348
+ })),
1349
+ };
1350
+ }
1351
+
1352
+ static toTableMetadata(arr) {
1353
+ if (!Array.isArray(arr) || !arr.every((row) => Array.isArray(row) && row.every((cell) => typeof cell === 'string'))) {
1354
+ throw new TypeError('Table must be a nested array of strings');
1355
+ }
1356
+
1357
+ const [header, ...rows] = arr;
1358
+
1359
+ const maxLen = Math.max(header.length, ...rows.map((r) => r.length));
1360
+
1361
+ const normalize = (r) => [...r, ...Array(maxLen - r.length).fill('')];
1362
+
1363
+ const unified_rows = [
1364
+ {
1365
+ is_header: true,
1366
+ cells: normalize(header),
1367
+ },
1368
+ ...rows.map((r) => ({
1369
+ is_header: false,
1370
+ cells: normalize(r),
1371
+ })),
1372
+ ];
1373
+
1374
+ const rowsMeta = unified_rows.map((r) => ({
1375
+ items: r.cells,
1376
+ ...(r.is_header ? { isHeading: true } : {}),
1377
+ }));
1378
+
1379
+ return {
1380
+ title: '',
1381
+ rows: rowsMeta,
1382
+ unified_rows,
1383
+ };
1384
+ }
1385
+ }
1386
+
1387
+ export { VERSION, Button, ButtonV2, Carousel, AIRich };