baileys-mbuilder 4.5.0-beta → 4.5.0-custom

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