@nuiisweety/baileys 0.1.4 → 0.1.6
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 +821 -657
- package/README.md.bak +1015 -461
- package/lib/Utils/messages.js +10 -1
- package/lib/Utils/messages.js.backup +1768 -0
- package/lib/Utils/rich-message-utils.js +289 -49
- package/lib/Utils/rich-message-utils.js.backup +706 -0
- package/package.json +1 -1
|
@@ -0,0 +1,706 @@
|
|
|
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 } 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
|
+
/* ─────────────────────────────────────────────────────────────
|
|
538
|
+
MAIN BUILDER — prepareRichResponseMessage
|
|
539
|
+
───────────────────────────────────────────────────────────── */
|
|
540
|
+
|
|
541
|
+
export const prepareRichResponseMessage = (content) => {
|
|
542
|
+
const {
|
|
543
|
+
code, contentText, disclaimerText, footerText, headerText,
|
|
544
|
+
language, links, noHeading, richResponse, table, title,
|
|
545
|
+
// sub-types baru
|
|
546
|
+
gridImage, inlineImage, dynamic: dynamicContent,
|
|
547
|
+
map: mapContent, latex, contentItems
|
|
548
|
+
} = content;
|
|
549
|
+
|
|
550
|
+
let submessages = [];
|
|
551
|
+
|
|
552
|
+
/* ── mode array (richResponse) — multi-section campuran ── */
|
|
553
|
+
if (Array.isArray(richResponse)) {
|
|
554
|
+
// headerText dan footerText tetap dihormati meskipun pakai mode array
|
|
555
|
+
if (headerText) submessages.push(makeTextSub(headerText));
|
|
556
|
+
submessages.push(...richResponse.map(sub => {
|
|
557
|
+
if (sub.text != null) return makeTextSub(sub.text, sub.inlineEntities);
|
|
558
|
+
if (sub.code != null) return makeCodeSub(sub.code, sub.language || 'javascript');
|
|
559
|
+
if (sub.table != null) return makeTableSub(sub.table, sub.title, sub.noHeading);
|
|
560
|
+
if (sub.gridImage != null) return makeGridImageSub(sub.gridImage.gridImageUrl, sub.gridImage.imageUrls);
|
|
561
|
+
if (sub.inlineImage != null) return makeInlineImageSub(sub.inlineImage.imageUrl, sub.inlineImage.imageText, sub.inlineImage.alignment, sub.inlineImage.tapLinkUrl);
|
|
562
|
+
if (sub.dynamic != null) return makeDynamicSub(sub.dynamic.url, sub.dynamic.type, sub.dynamic.version, sub.dynamic.loopCount);
|
|
563
|
+
if (sub.map != null) return makeMapSub(sub.map.centerLatitude, sub.map.centerLongitude, sub.map);
|
|
564
|
+
if (sub.latex != null) return makeLatexSub(sub.latex.text, sub.latex.expressions);
|
|
565
|
+
if (sub.contentItems != null) return makeContentItemsSub(sub.contentItems.items, sub.contentItems.contentType);
|
|
566
|
+
return sub; // passthrough kalau sudah bentuk proto
|
|
567
|
+
}).filter(Boolean));
|
|
568
|
+
if (footerText) submessages.push(makeTextSub(footerText));
|
|
569
|
+
|
|
570
|
+
/* ── mode flat (convenience fields) ── */
|
|
571
|
+
} else {
|
|
572
|
+
if (headerText) submessages.push(makeTextSub(headerText));
|
|
573
|
+
if (contentText) submessages.push(makeTextSub(contentText));
|
|
574
|
+
|
|
575
|
+
if (code) {
|
|
576
|
+
submessages.push(makeCodeSub(code, language || 'javascript'));
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
if (table) {
|
|
580
|
+
submessages.push(makeTableSub(table, title, noHeading));
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
if (gridImage) {
|
|
584
|
+
submessages.push(makeGridImageSub(gridImage.gridImageUrl, gridImage.imageUrls));
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
if (inlineImage) {
|
|
588
|
+
submessages.push(makeInlineImageSub(inlineImage.imageUrl, inlineImage.imageText, inlineImage.alignment, inlineImage.tapLinkUrl));
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
if (dynamicContent) {
|
|
592
|
+
submessages.push(makeDynamicSub(dynamicContent.url, dynamicContent.type, dynamicContent.version, dynamicContent.loopCount));
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
if (mapContent) {
|
|
596
|
+
submessages.push(makeMapSub(mapContent.centerLatitude, mapContent.centerLongitude, mapContent));
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
if (latex) {
|
|
600
|
+
submessages.push(makeLatexSub(latex.text, latex.expressions));
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
if (contentItems) {
|
|
604
|
+
submessages.push(makeContentItemsSub(contentItems.items, contentItems.contentType));
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
/* links — bisa dikombinasi dengan tipe lain di atas */
|
|
608
|
+
if (links && Array.isArray(links)) {
|
|
609
|
+
links.forEach((linkField, index) => {
|
|
610
|
+
const prefix = 'SS_' + index;
|
|
611
|
+
const url = linkField.url || DONATE_URL;
|
|
612
|
+
const sources = (linkField.sources || []).map(s => ({
|
|
613
|
+
source_type: 'THIRD_PARTY',
|
|
614
|
+
source_display_name: s.displayName || 'Source',
|
|
615
|
+
source_subtitle: s.subtitle || '',
|
|
616
|
+
source_url: s.url || url
|
|
617
|
+
}));
|
|
618
|
+
submessages.push(makeTextSub(
|
|
619
|
+
linkField.text + ` {{${prefix}}}¹{{/${prefix}}} `,
|
|
620
|
+
[{
|
|
621
|
+
key: prefix,
|
|
622
|
+
metadata: {
|
|
623
|
+
reference_id: index + 1,
|
|
624
|
+
reference_url: url,
|
|
625
|
+
reference_title: linkField.title || url,
|
|
626
|
+
reference_display_name: linkField.displayName || url,
|
|
627
|
+
sources,
|
|
628
|
+
__typename: 'GenAISearchCitationItem'
|
|
629
|
+
}
|
|
630
|
+
}]
|
|
631
|
+
));
|
|
632
|
+
});
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
if (footerText) submessages.push(makeTextSub(footerText));
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
/* build unifiedResponse JSON */
|
|
639
|
+
const unified = toUnified(submessages);
|
|
640
|
+
|
|
641
|
+
const richResponseMessage = proto.AIRichResponseMessage.create({
|
|
642
|
+
submessages,
|
|
643
|
+
messageType: proto.AIRichResponseMessageType.AI_RICH_RESPONSE_TYPE_STANDARD,
|
|
644
|
+
unifiedResponse: {
|
|
645
|
+
data: Buffer.from(JSON.stringify(unified), 'utf-8')
|
|
646
|
+
},
|
|
647
|
+
contextInfo: {
|
|
648
|
+
isForwarded: true,
|
|
649
|
+
forwardingScore: 1,
|
|
650
|
+
forwardedAiBotMessageInfo: { botJid: '867051314767696@bot' },
|
|
651
|
+
forwardOrigin: 4
|
|
652
|
+
}
|
|
653
|
+
});
|
|
654
|
+
|
|
655
|
+
const message = wrapToBotForwardedMessage(richResponseMessage);
|
|
656
|
+
const botMetadata = message.messageContextInfo.botMetadata;
|
|
657
|
+
|
|
658
|
+
if (disclaimerText) {
|
|
659
|
+
botMetadata.messageDisclaimerText = disclaimerText;
|
|
660
|
+
}
|
|
661
|
+
botMetadata.botResponseId = unified.response_id;
|
|
662
|
+
|
|
663
|
+
return message;
|
|
664
|
+
};
|
|
665
|
+
|
|
666
|
+
/* ─────────────────────────────────────────────────────────────
|
|
667
|
+
BOT WRAPPER HELPERS
|
|
668
|
+
───────────────────────────────────────────────────────────── */
|
|
669
|
+
|
|
670
|
+
export const botMetadataSignature = () => {
|
|
671
|
+
const signature = new Uint8Array(64);
|
|
672
|
+
getRandomValues(signature);
|
|
673
|
+
return signature;
|
|
674
|
+
};
|
|
675
|
+
|
|
676
|
+
export const botMetadataCertificate = (length = 685) => {
|
|
677
|
+
const certificate = new Uint8Array(length);
|
|
678
|
+
certificate[0] = 48;
|
|
679
|
+
certificate[1] = 130;
|
|
680
|
+
getRandomValues(certificate.subarray(2));
|
|
681
|
+
return certificate;
|
|
682
|
+
};
|
|
683
|
+
|
|
684
|
+
export const wrapToBotForwardedMessage = (richResponseMessage) => ({
|
|
685
|
+
messageContextInfo: {
|
|
686
|
+
botMetadata: {
|
|
687
|
+
verificationMetadata: {
|
|
688
|
+
proofs: [
|
|
689
|
+
{
|
|
690
|
+
certificateChain: [
|
|
691
|
+
botMetadataCertificate(),
|
|
692
|
+
botMetadataCertificate(892)
|
|
693
|
+
],
|
|
694
|
+
version: 1,
|
|
695
|
+
useCase: 1,
|
|
696
|
+
signature: botMetadataSignature()
|
|
697
|
+
}
|
|
698
|
+
]
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
},
|
|
702
|
+
botForwardedMessage: {
|
|
703
|
+
message: { richResponseMessage }
|
|
704
|
+
}
|
|
705
|
+
});
|
|
706
|
+
//# sourceMappingURL=rich-message-utils.js.map
|