@ryuu-reinzz/haruka-lib 3.7.1 → 4.0.0-beta.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.
package/main/socket.js CHANGED
@@ -1,7 +1,46 @@
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:
15
+ * - ~ Ahmad tumbuh kembang
16
+ * - RyuuReinzz (Added "messageBuilder" wrapper, "extendSocketsBotz", Owner of @ryuu-reinzz/baileys)
17
+ *
18
+ * WhatsApp: wa.me/6282139672290
19
+ * Channel: https://whatsapp.com/channel/0029VbCV1ck8fewpdNb2TY2k
20
+ *
21
+ * Owner WhatsApp: wa.me/6288246552068 RyuuReinzz
22
+ *
23
+ * Note:
24
+ * This project is being sold because it has been
25
+ * modified and has been added to
26
+ * many other things that make it more valuable.
27
+ *
28
+ * Copyright (c) 2026 Nixel
29
+ * Copyright (c) 2026 RyuuReinzz
30
+ *
31
+ * Permission is granted to use and modify this library
32
+ * for personal or commercial projects.
33
+ *
34
+ * Reuploading, reselling, relicensing, or redistributing
35
+ * this library as a standalone product is prohibited.
36
+ *
37
+ * Do not claim this project as your own original work.
38
+ */
1
39
  import crypto from "crypto";
2
40
  import fetch from "node-fetch";
3
41
  import fs from "fs";
4
42
  import path from "path";
43
+ import sharp from "sharp";
5
44
  import {
6
45
  fileURLToPath
7
46
  } from 'url';
@@ -16,7 +55,7 @@ const __dirname = dirname(__filename);
16
55
  * @param {import('baileys').WASocket} socket
17
56
  */
18
57
 
19
- export default function addProperty(socket, baileys) {
58
+ function addProperty(socket, baileys) {
20
59
  const {
21
60
  proto,
22
61
  generateWAMessageFromContent,
@@ -26,9 +65,1667 @@ export default function addProperty(socket, baileys) {
26
65
  generateMessageID,
27
66
  generateWAMessage
28
67
  } = baileys;
68
+ let sendMessage = socket.sendMessage;
69
+
70
+ function extractIE(text, {
71
+ extract = true,
72
+ hyperlink = true,
73
+ citation = true,
74
+ latex = true
75
+ } = {}) {
76
+ if (!extract) {
77
+ return {
78
+ text,
79
+ ie: [],
80
+ };
81
+ }
82
+ let ie = [],
83
+ result = '',
84
+ last = 0,
85
+ citation_index = 1,
86
+ hyperlink_index = 0,
87
+ latex_index = 0,
88
+ stack = [];
89
+ for (let i = 0; i < text.length; i++) {
90
+ if (text[i] == '[' && text[i - 1] != '\\') {
91
+ stack.push(i);
92
+ } else if (text[i] == ']' && (text[i + 1] == '(' || text[i + 1] == '<')) {
93
+ let start = stack.pop();
94
+ if (start == null) continue;
95
+ let open = text[i + 1],
96
+ close = open == '(' ? ')' : '>',
97
+ type = open == '(' ? 'link' : 'latex',
98
+ end = i + 2,
99
+ depth = 1;
100
+ while (end < text.length && depth) {
101
+ if (text[end] == open && text[end - 1] != '\\') depth++;
102
+ else if (text[end] == close && text[end - 1] != '\\') depth--;
103
+ end++;
104
+ }
105
+ if (depth) continue;
106
+ let raw = text.slice(start + 1, i).trim(),
107
+ url = text.slice(i + 2, end - 1).trim(),
108
+ key,
109
+ tag,
110
+ data;
111
+ if (type == 'latex') {
112
+ if (!latex) continue;
113
+ let [txt = '', width = null, height = null, font_height = null, padding = null] = raw.split('|');
114
+ key = `\u004E\u0049\u0058\u0045\u004C_LATEX_${latex_index++}`;
115
+ tag = `{{${key}}}${txt || 'image'}{{/${key}}}`;
116
+ data = {
117
+ type: 'latex',
118
+ ie: {
119
+ key,
120
+ text: txt,
121
+ url,
122
+ width,
123
+ height,
124
+ font_height,
125
+ padding,
126
+ },
127
+ };
128
+ } else if (raw) {
129
+ if (!hyperlink) continue;
130
+ key = `\u004E\u0049\u0058\u0045\u004C_HYPERLINK_${hyperlink_index++}`;
131
+ tag = `{{${key}}}${url}{{/${key}}}`;
132
+ data = {
133
+ type: 'hyperlink',
134
+ ie: {
135
+ key,
136
+ text: raw,
137
+ url,
138
+ },
139
+ };
140
+ } else {
141
+ if (!citation) continue;
142
+ key = `\u004E\u0049\u0058\u0045\u004C_CITATION_${citation_index - 1}`;
143
+ tag = `{{${key}}}${url}{{/${key}}}`;
144
+ data = {
145
+ type: 'citation',
146
+ ie: {
147
+ reference_id: citation_index++,
148
+ key,
149
+ text: '',
150
+ url,
151
+ },
152
+ };
153
+ }
154
+ result += text.slice(last, start) + tag;
155
+ last = end;
156
+ ie.push(data);
157
+ i = end - 1;
158
+ }
159
+ }
160
+ result += text.slice(last);
161
+ return {
162
+ text: result,
163
+ ie,
164
+ };
165
+ }
166
+
167
+ class BaseBuilder {
168
+ constructor() {
169
+ this._title = '';
170
+ this._subtitle = '';
171
+ this._body = '';
172
+ this._footer = '';
173
+ this._contextInfo = {};
174
+ this._extraPayload = {};
175
+ }
176
+
177
+ setTitle(title) {
178
+ if (typeof title !== 'string') {
179
+ throw new TypeError('Title must be a string');
180
+ }
181
+ this._title = title;
182
+ return this;
183
+ }
184
+
185
+ setSubtitle(subtitle) {
186
+ if (typeof subtitle !== 'string') {
187
+ throw new TypeError('Subtitle must be a string');
188
+ }
189
+ this._subtitle = subtitle;
190
+ return this;
191
+ }
192
+
193
+ setBody(body) {
194
+ if (typeof body !== 'string') {
195
+ throw new TypeError('Body must be a string');
196
+ }
197
+ this._body = body;
198
+ return this;
199
+ }
200
+
201
+ setFooter(footer) {
202
+ if (typeof footer !== 'string') {
203
+ throw new TypeError('Footer must be a string');
204
+ }
205
+ this._footer = footer;
206
+ return this;
207
+ }
208
+
209
+ setContextInfo(obj) {
210
+ if (typeof obj !== 'object' || obj === null || Array.isArray(obj)) {
211
+ throw new TypeError('ContextInfo must be a plain object');
212
+ }
213
+
214
+ this._contextInfo = obj;
215
+ return this;
216
+ }
217
+
218
+ addPayload(obj) {
219
+ if (typeof obj !== 'object' || obj === null || Array.isArray(obj)) {
220
+ throw new TypeError('Payload must be a plain object');
221
+ }
222
+
223
+ Object.assign(this._extraPayload, obj);
224
+
225
+ return this;
226
+ }
227
+
228
+ static async resize(buffer, x, y, fit = 'cover') {
229
+ return await sharp(buffer)
230
+ .resize(x, y, {
231
+ fit,
232
+ position: 'center',
233
+ background: {
234
+ r: 0,
235
+ g: 0,
236
+ b: 0,
237
+ alpha: 0
238
+ },
239
+ })
240
+ .png()
241
+ .toBuffer();
242
+ }
243
+
244
+ static async fetchBuffer(url, options = {}, config = {}) {
245
+ try {
246
+ let response = await fetch(url, options);
247
+ if (!response.ok) throw Error(`HTTP ${response.status}`);
248
+ return Buffer.from(await response.arrayBuffer());
249
+ } catch (error) {
250
+ if (config.silent) return Buffer.alloc(0);
251
+ throw error;
252
+ }
253
+ }
254
+ }
255
+
256
+ class Button extends BaseBuilder {
257
+ #client;
258
+
259
+ constructor(client) {
260
+ super();
261
+ if (!client) {
262
+ throw new Error('Socket is required');
263
+ }
264
+ this.#client = client;
265
+
266
+ this._buttons = [];
267
+ this._data;
268
+ this._currentSelectionIndex = -1;
269
+ this._currentSectionIndex = -1;
270
+ this._params = {};
271
+ }
272
+
273
+ setVideo(path, options = {}) {
274
+ if (!path) throw new Error('Url or buffer needed');
275
+ Buffer.isBuffer(path) ? (this._data = {
276
+ video: path,
277
+ ...options
278
+ }) : (this._data = {
279
+ video: {
280
+ url: path
281
+ },
282
+ ...options
283
+ });
284
+ return this;
285
+ }
286
+
287
+ setImage(path, options = {}) {
288
+ if (!path) throw new Error('Url or buffer needed');
289
+ Buffer.isBuffer(path) ? (this._data = {
290
+ image: path,
291
+ ...options
292
+ }) : (this._data = {
293
+ image: {
294
+ url: path
295
+ },
296
+ ...options
297
+ });
298
+ return this;
299
+ }
300
+
301
+ setDocument(path, options = {}) {
302
+ if (!path) throw new Error('Url or buffer needed');
303
+ Buffer.isBuffer(path) ? (this._data = {
304
+ document: path,
305
+ ...options
306
+ }) : (this._data = {
307
+ document: {
308
+ url: path
309
+ },
310
+ ...options
311
+ });
312
+ return this;
313
+ }
314
+
315
+ setMedia(obj) {
316
+ if (typeof obj !== 'object' || obj === null || Array.isArray(obj)) {
317
+ throw new TypeError('Media must be a plain object');
318
+ }
319
+
320
+ this._data = obj;
321
+ return this;
322
+ }
323
+
324
+ clearButtons() {
325
+ this._buttons = [];
326
+ return this;
327
+ }
328
+
329
+ setParams(obj) {
330
+ this._params = obj;
331
+ return this;
332
+ }
333
+
334
+ addButton(name, params) {
335
+ this._buttons.push({
336
+ name,
337
+ buttonParamsJson: typeof params === 'string' ? params : JSON.stringify(params),
338
+ });
339
+
340
+ return this;
341
+ }
342
+
343
+ makeRow(header = '', title = '', description = '', id = '') {
344
+ if (this._currentSelectionIndex === -1 || this._currentSectionIndex === -1) {
345
+ throw new Error('You need to create a selection and a section first');
346
+ }
347
+ const buttonParams = JSON.parse(this._buttons[this._currentSelectionIndex].buttonParamsJson);
348
+ buttonParams.sections[this._currentSectionIndex].rows.push({
349
+ header,
350
+ title,
351
+ description,
352
+ id
353
+ });
354
+ this._buttons[this._currentSelectionIndex].buttonParamsJson = JSON.stringify(buttonParams);
355
+ return this;
356
+ }
357
+
358
+ makeSection(title = '', highlight_label = '') {
359
+ if (this._currentSelectionIndex === -1) {
360
+ throw new Error('You need to create a selection first');
361
+ }
362
+ const buttonParams = JSON.parse(this._buttons[this._currentSelectionIndex].buttonParamsJson);
363
+ buttonParams.sections.push({
364
+ title,
365
+ highlight_label,
366
+ rows: []
367
+ });
368
+ this._currentSectionIndex = buttonParams.sections.length - 1;
369
+ this._buttons[this._currentSelectionIndex].buttonParamsJson = JSON.stringify(buttonParams);
370
+ return this;
371
+ }
372
+
373
+ addSelection(title, options = {}) {
374
+ this._buttons.push({
375
+ ...options,
376
+ name: 'single_select',
377
+ buttonParamsJson: JSON.stringify({
378
+ title,
379
+ sections: []
380
+ })
381
+ });
382
+ this._currentSelectionIndex = this._buttons.length - 1;
383
+ this._currentSectionIndex = -1;
384
+ return this;
385
+ }
386
+
387
+ addReply(display_text = '', id = '', options = {}) {
388
+ this._buttons.push({
389
+ name: 'quick_reply',
390
+ buttonParamsJson: JSON.stringify({
391
+ display_text,
392
+ id,
393
+ ...options,
394
+ }),
395
+ });
396
+ return this;
397
+ }
398
+
399
+ addCall(display_text = '', id = '', options = {}) {
400
+ this._buttons.push({
401
+ name: 'cta_call',
402
+ buttonParamsJson: JSON.stringify({
403
+ display_text,
404
+ id,
405
+ ...options,
406
+ }),
407
+ });
408
+ return this;
409
+ }
410
+
411
+ addReminder(display_text = '', id = '', options = {}) {
412
+ this._buttons.push({
413
+ name: 'cta_reminder',
414
+ buttonParamsJson: JSON.stringify({
415
+ display_text,
416
+ id,
417
+ ...options,
418
+ }),
419
+ });
420
+ return this;
421
+ }
422
+
423
+ addCancelReminder(display_text = '', id = '', options = {}) {
424
+ this._buttons.push({
425
+ name: 'cta_cancel_reminder',
426
+ buttonParamsJson: JSON.stringify({
427
+ display_text,
428
+ id,
429
+ ...options,
430
+ }),
431
+ });
432
+ return this;
433
+ }
434
+
435
+ addAddress(display_text = '', id = '', options = {}) {
436
+ this._buttons.push({
437
+ name: 'address_message',
438
+ buttonParamsJson: JSON.stringify({
439
+ display_text,
440
+ id,
441
+ ...options,
442
+ }),
443
+ });
444
+ return this;
445
+ }
446
+
447
+ addLocation(options = {}) {
448
+ this._buttons.push({
449
+ name: 'send_location',
450
+ buttonParamsJson: JSON.stringify(options),
451
+ });
452
+ return this;
453
+ }
454
+
455
+ addUrl(display_text = '', url = '', webview_interaction = false, options = {}) {
456
+ this._buttons.push({
457
+ ...options,
458
+ name: 'cta_url',
459
+ buttonParamsJson: JSON.stringify({
460
+ display_text,
461
+ url,
462
+ webview_interaction,
463
+ ...options,
464
+ }),
465
+ });
466
+ return this;
467
+ }
468
+
469
+ addCopy(display_text = '', copy_code = '', options = {}) {
470
+ this._buttons.push({
471
+ name: 'cta_copy',
472
+ buttonParamsJson: JSON.stringify({
473
+ display_text,
474
+ copy_code,
475
+ ...options,
476
+ }),
477
+ });
478
+ return this;
479
+ }
480
+
481
+ static paramsList = {
482
+ limited_time_offer: {
483
+ text: 'string',
484
+ url: 'string',
485
+ copy_code: 'string',
486
+ expiration_time: 'number',
487
+ },
488
+ bottom_sheet: {
489
+ in_thread_buttons_limit: 'number',
490
+ divider_indices: ['number'],
491
+ list_title: 'string',
492
+ button_title: 'string',
493
+ },
494
+ tap_target_configuration: {
495
+ title: 'string',
496
+ description: 'string',
497
+ canonical_url: 'string',
498
+ domain: 'string',
499
+ buttonIndex: 'number',
500
+ },
501
+ };
502
+
503
+ async toCard() {
504
+ return {
505
+ body: {
506
+ text: this._body,
507
+ },
508
+ footer: {
509
+ text: this._footer,
510
+ },
511
+ header: {
512
+ title: this._title,
513
+ subtitle: this._subtitle,
514
+ hasMediaAttachment: !!this._data,
515
+ ...(this._data ?
516
+ await prepareWAMessageMedia(this._data, {
517
+ upload: this.#client.waUploadToServer
518
+ }).catch((e) => {
519
+ if (String(e).includes('Invalid media type')) return this._data;
520
+ throw e;
521
+ }) : {}),
522
+ },
523
+ nativeFlowMessage: {
524
+ messageParamsJson: JSON.stringify(this._params),
525
+ buttons: this._buttons,
526
+ },
527
+ };
528
+ }
529
+
530
+ async build(jid, {
531
+ ...options
532
+ } = {}) {
533
+ const message = await this.toCard();
534
+
535
+ return generateWAMessageFromContent(
536
+ jid, {
537
+ ...this._extraPayload,
538
+ interactiveMessage: {
539
+ ...message,
540
+ contextInfo: this._contextInfo,
541
+ },
542
+ }, {
543
+ ...options
544
+ }
545
+ );
546
+ }
547
+
548
+ async send(jid, {
549
+ ...options
550
+ } = {}) {
551
+ const msg = await this.build(jid, options);
552
+
553
+ await this.#client.relayMessage(msg.key.remoteJid, msg.message, {
554
+ messageId: msg.key.id,
555
+ additionalNodes: [{
556
+ tag: 'biz',
557
+ attrs: {},
558
+ content: [{
559
+ tag: 'interactive',
560
+ attrs: {
561
+ type: 'native_flow',
562
+ v: '1'
563
+ },
564
+ content: [{
565
+ tag: 'native_flow',
566
+ attrs: {
567
+ v: '9',
568
+ name: 'mixed'
569
+ }
570
+ }],
571
+ }, ],
572
+ }, ],
573
+ ...options,
574
+ });
575
+ return msg;
576
+ }
577
+ }
578
+
579
+ class ButtonV2 extends BaseBuilder {
580
+ #client;
581
+
582
+ constructor(client) {
583
+ super();
584
+ if (!client) {
585
+ throw new Error('Socket is required');
586
+ }
587
+
588
+ this.#client = client;
589
+ this._image;
590
+ this._data;
591
+ this._buttons = [];
592
+ }
593
+
594
+ addButton(displayText = '', buttonId = crypto.randomUUID()) {
595
+ this._buttons.push({
596
+ buttonId,
597
+ buttonText: {
598
+ displayText
599
+ },
600
+ type: 1,
601
+ });
602
+ return this;
603
+ }
604
+
605
+ addRawButton(obj) {
606
+ if (typeof obj !== 'object' || obj === null || Array.isArray(obj)) {
607
+ throw new TypeError('Buttons must be a plain object');
608
+ }
609
+
610
+ this._buttons.push(obj);
611
+ return this;
612
+ }
613
+
614
+ setThumbnail(path) {
615
+ if (!path) throw new Error('Url or buffer needed');
616
+ this._image = path;
617
+ return this;
618
+ }
619
+
620
+ setMedia(obj) {
621
+ if (typeof obj !== 'object' || obj === null || Array.isArray(obj)) {
622
+ throw new TypeError('Media must be a plain object');
623
+ }
624
+
625
+ this._data = obj;
626
+ return this;
627
+ }
628
+
629
+ async build(jid, {
630
+ ...options
631
+ } = {}) {
632
+ let _thumbnail = this._image ? await BaseBuilder.resize(Buffer.isBuffer(this._image) ? this._image : await BaseBuilder.fetchBuffer(this._image, {}, {
633
+ silent: true
634
+ }), 300, 300) : null;
635
+ const msg = generateWAMessageFromContent(
636
+ jid, {
637
+ ...this._extraPayload,
638
+ buttonsMessage: {
639
+ contentText: this._body,
640
+ footerText: this._footer,
641
+ ...(this._data ?
642
+ this._data : {
643
+ headerType: 6,
644
+ locationMessage: {
645
+ degreesLatitude: 0,
646
+ degreesLongitude: 0,
647
+ name: this._title,
648
+ address: this._subtitle,
649
+ jpegThumbnail: _thumbnail,
650
+ },
651
+ }),
652
+ viewOnce: true,
653
+ contextInfo: this._contextInfo,
654
+ buttons: [...this._buttons],
655
+ },
656
+ }, {
657
+ ...options
658
+ }
659
+ );
660
+ return msg;
661
+ }
662
+
663
+ async send(jid, {
664
+ ...options
665
+ } = {}) {
666
+ if (this._buttons.length < 1) throw new Error('ButtonV2 requires at least one button');
667
+ const msg = await this.build(jid, options);
668
+
669
+ await this.#client.relayMessage(msg.key.remoteJid, msg.message, {
670
+ messageId: msg.key.id,
671
+ additionalNodes: [{
672
+ tag: 'biz',
673
+ attrs: {},
674
+ content: [{
675
+ tag: 'interactive',
676
+ attrs: {
677
+ type: 'native_flow',
678
+ v: '1'
679
+ },
680
+ content: [{
681
+ tag: 'native_flow',
682
+ attrs: {
683
+ v: '9',
684
+ name: 'mixed'
685
+ }
686
+ }],
687
+ }, ],
688
+ }, ],
689
+ ...options,
690
+ });
691
+ return msg;
692
+ }
693
+ }
694
+
695
+ class Carousel extends BaseBuilder {
696
+ #client;
697
+
698
+ constructor(client) {
699
+ super();
700
+ if (!client) {
701
+ throw new Error('Socket is required');
702
+ }
703
+
704
+ this.#client = client;
705
+ this._cards = [];
706
+ }
707
+
708
+ addCard(card) {
709
+ const cards = Array.isArray(card) ? card : [card];
710
+ const baseIndex = this._cards.length;
711
+
712
+ for (const [index, c] of cards.entries()) {
713
+ if (!c?.header?.hasMediaAttachment) {
714
+ throw new Error(`Card [${baseIndex + index}] must include an image or video in header`);
715
+ }
716
+ }
717
+
718
+ this._cards.push(...cards);
719
+ return this;
720
+ }
721
+
722
+ build(jid, {
723
+ ...options
724
+ } = {}) {
725
+ return generateWAMessageFromContent(
726
+ jid, {
727
+ ...this._extraPayload,
728
+ interactiveMessage: {
729
+ header: {
730
+ hasMediaAttachment: false,
731
+ },
732
+ body: {
733
+ text: this._body
734
+ },
735
+ footer: {
736
+ text: this._footer
737
+ },
738
+ contextInfo: this._contextInfo,
739
+ carouselMessage: {
740
+ cards: this._cards,
741
+ },
742
+ },
743
+ }, {
744
+ ...options
745
+ }
746
+ );
747
+ }
748
+
749
+ async send(jid, {
750
+ ...options
751
+ } = {}) {
752
+ const msg = this.build(jid, options);
753
+
754
+ await this.#client.relayMessage(msg.key.remoteJid, msg.message, {
755
+ messageId: msg.key.id,
756
+ additionalNodes: [{
757
+ tag: 'biz',
758
+ attrs: {},
759
+ content: [{
760
+ tag: 'interactive',
761
+ attrs: {
762
+ type: 'native_flow',
763
+ v: '1'
764
+ },
765
+ content: [{
766
+ tag: 'native_flow',
767
+ attrs: {
768
+ v: '9',
769
+ name: 'mixed'
770
+ }
771
+ }],
772
+ }, ],
773
+ }, ],
774
+ ...options,
775
+ });
776
+ return msg;
777
+ }
778
+ }
779
+
780
+ class AIRich extends BaseBuilder {
781
+ #client;
782
+
783
+ constructor(client) {
784
+ if (!client) {
785
+ throw new Error('Socket is required');
786
+ }
787
+
788
+ super();
789
+ this.#client = client;
790
+ this._contextInfo = {};
791
+ this._submessages = [];
792
+ this._sections = [];
793
+ this._richResponseSources = [];
794
+ }
795
+
796
+ static newLayout(name, data) {
797
+ return {
798
+ view_model: {
799
+ [Array.isArray(data) ? 'primitives' : 'primitive']: data,
800
+ __typename: `GenAI${name}LayoutViewModel`,
801
+ },
802
+ };
803
+ }
804
+
805
+ addSubmessage(submessage) {
806
+ const items = Array.isArray(submessage) ? submessage : [submessage];
807
+
808
+ for (const item of items) {
809
+ if (typeof item !== 'object' || item === null || Array.isArray(item)) {
810
+ throw new TypeError('Submessage must be a plain object or array of plain objects');
811
+ }
812
+
813
+ this._submessages.push(item);
814
+ }
815
+
816
+ return this;
817
+ }
818
+
819
+ addSection(section) {
820
+ const items = Array.isArray(section) ? section : [section];
821
+
822
+ for (const item of items) {
823
+ if (typeof item !== 'object' || item === null || Array.isArray(item)) {
824
+ throw new TypeError('Section must be a plain object or array of plain objects');
825
+ }
826
+
827
+ this._sections.push(item);
828
+ }
829
+
830
+ return this;
831
+ }
832
+
833
+ addText(text, {
834
+ hyperlink = true,
835
+ citation = true,
836
+ latex = true
837
+ } = {}) {
838
+ if (typeof text != 'string') {
839
+ throw new TypeError('Text must be a string');
840
+ }
841
+
842
+ const extractedIE = extractIE(text, {
843
+ hyperlink,
844
+ citation,
845
+ latex,
846
+ });
847
+
848
+ const inline_entities = extractedIE.ie.map(({
849
+ type,
850
+ ie
851
+ }) => {
852
+ if (type == 'hyperlink') {
853
+ return {
854
+ key: ie.key,
855
+ metadata: {
856
+ display_name: ie.text,
857
+ is_trusted: true,
858
+ url: ie.url,
859
+ __typename: 'GenAIInlineLinkItem',
860
+ },
861
+ };
862
+ }
863
+ if (type == 'citation') {
864
+ return {
865
+ key: ie.key,
866
+ metadata: {
867
+ reference_id: ie.reference_id,
868
+ reference_url: ie.url,
869
+ reference_title: ie.url,
870
+ reference_display_name: ie.url,
871
+ sources: [],
872
+ __typename: 'GenAISearchCitationItem',
873
+ },
874
+ };
875
+ }
876
+ if (type == 'latex') {
877
+ return {
878
+ key: ie.key,
879
+ metadata: {
880
+ latex_expression: ie.text,
881
+ latex_image: {
882
+ url: ie.url,
883
+ width: Number(ie.width) || 100,
884
+ height: Number(ie.height) || 100,
885
+ },
886
+ font_height: Number(ie.font_height) || 83.333333333333,
887
+ padding: Number(ie.padding) || 15,
888
+ __typename: 'GenAILatexItem',
889
+ },
890
+ };
891
+ }
892
+
893
+ return {
894
+ key: ie.key,
895
+ metadata: {
896
+ latex_expression: ie.text,
897
+ latex_image: {
898
+ url: ie.url,
899
+ width,
900
+ height,
901
+ },
902
+ font_height: Number(ie.font_height) || 83.333333333333,
903
+ padding: Number(ie.padding) || 15,
904
+ __typename: 'GenAILatexItem',
905
+ },
906
+ };
907
+ });
29
908
 
909
+ this._submessages.push({
910
+ messageType: 2,
911
+ messageText: extractedIE.text,
912
+ });
913
+
914
+ this._sections.push(
915
+ AIRich.newLayout('Single', {
916
+ text: extractedIE.text,
917
+ ...(inline_entities.length && {
918
+ inline_entities,
919
+ }),
920
+ __typename: 'GenAIMarkdownTextUXPrimitive',
921
+ })
922
+ );
923
+
924
+ return this;
925
+ }
926
+
927
+ addCode(language, code) {
928
+ if (typeof language !== 'string' || typeof code !== 'string') {
929
+ throw new TypeError('Language and code must be a string');
930
+ }
931
+
932
+ const meta = AIRich.tokenizer(code, language);
933
+
934
+ this._submessages.push({
935
+ messageType: 5,
936
+ codeMetadata: {
937
+ codeLanguage: language,
938
+ codeBlocks: meta.codeBlock,
939
+ },
940
+ });
941
+
942
+ this._sections.push(
943
+ AIRich.newLayout('Single', {
944
+ language,
945
+ code_blocks: meta.unified_codeBlock,
946
+ __typename: 'GenAICodeUXPrimitive',
947
+ })
948
+ );
949
+
950
+ return this;
951
+ }
952
+
953
+ addTable(table) {
954
+ if (!Array.isArray(table)) {
955
+ throw new TypeError('Table must be an array');
956
+ }
957
+
958
+ const meta = AIRich.toTableMetadata(table);
959
+
960
+ this._submessages.push({
961
+ messageType: 4,
962
+ tableMetadata: {
963
+ title: meta.title,
964
+ rows: meta.rows,
965
+ },
966
+ });
967
+
968
+ this._sections.push(
969
+ AIRich.newLayout('Single', {
970
+ rows: meta.unified_rows,
971
+ __typename: 'GenATableUXPrimitive',
972
+ })
973
+ );
974
+
975
+ return this;
976
+ }
977
+
978
+
979
+ addSource(sources = []) {
980
+ if (!(Array.isArray(sources) && (sources.every((item) => typeof item === 'string') || sources.every((item) => Array.isArray(item) && item.every((v) => typeof v === 'string'))))) {
981
+ throw new TypeError('Sources must be a string array or an array of string arrays');
982
+ }
983
+
984
+ if (sources.every((item) => typeof item === 'string')) {
985
+ sources = [sources];
986
+ }
987
+
988
+ const source = sources.map(([profile_url, url, text]) => ({
989
+ source_type: 'THIRD_PARTY',
990
+ source_display_name: text ?? '',
991
+ source_subtitle: 'AI',
992
+ source_url: url ?? '',
993
+ favicon: {
994
+ url: profile_url ?? '',
995
+ mime_type: 'image/jpeg',
996
+ width: 16,
997
+ height: 16,
998
+ },
999
+ }));
1000
+
1001
+ this._sections.push(
1002
+ AIRich.newLayout('Single', {
1003
+ sources: source,
1004
+ __typename: 'GenAISearchResultPrimitive',
1005
+ })
1006
+ );
1007
+
1008
+ return this;
1009
+ }
1010
+
1011
+ addReels(reelsItems = []) {
1012
+ if (
1013
+ !(
1014
+ (reelsItems && typeof reelsItems === 'object' && !Array.isArray(reelsItems)) ||
1015
+ (Array.isArray(reelsItems) && reelsItems.every((item) => item && typeof item === 'object' && !Array.isArray(item)))
1016
+ )
1017
+ ) {
1018
+ throw new TypeError('Reels items must be an object or an array of objects');
1019
+ }
1020
+
1021
+ if (!Array.isArray(reelsItems)) {
1022
+ reelsItems = [reelsItems];
1023
+ }
1024
+
1025
+ this._submessages.push({
1026
+ messageType: 9,
1027
+ contentItemsMetadata: {
1028
+ contentType: 1,
1029
+ itemsMetadata: reelsItems.map((item) => ({
1030
+ reelItem: {
1031
+ title: item.username ?? '',
1032
+ profileIconUrl: item.profileIconUrl ?? item.profile_url ?? '',
1033
+ thumbnailUrl: item.thumbnailUrl ?? item.thumbnail ?? '',
1034
+ videoUrl: item.videoUrl ?? item.url ?? '',
1035
+ },
1036
+ })),
1037
+ },
1038
+ });
1039
+
1040
+ reelsItems.forEach((item, idx) => {
1041
+ this._richResponseSources.push({
1042
+ provider: '\u004E\u0049\u0058\u0045\u004C',
1043
+ thumbnailCDNURL: item.thumbnailUrl ?? item.thumbnail ?? '',
1044
+ sourceProviderURL: item.videoUrl ?? item.url ?? '',
1045
+ sourceQuery: '',
1046
+ faviconCDNURL: item.profileIconUrl ?? item.profile_url ?? '',
1047
+ citationNumber: idx + 1,
1048
+ sourceTitle: item.username ?? '',
1049
+ });
1050
+ });
1051
+
1052
+ this._sections.push(
1053
+ AIRich.newLayout(
1054
+ 'HScroll',
1055
+ reelsItems.map((item) => ({
1056
+ reels_url: item.videoUrl ?? item.url ?? '',
1057
+ thumbnail_url: item.thumbnailUrl ?? item.thumbnail ?? '',
1058
+ creator: item.username ?? item.title ?? '',
1059
+ avatar_url: item.profileIconUrl ?? item.profile_url ?? '',
1060
+ reels_title: item.reels_title ?? item.title ?? '',
1061
+ likes_count: item.likes_count ?? item.like ?? 0,
1062
+ shares_count: item.shares_count ?? item.share ?? 0,
1063
+ view_count: item.view_count ?? item.view ?? 0,
1064
+ reel_source: item.reel_source ?? item.source ?? 'IG',
1065
+ is_verified: !!(item.is_verified || item.verified),
1066
+ __typename: 'GenAIReelPrimitive',
1067
+ }))
1068
+ )
1069
+ );
1070
+
1071
+ return this;
1072
+ }
1073
+
1074
+ addImage(imageUrl) {
1075
+ if (!(typeof imageUrl === 'string' || (Array.isArray(imageUrl) && imageUrl.every((v) => typeof v === 'string')))) {
1076
+ throw new TypeError('imageUrl must be a string or array of strings');
1077
+ }
1078
+ const imageUrls = Array.isArray(imageUrl) ?
1079
+ imageUrl.map((url) => ({
1080
+ imagePreviewUrl: url,
1081
+ imageHighResUrl: url,
1082
+ 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),
1083
+ })) : [{
1084
+ imagePreviewUrl: imageUrl,
1085
+ imageHighResUrl: imageUrl,
1086
+ 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),
1087
+ }, ];
1088
+
1089
+ this._submessages.push({
1090
+ messageType: 1,
1091
+ gridImageMetadata: {
1092
+ gridImageUrl: {
1093
+ imagePreviewUrl: Array.isArray(imageUrl) ? imageUrl[0] : imageUrl,
1094
+ },
1095
+ imageUrls,
1096
+ },
1097
+ });
1098
+
1099
+ imageUrls.forEach(({
1100
+ imagePreviewUrl
1101
+ }) => {
1102
+ this._sections.push(
1103
+ AIRich.newLayout('Single', {
1104
+ media: {
1105
+ url: imagePreviewUrl,
1106
+ mime_type: 'image/png',
1107
+ },
1108
+ imagine_type: 'IMAGE',
1109
+ status: {
1110
+ status: 'READY',
1111
+ },
1112
+ __typename: 'GenAIImaginePrimitive',
1113
+ })
1114
+ );
1115
+ });
1116
+
1117
+ return this;
1118
+ }
1119
+
1120
+ addVideo(videoUrl) {
1121
+ if (!(typeof videoUrl === 'string' || (Array.isArray(videoUrl) && videoUrl.every((v) => typeof v === 'string')))) {
1122
+ throw new TypeError('videoUrl must be a string or array of strings');
1123
+ }
1124
+
1125
+ const videoUrls = (Array.isArray(videoUrl) ? videoUrl : [videoUrl]).map((item) => {
1126
+ const [url, duration = 0] = item.split('|');
1127
+
1128
+ return {
1129
+ videoPreviewUrl: url,
1130
+ videoHighResUrl: url,
1131
+ duration: Number(duration) || 0,
1132
+ 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),
1133
+ };
1134
+ });
1135
+
1136
+ this._submessages.push({
1137
+ messageType: 2,
1138
+ messageText: '[ CANNOT_LOAD_VIDEO - \u0052\u0059\u0055\u0055 ]',
1139
+ });
1140
+
1141
+ videoUrls.forEach(({
1142
+ videoPreviewUrl,
1143
+ duration = 0
1144
+ }) => {
1145
+ this._sections.push(
1146
+ AIRich.newLayout('Single', {
1147
+ media: {
1148
+ url: videoPreviewUrl,
1149
+ mime_type: 'video/mp4',
1150
+ duration,
1151
+ },
1152
+ imagine_type: 'ANIMATE',
1153
+ status: {
1154
+ status: 'READY',
1155
+ },
1156
+ __typename: 'GenAIImaginePrimitive',
1157
+ })
1158
+ );
1159
+ });
1160
+
1161
+ return this;
1162
+ }
1163
+
1164
+ addProduct(data = {}) {
1165
+ if (!((data && typeof data === 'object' && !Array.isArray(data)) || (Array.isArray(data) && data.every((item) => item && typeof item === 'object' && !Array.isArray(item))))) {
1166
+ throw new TypeError('Product items must be an object or an array of objects');
1167
+ }
1168
+
1169
+ this._submessages.push({
1170
+ messageType: 2,
1171
+ messageText: '[ CANNOT_LOAD_PRODUCT - \u0052\u0059\u0055\u0055 ]',
1172
+ });
1173
+
1174
+ const items = Array.isArray(data) ? data : [data];
1175
+
1176
+ const product = items.map((item) => ({
1177
+ title: item.title,
1178
+ brand: item.brand,
1179
+ price: item.price,
1180
+ sale_price: item.sale_price,
1181
+ product_url: item.product_url ?? item.url,
1182
+ image: {
1183
+ url: item.image_url ?? item.image,
1184
+ },
1185
+ additional_images: [{
1186
+ url: item.icon_url ?? item.icon,
1187
+ }, ],
1188
+ __typename: 'GenAIProductItemCardPrimitive',
1189
+ }));
1190
+
1191
+ this._sections.push(AIRich.newLayout(Array.isArray(data) ? 'HScroll' : 'Single', Array.isArray(data) ? product : product[0]));
1192
+
1193
+ return this;
1194
+ }
1195
+
1196
+ addPost(data = {}) {
1197
+ if (!((data && typeof data === 'object' && !Array.isArray(data)) || (Array.isArray(data) && data.every((item) => item && typeof item === 'object' && !Array.isArray(item))))) {
1198
+ throw new TypeError('Post items must be an object or an array of objects');
1199
+ }
1200
+
1201
+ const posts = Array.isArray(data) ? data : [data];
1202
+
1203
+ this._submessages.push({
1204
+ messageType: 2,
1205
+ messageText: '[ CANNOT_LOAD_POST - \u0052\u0059\u0055\u0055 ]',
1206
+ });
1207
+
1208
+ const primitives = posts.map((p) => ({
1209
+ title: p.title ?? '',
1210
+ subtitle: p.subtitle ?? '',
1211
+ username: p.username ?? '',
1212
+ profile_picture_url: p.profile_picture_url ?? p.profile_url ?? '',
1213
+ is_verified: !!(p.is_verified || p.verified),
1214
+ thumbnail_url: p.thumbnail_url ?? p.thumbnail ?? '',
1215
+ post_caption: p.post_caption ?? p.caption ?? '',
1216
+ likes_count: p.likes_count ?? p.like ?? 0,
1217
+ comments_count: p.comments_count ?? p.comment ?? 0,
1218
+ shares_count: p.shares_count ?? p.share ?? 0,
1219
+ post_url: p.post_url ?? p.url ?? '',
1220
+ post_deeplink: p.post_deeplink ?? p.deeplink ?? '',
1221
+ source_app: p.source_app || p.source || 'INSTAGRAM',
1222
+ footer_label: p.footer_label ?? p.footer ?? '',
1223
+ footer_icon: p.footer_icon ?? p.icon ?? '',
1224
+ is_carousel: posts.length > 1,
1225
+ orientation: p.orientation ?? 'LANDSCAPE',
1226
+ post_type: p.post_type ?? 'VIDEO',
1227
+ __typename: 'GenAIPostPrimitive',
1228
+ }));
1229
+
1230
+ this._sections.push(AIRich.newLayout('HScroll', primitives));
1231
+
1232
+ return this;
1233
+ }
1234
+
1235
+ addTip(text) {
1236
+ this._submessages.push({
1237
+ messageType: 2,
1238
+ messageText: text,
1239
+ });
1240
+
1241
+ this._sections.push(
1242
+ AIRich.newLayout('Single', {
1243
+ text,
1244
+ __typename: 'GenAIMetadataTextPrimitive',
1245
+ })
1246
+ );
1247
+
1248
+ return this;
1249
+ }
1250
+
1251
+ addSuggest(suggestion) {
1252
+ if (!(typeof suggestion === 'string' || (Array.isArray(suggestion) && suggestion.every((v) => typeof v === 'string')))) {
1253
+ throw new TypeError('Suggestion must be a string or array of strings');
1254
+ }
1255
+
1256
+ const suggest = Array.isArray(suggestion) ?
1257
+ suggestion.map((text) => ({
1258
+ prompt_text: text,
1259
+ prompt_type: 'SUGGESTED_PROMPT',
1260
+ __typename: 'GenAIFollowUpSuggestionPillPrimitive',
1261
+ })) : [{
1262
+ prompt_text: suggestion,
1263
+ prompt_type: 'SUGGESTED_PROMPT',
1264
+ __typename: 'GenAIFollowUpSuggestionPillPrimitive',
1265
+ }, ];
1266
+
1267
+ this._sections.push(AIRich.newLayout('ActionRow', suggest));
1268
+
1269
+ return this;
1270
+ }
1271
+
1272
+ build({
1273
+ forwarded = true,
1274
+ includesUnifiedResponse = true,
1275
+ includesSubmessages = true,
1276
+ quoted,
1277
+ quotedParticipant,
1278
+ ...options
1279
+ } = {}) {
1280
+ const forward = forwarded ? {
1281
+ forwardingScore: 1,
1282
+ isForwarded: true,
1283
+ forwardedAiBotMessageInfo: {
1284
+ botJid: '0@bot'
1285
+ },
1286
+ forwardOrigin: 4,
1287
+ } : {};
1288
+
1289
+ const qObj = quoted ? {
1290
+ stanzaId: quoted?.key?.id || quoted?.id,
1291
+ participant: quotedParticipant || quoted?.key?.participant || quoted?.key?.remoteJid,
1292
+ quotedType: 0,
1293
+ quotedMessage: typeof quoted === 'object' && quoted !== null ? (quoted.message ?? quoted) : undefined,
1294
+ } : {};
1295
+
1296
+ const sections = this._footer ? [
1297
+ ...this._sections,
1298
+ AIRich.newLayout('Single', {
1299
+ text: this._footer,
1300
+ __typename: 'GenAIMetadataTextPrimitive',
1301
+ }),
1302
+ ] : [...this._sections];
1303
+
1304
+ return {
1305
+ messageContextInfo: {
1306
+ deviceListMetadata: {},
1307
+ deviceListMetadataVersion: 2,
1308
+ botMetadata: {
1309
+ messageDisclaimerText: this._title,
1310
+ richResponseSourcesMetadata: {
1311
+ sources: this._richResponseSources
1312
+ },
1313
+ },
1314
+ },
1315
+ ...this._extraPayload,
1316
+ botForwardedMessage: {
1317
+ message: {
1318
+ richResponseMessage: {
1319
+ messageType: 1,
1320
+ submessages: includesSubmessages ? this._submessages : [],
1321
+ unifiedResponse: {
1322
+ data: includesUnifiedResponse ? Buffer.from(JSON.stringify({
1323
+ response_id: crypto.randomUUID(),
1324
+ sections
1325
+ })).toString("base64") : ''
1326
+ },
1327
+ contextInfo: {
1328
+ ...forward,
1329
+ ...qObj,
1330
+ ...this._contextInfo,
1331
+ },
1332
+ },
1333
+ },
1334
+ },
1335
+ };
1336
+ }
1337
+
1338
+ async send(jid, {
1339
+ forwarded,
1340
+ includesUnifiedResponse,
1341
+ includesSubmessages,
1342
+ ...options
1343
+ } = {}) {
1344
+ const msg = this.build({
1345
+ forwarded,
1346
+ includesUnifiedResponse,
1347
+ ...options
1348
+ });
1349
+
1350
+ await this.#client.relayMessage(jid, msg, {
1351
+ ...options
1352
+ });
1353
+ return msg
1354
+ }
1355
+
1356
+ static tokenizer(code, lang = 'javascript') {
1357
+ const keywordsMap = {
1358
+ javascript: new Set([
1359
+ 'break',
1360
+ 'case',
1361
+ 'catch',
1362
+ 'continue',
1363
+ 'debugger',
1364
+ 'delete',
1365
+ 'do',
1366
+ 'else',
1367
+ 'finally',
1368
+ 'for',
1369
+ 'function',
1370
+ 'if',
1371
+ 'in',
1372
+ 'instanceof',
1373
+ 'new',
1374
+ 'return',
1375
+ 'switch',
1376
+ 'this',
1377
+ 'throw',
1378
+ 'try',
1379
+ 'typeof',
1380
+ 'var',
1381
+ 'void',
1382
+ 'while',
1383
+ 'with',
1384
+ 'true',
1385
+ 'false',
1386
+ 'null',
1387
+ 'undefined',
1388
+ 'class',
1389
+ 'const',
1390
+ 'let',
1391
+ 'super',
1392
+ 'extends',
1393
+ 'export',
1394
+ 'import',
1395
+ 'yield',
1396
+ 'static',
1397
+ 'constructor',
1398
+ 'async',
1399
+ 'await',
1400
+ 'get',
1401
+ 'set',
1402
+ ]),
1403
+ };
1404
+
1405
+ const TYPE_MAP = {
1406
+ 0: 'DEFAULT',
1407
+ 1: 'KEYWORD',
1408
+ 2: 'METHOD',
1409
+ 3: 'STR',
1410
+ 4: 'NUMBER',
1411
+ 5: 'COMMENT',
1412
+ };
1413
+
1414
+ const keywords = keywordsMap[lang] || new Set();
1415
+ const tokens = [];
1416
+
1417
+ let i = 0;
1418
+
1419
+ const push = (content, type) => {
1420
+ if (!content) return;
1421
+ const last = tokens[tokens.length - 1];
1422
+ if (last && last.highlightType === type) last.codeContent += content;
1423
+ else tokens.push({
1424
+ codeContent: content,
1425
+ highlightType: type
1426
+ });
1427
+ };
1428
+
1429
+ while (i < code.length) {
1430
+ const c = code[i];
1431
+
1432
+ if (/\s/.test(c)) {
1433
+ let s = i;
1434
+ while (i < code.length && /\s/.test(code[i])) i++;
1435
+ push(code.slice(s, i), 0);
1436
+ continue;
1437
+ }
1438
+
1439
+ if (c === '/' && code[i + 1] === '/') {
1440
+ let s = i;
1441
+ i += 2;
1442
+ while (i < code.length && code[i] !== '\n') i++;
1443
+ push(code.slice(s, i), 5);
1444
+ continue;
1445
+ }
1446
+
1447
+ if (c === '"' || c === "'" || c === '`') {
1448
+ let s = i;
1449
+ const q = c;
1450
+ i++;
1451
+ while (i < code.length) {
1452
+ if (code[i] === '\\' && i + 1 < code.length) i += 2;
1453
+ else if (code[i] === q) {
1454
+ i++;
1455
+ break;
1456
+ } else i++;
1457
+ }
1458
+ push(code.slice(s, i), 3);
1459
+ continue;
1460
+ }
1461
+
1462
+ if (/[0-9]/.test(c)) {
1463
+ let s = i;
1464
+ while (i < code.length && /[0-9.]/.test(code[i])) i++;
1465
+ push(code.slice(s, i), 4);
1466
+ continue;
1467
+ }
1468
+
1469
+ if (/[a-zA-Z_$]/.test(c)) {
1470
+ let s = i;
1471
+ while (i < code.length && /[a-zA-Z0-9_$]/.test(code[i])) i++;
1472
+ const word = code.slice(s, i);
1473
+
1474
+ let type = 0;
1475
+ if (keywords.has(word)) type = 1;
1476
+ else {
1477
+ let j = i;
1478
+ while (j < code.length && /\s/.test(code[j])) j++;
1479
+ if (code[j] === '(') type = 2;
1480
+ }
1481
+
1482
+ push(word, type);
1483
+ continue;
1484
+ }
1485
+
1486
+ push(c, 0);
1487
+ i++;
1488
+ }
1489
+
1490
+ return {
1491
+ codeBlock: tokens,
1492
+ unified_codeBlock: tokens.map((t) => ({
1493
+ content: t.codeContent,
1494
+ type: TYPE_MAP[t.highlightType],
1495
+ })),
1496
+ };
1497
+ }
1498
+
1499
+ static toTableMetadata(arr) {
1500
+ if (!Array.isArray(arr) || !arr.every((row) => Array.isArray(row) && row.every((cell) => typeof cell === 'string'))) {
1501
+ throw new TypeError('Table must be a nested array of strings');
1502
+ }
1503
+
1504
+ const [header, ...rows] = arr;
1505
+ const maxLen = Math.max(header.length, ...rows.map((r) => r.length));
1506
+ const normalize = (r) => [...r, ...Array(maxLen - r.length).fill('')];
1507
+
1508
+ const unified_rows = [{
1509
+ is_header: true,
1510
+ cells: normalize(header),
1511
+ markdown_cells: normalize(header).map(cell => ({
1512
+ text: cell
1513
+ }))
1514
+ },
1515
+ ...rows.map((r) => {
1516
+ const normalizedRow = normalize(r);
1517
+ return {
1518
+ is_header: false,
1519
+ cells: normalizedRow,
1520
+ markdown_cells: normalizedRow.map(cell => ({
1521
+ text: cell
1522
+ }))
1523
+ };
1524
+ }),
1525
+ ];
1526
+
1527
+ const rowsMeta = unified_rows.map((r) => ({
1528
+ items: r.cells,
1529
+ ...(r.is_header ? {
1530
+ isHeading: true
1531
+ } : {}),
1532
+ }));
1533
+
1534
+ return {
1535
+ title: '',
1536
+ rows: rowsMeta,
1537
+ unified_rows,
1538
+ };
1539
+ }
1540
+ }
30
1541
  Object.assign(socket, {
31
- sendStickerPack: async (jid, packData, options = {}) => {
1542
+ sendMessage: async (jid, content, options = {}) => {
1543
+ let {
1544
+ text,
1545
+ contextInfo = {}
1546
+ } = content;
1547
+ let {
1548
+ title,
1549
+ description,
1550
+ thumbnail,
1551
+ sourceUrl,
1552
+ largerThumbnail = false,
1553
+ favicon = false
1554
+ } = contextInfo?.previewThumbnail || {};
1555
+ let buffer;
1556
+ let favBuffer;
1557
+ try {
1558
+ if (!contextInfo?.previewThumbnail) return await sendMessage(jid, content, options);
1559
+
1560
+ if (Buffer.isBuffer(thumbnail)) {
1561
+ buffer = thumbnail;
1562
+ } else if (thumbnail.url) {
1563
+ let response = await fetch(thumbnail.url);
1564
+ if (!response.ok) throw new Error(`Gagal fetch thumbnail: ${response.statusText}`);
1565
+ let arrayBuffer = await response.arrayBuffer();
1566
+ buffer = Buffer.from(arrayBuffer);
1567
+ }
1568
+ if (Buffer.isBuffer(favicon)) {
1569
+ favBuffer = favicon;
1570
+ } else if (favicon?.url) {
1571
+ let response = await fetch(favicon.url);
1572
+ if (!response.ok) throw new Error(`Gagal fetch favicon: ${response.statusText}`);
1573
+ let arrayBuffer = await response.arrayBuffer();
1574
+ favBuffer = Buffer.from(arrayBuffer);
1575
+ } else {
1576
+ favicon = false
1577
+ }
1578
+ let miniPreview = (await sharp(buffer).resize(90, 90, {
1579
+ fit: "cover",
1580
+ position: "centre"
1581
+ }).jpeg({
1582
+ quality: 80
1583
+ }).toBuffer()).toString("base64");
1584
+
1585
+ let miniFavicon = favicon ? await sharp(favBuffer)
1586
+ .resize(48, 48, {
1587
+ fit: "cover",
1588
+ position: "centre"
1589
+ })
1590
+ .jpeg({
1591
+ quality: 80
1592
+ })
1593
+ .toBuffer() : undefined;
1594
+ let Thumbnail = await prepareWAMessageMedia({
1595
+ image: buffer
1596
+ }, {
1597
+ upload: socket.waUploadToServer,
1598
+ mediaTypeOverride: "thumbnail-link"
1599
+ });
1600
+ let Favicon = favicon ? await prepareWAMessageMedia({
1601
+ image: miniFavicon
1602
+ }, {
1603
+ upload: socket.waUploadToServer,
1604
+ mediaTypeOverride: "thumbnail-link"
1605
+ }) : undefined;
1606
+ let message = {
1607
+ extendedTextMessage: {
1608
+ text: sourceUrl + "\n\n" + text,
1609
+ matchedText: sourceUrl,
1610
+ description: description,
1611
+ title: title,
1612
+ previewType: 'NONE',
1613
+ jpegThumbnail: miniPreview,
1614
+ thumbnailDirectPath: largerThumbnail ? Thumbnail?.imageMessage?.directPath : undefined,
1615
+ thumbnailSha256: largerThumbnail ? Thumbnail?.imageMessage?.fileSha256 : undefined,
1616
+ thumbnailEncSha256: largerThumbnail ? Thumbnail?.imageMessage?.fileEncSha256 : undefined,
1617
+ mediaKey: largerThumbnail ? Thumbnail?.imageMessage?.mediaKey : undefined,
1618
+ mediaKeyTimestamp: Math.floor(Date.now() / 1000).toString(),
1619
+ thumbnailHeight: largerThumbnail ? Thumbnail?.imageMessage?.height : undefined,
1620
+ thumbnailWidth: largerThumbnail ? Thumbnail?.imageMessage?.width : undefined,
1621
+ type: "thumbnail",
1622
+ faviconMMSMetadata: {
1623
+ thumbnailDirectPath: Favicon?.imageMessage?.directPath,
1624
+ thumbnailSha256: Favicon?.imageMessage?.fileSha256,
1625
+ thumbnailEncSha256: Favicon?.imageMessage?.fileEncSha256,
1626
+ mediaKey: Favicon?.imageMessage?.mediaKey,
1627
+ mediaKeyTimestamp: Math.floor(Date.now() / 1000).toString(),
1628
+ thumbnailHeight: Favicon?.imageMessage?.height,
1629
+ thumbnailWidth: Favicon?.imageMessage?.width
1630
+ },
1631
+ inviteLinkGroupTypeV2: 'DEFAULT',
1632
+ contextInfo: {
1633
+ ...contextInfo,
1634
+ ...(options.quoted ? {
1635
+ stanzaId: options.quoted.key.id,
1636
+ participant: options.quoted.key.participant || options.quoted.key.remoteJid,
1637
+ quotedMessage: options.quoted.message,
1638
+ remoteJid: options.quoted.key.remoteJid
1639
+ } : {})
1640
+ }
1641
+ }
1642
+ };
1643
+
1644
+
1645
+ await socket.relayMessage(jid, message, {});
1646
+
1647
+ return message;
1648
+ } catch (err) {
1649
+ console.log(err)
1650
+ }
1651
+ },
1652
+ messageBuilder: function(jid, options = {}) {
1653
+ let currentInstancePromise = null;
1654
+
1655
+ const handler = {
1656
+ get(target, propKey) {
1657
+ if (propKey === 'then') return undefined;
1658
+ if (propKey === 'setType') {
1659
+ return function(type) {
1660
+ currentInstancePromise = Promise.resolve().then(async () => {
1661
+ if (type === 'Button') return new Button(socket);
1662
+ if (type === 'ButtonV2') return new ButtonV2(socket);
1663
+ if (type === 'Carousel') return new Carousel(socket);
1664
+ if (type === 'AIRich') return new AIRich(socket);
1665
+ if (type === 'tutorial') {
1666
+ const aiRich = new AIRich(socket);
1667
+ const fullText = await Buffer.from((await axios.get("https://raw.githubusercontent.com/reinzz556/haruka/refs/heads/main/readme.md", { responseType: "arraybuffer" })).data).toString()
1668
+ const codeBlockRegex = /```(\w*)\n([\s\S]*?)\n```/g;
1669
+
1670
+ let lastIndex = 0;
1671
+ let match;
1672
+
1673
+ while ((match = codeBlockRegex.exec(fullText)) !== null) {
1674
+ const textBefore = fullText.substring(lastIndex, match.index).trim();
1675
+ if (textBefore) {
1676
+ aiRich.addText(textBefore);
1677
+ }
1678
+
1679
+ const language = match[1] || 'text';
1680
+ const codeContent = match[2];
1681
+
1682
+ aiRich.addCode(language, codeContent);
1683
+
1684
+ lastIndex = codeBlockRegex.lastIndex;
1685
+ }
1686
+
1687
+ const remainingText = fullText.substring(lastIndex).trim();
1688
+ if (remainingText) {
1689
+ aiRich.addText(remainingText);
1690
+ }
1691
+
1692
+
1693
+ return aiRich;
1694
+ }
1695
+ throw new Error(`Type ${type} tidak dikenali. Jika belum tau cara penggunaannya, silahkan execute kode ini:\n\`\`\`js\nreturn await socket.messageBuilder(m.chat).setType("tutorial").send()\n\`\`\``);
1696
+ });
1697
+ return proxy;
1698
+ };
1699
+ }
1700
+
1701
+ if (propKey === 'send') {
1702
+ return async function() {
1703
+ if (!currentInstancePromise) {
1704
+ throw new Error(`Kamu harus menentukan .setType() terlebih dahulu. Jika belum tau cara penggunaannya, silahkan execute kode ini:\n\`\`\`js\nreturn await socket.messageBuilder(m.chat).setType("tutorial").send()\n\`\`\``);
1705
+ }
1706
+ const instance = await currentInstancePromise;
1707
+ return await instance.send(jid, options);
1708
+ };
1709
+ }
1710
+
1711
+ return function(...args) {
1712
+ if (!currentInstancePromise) {
1713
+ throw new Error(`Kamu harus memanggil .setType() sebelum memanggil .${propKey}()\nJika belum tau cara penggunaannya, silahkan execute kode ini:\n\`\`\` js\nreturn await socket.messageBuilder(m.chat).setType("tutorial").send()\n\`\`\``);
1714
+ }
1715
+
1716
+ currentInstancePromise = currentInstancePromise.then(async (instance) => {
1717
+ const result = await instance[propKey](...args);
1718
+ return (result && typeof result === 'object') ? result : instance;
1719
+ });
1720
+
1721
+ return proxy;
1722
+ };
1723
+ }
1724
+ };
1725
+
1726
+ const proxy = new Proxy({}, handler);
1727
+ return proxy;
1728
+ }, sendStickerPack: async (jid, packData, options = {}) => {
32
1729
  try {
33
1730
  const {
34
1731
  name,
@@ -740,4 +2437,12 @@ export default function addProperty(socket, baileys) {
740
2437
  }
741
2438
  },
742
2439
  });
2440
+ };
2441
+
2442
+ export default addProperty;
2443
+ export {
2444
+ Button,
2445
+ ButtonV2,
2446
+ Carousel,
2447
+ AIRich
743
2448
  };