@nuiisweety/baileys 0.1.8 → 0.1.10
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/README.md +229 -58
- package/README.md.bak +229 -58
- package/README.md.original +1950 -0
- package/lib/Utils/rich-message-utils.js +8 -6
- package/lib/Utils/rich-message-utils.js.original +1323 -0
- package/package.json +1 -1
|
@@ -0,0 +1,1323 @@
|
|
|
1
|
+
import { getRandomValues, randomUUID, randomBytes } from 'crypto';
|
|
2
|
+
import { DONATE_URL, LEXER_REGEX } from '../Defaults/index.js';
|
|
3
|
+
import { LANGUAGE_KEYWORDS } from '../WABinary/constants.js';
|
|
4
|
+
import { CodeHighlightType, RichSubMessageType } from '../Types/RichType.js';
|
|
5
|
+
import { proto } from '../../WAProto/index.js';
|
|
6
|
+
import { unixTimestampSeconds, generateMessageIDV2 } from './generics.js';
|
|
7
|
+
|
|
8
|
+
const NOOP = new Set([]);
|
|
9
|
+
|
|
10
|
+
/* ─────────────────────────────────────────────────────────────
|
|
11
|
+
LATEX URL GENERATOR
|
|
12
|
+
WhatsApp client membutuhkan URL gambar render untuk setiap
|
|
13
|
+
LaTeX expression. Tanpa URL, pesan tampil kosong.
|
|
14
|
+
Gunakan latex.codecogs.com sebagai fallback gratis~
|
|
15
|
+
───────────────────────────────────────────────────────────── */
|
|
16
|
+
const buildLatexUrl = (expression) => {
|
|
17
|
+
if (!expression) return null;
|
|
18
|
+
const encoded = encodeURIComponent(expression);
|
|
19
|
+
return `https://latex.codecogs.com/png.image?\dpi{150}\bg{white}${encoded}`;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/* ─────────────────────────────────────────────────────────────
|
|
23
|
+
IMAGE URL HELPER
|
|
24
|
+
Konversi objek AIRichResponseImageURL ke snake_case JSON
|
|
25
|
+
agar WA client bisa baca dengan benar~
|
|
26
|
+
───────────────────────────────────────────────────────────── */
|
|
27
|
+
const toImageUrlJson = (imgUrl) => {
|
|
28
|
+
if (!imgUrl) return null;
|
|
29
|
+
// support both plain string and object
|
|
30
|
+
if (typeof imgUrl === 'string') return { image_preview_url: imgUrl, image_high_res_url: imgUrl, source_url: null };
|
|
31
|
+
return {
|
|
32
|
+
image_preview_url: imgUrl.imagePreviewUrl || imgUrl.image_preview_url || null,
|
|
33
|
+
image_high_res_url: imgUrl.imageHighResUrl || imgUrl.image_high_res_url || null,
|
|
34
|
+
source_url: imgUrl.sourceUrl || imgUrl.source_url || null
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/* ─────────────────────────────────────────────────────────────
|
|
39
|
+
TOKENIZER
|
|
40
|
+
───────────────────────────────────────────────────────────── */
|
|
41
|
+
|
|
42
|
+
export const tokenizeCode = (code, language = 'javascript') => {
|
|
43
|
+
const keywords = LANGUAGE_KEYWORDS[language] || NOOP;
|
|
44
|
+
const blocks = [];
|
|
45
|
+
LEXER_REGEX.lastIndex = 0;
|
|
46
|
+
let match;
|
|
47
|
+
while ((match = LEXER_REGEX.exec(code)) !== null) {
|
|
48
|
+
if (match[1]) {
|
|
49
|
+
blocks.push({ highlightType: CodeHighlightType.COMMENT, codeContent: match[1] });
|
|
50
|
+
} else if (match[2]) {
|
|
51
|
+
blocks.push({ highlightType: CodeHighlightType.STRING, codeContent: match[2] });
|
|
52
|
+
} else if (match[3]) {
|
|
53
|
+
blocks.push({
|
|
54
|
+
highlightType: keywords.has(match[3]) ? CodeHighlightType.KEYWORD : CodeHighlightType.METHOD,
|
|
55
|
+
codeContent: match[3],
|
|
56
|
+
});
|
|
57
|
+
} else if (match[4]) {
|
|
58
|
+
blocks.push({
|
|
59
|
+
highlightType: keywords.has(match[4]) ? CodeHighlightType.KEYWORD : CodeHighlightType.DEFAULT,
|
|
60
|
+
codeContent: match[4],
|
|
61
|
+
});
|
|
62
|
+
} else if (match[5]) {
|
|
63
|
+
blocks.push({ highlightType: CodeHighlightType.NUMBER, codeContent: match[5] });
|
|
64
|
+
} else {
|
|
65
|
+
blocks.push({ highlightType: CodeHighlightType.DEFAULT, codeContent: match[6] });
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return blocks;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
/* ─────────────────────────────────────────────────────────────
|
|
72
|
+
INCOMING DECODER — parse rich message yang diterima
|
|
73
|
+
───────────────────────────────────────────────────────────── */
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Parse sebuah AIRichResponseSubMessage proto menjadi object JS yang mudah dipakai.
|
|
77
|
+
* Return null kalau tipe tidak dikenal.
|
|
78
|
+
*/
|
|
79
|
+
export const parseRichSubMessage = (submessage) => {
|
|
80
|
+
if (!submessage) return null;
|
|
81
|
+
const type = submessage.messageType;
|
|
82
|
+
|
|
83
|
+
switch (type) {
|
|
84
|
+
case RichSubMessageType.TEXT:
|
|
85
|
+
return {
|
|
86
|
+
type: 'text',
|
|
87
|
+
text: submessage.messageText || '',
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
case RichSubMessageType.CODE:
|
|
91
|
+
return {
|
|
92
|
+
type: 'code',
|
|
93
|
+
language: submessage.codeMetadata?.codeLanguage || 'plain',
|
|
94
|
+
blocks: (submessage.codeMetadata?.codeBlocks || []).map(b => ({
|
|
95
|
+
highlight: CodeHighlightType[b.highlightType] ?? 'DEFAULT',
|
|
96
|
+
content: b.codeContent || '',
|
|
97
|
+
})),
|
|
98
|
+
/** Helper: ambil source code mentah tanpa highlight info */
|
|
99
|
+
get raw() {
|
|
100
|
+
return this.blocks.map(b => b.content).join('');
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
case RichSubMessageType.TABLE:
|
|
105
|
+
return {
|
|
106
|
+
type: 'table',
|
|
107
|
+
title: submessage.tableMetadata?.title || '',
|
|
108
|
+
rows: (submessage.tableMetadata?.rows || []).map(row => ({
|
|
109
|
+
isHeading: row.isHeading ?? false,
|
|
110
|
+
items: row.items || [],
|
|
111
|
+
})),
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
case RichSubMessageType.GRID_IMAGE:
|
|
115
|
+
return {
|
|
116
|
+
type: 'gridImage',
|
|
117
|
+
gridImageUrl: submessage.gridImageMetadata?.gridImageUrl || null,
|
|
118
|
+
imageUrls: submessage.gridImageMetadata?.imageUrls || [],
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
case RichSubMessageType.INLINE_IMAGE:
|
|
122
|
+
return {
|
|
123
|
+
type: 'inlineImage',
|
|
124
|
+
imageUrl: submessage.imageMetadata?.imageUrl || null,
|
|
125
|
+
imageText: submessage.imageMetadata?.imageText || '',
|
|
126
|
+
alignment: submessage.imageMetadata?.alignment ?? 0,
|
|
127
|
+
tapLinkUrl: submessage.imageMetadata?.tapLinkUrl || null,
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
case RichSubMessageType.DYNAMIC:
|
|
131
|
+
return {
|
|
132
|
+
type: 'dynamic',
|
|
133
|
+
dynamicType: submessage.dynamicMetadata?.type ?? 0,
|
|
134
|
+
version: submessage.dynamicMetadata?.version ?? 0,
|
|
135
|
+
url: submessage.dynamicMetadata?.url || null,
|
|
136
|
+
loopCount: submessage.dynamicMetadata?.loopCount ?? 0,
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
case RichSubMessageType.MAP:
|
|
140
|
+
return {
|
|
141
|
+
type: 'map',
|
|
142
|
+
centerLatitude: submessage.mapMetadata?.centerLatitude ?? 0,
|
|
143
|
+
centerLongitude: submessage.mapMetadata?.centerLongitude ?? 0,
|
|
144
|
+
latitudeDelta: submessage.mapMetadata?.latitudeDelta ?? 0,
|
|
145
|
+
longitudeDelta: submessage.mapMetadata?.longitudeDelta ?? 0,
|
|
146
|
+
showInfoList: submessage.mapMetadata?.showInfoList ?? false,
|
|
147
|
+
annotations: (submessage.mapMetadata?.annotations || []).map(a => ({
|
|
148
|
+
number: a.annotationNumber ?? 0,
|
|
149
|
+
latitude: a.latitude ?? 0,
|
|
150
|
+
longitude: a.longitude ?? 0,
|
|
151
|
+
title: a.title || '',
|
|
152
|
+
body: a.body || '',
|
|
153
|
+
})),
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
case RichSubMessageType.LATEX:
|
|
157
|
+
return {
|
|
158
|
+
type: 'latex',
|
|
159
|
+
text: submessage.latexMetadata?.text || '',
|
|
160
|
+
expressions: (submessage.latexMetadata?.expressions || []).map(e => ({
|
|
161
|
+
expression: e.latexExpression || '',
|
|
162
|
+
url: e.url || null,
|
|
163
|
+
width: e.width ?? 0,
|
|
164
|
+
height: e.height ?? 0,
|
|
165
|
+
})),
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
case RichSubMessageType.CONTENT_ITEMS:
|
|
169
|
+
return {
|
|
170
|
+
type: 'contentItems',
|
|
171
|
+
contentType: submessage.contentItemsMetadata?.contentType ?? 0,
|
|
172
|
+
items: (submessage.contentItemsMetadata?.itemsMetadata || []).map(item => {
|
|
173
|
+
if (item.reelItem) {
|
|
174
|
+
return {
|
|
175
|
+
kind: 'reel',
|
|
176
|
+
title: item.reelItem.title || '',
|
|
177
|
+
profileIconUrl: item.reelItem.profileIconUrl || null,
|
|
178
|
+
thumbnailUrl: item.reelItem.thumbnailUrl || null,
|
|
179
|
+
videoUrl: item.reelItem.videoUrl || null,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
return { kind: 'unknown' };
|
|
183
|
+
}),
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
default:
|
|
187
|
+
return { type: 'unknown', raw: submessage };
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Parse seluruh AIRichResponseMessage menjadi array parsed submessages.
|
|
193
|
+
* Bisa dipanggil langsung dari handler messages.upsert:
|
|
194
|
+
*
|
|
195
|
+
* const inner = normalizeMessageContent(msg.message)
|
|
196
|
+
* const parsed = parseRichMessage(inner?.richResponseMessage)
|
|
197
|
+
*/
|
|
198
|
+
export const parseRichMessage = (richResponseMessage) => {
|
|
199
|
+
if (!richResponseMessage) return null;
|
|
200
|
+
|
|
201
|
+
const submessages = (richResponseMessage.submessages || [])
|
|
202
|
+
.map(parseRichSubMessage)
|
|
203
|
+
.filter(Boolean);
|
|
204
|
+
|
|
205
|
+
let unifiedData = null;
|
|
206
|
+
if (richResponseMessage.unifiedResponse?.data) {
|
|
207
|
+
try {
|
|
208
|
+
const buf = richResponseMessage.unifiedResponse.data;
|
|
209
|
+
unifiedData = JSON.parse(Buffer.isBuffer(buf) ? buf.toString('utf-8') : Buffer.from(buf).toString('utf-8'));
|
|
210
|
+
} catch (_) { /* ignore parse error */ }
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return {
|
|
214
|
+
messageType: richResponseMessage.messageType ?? 0,
|
|
215
|
+
submessages,
|
|
216
|
+
unifiedData,
|
|
217
|
+
};
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
/* ─────────────────────────────────────────────────────────────
|
|
221
|
+
OUTGOING BUILDER — unified response JSON per subtype
|
|
222
|
+
───────────────────────────────────────────────────────────── */
|
|
223
|
+
|
|
224
|
+
const buildUnifiedSection = (submessage) => {
|
|
225
|
+
switch (submessage.messageType) {
|
|
226
|
+
case RichSubMessageType.TEXT:
|
|
227
|
+
return {
|
|
228
|
+
view_model: {
|
|
229
|
+
primitive: {
|
|
230
|
+
text: submessage.messageText,
|
|
231
|
+
inline_entities: submessage.inlineEntities || [],
|
|
232
|
+
__typename: 'GenAIMarkdownTextUXPrimitive'
|
|
233
|
+
},
|
|
234
|
+
__typename: 'GenAISingleLayoutViewModel'
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
case RichSubMessageType.CODE: {
|
|
239
|
+
const cm = submessage.codeMetadata;
|
|
240
|
+
return {
|
|
241
|
+
view_model: {
|
|
242
|
+
primitive: {
|
|
243
|
+
language: cm.codeLanguage,
|
|
244
|
+
code_blocks: cm.codeBlocks.map(b => ({
|
|
245
|
+
content: b.codeContent,
|
|
246
|
+
type: CodeHighlightType[b.highlightType]
|
|
247
|
+
})),
|
|
248
|
+
__typename: 'GenAICodeUXPrimitive'
|
|
249
|
+
},
|
|
250
|
+
__typename: 'GenAISingleLayoutViewModel'
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
case RichSubMessageType.TABLE: {
|
|
256
|
+
const tm = submessage.tableMetadata;
|
|
257
|
+
return {
|
|
258
|
+
view_model: {
|
|
259
|
+
primitive: {
|
|
260
|
+
title: tm.title,
|
|
261
|
+
rows: tm.rows.map(row => ({
|
|
262
|
+
is_header: row.isHeading,
|
|
263
|
+
cells: row.items,
|
|
264
|
+
markdown_cells: row.items.map(item => ({ text: item }))
|
|
265
|
+
})),
|
|
266
|
+
__typename: 'GenATableUXPrimitive'
|
|
267
|
+
},
|
|
268
|
+
__typename: 'GenAISingleLayoutViewModel'
|
|
269
|
+
}
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
case RichSubMessageType.GRID_IMAGE: {
|
|
274
|
+
const gm = submessage.gridImageMetadata;
|
|
275
|
+
return {
|
|
276
|
+
view_model: {
|
|
277
|
+
primitive: {
|
|
278
|
+
grid_image_url: toImageUrlJson(gm.gridImageUrl),
|
|
279
|
+
image_urls: (gm.imageUrls || []).map(toImageUrlJson),
|
|
280
|
+
__typename: 'GenAIGridImageUXPrimitive'
|
|
281
|
+
},
|
|
282
|
+
__typename: 'GenAISingleLayoutViewModel'
|
|
283
|
+
}
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
case RichSubMessageType.INLINE_IMAGE: {
|
|
288
|
+
const im = submessage.imageMetadata;
|
|
289
|
+
return {
|
|
290
|
+
view_model: {
|
|
291
|
+
primitive: {
|
|
292
|
+
image_url: toImageUrlJson(im.imageUrl),
|
|
293
|
+
image_text: im.imageText,
|
|
294
|
+
alignment: im.alignment ?? 0,
|
|
295
|
+
tap_link_url: im.tapLinkUrl || null,
|
|
296
|
+
__typename: 'GenAIInlineImageUXPrimitive'
|
|
297
|
+
},
|
|
298
|
+
__typename: 'GenAISingleLayoutViewModel'
|
|
299
|
+
}
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
case RichSubMessageType.DYNAMIC: {
|
|
304
|
+
const dm = submessage.dynamicMetadata;
|
|
305
|
+
return {
|
|
306
|
+
view_model: {
|
|
307
|
+
primitive: {
|
|
308
|
+
type: dm.type ?? 0,
|
|
309
|
+
version: dm.version ?? 0,
|
|
310
|
+
url: dm.url,
|
|
311
|
+
loop_count: dm.loopCount ?? 0,
|
|
312
|
+
__typename: 'GenAIDynamicUXPrimitive'
|
|
313
|
+
},
|
|
314
|
+
__typename: 'GenAISingleLayoutViewModel'
|
|
315
|
+
}
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
case RichSubMessageType.MAP: {
|
|
320
|
+
const mm = submessage.mapMetadata;
|
|
321
|
+
return {
|
|
322
|
+
view_model: {
|
|
323
|
+
primitive: {
|
|
324
|
+
center_latitude: mm.centerLatitude,
|
|
325
|
+
center_longitude: mm.centerLongitude,
|
|
326
|
+
latitude_delta: mm.latitudeDelta,
|
|
327
|
+
longitude_delta: mm.longitudeDelta,
|
|
328
|
+
show_info_list: mm.showInfoList ?? false,
|
|
329
|
+
annotations: (mm.annotations || []).map(a => ({
|
|
330
|
+
annotation_number: a.annotationNumber,
|
|
331
|
+
latitude: a.latitude,
|
|
332
|
+
longitude: a.longitude,
|
|
333
|
+
title: a.title,
|
|
334
|
+
body: a.body
|
|
335
|
+
})),
|
|
336
|
+
__typename: 'GenAIMapUXPrimitive'
|
|
337
|
+
},
|
|
338
|
+
__typename: 'GenAISingleLayoutViewModel'
|
|
339
|
+
}
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
case RichSubMessageType.LATEX: {
|
|
344
|
+
const lm = submessage.latexMetadata;
|
|
345
|
+
return {
|
|
346
|
+
view_model: {
|
|
347
|
+
primitive: {
|
|
348
|
+
text: lm.text,
|
|
349
|
+
expressions: (lm.expressions || []).map(e => ({
|
|
350
|
+
latex_expression: e.latexExpression,
|
|
351
|
+
url: e.url,
|
|
352
|
+
width: e.width,
|
|
353
|
+
height: e.height,
|
|
354
|
+
font_height: e.fontHeight,
|
|
355
|
+
image_top_padding: e.imageTopPadding,
|
|
356
|
+
image_leading_padding: e.imageLeadingPadding,
|
|
357
|
+
image_bottom_padding: e.imageBottomPadding,
|
|
358
|
+
image_trailing_padding: e.imageTrailingPadding
|
|
359
|
+
})),
|
|
360
|
+
__typename: 'GenAILatexUXPrimitive'
|
|
361
|
+
},
|
|
362
|
+
__typename: 'GenAISingleLayoutViewModel'
|
|
363
|
+
}
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
case RichSubMessageType.CONTENT_ITEMS: {
|
|
368
|
+
const ci = submessage.contentItemsMetadata;
|
|
369
|
+
return {
|
|
370
|
+
view_model: {
|
|
371
|
+
primitive: {
|
|
372
|
+
content_type: ci.contentType ?? 0,
|
|
373
|
+
items_metadata: (ci.itemsMetadata || []).map(item => {
|
|
374
|
+
if (item.reelItem) {
|
|
375
|
+
return {
|
|
376
|
+
reel_item: {
|
|
377
|
+
title: item.reelItem.title,
|
|
378
|
+
profile_icon_url: item.reelItem.profileIconUrl,
|
|
379
|
+
thumbnail_url: item.reelItem.thumbnailUrl,
|
|
380
|
+
video_url: item.reelItem.videoUrl
|
|
381
|
+
}
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
return {};
|
|
385
|
+
}),
|
|
386
|
+
__typename: 'GenAIContentItemsUXPrimitive'
|
|
387
|
+
},
|
|
388
|
+
__typename: 'GenAISingleLayoutViewModel'
|
|
389
|
+
}
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
default:
|
|
394
|
+
return submessage;
|
|
395
|
+
}
|
|
396
|
+
};
|
|
397
|
+
|
|
398
|
+
export const toUnified = (submessages) => ({
|
|
399
|
+
response_id: randomUUID(),
|
|
400
|
+
sections: submessages.map(buildUnifiedSection)
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
/* ─────────────────────────────────────────────────────────────
|
|
404
|
+
PREPARE HELPERS — builder per tipe submessage
|
|
405
|
+
───────────────────────────────────────────────────────────── */
|
|
406
|
+
|
|
407
|
+
/** Buat submessage TEXT */
|
|
408
|
+
const makeTextSub = (text, inlineEntities) => ({
|
|
409
|
+
messageType: RichSubMessageType.TEXT,
|
|
410
|
+
messageText: text,
|
|
411
|
+
...(inlineEntities ? { inlineEntities } : {})
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
/** Buat submessage CODE dengan auto-tokenize */
|
|
415
|
+
const makeCodeSub = (code, language = 'javascript') => ({
|
|
416
|
+
messageType: RichSubMessageType.CODE,
|
|
417
|
+
codeMetadata: {
|
|
418
|
+
codeLanguage: language,
|
|
419
|
+
codeBlocks: tokenizeCode(code, language)
|
|
420
|
+
}
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
/** Buat submessage TABLE */
|
|
424
|
+
const makeTableSub = (rows, title, noHeading) => ({
|
|
425
|
+
messageType: RichSubMessageType.TABLE,
|
|
426
|
+
tableMetadata: {
|
|
427
|
+
title: title || '',
|
|
428
|
+
rows: rows.map((items, i) => ({
|
|
429
|
+
isHeading: !noHeading && i === 0,
|
|
430
|
+
items
|
|
431
|
+
}))
|
|
432
|
+
}
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
/** Normalize input ke format AIRichResponseImageURL */
|
|
436
|
+
const normalizeImageUrl = (u) => {
|
|
437
|
+
if (!u) return null;
|
|
438
|
+
if (typeof u === 'string') return { imagePreviewUrl: u, imageHighResUrl: u, sourceUrl: null };
|
|
439
|
+
return {
|
|
440
|
+
imagePreviewUrl: u.imagePreviewUrl || u.image_preview_url || null,
|
|
441
|
+
imageHighResUrl: u.imageHighResUrl || u.image_high_res_url || null,
|
|
442
|
+
sourceUrl: u.sourceUrl || u.source_url || null
|
|
443
|
+
};
|
|
444
|
+
};
|
|
445
|
+
|
|
446
|
+
/** Buat submessage GRID_IMAGE */
|
|
447
|
+
const makeGridImageSub = (gridImageUrl, imageUrls = []) => ({
|
|
448
|
+
messageType: RichSubMessageType.GRID_IMAGE,
|
|
449
|
+
gridImageMetadata: {
|
|
450
|
+
gridImageUrl: normalizeImageUrl(gridImageUrl),
|
|
451
|
+
imageUrls: (imageUrls || []).map(normalizeImageUrl)
|
|
452
|
+
}
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
/** Buat submessage INLINE_IMAGE */
|
|
456
|
+
const makeInlineImageSub = (imageUrl, imageText = '', alignment = 0, tapLinkUrl = null) => ({
|
|
457
|
+
messageType: RichSubMessageType.INLINE_IMAGE,
|
|
458
|
+
imageMetadata: {
|
|
459
|
+
imageUrl: normalizeImageUrl(imageUrl),
|
|
460
|
+
imageText,
|
|
461
|
+
alignment,
|
|
462
|
+
tapLinkUrl
|
|
463
|
+
}
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
/** Buat submessage DYNAMIC (animated image/GIF) */
|
|
467
|
+
const makeDynamicSub = (url, dynamicType = 1, version = 1, loopCount = 0) => ({
|
|
468
|
+
messageType: RichSubMessageType.DYNAMIC,
|
|
469
|
+
dynamicMetadata: { type: dynamicType, version, url, loopCount }
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
/** Buat submessage MAP */
|
|
473
|
+
const makeMapSub = (centerLatitude, centerLongitude, options = {}) => ({
|
|
474
|
+
messageType: RichSubMessageType.MAP,
|
|
475
|
+
mapMetadata: {
|
|
476
|
+
centerLatitude,
|
|
477
|
+
centerLongitude,
|
|
478
|
+
latitudeDelta: options.latitudeDelta ?? 0.05,
|
|
479
|
+
longitudeDelta: options.longitudeDelta ?? 0.05,
|
|
480
|
+
showInfoList: options.showInfoList ?? false,
|
|
481
|
+
annotations: (options.annotations || []).map((a, i) => ({
|
|
482
|
+
annotationNumber: a.number ?? i + 1,
|
|
483
|
+
latitude: a.latitude,
|
|
484
|
+
longitude: a.longitude,
|
|
485
|
+
title: a.title || '',
|
|
486
|
+
body: a.body || ''
|
|
487
|
+
}))
|
|
488
|
+
}
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
/** Buat submessage LATEX */
|
|
492
|
+
const makeLatexSub = (text, expressions = []) => ({
|
|
493
|
+
messageType: RichSubMessageType.LATEX,
|
|
494
|
+
latexMetadata: {
|
|
495
|
+
text,
|
|
496
|
+
expressions: expressions.map(e => {
|
|
497
|
+
const expr = e.expression || e.latexExpression || '';
|
|
498
|
+
// url WAJIB ada agar WA client bisa render — auto-generate jika tidak disupply
|
|
499
|
+
const url = e.url || buildLatexUrl(expr);
|
|
500
|
+
return {
|
|
501
|
+
latexExpression: expr,
|
|
502
|
+
url,
|
|
503
|
+
width: e.width ?? 120,
|
|
504
|
+
height: e.height ?? 40,
|
|
505
|
+
fontHeight: e.fontHeight ?? 0,
|
|
506
|
+
imageTopPadding: e.imageTopPadding ?? 0,
|
|
507
|
+
imageLeadingPadding: e.imageLeadingPadding ?? 0,
|
|
508
|
+
imageBottomPadding: e.imageBottomPadding ?? 0,
|
|
509
|
+
imageTrailingPadding: e.imageTrailingPadding ?? 0
|
|
510
|
+
};
|
|
511
|
+
})
|
|
512
|
+
}
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
/** Buat submessage CONTENT_ITEMS (carousel reel) */
|
|
516
|
+
const makeContentItemsSub = (items, contentType = 0) => ({
|
|
517
|
+
messageType: RichSubMessageType.CONTENT_ITEMS,
|
|
518
|
+
contentItemsMetadata: {
|
|
519
|
+
contentType,
|
|
520
|
+
itemsMetadata: items.map(item => {
|
|
521
|
+
if (item.reelItem || item.kind === 'reel') {
|
|
522
|
+
const r = item.reelItem || item;
|
|
523
|
+
return {
|
|
524
|
+
reelItem: {
|
|
525
|
+
title: r.title || '',
|
|
526
|
+
profileIconUrl: r.profileIconUrl || null,
|
|
527
|
+
thumbnailUrl: r.thumbnailUrl || null,
|
|
528
|
+
videoUrl: r.videoUrl || null
|
|
529
|
+
}
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
return {};
|
|
533
|
+
})
|
|
534
|
+
}
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
/** Buat section SOURCE (search result links) — hanya unified, tidak ada submessage proto */
|
|
538
|
+
const makeSourceSection = (sources = []) => {
|
|
539
|
+
const list = Array.isArray(sources) ? sources : [sources];
|
|
540
|
+
return {
|
|
541
|
+
view_model: {
|
|
542
|
+
primitive: {
|
|
543
|
+
sources: list.map(s => ({
|
|
544
|
+
source_url: s.url ?? s.source_url ?? '',
|
|
545
|
+
source_title: s.title ?? s.source_title ?? '',
|
|
546
|
+
source_display_name: s.display_name ?? s.displayName ?? s.title ?? '',
|
|
547
|
+
source_subtitle: s.subtitle ?? s.source_subtitle ?? '',
|
|
548
|
+
source_type: s.source_type ?? 'THIRD_PARTY',
|
|
549
|
+
favicon: {
|
|
550
|
+
url: s.favicon ?? s.faviconCDNURL ?? '',
|
|
551
|
+
width: 16,
|
|
552
|
+
height: 16
|
|
553
|
+
}
|
|
554
|
+
})),
|
|
555
|
+
__typename: 'GenAISearchResultPrimitive'
|
|
556
|
+
},
|
|
557
|
+
__typename: 'GenAISingleLayoutViewModel'
|
|
558
|
+
}
|
|
559
|
+
};
|
|
560
|
+
};
|
|
561
|
+
|
|
562
|
+
/** Buat submessage + unified section untuk IMAGE (grid image style) */
|
|
563
|
+
const makeImageSub = (imageUrl) => {
|
|
564
|
+
const urls = Array.isArray(imageUrl) ? imageUrl : [imageUrl];
|
|
565
|
+
const imageUrls = urls.map(u => ({
|
|
566
|
+
imagePreviewUrl: u,
|
|
567
|
+
imageHighResUrl: u,
|
|
568
|
+
sourceUrl: null
|
|
569
|
+
}));
|
|
570
|
+
return {
|
|
571
|
+
sub: {
|
|
572
|
+
messageType: RichSubMessageType.GRID_IMAGE,
|
|
573
|
+
gridImageMetadata: {
|
|
574
|
+
gridImageUrl: normalizeImageUrl(urls[0]),
|
|
575
|
+
imageUrls: imageUrls.map(normalizeImageUrl)
|
|
576
|
+
}
|
|
577
|
+
},
|
|
578
|
+
sections: imageUrls.map(({ imagePreviewUrl }) => ({
|
|
579
|
+
view_model: {
|
|
580
|
+
primitive: {
|
|
581
|
+
media: { url: imagePreviewUrl, mime_type: 'image/png' },
|
|
582
|
+
imagine_type: 'IMAGE',
|
|
583
|
+
status: { status: 'READY' },
|
|
584
|
+
__typename: 'GenAIImaginePrimitive'
|
|
585
|
+
},
|
|
586
|
+
__typename: 'GenAISingleLayoutViewModel'
|
|
587
|
+
}
|
|
588
|
+
}))
|
|
589
|
+
};
|
|
590
|
+
};
|
|
591
|
+
|
|
592
|
+
/** Buat submessage + unified section untuk VIDEO */
|
|
593
|
+
const makeVideoSub = (videoUrl) => {
|
|
594
|
+
const urls = Array.isArray(videoUrl) ? videoUrl : [videoUrl];
|
|
595
|
+
const parsed = urls.map(item => {
|
|
596
|
+
const [url, duration = '0'] = item.split('|');
|
|
597
|
+
return { url, duration: Number(duration) || 0 };
|
|
598
|
+
});
|
|
599
|
+
return {
|
|
600
|
+
sub: {
|
|
601
|
+
messageType: RichSubMessageType.TEXT,
|
|
602
|
+
messageText: '[ CANNOT_LOAD_VIDEO ]'
|
|
603
|
+
},
|
|
604
|
+
sections: parsed.map(({ url, duration }) => ({
|
|
605
|
+
view_model: {
|
|
606
|
+
primitive: {
|
|
607
|
+
media: { url, mime_type: 'video/mp4', duration },
|
|
608
|
+
imagine_type: 'ANIMATE',
|
|
609
|
+
status: { status: 'READY' },
|
|
610
|
+
__typename: 'GenAIImaginePrimitive'
|
|
611
|
+
},
|
|
612
|
+
__typename: 'GenAISingleLayoutViewModel'
|
|
613
|
+
}
|
|
614
|
+
}))
|
|
615
|
+
};
|
|
616
|
+
};
|
|
617
|
+
|
|
618
|
+
/** Buat submessage + unified section untuk REELS (HScroll) */
|
|
619
|
+
const makeReelsSub = (reelsItems = []) => {
|
|
620
|
+
const list = Array.isArray(reelsItems) ? reelsItems : [reelsItems];
|
|
621
|
+
return {
|
|
622
|
+
sub: {
|
|
623
|
+
messageType: RichSubMessageType.CONTENT_ITEMS,
|
|
624
|
+
contentItemsMetadata: {
|
|
625
|
+
contentType: 1,
|
|
626
|
+
itemsMetadata: list.map(item => ({
|
|
627
|
+
reelItem: {
|
|
628
|
+
title: item.username ?? item.title ?? '',
|
|
629
|
+
profileIconUrl: item.profileIconUrl ?? item.profile_url ?? null,
|
|
630
|
+
thumbnailUrl: item.thumbnailUrl ?? item.thumbnail ?? null,
|
|
631
|
+
videoUrl: item.videoUrl ?? item.url ?? null
|
|
632
|
+
}
|
|
633
|
+
}))
|
|
634
|
+
}
|
|
635
|
+
},
|
|
636
|
+
section: {
|
|
637
|
+
view_model: {
|
|
638
|
+
primitives: list.map(item => ({
|
|
639
|
+
reels_url: item.videoUrl ?? item.url ?? '',
|
|
640
|
+
thumbnail_url: item.thumbnailUrl ?? item.thumbnail ?? '',
|
|
641
|
+
creator: item.username ?? item.title ?? '',
|
|
642
|
+
avatar_url: item.profileIconUrl ?? item.profile_url ?? '',
|
|
643
|
+
reels_title: item.reels_title ?? item.title ?? '',
|
|
644
|
+
likes_count: item.likes_count ?? item.like ?? 0,
|
|
645
|
+
shares_count: item.shares_count ?? item.share ?? 0,
|
|
646
|
+
view_count: item.view_count ?? item.view ?? 0,
|
|
647
|
+
reel_source: item.reel_source ?? item.source ?? 'IG',
|
|
648
|
+
is_verified: !!(item.is_verified || item.verified),
|
|
649
|
+
__typename: 'GenAIReelPrimitive'
|
|
650
|
+
})),
|
|
651
|
+
__typename: 'GenAIHScrollLayoutViewModel'
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
};
|
|
655
|
+
};
|
|
656
|
+
|
|
657
|
+
/** Buat unified section untuk PRODUCT (single: Single layout, array: HScroll) */
|
|
658
|
+
const makeProductSection = (data) => {
|
|
659
|
+
const items = Array.isArray(data) ? data : [data];
|
|
660
|
+
const primitives = items.map(item => ({
|
|
661
|
+
title: item.title ?? '',
|
|
662
|
+
brand: item.brand ?? '',
|
|
663
|
+
price: item.price ?? '',
|
|
664
|
+
sale_price: item.sale_price ?? item.salePrice ?? '',
|
|
665
|
+
product_url: item.product_url ?? item.url ?? '',
|
|
666
|
+
image: { url: item.image_url ?? item.image ?? '' },
|
|
667
|
+
additional_images: [{ url: item.icon_url ?? item.icon ?? '' }],
|
|
668
|
+
__typename: 'GenAIProductItemCardPrimitive'
|
|
669
|
+
}));
|
|
670
|
+
const isMultiple = Array.isArray(data);
|
|
671
|
+
return {
|
|
672
|
+
sub: {
|
|
673
|
+
messageType: RichSubMessageType.TEXT,
|
|
674
|
+
messageText: '[ CANNOT_LOAD_PRODUCT ]'
|
|
675
|
+
},
|
|
676
|
+
section: isMultiple
|
|
677
|
+
? {
|
|
678
|
+
view_model: {
|
|
679
|
+
primitives,
|
|
680
|
+
__typename: 'GenAIHScrollLayoutViewModel'
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
: {
|
|
684
|
+
view_model: {
|
|
685
|
+
primitive: primitives[0],
|
|
686
|
+
__typename: 'GenAISingleLayoutViewModel'
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
};
|
|
690
|
+
};
|
|
691
|
+
|
|
692
|
+
/** Buat unified section untuk POST (HScroll) */
|
|
693
|
+
const makePostSection = (data) => {
|
|
694
|
+
const posts = Array.isArray(data) ? data : [data];
|
|
695
|
+
const primitives = posts.map(p => ({
|
|
696
|
+
title: p.title ?? '',
|
|
697
|
+
subtitle: p.subtitle ?? '',
|
|
698
|
+
username: p.username ?? '',
|
|
699
|
+
profile_picture_url: p.profile_picture_url ?? p.profile_url ?? '',
|
|
700
|
+
is_verified: !!(p.is_verified || p.verified),
|
|
701
|
+
thumbnail_url: p.thumbnail_url ?? p.thumbnail ?? '',
|
|
702
|
+
post_caption: p.post_caption ?? p.caption ?? '',
|
|
703
|
+
likes_count: p.likes_count ?? p.like ?? 0,
|
|
704
|
+
comments_count: p.comments_count ?? p.comment ?? 0,
|
|
705
|
+
shares_count: p.shares_count ?? p.share ?? 0,
|
|
706
|
+
post_url: p.post_url ?? p.url ?? '',
|
|
707
|
+
post_deeplink: p.post_deeplink ?? p.deeplink ?? '',
|
|
708
|
+
source_app: p.source_app ?? p.source ?? 'INSTAGRAM',
|
|
709
|
+
footer_label: p.footer_label ?? p.footer ?? '',
|
|
710
|
+
footer_icon: p.footer_icon ?? p.icon ?? '',
|
|
711
|
+
is_carousel: posts.length > 1,
|
|
712
|
+
orientation: p.orientation ?? 'LANDSCAPE',
|
|
713
|
+
post_type: p.post_type ?? 'VIDEO',
|
|
714
|
+
__typename: 'GenAIPostPrimitive'
|
|
715
|
+
}));
|
|
716
|
+
return {
|
|
717
|
+
sub: {
|
|
718
|
+
messageType: RichSubMessageType.TEXT,
|
|
719
|
+
messageText: '[ CANNOT_LOAD_POST ]'
|
|
720
|
+
},
|
|
721
|
+
section: {
|
|
722
|
+
view_model: {
|
|
723
|
+
primitives,
|
|
724
|
+
__typename: 'GenAIHScrollLayoutViewModel'
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
};
|
|
728
|
+
};
|
|
729
|
+
|
|
730
|
+
/** Buat submessage + unified section untuk TIP (metadata text) */
|
|
731
|
+
const makeTipSub = (text) => ({
|
|
732
|
+
sub: {
|
|
733
|
+
messageType: RichSubMessageType.TEXT,
|
|
734
|
+
messageText: text
|
|
735
|
+
},
|
|
736
|
+
section: {
|
|
737
|
+
view_model: {
|
|
738
|
+
primitive: {
|
|
739
|
+
text,
|
|
740
|
+
__typename: 'GenAIMetadataTextPrimitive'
|
|
741
|
+
},
|
|
742
|
+
__typename: 'GenAISingleLayoutViewModel'
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
});
|
|
746
|
+
|
|
747
|
+
/** Buat unified section untuk SUGGEST (ActionRow pill buttons) — tidak ada submessage proto */
|
|
748
|
+
const makeSuggestSection = (suggestion) => {
|
|
749
|
+
const suggestions = Array.isArray(suggestion) ? suggestion : [suggestion];
|
|
750
|
+
return {
|
|
751
|
+
view_model: {
|
|
752
|
+
primitives: suggestions.map(text => ({
|
|
753
|
+
prompt_text: text,
|
|
754
|
+
prompt_type: 'SUGGESTED_PROMPT',
|
|
755
|
+
__typename: 'GenAIFollowUpSuggestionPillPrimitive'
|
|
756
|
+
})),
|
|
757
|
+
__typename: 'GenAIActionRowLayoutViewModel'
|
|
758
|
+
}
|
|
759
|
+
};
|
|
760
|
+
};
|
|
761
|
+
|
|
762
|
+
/* ─────────────────────────────────────────────────────────────
|
|
763
|
+
MAIN BUILDER — prepareRichResponseMessage
|
|
764
|
+
───────────────────────────────────────────────────────────── */
|
|
765
|
+
|
|
766
|
+
export const prepareRichResponseMessage = (content) => {
|
|
767
|
+
const {
|
|
768
|
+
code, contentText, disclaimerText, footerText, headerText,
|
|
769
|
+
language, links, noHeading, richResponse, table, title,
|
|
770
|
+
// sub-types lama
|
|
771
|
+
gridImage, inlineImage, dynamic: dynamicContent,
|
|
772
|
+
map: mapContent, latex, contentItems,
|
|
773
|
+
// sub-types baru (prefixed 'rich' agar tidak konflik dengan media biasa)
|
|
774
|
+
richImage, richVideo, reels, source, richProduct, richPost, tip, suggest
|
|
775
|
+
} = content;
|
|
776
|
+
|
|
777
|
+
let submessages = [];
|
|
778
|
+
// extraSections: section-section unified yang tidak punya proto submessage 1:1
|
|
779
|
+
// (source, product, post, suggest) atau punya struktur multi-section (image, video, reels)
|
|
780
|
+
let extraSections = null;
|
|
781
|
+
|
|
782
|
+
/**
|
|
783
|
+
* Helper: push ke submessages dan optionally ke extraSections.
|
|
784
|
+
* Untuk tipe yang punya make*Sub yang mengembalikan { sub, sections[] } atau { sub, section }
|
|
785
|
+
*/
|
|
786
|
+
const pushRich = (built) => {
|
|
787
|
+
if (!built) return;
|
|
788
|
+
if (built.sub) submessages.push(built.sub);
|
|
789
|
+
if (!extraSections) extraSections = [];
|
|
790
|
+
if (built.sections) extraSections.push(...built.sections);
|
|
791
|
+
else if (built.section) extraSections.push(built.section);
|
|
792
|
+
};
|
|
793
|
+
|
|
794
|
+
/* ── mode array (richResponse) — multi-section campuran ── */
|
|
795
|
+
if (Array.isArray(richResponse)) {
|
|
796
|
+
if (headerText) submessages.push(makeTextSub(headerText));
|
|
797
|
+
richResponse.forEach(sub => {
|
|
798
|
+
if (sub.text != null) { submessages.push(makeTextSub(sub.text, sub.inlineEntities)); return; }
|
|
799
|
+
if (sub.code != null) { submessages.push(makeCodeSub(sub.code, sub.language || 'javascript')); return; }
|
|
800
|
+
if (sub.table != null) { submessages.push(makeTableSub(sub.table, sub.title, sub.noHeading)); return; }
|
|
801
|
+
if (sub.gridImage != null) { submessages.push(makeGridImageSub(sub.gridImage.gridImageUrl, sub.gridImage.imageUrls)); return; }
|
|
802
|
+
if (sub.inlineImage != null) { submessages.push(makeInlineImageSub(sub.inlineImage.imageUrl, sub.inlineImage.imageText, sub.inlineImage.alignment, sub.inlineImage.tapLinkUrl)); return; }
|
|
803
|
+
if (sub.dynamic != null) { submessages.push(makeDynamicSub(sub.dynamic.url, sub.dynamic.type, sub.dynamic.version, sub.dynamic.loopCount)); return; }
|
|
804
|
+
if (sub.map != null) { submessages.push(makeMapSub(sub.map.centerLatitude, sub.map.centerLongitude, sub.map)); return; }
|
|
805
|
+
if (sub.latex != null) { submessages.push(makeLatexSub(sub.latex.text, sub.latex.expressions)); return; }
|
|
806
|
+
if (sub.contentItems != null) { submessages.push(makeContentItemsSub(sub.contentItems.items, sub.contentItems.contentType)); return; }
|
|
807
|
+
// tipe baru
|
|
808
|
+
if (sub.richImage != null) { pushRich(makeImageSub(sub.richImage)); return; }
|
|
809
|
+
if (sub.richVideo != null) { pushRich(makeVideoSub(sub.richVideo)); return; }
|
|
810
|
+
if (sub.reels != null) { pushRich(makeReelsSub(sub.reels)); return; }
|
|
811
|
+
if (sub.source != null) { if (!extraSections) extraSections = []; extraSections.push(makeSourceSection(sub.source)); return; }
|
|
812
|
+
if (sub.richProduct != null) { pushRich(makeProductSection(sub.richProduct)); return; }
|
|
813
|
+
if (sub.richPost != null) { pushRich(makePostSection(sub.richPost)); return; }
|
|
814
|
+
if (sub.tip != null) { pushRich(makeTipSub(sub.tip)); return; }
|
|
815
|
+
if (sub.suggest != null) { if (!extraSections) extraSections = []; extraSections.push(makeSuggestSection(sub.suggest)); return; }
|
|
816
|
+
submessages.push(sub); // passthrough kalau sudah bentuk proto
|
|
817
|
+
});
|
|
818
|
+
if (footerText) submessages.push(makeTextSub(footerText));
|
|
819
|
+
|
|
820
|
+
/* ── mode flat (convenience fields) ── */
|
|
821
|
+
} else {
|
|
822
|
+
if (headerText) submessages.push(makeTextSub(headerText));
|
|
823
|
+
if (contentText) submessages.push(makeTextSub(contentText));
|
|
824
|
+
|
|
825
|
+
if (code) submessages.push(makeCodeSub(code, language || 'javascript'));
|
|
826
|
+
if (table) submessages.push(makeTableSub(table, title, noHeading));
|
|
827
|
+
if (gridImage) submessages.push(makeGridImageSub(gridImage.gridImageUrl, gridImage.imageUrls));
|
|
828
|
+
if (inlineImage) submessages.push(makeInlineImageSub(inlineImage.imageUrl, inlineImage.imageText, inlineImage.alignment, inlineImage.tapLinkUrl));
|
|
829
|
+
if (dynamicContent) submessages.push(makeDynamicSub(dynamicContent.url, dynamicContent.type, dynamicContent.version, dynamicContent.loopCount));
|
|
830
|
+
if (mapContent) submessages.push(makeMapSub(mapContent.centerLatitude, mapContent.centerLongitude, mapContent));
|
|
831
|
+
if (latex) submessages.push(makeLatexSub(latex.text, latex.expressions));
|
|
832
|
+
if (contentItems) submessages.push(makeContentItemsSub(contentItems.items, contentItems.contentType));
|
|
833
|
+
|
|
834
|
+
// ── tipe baru ──
|
|
835
|
+
if (richImage) pushRich(makeImageSub(richImage));
|
|
836
|
+
if (richVideo) pushRich(makeVideoSub(richVideo));
|
|
837
|
+
if (reels) pushRich(makeReelsSub(reels));
|
|
838
|
+
if (source) { if (!extraSections) extraSections = []; extraSections.push(makeSourceSection(source)); }
|
|
839
|
+
if (richProduct) pushRich(makeProductSection(richProduct));
|
|
840
|
+
if (richPost) pushRich(makePostSection(richPost));
|
|
841
|
+
if (tip) pushRich(makeTipSub(tip));
|
|
842
|
+
if (suggest) { if (!extraSections) extraSections = []; extraSections.push(makeSuggestSection(suggest)); }
|
|
843
|
+
|
|
844
|
+
/* links — bisa dikombinasi dengan tipe lain di atas */
|
|
845
|
+
if (links && Array.isArray(links)) {
|
|
846
|
+
links.forEach((linkField, index) => {
|
|
847
|
+
const prefix = 'SS_' + index;
|
|
848
|
+
const url = linkField.url || DONATE_URL;
|
|
849
|
+
const sources = (linkField.sources || []).map(s => ({
|
|
850
|
+
source_type: 'THIRD_PARTY',
|
|
851
|
+
source_display_name: s.displayName || 'Source',
|
|
852
|
+
source_subtitle: s.subtitle || '',
|
|
853
|
+
source_url: s.url || url
|
|
854
|
+
}));
|
|
855
|
+
submessages.push(makeTextSub(
|
|
856
|
+
linkField.text + ` {{${prefix}}}¹{{/${prefix}}} `,
|
|
857
|
+
[{
|
|
858
|
+
key: prefix,
|
|
859
|
+
metadata: {
|
|
860
|
+
reference_id: index + 1,
|
|
861
|
+
reference_url: url,
|
|
862
|
+
reference_title: linkField.title || url,
|
|
863
|
+
reference_display_name: linkField.displayName || url,
|
|
864
|
+
sources,
|
|
865
|
+
__typename: 'GenAISearchCitationItem'
|
|
866
|
+
}
|
|
867
|
+
}]
|
|
868
|
+
));
|
|
869
|
+
});
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
if (footerText) submessages.push(makeTextSub(footerText));
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
/* build unifiedResponse JSON — gabungkan sections dari submessages + extraSections */
|
|
876
|
+
const baseUnified = toUnified(submessages);
|
|
877
|
+
const unified = extraSections && extraSections.length > 0
|
|
878
|
+
? { ...baseUnified, sections: [...baseUnified.sections, ...extraSections] }
|
|
879
|
+
: baseUnified;
|
|
880
|
+
|
|
881
|
+
const richResponseMessage = proto.AIRichResponseMessage.create({
|
|
882
|
+
submessages,
|
|
883
|
+
messageType: proto.AIRichResponseMessageType.AI_RICH_RESPONSE_TYPE_STANDARD,
|
|
884
|
+
unifiedResponse: {
|
|
885
|
+
data: Buffer.from(JSON.stringify(unified), 'utf-8')
|
|
886
|
+
},
|
|
887
|
+
contextInfo: {
|
|
888
|
+
isForwarded: true,
|
|
889
|
+
forwardingScore: 1,
|
|
890
|
+
forwardedAiBotMessageInfo: { botJid: '867051314767696@bot' },
|
|
891
|
+
forwardOrigin: 4
|
|
892
|
+
}
|
|
893
|
+
});
|
|
894
|
+
|
|
895
|
+
const message = wrapToBotForwardedMessage(richResponseMessage);
|
|
896
|
+
const botMetadata = message.messageContextInfo.botMetadata;
|
|
897
|
+
|
|
898
|
+
if (disclaimerText) {
|
|
899
|
+
botMetadata.messageDisclaimerText = disclaimerText;
|
|
900
|
+
}
|
|
901
|
+
botMetadata.botResponseId = unified.response_id;
|
|
902
|
+
|
|
903
|
+
return message;
|
|
904
|
+
};
|
|
905
|
+
|
|
906
|
+
/* ─────────────────────────────────────────────────────────────
|
|
907
|
+
BOT WRAPPER HELPERS
|
|
908
|
+
───────────────────────────────────────────────────────────── */
|
|
909
|
+
|
|
910
|
+
export const botMetadataSignature = () => {
|
|
911
|
+
const signature = new Uint8Array(64);
|
|
912
|
+
getRandomValues(signature);
|
|
913
|
+
return signature;
|
|
914
|
+
};
|
|
915
|
+
|
|
916
|
+
export const botMetadataCertificate = (length = 685) => {
|
|
917
|
+
const certificate = new Uint8Array(length);
|
|
918
|
+
certificate[0] = 48;
|
|
919
|
+
certificate[1] = 130;
|
|
920
|
+
getRandomValues(certificate.subarray(2));
|
|
921
|
+
return certificate;
|
|
922
|
+
};
|
|
923
|
+
|
|
924
|
+
export const wrapToBotForwardedMessage = (richResponseMessage) => ({
|
|
925
|
+
messageContextInfo: {
|
|
926
|
+
botMetadata: {
|
|
927
|
+
verificationMetadata: {
|
|
928
|
+
proofs: [
|
|
929
|
+
{
|
|
930
|
+
certificateChain: [
|
|
931
|
+
botMetadataCertificate(),
|
|
932
|
+
botMetadataCertificate(892)
|
|
933
|
+
],
|
|
934
|
+
version: 1,
|
|
935
|
+
useCase: 1,
|
|
936
|
+
signature: botMetadataSignature()
|
|
937
|
+
}
|
|
938
|
+
]
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
},
|
|
942
|
+
botForwardedMessage: {
|
|
943
|
+
message: { richResponseMessage }
|
|
944
|
+
}
|
|
945
|
+
});
|
|
946
|
+
|
|
947
|
+
/* ─────────────────────────────────────────────────────────────
|
|
948
|
+
STANDALONE GENERATORS (kompatibel dengan baileys upstream)
|
|
949
|
+
Semua fungsi di bawah ini di-port dari baileys-main/src/Utils/rich-messages.js
|
|
950
|
+
agar API nuii 100% kompatibel dengan baileys — termasuk sendTable, sendLatex, dll.
|
|
951
|
+
───────────────────────────────────────────────────────────── */
|
|
952
|
+
|
|
953
|
+
/* helper: bangun contextInfo dari quoted */
|
|
954
|
+
const buildRichContextInfo = (quoted) => {
|
|
955
|
+
const ctxInfo = {
|
|
956
|
+
isForwarded: true,
|
|
957
|
+
forwardingScore: 1,
|
|
958
|
+
forwardedAiBotMessageInfo: { botJid: '867051314767696@bot' },
|
|
959
|
+
forwardOrigin: 4,
|
|
960
|
+
};
|
|
961
|
+
if (quoted?.key) {
|
|
962
|
+
ctxInfo.stanzaId = quoted.key.id;
|
|
963
|
+
ctxInfo.participant = quoted.key.participant || quoted.sender || quoted.key.remoteJid;
|
|
964
|
+
ctxInfo.quotedMessage = quoted.message;
|
|
965
|
+
}
|
|
966
|
+
return ctxInfo;
|
|
967
|
+
};
|
|
968
|
+
|
|
969
|
+
/* helper: wrap submessages + contextInfo ke botForwardedMessage (format lama/V1) */
|
|
970
|
+
const buildBotForwardedMessage = (submessages, contextInfo, unifiedResponse) => {
|
|
971
|
+
const richResponse = { messageType: 1, submessages, contextInfo };
|
|
972
|
+
if (unifiedResponse) richResponse.unifiedResponse = unifiedResponse;
|
|
973
|
+
return { botForwardedMessage: { message: { richResponseMessage: richResponse } } };
|
|
974
|
+
};
|
|
975
|
+
|
|
976
|
+
/* helper: wrap proto AIRichResponseMessage ke botForwardedMessage dengan messageContextInfo */
|
|
977
|
+
const _wrapProtoToResult = (submessages, quoted) => {
|
|
978
|
+
const unified = toUnified(submessages);
|
|
979
|
+
const richResponseMessage = proto.AIRichResponseMessage.create({
|
|
980
|
+
submessages,
|
|
981
|
+
messageType: proto.AIRichResponseMessageType.AI_RICH_RESPONSE_TYPE_STANDARD,
|
|
982
|
+
unifiedResponse: { data: Buffer.from(JSON.stringify(unified), 'utf-8') },
|
|
983
|
+
contextInfo: buildRichContextInfo(quoted),
|
|
984
|
+
});
|
|
985
|
+
return { message: wrapToBotForwardedMessage(richResponseMessage), messageId: generateMessageIDV2() };
|
|
986
|
+
};
|
|
987
|
+
|
|
988
|
+
// ── Table ──────────────────────────────────────────────────
|
|
989
|
+
|
|
990
|
+
export const generateTableContent = (title, headers, rows, quoted, options = {}) => {
|
|
991
|
+
const { footer, headerText } = options;
|
|
992
|
+
const tableRows = [{ items: headers, isHeading: true }, ...rows.map(row => ({ items: row.map(String) }))];
|
|
993
|
+
const subs = [];
|
|
994
|
+
if (headerText) subs.push({ messageType: RichSubMessageType.TEXT, messageText: headerText });
|
|
995
|
+
subs.push({ messageType: RichSubMessageType.TABLE, tableMetadata: { title, rows: tableRows } });
|
|
996
|
+
if (footer) subs.push({ messageType: RichSubMessageType.TEXT, messageText: footer });
|
|
997
|
+
return _wrapProtoToResult(subs, quoted);
|
|
998
|
+
};
|
|
999
|
+
|
|
1000
|
+
export const generateListContent = (title, items, quoted, options = {}) => {
|
|
1001
|
+
const { footer, headerText } = options;
|
|
1002
|
+
const tableRows = items.map(item => ({ items: Array.isArray(item) ? item.map(String) : [String(item)] }));
|
|
1003
|
+
const subs = [];
|
|
1004
|
+
if (headerText) subs.push({ messageType: RichSubMessageType.TEXT, messageText: headerText });
|
|
1005
|
+
subs.push({ messageType: RichSubMessageType.TABLE, tableMetadata: { title, rows: tableRows } });
|
|
1006
|
+
if (footer) subs.push({ messageType: RichSubMessageType.TEXT, messageText: footer });
|
|
1007
|
+
return _wrapProtoToResult(subs, quoted);
|
|
1008
|
+
};
|
|
1009
|
+
|
|
1010
|
+
export const toTableMetadataV2 = (arr) => {
|
|
1011
|
+
if (!Array.isArray(arr) || arr.length === 0) throw new Error('Input must be a non-empty array');
|
|
1012
|
+
const [title, headerStr, ...rest] = arr;
|
|
1013
|
+
const splitCols = (str) => typeof str !== 'string' ? [] : str.includes('|') ? str.split('|').map(s => s.trim()) : str.split(',').map(s => s.trim());
|
|
1014
|
+
const splitRows = (str) => typeof str !== 'string' ? [] : str.split(';;').map(row => splitCols(row));
|
|
1015
|
+
const header = splitCols(headerStr);
|
|
1016
|
+
const parsedRows = rest.flatMap(splitRows);
|
|
1017
|
+
const maxLen = Math.max(header.length, ...parsedRows.map(r => r.length));
|
|
1018
|
+
const unified_rows = [
|
|
1019
|
+
{ is_header: true, cells: [...header, ...Array(maxLen - header.length).fill('')] },
|
|
1020
|
+
...parsedRows.map(cells => ({ is_header: false, cells: [...cells, ...Array(maxLen - cells.length).fill('')] }))
|
|
1021
|
+
];
|
|
1022
|
+
const rows = unified_rows.map(r => ({ items: r.cells, ...(r.is_header ? { isHeading: true } : {}) }));
|
|
1023
|
+
return { title, rows, unified_rows };
|
|
1024
|
+
};
|
|
1025
|
+
|
|
1026
|
+
export const generateTableContentV2 = (table, quoted, options = {}) => {
|
|
1027
|
+
const { title, footer, headerText, text } = options;
|
|
1028
|
+
const { unified_rows } = toTableMetadataV2(table);
|
|
1029
|
+
const subs = [];
|
|
1030
|
+
const sections = [];
|
|
1031
|
+
if (headerText || title) sections.push({ view_model: { primitive: { text: headerText || title, __typename: 'GenAIMarkdownTextUXPrimitive' }, __typename: 'GenAISingleLayoutViewModel' } });
|
|
1032
|
+
if (text) sections.push({ view_model: { primitive: { text, __typename: 'GenAIMarkdownTextUXPrimitive' }, __typename: 'GenAISingleLayoutViewModel' } });
|
|
1033
|
+
sections.push({ view_model: { primitive: { rows: unified_rows, __typename: 'GenATableUXPrimitive' }, __typename: 'GenAISingleLayoutViewModel' } });
|
|
1034
|
+
if (footer) sections.push({ view_model: { primitive: { text: footer, __typename: 'GenAIMarkdownTextUXPrimitive' }, __typename: 'GenAISingleLayoutViewModel' } });
|
|
1035
|
+
// using randomUUID and randomBytes from top-level import
|
|
1036
|
+
const base64Data = Buffer.from(JSON.stringify({ response_id: randomUUID(), sections })).toString('base64');
|
|
1037
|
+
const ctxInfo = buildRichContextInfo(quoted);
|
|
1038
|
+
ctxInfo.forwardingScore = 2;
|
|
1039
|
+
ctxInfo.botMessageSharingInfo = { botEntryPointOrigin: 1, forwardScore: 2 };
|
|
1040
|
+
const content = {
|
|
1041
|
+
messageContextInfo: { threadId: [], deviceListMetadata: { senderKeyIndexes: [], recipientKeyIndexes: [], recipientKeyHash: '', recipientTimestamp: Math.floor(Date.now() / 1000) }, deviceListMetadataVersion: 2, messageSecret: randomBytes(32) },
|
|
1042
|
+
botForwardedMessage: { message: { richResponseMessage: { submessages: subs, messageType: 1, unifiedResponse: { data: base64Data }, contextInfo: ctxInfo } } }
|
|
1043
|
+
};
|
|
1044
|
+
return { message: content, messageId: generateMessageIDV2() };
|
|
1045
|
+
};
|
|
1046
|
+
|
|
1047
|
+
// ── Code Block ────────────────────────────────────────────
|
|
1048
|
+
|
|
1049
|
+
export const generateCodeBlockContent = (code, quoted, options = {}) => {
|
|
1050
|
+
const { title, footer, language = 'javascript' } = options;
|
|
1051
|
+
const subs = [];
|
|
1052
|
+
if (title) subs.push({ messageType: RichSubMessageType.TEXT, messageText: title });
|
|
1053
|
+
subs.push({ messageType: RichSubMessageType.CODE, codeMetadata: { codeLanguage: language, codeBlocks: tokenizeCode(code, language) } });
|
|
1054
|
+
if (footer) subs.push({ messageType: RichSubMessageType.TEXT, messageText: footer });
|
|
1055
|
+
return _wrapProtoToResult(subs, quoted);
|
|
1056
|
+
};
|
|
1057
|
+
|
|
1058
|
+
export const generateCodeBlockContentV2 = (code, quoted, options = {}) => {
|
|
1059
|
+
const { title, footer, language = 'javascript', text } = options;
|
|
1060
|
+
const { unified_codeBlock } = tokenizeCodeV2(code, language);
|
|
1061
|
+
const sections = [];
|
|
1062
|
+
if (text) sections.push({ view_model: { primitive: { text, __typename: 'GenAIMarkdownTextUXPrimitive' }, __typename: 'GenAISingleLayoutViewModel' } });
|
|
1063
|
+
sections.push({ view_model: { primitive: { language, code_blocks: unified_codeBlock, __typename: 'GenAICodeUXPrimitive' }, __typename: 'GenAISingleLayoutViewModel' } });
|
|
1064
|
+
if (footer) sections.push({ view_model: { primitive: { text: footer, __typename: 'GenAIMarkdownTextUXPrimitive' }, __typename: 'GenAISingleLayoutViewModel' } });
|
|
1065
|
+
// using randomUUID and randomBytes from top-level import
|
|
1066
|
+
const base64Data = Buffer.from(JSON.stringify({ response_id: randomUUID(), sections })).toString('base64');
|
|
1067
|
+
const ctxInfo = buildRichContextInfo(quoted);
|
|
1068
|
+
ctxInfo.forwardingScore = 2;
|
|
1069
|
+
ctxInfo.mentionedJid = [];
|
|
1070
|
+
ctxInfo.groupMentions = [];
|
|
1071
|
+
ctxInfo.statusAttributions = [];
|
|
1072
|
+
ctxInfo.botMessageSharingInfo = { botEntryPointOrigin: 1, forwardScore: 2 };
|
|
1073
|
+
const content = {
|
|
1074
|
+
messageContextInfo: { threadId: [], deviceListMetadata: { senderKeyIndexes: [], recipientKeyIndexes: [], recipientKeyHash: '', recipientTimestamp: Math.floor(Date.now() / 1000) }, deviceListMetadataVersion: 2, messageSecret: randomBytes(32) },
|
|
1075
|
+
botForwardedMessage: { message: { richResponseMessage: { submessages: [], messageType: 1, unifiedResponse: { data: base64Data }, contextInfo: ctxInfo } } }
|
|
1076
|
+
};
|
|
1077
|
+
return { message: content, messageId: generateMessageIDV2() };
|
|
1078
|
+
};
|
|
1079
|
+
|
|
1080
|
+
// ── Rich Message (generic) ────────────────────────────────
|
|
1081
|
+
|
|
1082
|
+
export const generateRichMessageContent = (submessages, quoted) => {
|
|
1083
|
+
return _wrapProtoToResult(submessages, quoted);
|
|
1084
|
+
};
|
|
1085
|
+
|
|
1086
|
+
export const generateUnifiedResponseContent = (quoted, captured) => {
|
|
1087
|
+
const richResponseMessage = proto.AIRichResponseMessage.create({
|
|
1088
|
+
submessages: captured.submessages,
|
|
1089
|
+
messageType: proto.AIRichResponseMessageType.AI_RICH_RESPONSE_TYPE_STANDARD,
|
|
1090
|
+
unifiedResponse: captured.unifiedResponse,
|
|
1091
|
+
contextInfo: buildRichContextInfo(quoted),
|
|
1092
|
+
});
|
|
1093
|
+
return { message: wrapToBotForwardedMessage(richResponseMessage), messageId: generateMessageIDV2() };
|
|
1094
|
+
};
|
|
1095
|
+
|
|
1096
|
+
export const captureUnifiedResponse = (msg) => {
|
|
1097
|
+
const botFwd = msg?.botForwardedMessage?.message;
|
|
1098
|
+
if (!botFwd) return null;
|
|
1099
|
+
const rich = botFwd.richResponseMessage;
|
|
1100
|
+
if (!rich?.unifiedResponse?.data) return null;
|
|
1101
|
+
return { unifiedResponse: { data: rich.unifiedResponse.data }, submessages: rich.submessages || [], contextInfo: rich.contextInfo || {} };
|
|
1102
|
+
};
|
|
1103
|
+
|
|
1104
|
+
// ── Link ──────────────────────────────────────────────────
|
|
1105
|
+
|
|
1106
|
+
export const generateLinkContent = (text, links, quoted, options = {}) => {
|
|
1107
|
+
const { footer, botJid = '867051314767696@bot', forwardingScore = 3, citations = [], proofs = [] } = options;
|
|
1108
|
+
const subs = [];
|
|
1109
|
+
const fullText = footer ? `${text}${footer}` : text;
|
|
1110
|
+
subs.push({ messageType: RichSubMessageType.TEXT, messageText: fullText });
|
|
1111
|
+
const sections = [];
|
|
1112
|
+
const inlineEntities = links.map((link, i) => {
|
|
1113
|
+
const url = typeof link === 'string' ? link : link.url;
|
|
1114
|
+
const displayName = typeof link === 'object' && link.displayName ? link.displayName : citations[i]?.sourceTitle || `Link ${i + 1}`;
|
|
1115
|
+
return { key: `IE_${i}`, metadata: { display_name: displayName, is_trusted: false, url, __typename: 'GenAIInlineLinkItem' } };
|
|
1116
|
+
});
|
|
1117
|
+
sections.push({ view_model: { primitive: { text, inline_entities: inlineEntities, __typename: 'GenAIMarkdownTextUXPrimitive' }, __typename: 'GenAISingleLayoutViewModel' } });
|
|
1118
|
+
if (footer) sections.push({ view_model: { primitive: { text: footer, __typename: 'GenAIMarkdownTextUXPrimitive' }, __typename: 'GenAISingleLayoutViewModel' } });
|
|
1119
|
+
// using randomUUID and randomBytes from top-level import
|
|
1120
|
+
const base64Data = Buffer.from(JSON.stringify({ response_id: randomUUID(), sections })).toString('base64');
|
|
1121
|
+
const ctxInfo = buildRichContextInfo(quoted);
|
|
1122
|
+
ctxInfo.forwardingScore = forwardingScore;
|
|
1123
|
+
ctxInfo.forwardedAiBotMessageInfo = { botJid };
|
|
1124
|
+
ctxInfo.botMessageSharingInfo = { forwardScore: forwardingScore };
|
|
1125
|
+
const messageContextInfo = { messageSecret: randomBytes(32) };
|
|
1126
|
+
if (citations.length > 0 || proofs.length > 0) {
|
|
1127
|
+
const botMetadata = {};
|
|
1128
|
+
if (citations.length > 0) botMetadata.richResponseSourcesMetadata = { sources: citations.map((c, i) => ({ provider: 1, thumbnailCdnUrl: '', sourceProviderUrl: typeof links[i] === 'string' ? links[i] : links[i]?.url || '', sourceQuery: c.sourceQuery || '', faviconCdnUrl: c.faviconCdnUrl || '', citationNumber: c.citationNumber ?? i + 1, sourceTitle: c.sourceTitle || '' })) };
|
|
1129
|
+
if (proofs.length > 0) botMetadata.verificationMetadata = { proofs: proofs.map(p => ({ version: p.version || 1, useCase: p.useCase || 1, signature: p.signature || '', certificateChain: p.certificateChain || [] })) };
|
|
1130
|
+
messageContextInfo.botMetadata = botMetadata;
|
|
1131
|
+
}
|
|
1132
|
+
const content = { messageContextInfo, botForwardedMessage: { message: { richResponseMessage: { messageType: 1, submessages: subs, unifiedResponse: { data: base64Data }, contextInfo: ctxInfo } } } };
|
|
1133
|
+
return { message: content, messageId: generateMessageIDV2() };
|
|
1134
|
+
};
|
|
1135
|
+
|
|
1136
|
+
export const generateLinkContentV2 = (text, links, quoted, options = {}) => {
|
|
1137
|
+
const { footer, searchEngine = 'MAME' } = options;
|
|
1138
|
+
const subs = [];
|
|
1139
|
+
const fullText = footer ? `${text}${footer}` : text;
|
|
1140
|
+
subs.push({ messageType: RichSubMessageType.TEXT, messageText: fullText });
|
|
1141
|
+
const sections = [];
|
|
1142
|
+
const inlineEntities = links.map((link, i) => {
|
|
1143
|
+
const url = typeof link === 'string' ? link : link.url;
|
|
1144
|
+
const displayName = typeof link === 'object' && link.displayName ? link.displayName : `Link ${i + 1}`;
|
|
1145
|
+
const sourceDisplayName = typeof link === 'object' && link.sourceDisplayName ? link.sourceDisplayName : `Source ${i + 1}`;
|
|
1146
|
+
const sourceSubtitle = typeof link === 'object' && link.sourceSubtitle ? link.sourceSubtitle : '';
|
|
1147
|
+
return { key: `IE_${i}`, metadata: { reference_id: i + 1, reference_url: url, reference_title: displayName, reference_display_name: displayName, sources: [{ source_type: 'THIRD_PARTY', source_display_name: sourceDisplayName, source_subtitle: sourceSubtitle, source_url: url }], __typename: 'GenAISearchCitationItem' } };
|
|
1148
|
+
});
|
|
1149
|
+
sections.push({ view_model: { primitive: { text, inline_entities: inlineEntities, __typename: 'GenAIMarkdownTextUXPrimitive' }, __typename: 'GenAISingleLayoutViewModel' } });
|
|
1150
|
+
const searchSources = links.map((link, i) => {
|
|
1151
|
+
const url = typeof link === 'string' ? link : link.url;
|
|
1152
|
+
const sourceDisplayName = typeof link === 'object' && link.sourceDisplayName ? link.sourceDisplayName : `Source ${i + 1}`;
|
|
1153
|
+
const sourceSubtitle = typeof link === 'object' && link.sourceSubtitle ? link.sourceSubtitle : '';
|
|
1154
|
+
return { source_type: 'THIRD_PARTY', source_display_name: sourceDisplayName, source_subtitle: sourceSubtitle, source_url: url };
|
|
1155
|
+
});
|
|
1156
|
+
sections.push({ view_model: { primitive: { sources: searchSources, search_engine: searchEngine, __typename: 'GenAISearchResultPrimitive' }, __typename: 'GenAISingleLayoutViewModel' } });
|
|
1157
|
+
if (footer) sections.push({ view_model: { primitive: { text: footer, __typename: 'GenAIMarkdownTextUXPrimitive' }, __typename: 'GenAISingleLayoutViewModel' } });
|
|
1158
|
+
// using randomUUID and randomBytes from top-level import
|
|
1159
|
+
const base64Data = Buffer.from(JSON.stringify({ response_id: randomUUID(), sections })).toString('base64');
|
|
1160
|
+
const ctxInfo = { isForwarded: true, forwardOrigin: 4 };
|
|
1161
|
+
if (quoted?.key) { ctxInfo.participant = quoted.key.participant || quoted.sender || quoted.key.remoteJid; ctxInfo.quotedMessage = quoted.message; }
|
|
1162
|
+
const content = { messageContextInfo: { threadId: [], messageSecret: randomBytes(32) }, botForwardedMessage: { message: { richResponseMessage: { messageType: 1, submessages: subs, unifiedResponse: { data: base64Data }, contextInfo: ctxInfo } } } };
|
|
1163
|
+
return { message: content, messageId: generateMessageIDV2() };
|
|
1164
|
+
};
|
|
1165
|
+
|
|
1166
|
+
// ── LaTeX (3 varian, port dari baileys) ──────────────────
|
|
1167
|
+
|
|
1168
|
+
/**
|
|
1169
|
+
* generateLatexContent — kirim LaTeX dengan URL gambar yang sudah ada (pre-rendered).
|
|
1170
|
+
* Jika `expr.url` tidak diisi, otomatis di-generate via buildLatexUrl (codecogs).
|
|
1171
|
+
*
|
|
1172
|
+
* @param {object} quoted - pesan yang di-quote (boleh null)
|
|
1173
|
+
* @param {object} options - { text, expressions, headerText, footer }
|
|
1174
|
+
* expressions: Array<{ latexExpression, url?, width?, height?,
|
|
1175
|
+
* fontHeight?, imageTopPadding?, imageLeadingPadding?,
|
|
1176
|
+
* imageBottomPadding?, imageTrailingPadding? }>
|
|
1177
|
+
*/
|
|
1178
|
+
export const generateLatexContent = (quoted, options) => {
|
|
1179
|
+
const { text, expressions, headerText, footer } = options;
|
|
1180
|
+
const subs = [];
|
|
1181
|
+
if (headerText) subs.push({ messageType: RichSubMessageType.TEXT, messageText: headerText });
|
|
1182
|
+
const latexExpressions = expressions.map((expr) => {
|
|
1183
|
+
const entry = {
|
|
1184
|
+
latexExpression: expr.latexExpression,
|
|
1185
|
+
url: expr.url || buildLatexUrl(expr.latexExpression),
|
|
1186
|
+
width: expr.width ?? 120,
|
|
1187
|
+
height: expr.height ?? 40,
|
|
1188
|
+
};
|
|
1189
|
+
if (expr.fontHeight !== undefined) entry.fontHeight = expr.fontHeight;
|
|
1190
|
+
if (expr.imageTopPadding !== undefined) entry.imageTopPadding = expr.imageTopPadding;
|
|
1191
|
+
if (expr.imageLeadingPadding !== undefined) entry.imageLeadingPadding = expr.imageLeadingPadding;
|
|
1192
|
+
if (expr.imageBottomPadding !== undefined) entry.imageBottomPadding = expr.imageBottomPadding;
|
|
1193
|
+
if (expr.imageTrailingPadding !== undefined) entry.imageTrailingPadding = expr.imageTrailingPadding;
|
|
1194
|
+
return entry;
|
|
1195
|
+
});
|
|
1196
|
+
subs.push({ messageType: RichSubMessageType.LATEX, latexMetadata: { text: text || '', expressions: latexExpressions } });
|
|
1197
|
+
if (footer) subs.push({ messageType: RichSubMessageType.TEXT, messageText: footer });
|
|
1198
|
+
return _wrapProtoToResult(subs, quoted);
|
|
1199
|
+
};
|
|
1200
|
+
|
|
1201
|
+
/**
|
|
1202
|
+
* generateLatexImageContent — render LaTeX ke PNG via renderLatexToPng,
|
|
1203
|
+
* upload via uploadFn, kirim sebagai LaTeX message dengan URL hasil upload.
|
|
1204
|
+
*
|
|
1205
|
+
* @param {object} quoted - pesan yang di-quote (boleh null)
|
|
1206
|
+
* @param {object} options - { text, expressions, headerText, footer }
|
|
1207
|
+
* @param {Function} uploadFn - async (buffer, type) => { url, directPath }
|
|
1208
|
+
* @param {Function} renderLatexToPng - async (latexExpression) => { buffer, width, height }
|
|
1209
|
+
*/
|
|
1210
|
+
export const generateLatexImageContent = async (quoted, options, uploadFn, renderLatexToPng) => {
|
|
1211
|
+
const { text, expressions, headerText, footer } = options;
|
|
1212
|
+
const subs = [];
|
|
1213
|
+
if (headerText) subs.push({ messageType: RichSubMessageType.TEXT, messageText: headerText });
|
|
1214
|
+
const latexExpressions = await Promise.all(
|
|
1215
|
+
expressions.map(async (expr) => {
|
|
1216
|
+
const { buffer, width, height } = await renderLatexToPng(expr.latexExpression);
|
|
1217
|
+
const uploadResult = await uploadFn(buffer, 'image');
|
|
1218
|
+
const imageUrl = uploadResult.url || uploadResult.directPath;
|
|
1219
|
+
return { latexExpression: expr.latexExpression, url: imageUrl, width, height };
|
|
1220
|
+
}),
|
|
1221
|
+
);
|
|
1222
|
+
subs.push({ messageType: RichSubMessageType.LATEX, latexMetadata: { text: text || '', expressions: latexExpressions } });
|
|
1223
|
+
if (footer) subs.push({ messageType: RichSubMessageType.TEXT, messageText: footer });
|
|
1224
|
+
return _wrapProtoToResult(subs, quoted);
|
|
1225
|
+
};
|
|
1226
|
+
|
|
1227
|
+
/**
|
|
1228
|
+
* generateLatexInlineImageContent — render LaTeX ke PNG, upload, kirim sebagai
|
|
1229
|
+
* deretan INLINE_IMAGE (satu per expression). Fallback untuk klien tanpa LaTeX native.
|
|
1230
|
+
*
|
|
1231
|
+
* @param {object} quoted - pesan yang di-quote (boleh null)
|
|
1232
|
+
* @param {object} options - { text, expressions, headerText, footer }
|
|
1233
|
+
* @param {Function} uploadFn - async (buffer, type) => { url, directPath }
|
|
1234
|
+
* @param {Function} renderLatexToPng - async (latexExpression) => { buffer, width, height }
|
|
1235
|
+
*/
|
|
1236
|
+
export const generateLatexInlineImageContent = async (quoted, options, uploadFn, renderLatexToPng) => {
|
|
1237
|
+
const { text, expressions, headerText, footer } = options;
|
|
1238
|
+
const subs = [];
|
|
1239
|
+
if (headerText) subs.push({ messageType: RichSubMessageType.TEXT, messageText: headerText });
|
|
1240
|
+
if (text) subs.push({ messageType: RichSubMessageType.TEXT, messageText: text });
|
|
1241
|
+
for (const expr of expressions) {
|
|
1242
|
+
const { buffer, width, height } = await renderLatexToPng(expr.latexExpression);
|
|
1243
|
+
const uploadResult = await uploadFn(buffer, 'image');
|
|
1244
|
+
const imageUrl = uploadResult.url || uploadResult.directPath;
|
|
1245
|
+
subs.push({
|
|
1246
|
+
messageType: RichSubMessageType.INLINE_IMAGE,
|
|
1247
|
+
imageMetadata: {
|
|
1248
|
+
imageUrl: { imagePreviewUrl: imageUrl, imageHighResUrl: imageUrl },
|
|
1249
|
+
imageText: expr.latexExpression,
|
|
1250
|
+
alignment: 2,
|
|
1251
|
+
},
|
|
1252
|
+
});
|
|
1253
|
+
}
|
|
1254
|
+
if (footer) subs.push({ messageType: RichSubMessageType.TEXT, messageText: footer });
|
|
1255
|
+
return _wrapProtoToResult(subs, quoted);
|
|
1256
|
+
};
|
|
1257
|
+
|
|
1258
|
+
// ── tokenizeCodeV2 (dipakai oleh generateCodeBlockContentV2) ─
|
|
1259
|
+
|
|
1260
|
+
export const tokenizeCodeV2 = (code, language = 'javascript') => {
|
|
1261
|
+
const keywords = LANGUAGE_KEYWORDS[language] || LANGUAGE_KEYWORDS['javascript'] || new Set();
|
|
1262
|
+
const tokens = [];
|
|
1263
|
+
let i = 0;
|
|
1264
|
+
const n = code.length;
|
|
1265
|
+
const push = (codeContent, highlightType) => {
|
|
1266
|
+
if (!codeContent) return;
|
|
1267
|
+
const last = tokens[tokens.length - 1];
|
|
1268
|
+
if (last && last.highlightType === highlightType) last.codeContent += codeContent;
|
|
1269
|
+
else tokens.push({ codeContent, highlightType });
|
|
1270
|
+
};
|
|
1271
|
+
const isWordStart = (c) => /[a-zA-Z_$]/.test(c);
|
|
1272
|
+
const isWord = (c) => /[a-zA-Z0-9_$]/.test(c);
|
|
1273
|
+
const isNum = (c) => /[0-9]/.test(c);
|
|
1274
|
+
const HIGHLIGHT_TYPE_MAP = { 0: 'DEFAULT', 1: 'KEYWORD', 2: 'METHOD', 3: 'STR', 4: 'NUMBER', 5: 'COMMENT' };
|
|
1275
|
+
const isPyBash = ['python','py','bash','sh','shell'].includes(language);
|
|
1276
|
+
while (i < n) {
|
|
1277
|
+
const c = code[i];
|
|
1278
|
+
if (/\s/.test(c)) {
|
|
1279
|
+
let s = i; while (i < n && /\s/.test(code[i])) i++;
|
|
1280
|
+
push(code.slice(s, i), 0); continue;
|
|
1281
|
+
}
|
|
1282
|
+
if (c === '/' && code[i + 1] === '/') {
|
|
1283
|
+
let s = i; i += 2; while (i < n && code[i] !== '\n') i++;
|
|
1284
|
+
push(code.slice(s, i), 5); continue;
|
|
1285
|
+
}
|
|
1286
|
+
if (c === '/' && code[i + 1] === '*') {
|
|
1287
|
+
let s = i; i += 2;
|
|
1288
|
+
while (i < n - 1 && !(code[i] === '*' && code[i + 1] === '/')) i++;
|
|
1289
|
+
i += 2; push(code.slice(s, i), 5); continue;
|
|
1290
|
+
}
|
|
1291
|
+
if (c === '#' && isPyBash) {
|
|
1292
|
+
let s = i; i++; while (i < n && code[i] !== '\n') i++;
|
|
1293
|
+
push(code.slice(s, i), 5); continue;
|
|
1294
|
+
}
|
|
1295
|
+
if (c === '"' || c === "'" || c === '`') {
|
|
1296
|
+
let s = i; const q = c; i++;
|
|
1297
|
+
while (i < n) { if (code[i] === '\\' && i + 1 < n) i += 2; else if (code[i] === q) { i++; break; } else i++; }
|
|
1298
|
+
push(code.slice(s, i), 3); continue;
|
|
1299
|
+
}
|
|
1300
|
+
if (isNum(c)) {
|
|
1301
|
+
let s = i; while (i < n && /[0-9.xXa-fA-FeEbBoO_]/.test(code[i])) i++;
|
|
1302
|
+
push(code.slice(s, i), 4); continue;
|
|
1303
|
+
}
|
|
1304
|
+
if (isWordStart(c)) {
|
|
1305
|
+
let s = i; while (i < n && isWord(code[i])) i++;
|
|
1306
|
+
const word = code.slice(s, i);
|
|
1307
|
+
let type = 0;
|
|
1308
|
+
if (keywords.has(word)) type = 1;
|
|
1309
|
+
else { let j = i; while (j < n && /\s/.test(code[j])) j++; if (code[j] === '(') type = 2; }
|
|
1310
|
+
push(word, type); continue;
|
|
1311
|
+
}
|
|
1312
|
+
push(c, 0); i++;
|
|
1313
|
+
}
|
|
1314
|
+
return {
|
|
1315
|
+
codeBlock: tokens,
|
|
1316
|
+
unified_codeBlock: tokens.map(t => ({ content: t.codeContent, type: HIGHLIGHT_TYPE_MAP[t.highlightType] || 'DEFAULT' }))
|
|
1317
|
+
};
|
|
1318
|
+
};
|
|
1319
|
+
|
|
1320
|
+
/* Re-export LANGUAGE_KEYWORDS dari WABinary/constants.js agar kompatibel dengan baileys export */
|
|
1321
|
+
export { LANGUAGE_KEYWORDS } from '../WABinary/constants.js';
|
|
1322
|
+
|
|
1323
|
+
//# sourceMappingURL=rich-message-utils.js.map
|