@pontalabs/baileys 1.0.6 → 1.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (185) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +839 -267
  3. package/WAProto/GenerateStatics.sh +4 -3
  4. package/WAProto/WAProto.proto +511 -1215
  5. package/WAProto/index.d.ts +55 -14017
  6. package/WAProto/index.js +29 -97689
  7. package/lib/Defaults/index.d.ts +74 -72
  8. package/lib/Defaults/index.js +108 -94
  9. package/lib/Signal/Group/ciphertext-message.d.ts +7 -8
  10. package/lib/Signal/Group/ciphertext-message.js +16 -9
  11. package/lib/Signal/Group/group-session-builder.d.ts +13 -11
  12. package/lib/Signal/Group/group-session-builder.js +61 -19
  13. package/lib/Signal/Group/group_cipher.d.ts +16 -14
  14. package/lib/Signal/Group/group_cipher.js +70 -41
  15. package/lib/Signal/Group/index.d.ts +11 -12
  16. package/lib/Signal/Group/index.js +61 -12
  17. package/lib/Signal/Group/keyhelper.d.ts +14 -9
  18. package/lib/Signal/Group/keyhelper.js +58 -10
  19. package/lib/Signal/Group/sender-chain-key.d.ts +13 -13
  20. package/lib/Signal/Group/sender-chain-key.js +34 -13
  21. package/lib/Signal/Group/sender-key-distribution-message.d.ts +16 -16
  22. package/lib/Signal/Group/sender-key-distribution-message.js +36 -28
  23. package/lib/Signal/Group/sender-key-message.d.ts +18 -18
  24. package/lib/Signal/Group/sender-key-message.js +45 -38
  25. package/lib/Signal/Group/sender-key-name.d.ts +15 -14
  26. package/lib/Signal/Group/sender-key-name.js +31 -20
  27. package/lib/Signal/Group/sender-key-record.d.ts +25 -24
  28. package/lib/Signal/Group/sender-key-record.js +35 -18
  29. package/lib/Signal/Group/sender-key-state.d.ts +34 -29
  30. package/lib/Signal/Group/sender-key-state.js +100 -37
  31. package/lib/Signal/Group/sender-message-key.d.ts +10 -11
  32. package/lib/Signal/Group/sender-message-key.js +22 -15
  33. package/lib/Signal/libsignal.d.ts +8 -5
  34. package/lib/Signal/libsignal.js +142 -288
  35. package/lib/Signal/lid-mapping.d.ts +28 -23
  36. package/lib/Signal/lid-mapping.js +184 -277
  37. package/lib/Socket/Client/index.d.ts +2 -3
  38. package/lib/Socket/Client/index.js +22 -3
  39. package/lib/Socket/Client/types.d.ts +15 -15
  40. package/lib/Socket/Client/types.js +15 -8
  41. package/lib/Socket/Client/websocket.d.ts +12 -12
  42. package/lib/Socket/Client/websocket.js +37 -29
  43. package/lib/Socket/business.d.ts +178 -208
  44. package/lib/Socket/business.js +127 -91
  45. package/lib/Socket/chats.d.ts +93 -120
  46. package/lib/Socket/chats.js +652 -727
  47. package/lib/Socket/community.js +10 -2
  48. package/lib/Socket/groups.d.ts +111 -143
  49. package/lib/Socket/groups.js +186 -165
  50. package/lib/Socket/index.d.ts +183 -252
  51. package/lib/Socket/index.js +18 -38
  52. package/lib/Socket/messages-recv.d.ts +167 -196
  53. package/lib/Socket/messages-recv.js +1567 -1443
  54. package/lib/Socket/messages-send.d.ts +158 -192
  55. package/lib/Socket/messages-send.js +1208 -1102
  56. package/lib/Socket/newsletter.d.ts +138 -163
  57. package/lib/Socket/newsletter.js +249 -153
  58. package/lib/Socket/socket.d.ts +36 -50
  59. package/lib/Socket/socket.js +820 -749
  60. package/lib/Store/index.d.ts +4 -4
  61. package/lib/Store/index.js +24 -4
  62. package/lib/Store/make-in-memory-store.d.ts +104 -44
  63. package/lib/Store/make-in-memory-store.js +213 -199
  64. package/lib/Store/make-ordered-dictionary.d.ts +12 -13
  65. package/lib/Store/make-ordered-dictionary.js +45 -38
  66. package/lib/Store/object-repository.d.ts +10 -11
  67. package/lib/Store/object-repository.js +16 -9
  68. package/lib/Types/Auth.d.ts +97 -93
  69. package/lib/Types/Auth.js +3 -2
  70. package/lib/Types/Bussines.js +3 -2
  71. package/lib/Types/Call.d.ts +13 -14
  72. package/lib/Types/Call.js +3 -2
  73. package/lib/Types/Chat.d.ts +97 -78
  74. package/lib/Types/Chat.js +9 -8
  75. package/lib/Types/Contact.d.ts +9 -12
  76. package/lib/Types/Contact.js +3 -2
  77. package/lib/Types/Events.d.ts +176 -206
  78. package/lib/Types/Events.js +3 -2
  79. package/lib/Types/GroupMetadata.d.ts +48 -53
  80. package/lib/Types/GroupMetadata.js +3 -2
  81. package/lib/Types/Label.d.ts +14 -13
  82. package/lib/Types/Label.js +30 -24
  83. package/lib/Types/LabelAssociation.d.ts +20 -15
  84. package/lib/Types/LabelAssociation.js +12 -6
  85. package/lib/Types/Message.d.ts +412 -248
  86. package/lib/Types/Message.js +19 -17
  87. package/lib/Types/Product.d.ts +71 -58
  88. package/lib/Types/Product.js +3 -2
  89. package/lib/Types/Signal.d.ts +82 -71
  90. package/lib/Types/Signal.js +3 -2
  91. package/lib/Types/Socket.d.ts +81 -76
  92. package/lib/Types/Socket.js +3 -3
  93. package/lib/Types/State.d.ts +19 -75
  94. package/lib/Types/State.js +14 -56
  95. package/lib/Types/USync.d.ts +8 -8
  96. package/lib/Types/USync.js +3 -2
  97. package/lib/Types/index.d.ts +62 -47
  98. package/lib/Types/index.js +50 -26
  99. package/lib/Utils/auth-utils.d.ts +10 -13
  100. package/lib/Utils/auth-utils.js +472 -213
  101. package/lib/Utils/business.d.ts +20 -14
  102. package/lib/Utils/business.js +134 -110
  103. package/lib/Utils/chat-utils.d.ts +70 -88
  104. package/lib/Utils/chat-utils.js +403 -466
  105. package/lib/Utils/crypto.d.ts +46 -27
  106. package/lib/Utils/crypto.js +188 -117
  107. package/lib/Utils/decode-wa-message.d.ts +45 -58
  108. package/lib/Utils/decode-wa-message.js +311 -212
  109. package/lib/Utils/event-buffer.d.ts +17 -14
  110. package/lib/Utils/event-buffer.js +296 -321
  111. package/lib/Utils/generics.d.ts +92 -66
  112. package/lib/Utils/generics.js +436 -270
  113. package/lib/Utils/history.d.ts +23 -24
  114. package/lib/Utils/history.js +76 -106
  115. package/lib/Utils/index.d.ts +21 -24
  116. package/lib/Utils/index.js +41 -24
  117. package/lib/Utils/link-preview.d.ts +14 -12
  118. package/lib/Utils/link-preview.js +75 -40
  119. package/lib/Utils/logger.d.ts +11 -10
  120. package/lib/Utils/logger.js +7 -3
  121. package/lib/Utils/lt-hash.d.ts +14 -8
  122. package/lib/Utils/lt-hash.js +53 -3
  123. package/lib/Utils/make-mutex.d.ts +7 -7
  124. package/lib/Utils/make-mutex.js +44 -28
  125. package/lib/Utils/message-retry-manager.d.ts +88 -115
  126. package/lib/Utils/message-retry-manager.js +160 -265
  127. package/lib/Utils/messages-media.d.ts +107 -105
  128. package/lib/Utils/messages-media.js +619 -548
  129. package/lib/Utils/messages.d.ts +66 -52
  130. package/lib/Utils/messages.js +1558 -1730
  131. package/lib/Utils/noise-handler.d.ts +18 -18
  132. package/lib/Utils/noise-handler.js +130 -176
  133. package/lib/Utils/process-message.d.ts +32 -43
  134. package/lib/Utils/process-message.js +282 -484
  135. package/lib/Utils/rich-message-utils.js +724 -1220
  136. package/lib/Utils/signal.d.ts +32 -37
  137. package/lib/Utils/signal.js +105 -140
  138. package/lib/Utils/use-multi-file-auth-state.d.ts +5 -5
  139. package/lib/Utils/use-multi-file-auth-state.js +120 -70
  140. package/lib/Utils/use-single-file-auth-state.d.ts +13 -11
  141. package/lib/Utils/use-single-file-auth-state.js +66 -92
  142. package/lib/Utils/validate-connection.d.ts +13 -11
  143. package/lib/Utils/validate-connection.js +113 -128
  144. package/lib/WABinary/constants.d.ts +27 -25
  145. package/lib/WABinary/constants.js +1292 -1451
  146. package/lib/WABinary/decode.d.ts +9 -7
  147. package/lib/WABinary/decode.js +189 -163
  148. package/lib/WABinary/encode.d.ts +3 -3
  149. package/lib/WABinary/encode.js +155 -110
  150. package/lib/WABinary/generic-utils.d.ts +27 -14
  151. package/lib/WABinary/generic-utils.js +105 -177
  152. package/lib/WABinary/index.d.ts +5 -6
  153. package/lib/WABinary/index.js +25 -6
  154. package/lib/WABinary/jid-utils.d.ts +54 -41
  155. package/lib/WABinary/jid-utils.js +152 -83
  156. package/lib/WABinary/types.d.ts +13 -10
  157. package/lib/WABinary/types.js +3 -2
  158. package/lib/WAM/BinaryInfo.d.ts +15 -8
  159. package/lib/WAM/BinaryInfo.js +14 -7
  160. package/lib/WAM/constants.d.ts +37 -30
  161. package/lib/WAM/constants.js +11983 -19465
  162. package/lib/WAM/encode.d.ts +3 -3
  163. package/lib/WAM/encode.js +109 -95
  164. package/lib/WAM/index.d.ts +3 -4
  165. package/lib/WAM/index.js +23 -4
  166. package/lib/WAUSync/Protocols/USyncContactProtocol.d.ts +9 -9
  167. package/lib/WAUSync/Protocols/USyncContactProtocol.js +21 -37
  168. package/lib/WAUSync/Protocols/USyncDeviceProtocol.d.ts +22 -19
  169. package/lib/WAUSync/Protocols/USyncDeviceProtocol.js +26 -18
  170. package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.d.ts +12 -11
  171. package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.js +20 -12
  172. package/lib/WAUSync/Protocols/USyncStatusProtocol.d.ts +12 -11
  173. package/lib/WAUSync/Protocols/USyncStatusProtocol.js +24 -16
  174. package/lib/WAUSync/Protocols/index.d.ts +6 -6
  175. package/lib/WAUSync/Protocols/index.js +26 -6
  176. package/lib/WAUSync/USyncQuery.d.ts +28 -27
  177. package/lib/WAUSync/USyncQuery.js +62 -68
  178. package/lib/WAUSync/USyncUser.d.ts +10 -15
  179. package/lib/WAUSync/USyncUser.js +19 -20
  180. package/lib/WAUSync/index.d.ts +3 -4
  181. package/lib/WAUSync/index.js +23 -4
  182. package/lib/index.d.ts +12 -11
  183. package/lib/index.js +39 -12
  184. package/package.json +43 -87
  185. package/engine-requirements.js +0 -10
@@ -1,1341 +1,845 @@
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';
1
+ "use strict"
7
2
 
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
- ───────────────────────────────────────────────────────────── */
3
+ /**
4
+ * rich-message-utils.js
5
+ * @pontalabs-baileys
6
+ * Tambahkan file ini ke: node_modules/@whiskeysockets/baileys/lib/Utils/rich-message-utils.js
7
+ */
41
8
 
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;
9
+ Object.defineProperty(exports, "__esModule", { value: true })
10
+ exports.prepareRichResponseMessage = exports.tokenizeCode = void 0
11
+
12
+ const crypto_1 = require("crypto")
13
+ const WAProto_1 = require("../../WAProto")
14
+
15
+ // ─── Enums (sesuai AIRichResponseCodeHighlightType di WAProto) ────────────────
16
+
17
+ const CodeHighlightType = {
18
+ DEFAULT: 0,
19
+ KEYWORD: 1,
20
+ METHOD: 2,
21
+ STRING: 3,
22
+ NUMBER: 4,
23
+ COMMENT: 5,
24
+ }
25
+
26
+ const RichSubMessageType = {
27
+ UNKNOWN: 0, IMAGES: 1, TEXT: 2, TABLE: 4, CODE: 5, VIDEO: 7, SUGGESTIONS: 8,
28
+ REELS: 9, PRODUCT: 10, POST: 11, SOURCES: 12, TIP: 13, FOOTER: 14,
29
+ LATEX: 99
30
+ }
31
+
32
+ // ─── Keyword Sets ─────────────────────────────────────────────────────────────
33
+
34
+ const JS_KEYWORDS = new Set([
35
+ 'import','export','from','default','as','const','let','var','function','class','extends','new',
36
+ 'return','if','else','for','while','do','switch','case','break','continue',
37
+ 'try','catch','finally','throw','async','await','yield',
38
+ 'typeof','instanceof','in','of','delete','void',
39
+ 'true','false','null','undefined','NaN','Infinity',
40
+ 'this','super','static','get','set','debugger','with'
41
+ ])
42
+ const PYTHON_KEYWORDS = new Set([
43
+ 'import','from','as','def','class','return','if','elif','else',
44
+ 'for','while','break','continue','try','except','finally','raise',
45
+ 'with','yield','lambda','pass','del','global','nonlocal','assert',
46
+ 'True','False','None','and','or','not','in','is','async','await','self','print'
47
+ ])
48
+ const BASH_KEYWORDS = new Set([
49
+ 'if','then','else','elif','fi','case','esac','for','while',
50
+ 'until','do','done','in','function','select','time','coproc',
51
+ 'echo','printf','read','cd','pwd','exit','export','unset',
52
+ 'alias','unalias','source','exec','eval','test','shift',
53
+ 'trap','wait','jobs','kill','bg','fg','history','type',
54
+ 'ulimit','umask','set','true','false'
55
+ ])
56
+ const GO_KEYWORDS = new Set([
57
+ 'break','default','func','interface','select','case','defer','go','map','struct',
58
+ 'chan','else','goto','package','switch','const','fallthrough','if','range','type',
59
+ 'continue','for','import','return','var','true','false','nil'
60
+ ])
61
+ const CPP_KEYWORDS = new Set([
62
+ 'alignas','alignof','and','asm','auto','bool','break','case','catch','char','class',
63
+ 'const','constexpr','continue','decltype','default','delete','do','double','else',
64
+ 'enum','explicit','export','extern','false','float','for','friend','goto','if',
65
+ 'inline','int','long','mutable','namespace','new','noexcept','nullptr','operator',
66
+ 'private','protected','public','return','short','signed','sizeof','static','struct',
67
+ 'switch','template','this','throw','true','try','typedef','typename','union',
68
+ 'unsigned','using','virtual','void','volatile','while'
69
+ ])
70
+ const RUST_KEYWORDS = new Set([
71
+ 'as','break','const','continue','crate','else','enum','extern','false','fn',
72
+ 'for','if','impl','in','let','loop','match','mod','move','mut','pub','ref',
73
+ 'return','self','Self','static','struct','super','trait','true','type','unsafe',
74
+ 'use','where','while','async','await','dyn','try'
75
+ ])
76
+ const C_KEYWORDS = new Set([
77
+ 'auto','break','case','char','const','continue','default','do','double','else',
78
+ 'enum','extern','float','for','goto','if','inline','int','long','register',
79
+ 'restrict','return','short','signed','sizeof','static','struct','switch',
80
+ 'typedef','union','unsigned','void','volatile','while'
81
+ ])
82
+ const CSHARP_KEYWORDS = new Set([
83
+ 'abstract','as','base','bool','break','byte','case','catch','char','checked',
84
+ 'class','const','continue','decimal','default','delegate','do','double','else',
85
+ 'enum','event','explicit','extern','false','finally','fixed','float','for',
86
+ 'foreach','goto','if','implicit','in','int','interface','internal','is','lock',
87
+ 'long','namespace','new','null','object','operator','out','override','params',
88
+ 'private','protected','public','readonly','ref','return','sbyte','sealed','short',
89
+ 'sizeof','static','string','struct','switch','this','throw','true','try','typeof',
90
+ 'uint','ulong','unchecked','unsafe','ushort','using','virtual','void','volatile',
91
+ 'while','async','await','var','yield'
92
+ ])
93
+ const CSS_KEYWORDS = new Set([
94
+ 'import','media','font-face','keyframes','supports','charset',
95
+ 'important','root','hover','active','focus','visited','before','after',
96
+ 'not','nth-child','first-child','last-child','none','inherit','initial','auto'
97
+ ])
98
+ const HTML_KEYWORDS = new Set([
99
+ 'html','head','body','title','meta','link','script','style',
100
+ 'header','footer','main','section','article','div','span',
101
+ 'h1','h2','h3','h4','h5','h6','p','a','img','ul','ol','li',
102
+ 'table','tr','td','th','thead','tbody','form','input','button','select',
103
+ 'textarea','label','option','canvas','svg','iframe','video','audio'
104
+ ])
105
+ const CMD_KEYWORDS = new Set([
106
+ 'echo','set','if','else','for','in','do','goto','call','exit','shift',
107
+ 'pause','start','cls','rem','dir','copy','move','del','mkdir','rmdir',
108
+ 'type','ren','ping','ipconfig','netstat','shutdown'
109
+ ])
110
+ const POWERSHELL_KEYWORDS = new Set([
111
+ 'function','filter','param','begin','process','end','if','else','elseif',
112
+ 'switch','foreach','for','while','do','break','continue','return',
113
+ 'throw','try','catch','finally','$true','$false','$null',
114
+ 'Write-Host','Write-Output','Get-ChildItem','Test-Path','Invoke-Command'
115
+ ])
116
+
117
+ const LANGUAGE_KEYWORDS = {
118
+ css: CSS_KEYWORDS, html: HTML_KEYWORDS,
119
+ javascript: JS_KEYWORDS, typescript: JS_KEYWORDS, js: JS_KEYWORDS, ts: JS_KEYWORDS,
120
+ python: PYTHON_KEYWORDS, py: PYTHON_KEYWORDS,
121
+ go: GO_KEYWORDS, golang: GO_KEYWORDS,
122
+ cpp: CPP_KEYWORDS, 'c++': CPP_KEYWORDS,
123
+ rust: RUST_KEYWORDS, rs: RUST_KEYWORDS,
124
+ c: C_KEYWORDS, h: C_KEYWORDS,
125
+ csharp: CSHARP_KEYWORDS, cs: CSHARP_KEYWORDS,
126
+ bash: BASH_KEYWORDS, sh: BASH_KEYWORDS, zsh: BASH_KEYWORDS,
127
+ cmd: CMD_KEYWORDS, bat: CMD_KEYWORDS,
128
+ powershell: POWERSHELL_KEYWORDS, ps1: POWERSHELL_KEYWORDS
129
+ }
130
+
131
+ const LEXER_REGEX = /(\/\/.*|\/\*[\s\S]*?\*\/|#.*)|("(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|`[\s\S]*?`)|(\b[a-zA-Z_]\w*\b)(?=\s*\()|(\b[a-zA-Z_]\w*\b)|(\b\d+(?:\.\d+)?\b)|(\s+|[^\w\s]+)/g
132
+
133
+ const NOOP = new Set([])
134
+
135
+ // ─── tokenizeCode ─────────────────────────────────────────────────────────────
136
+
137
+ const tokenizeCode = (code, language = 'javascript') => {
138
+ const keywords = LANGUAGE_KEYWORDS[language] || NOOP
139
+ const blocks = []
140
+ LEXER_REGEX.lastIndex = 0
141
+ let match
47
142
  while ((match = LEXER_REGEX.exec(code)) !== null) {
48
143
  if (match[1]) {
49
- blocks.push({ highlightType: CodeHighlightType.COMMENT, codeContent: match[1] });
144
+ blocks.push({ highlightType: CodeHighlightType.COMMENT, codeContent: match[1] })
50
145
  } else if (match[2]) {
51
- blocks.push({ highlightType: CodeHighlightType.STRING, codeContent: match[2] });
146
+ blocks.push({ highlightType: CodeHighlightType.STRING, codeContent: match[2] })
52
147
  } else if (match[3]) {
53
148
  blocks.push({
54
149
  highlightType: keywords.has(match[3]) ? CodeHighlightType.KEYWORD : CodeHighlightType.METHOD,
55
150
  codeContent: match[3],
56
- });
151
+ })
57
152
  } else if (match[4]) {
58
153
  blocks.push({
59
154
  highlightType: keywords.has(match[4]) ? CodeHighlightType.KEYWORD : CodeHighlightType.DEFAULT,
60
155
  codeContent: match[4],
61
- });
156
+ })
62
157
  } else if (match[5]) {
63
- blocks.push({ highlightType: CodeHighlightType.NUMBER, codeContent: match[5] });
158
+ blocks.push({ highlightType: CodeHighlightType.NUMBER, codeContent: match[5] })
64
159
  } else {
65
- blocks.push({ highlightType: CodeHighlightType.DEFAULT, codeContent: match[6] });
160
+ blocks.push({ highlightType: CodeHighlightType.DEFAULT, codeContent: match[6] })
66
161
  }
67
162
  }
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;
163
+ return blocks
164
+ }
165
+ exports.tokenizeCode = tokenizeCode
82
166
 
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
- };
167
+ // ─── toUnified ────────────────────────────────────────────────────────────────
219
168
 
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 {
169
+ const toUnified = (submessages, uuid) => ({
170
+ response_id: uuid,
171
+ sections: submessages.flatMap((submessage) => {
172
+ if (submessage.messageType === RichSubMessageType.IMAGES) {
173
+ return (submessage._imageUrls || []).map(url => ({
228
174
  view_model: {
229
175
  primitive: {
230
- text: submessage.messageText,
231
- inline_entities: submessage.inlineEntities || [],
232
- __typename: 'GenAIMarkdownTextUXPrimitive'
176
+ media: { url, mime_type: submessage._mimeType || 'image/jpeg' },
177
+ imagine_type: 3,
178
+ status: { status: 'READY' },
179
+ __typename: 'GenAIImaginePrimitive'
233
180
  },
234
181
  __typename: 'GenAISingleLayoutViewModel'
235
182
  }
236
- };
237
-
238
- case RichSubMessageType.CODE: {
239
- const cm = submessage.codeMetadata;
183
+ }))
184
+ }
185
+ if (submessage.messageType === RichSubMessageType.TABLE) {
186
+ const tm = submessage.tableMetadata
240
187
  return {
241
188
  view_model: {
242
189
  primitive: {
243
- language: cm.codeLanguage,
244
- code_blocks: cm.codeBlocks.map(b => ({
245
- content: b.codeContent,
246
- type: CodeHighlightType[b.highlightType]
190
+ title: tm.title || null,
191
+ rows: tm.rows.map(row => ({
192
+ is_header: row.isHeading || false,
193
+ cells: row.items,
194
+ markdown_cells: row.items.map(item => ({ text: item }))
247
195
  })),
248
- __typename: 'GenAICodeUXPrimitive'
196
+ __typename: 'GenATableUXPrimitive'
249
197
  },
250
198
  __typename: 'GenAISingleLayoutViewModel'
251
199
  }
252
- };
200
+ }
253
201
  }
254
-
255
- case RichSubMessageType.TABLE: {
256
- const tm = submessage.tableMetadata;
202
+ if (submessage.messageType === RichSubMessageType.CODE) {
203
+ const cm = submessage.codeMetadata
257
204
  return {
258
205
  view_model: {
259
206
  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 }))
207
+ language: cm.codeLanguage,
208
+ code_blocks: cm.codeBlocks.map((block) => ({
209
+ content: block.codeContent,
210
+ type: Object.keys(CodeHighlightType).find(k => CodeHighlightType[k] === block.highlightType) || 'DEFAULT'
265
211
  })),
266
- __typename: 'GenATableUXPrimitive'
212
+ __typename: 'GenAICodeUXPrimitive'
267
213
  },
268
214
  __typename: 'GenAISingleLayoutViewModel'
269
215
  }
270
- };
216
+ }
271
217
  }
272
-
273
- case RichSubMessageType.GRID_IMAGE: {
274
- const gm = submessage.gridImageMetadata;
218
+ if (submessage.messageType === RichSubMessageType.TEXT) {
275
219
  return {
276
220
  view_model: {
277
221
  primitive: {
278
- grid_image_url: toImageUrlJson(gm.gridImageUrl),
279
- image_urls: (gm.imageUrls || []).map(toImageUrlJson),
280
- __typename: 'GenAIGridImageUXPrimitive'
222
+ text: submessage.messageText,
223
+ inline_entities: submessage.inlineEntities || [],
224
+ __typename: 'GenAIMarkdownTextUXPrimitive'
281
225
  },
282
226
  __typename: 'GenAISingleLayoutViewModel'
283
227
  }
284
- };
228
+ }
285
229
  }
286
-
287
- case RichSubMessageType.INLINE_IMAGE: {
288
- const im = submessage.imageMetadata;
230
+ if (submessage.messageType === RichSubMessageType.VIDEO) {
231
+ const vm = submessage.videoMetadata
289
232
  return {
290
233
  view_model: {
291
234
  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'
235
+ media: { url: vm.url, mime_type: vm.mimeType || 'video/mp4', duration: vm.duration || 10 },
236
+ imagine_type: 'ANIMATE',
237
+ status: { status: 'READY' },
238
+ __typename: 'GenAIImaginePrimitive'
297
239
  },
298
240
  __typename: 'GenAISingleLayoutViewModel'
299
241
  }
300
- };
242
+ }
301
243
  }
302
-
303
- case RichSubMessageType.DYNAMIC: {
304
- const dm = submessage.dynamicMetadata;
244
+ if (submessage.messageType === RichSubMessageType.SUGGESTIONS) {
305
245
  return {
306
246
  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
- },
247
+ primitives: submessage.suggestions.map(s => ({
248
+ prompt_text: s,
249
+ prompt_type: 'SUGGESTED_PROMPT',
250
+ __typename: 'GenAIFollowUpSuggestionPillPrimitive'
251
+ })),
252
+ __typename: 'GenAIActionRowLayoutViewModel'
253
+ }
254
+ }
255
+ }
256
+ if (submessage.messageType === RichSubMessageType.REELS) {
257
+ return {
258
+ view_model: {
259
+ primitives: (submessage._reelItems || []).map(i => ({
260
+ reels_url: i.videoUrl,
261
+ thumbnail_url: i.thumbnailUrl,
262
+ creator: i.title,
263
+ avatar_url: i.profileIconUrl,
264
+ reels_title: i.reels_title || '',
265
+ likes_count: i.likes_count || 0,
266
+ shares_count: i.shares_count || 0,
267
+ view_count: i.view_count || 0,
268
+ reel_source: i.reel_source || 'IG',
269
+ is_verified: i.is_verified || false,
270
+ __typename: 'GenAIReelPrimitive'
271
+ })),
272
+ __typename: 'GenAIHScrollLayoutViewModel'
273
+ }
274
+ }
275
+ }
276
+ if (submessage.messageType === RichSubMessageType.PRODUCT) {
277
+ const { isMultiple, products } = submessage.productMetadata
278
+ if (isMultiple) {
279
+ return {
280
+ view_model: {
281
+ primitives: products.map(p => ({ ...p, __typename: 'GenAIProductItemCardPrimitive' })),
282
+ __typename: 'GenAIHScrollLayoutViewModel'
283
+ }
284
+ }
285
+ }
286
+ return {
287
+ view_model: {
288
+ primitive: { ...products[0], __typename: 'GenAIProductItemCardPrimitive' },
314
289
  __typename: 'GenAISingleLayoutViewModel'
315
290
  }
316
- };
291
+ }
317
292
  }
318
-
319
- case RichSubMessageType.MAP: {
320
- const mm = submessage.mapMetadata;
293
+ if (submessage.messageType === RichSubMessageType.POST) {
294
+ const { isMultiple, posts } = submessage.postMetadata
295
+ if (isMultiple) {
296
+ return {
297
+ view_model: {
298
+ primitives: posts.map(p => ({ ...p, __typename: 'GenAIPostPrimitive' })),
299
+ __typename: 'GenAIHScrollLayoutViewModel'
300
+ }
301
+ }
302
+ }
321
303
  return {
322
304
  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
- },
305
+ primitive: { ...posts[0], __typename: 'GenAIPostPrimitive' },
338
306
  __typename: 'GenAISingleLayoutViewModel'
339
307
  }
340
- };
308
+ }
341
309
  }
342
-
343
- case RichSubMessageType.LATEX: {
344
- const lm = submessage.latexMetadata;
310
+ if (submessage.messageType === RichSubMessageType.SOURCES) {
345
311
  return {
346
312
  view_model: {
347
313
  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'
314
+ sources: submessage.sourcesMetadata.sources.map(s => Array.isArray(s) ? {
315
+ source_type: 'THIRD_PARTY',
316
+ source_display_name: s[2] || '',
317
+ source_subtitle: 'AI',
318
+ source_url: s[1] || '',
319
+ favicon: { url: s[0] || '', mime_type: 'image/jpeg', width: 16, height: 16 }
320
+ } : s),
321
+ __typename: 'GenAISearchResultPrimitive'
361
322
  },
362
323
  __typename: 'GenAISingleLayoutViewModel'
363
324
  }
364
- };
325
+ }
365
326
  }
366
-
367
- case RichSubMessageType.CONTENT_ITEMS: {
368
- const ci = submessage.contentItemsMetadata;
327
+ if (submessage.messageType === RichSubMessageType.TIP) {
328
+ return {
329
+ view_model: {
330
+ primitive: { text: submessage.tipText, __typename: 'GenAIMetadataTextPrimitive' },
331
+ __typename: 'GenAISingleLayoutViewModel'
332
+ }
333
+ }
334
+ }
335
+ if (submessage.messageType === RichSubMessageType.FOOTER) {
336
+ return {
337
+ view_model: {
338
+ primitive: { text: submessage.footerText, __typename: 'GenAIMetadataTextPrimitive' },
339
+ __typename: 'GenAISingleLayoutViewModel'
340
+ }
341
+ }
342
+ }
343
+ if (submessage.messageType === RichSubMessageType.LATEX) {
344
+ const lm = submessage.latexMetadata
345
+ const key = 'PONTA_LATEX_0'
369
346
  return {
370
347
  view_model: {
371
348
  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
- };
349
+ text: `{{${key}}}${lm.text || 'image'}{{/${key}}}`,
350
+ inline_entities: [{
351
+ key,
352
+ metadata: {
353
+ latex_expression: lm.text || '',
354
+ latex_image: { url: lm.url, width: lm.width || 100, height: lm.height || 100 },
355
+ font_height: lm.font_height || 83.33,
356
+ padding: lm.padding || 15,
357
+ __typename: 'GenAILatexItem'
383
358
  }
384
- return {};
385
- }),
386
- __typename: 'GenAIContentItemsUXPrimitive'
359
+ }],
360
+ __typename: 'GenAIMarkdownTextUXPrimitive'
387
361
  },
388
362
  __typename: 'GenAISingleLayoutViewModel'
389
363
  }
390
- };
364
+ }
391
365
  }
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;
366
+ return {}
367
+ })
368
+ })
369
+
370
+ // ─── botMetadataSignature / botMetadataCertificate ───────────────────────────
371
+
372
+ const botMetadataSignature = () => {
373
+ const sig = new Uint8Array(64)
374
+ crypto_1.getRandomValues(sig)
375
+ return sig
376
+ }
377
+
378
+ const botMetadataCertificate = (length = 685) => {
379
+ const cert = new Uint8Array(length)
380
+ cert[0] = 48
381
+ cert[1] = 130
382
+ crypto_1.getRandomValues(cert.subarray(2))
383
+ return cert
384
+ }
385
+
386
+ // ─── prepareRichResponseMessage ───────────────────────────────────────────────
387
+
388
+ const prepareRichResponseMessage = (content) => {
389
+
390
+ // Normalize ke array of items
391
+ // Support:
392
+ // { code, language } — single code block
393
+ // { codes: [{ code, language }, ...] } — multi code block
394
+ // { table: { title?, headers?, rows } } — single table
395
+ // { richText: 'string' } — single text
396
+ // { richImages: 'url' | ['url1', 'url2'] } — image grid shorthand
397
+ // { richSuggestions: ['a', 'b'] } — suggestion pills shorthand
398
+ // { richLatex: 'url' | { url, text?, ... } } — latex shorthand
399
+ // { richProduct: {} | [{}] } — product card(s) shorthand
400
+ // { richPost: {} | [{}] } — post card(s) shorthand
401
+ // { richReels: {} | [{}] } — reels carousel shorthand
402
+ // { richSources: [{...}|[fav,url,name]] } — search sources shorthand
403
+ // { richTip: 'text' } — tip/metadata text shorthand
404
+ // { richFooter: 'text' } — footer text shorthand
405
+ // { items: [{ text }|{ code }|{ table }|{ videoUrl }|{ images }|{ suggestions }
406
+ // |{ latexUrl }|{ product }|{ post }|{ reels }
407
+ // |{ sources }|{ tip }|{ footer }] }
408
+ // { richResponse: [{ text }|{ code }|{ table }|{ video }|{ images }|{ suggestions }
409
+ // |{ latex }|{ product }|{ post }|{ reels }
410
+ // |{ sources }|{ tip }|{ footer }] }
411
+
412
+ let submessages = []
413
+
414
+ if (Array.isArray(content.richResponse)) {
415
+ // ── richResponse array mode ───────────────────────────────────────────
416
+ submessages = content.richResponse.flatMap(sub => {
417
+ if (sub.text !== undefined) {
418
+ return {
419
+ messageType: RichSubMessageType.TEXT,
420
+ messageText: sub.text,
421
+ ...(sub.inlineEntities ? { inlineEntities: sub.inlineEntities } : {})
422
+ }
423
+ }
424
+ if (sub.code !== undefined) {
425
+ const lang = sub.language || 'javascript'
523
426
  return {
524
- reelItem: {
525
- title: r.title || '',
526
- profileIconUrl: r.profileIconUrl || null,
527
- thumbnailUrl: r.thumbnailUrl || null,
528
- videoUrl: r.videoUrl || null
427
+ messageType: RichSubMessageType.CODE,
428
+ codeMetadata: {
429
+ codeLanguage: lang,
430
+ codeBlocks: tokenizeCode(sub.code, lang)
529
431
  }
530
- };
432
+ }
531
433
  }
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
434
+ if (sub.table !== undefined) {
435
+ return {
436
+ messageType: RichSubMessageType.TABLE,
437
+ tableMetadata: {
438
+ ...(sub.title ? { title: sub.title } : {}),
439
+ rows: sub.table
553
440
  }
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)
441
+ }
576
442
  }
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'
443
+ if (sub.images !== undefined) {
444
+ const raw = sub.images
445
+ const urls = typeof raw === 'string' ? [raw] : (Array.isArray(raw) ? raw : [raw])
446
+ const mimeType = 'image/jpeg'
447
+ return {
448
+ messageType: RichSubMessageType.IMAGES,
449
+ gridImageMetadata: {
450
+ gridImageUrl: { imagePreviewUrl: urls[0] },
451
+ imageUrls: urls.map(url => ({ imagePreviewUrl: url, imageHighResUrl: url, sourceUrl: url }))
452
+ },
453
+ _imageUrls: urls,
454
+ _mimeType: mimeType
455
+ }
587
456
  }
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.DYNAMIC,
602
- dynamicMetadata: {
603
- type: 1,
604
- version: 1,
605
- url: parsed[0]?.url || '',
606
- loopCount: 0
457
+ if (sub.video !== undefined) {
458
+ const url = typeof sub.video === 'string' ? sub.video : sub.video.url
459
+ const mimeType = (typeof sub.video === 'object' && sub.video.mimeType) || 'video/mp4'
460
+ const duration = (typeof sub.video === 'object' && sub.video.duration) || 10
461
+ const fallbackText = (typeof sub.video === 'object' && sub.video.fallbackText) || '[ VIDEO - PontaCT ]'
462
+ return [
463
+ { messageType: RichSubMessageType.TEXT, messageText: fallbackText },
464
+ { messageType: RichSubMessageType.VIDEO, videoMetadata: { url, mimeType, duration } }
465
+ ]
607
466
  }
608
- },
609
- sections: parsed.map(({ url, duration }) => ({
610
- view_model: {
611
- primitive: {
612
- media: { url, mime_type: 'video/mp4', duration },
613
- imagine_type: 'ANIMATE',
614
- status: { status: 'READY' },
615
- __typename: 'GenAIImaginePrimitive'
616
- },
617
- __typename: 'GenAISingleLayoutViewModel'
467
+ if (sub.suggestions !== undefined) {
468
+ const arr = Array.isArray(sub.suggestions) ? sub.suggestions : [sub.suggestions]
469
+ return { messageType: RichSubMessageType.SUGGESTIONS, suggestions: arr }
618
470
  }
619
- }))
620
- };
621
- };
622
-
623
- /** Buat submessage + unified section untuk REELS (HScroll) */
624
- const makeReelsSub = (reelsItems = []) => {
625
- const list = Array.isArray(reelsItems) ? reelsItems : [reelsItems];
626
- return {
627
- sub: {
628
- messageType: RichSubMessageType.CONTENT_ITEMS,
629
- contentItemsMetadata: {
630
- contentType: 1,
631
- itemsMetadata: list.map(item => ({
632
- reelItem: {
633
- title: item.username ?? item.title ?? '',
634
- profileIconUrl: item.profileIconUrl ?? item.profile_url ?? null,
635
- thumbnailUrl: item.thumbnailUrl ?? item.thumbnail ?? null,
636
- videoUrl: item.videoUrl ?? item.url ?? null
471
+ if (sub.latex !== undefined) {
472
+ const raw = sub.latex
473
+ const url = typeof raw === 'string' ? raw : raw.url
474
+ const text = (typeof raw === 'object' && raw.text) || ''
475
+ return [
476
+ { messageType: RichSubMessageType.TEXT, messageText: text || '[latex]' },
477
+ {
478
+ messageType: RichSubMessageType.LATEX,
479
+ latexMetadata: {
480
+ url,
481
+ text,
482
+ width: Number((typeof raw === 'object' && raw.width) || 100),
483
+ height: Number((typeof raw === 'object' && raw.height) || 100),
484
+ font_height: Number((typeof raw === 'object' && raw.font_height) || 83.33),
485
+ padding: Number((typeof raw === 'object' && raw.padding) || 15)
486
+ }
637
487
  }
638
- }))
488
+ ]
639
489
  }
640
- },
641
- section: {
642
- view_model: {
643
- primitives: list.map(item => ({
644
- reels_url: item.videoUrl ?? item.url ?? '',
645
- thumbnail_url: item.thumbnailUrl ?? item.thumbnail ?? '',
646
- creator: item.username ?? item.title ?? '',
647
- avatar_url: item.profileIconUrl ?? item.profile_url ?? '',
648
- reels_title: item.reels_title ?? item.title ?? '',
649
- likes_count: item.likes_count ?? item.like ?? 0,
650
- shares_count: item.shares_count ?? item.share ?? 0,
651
- view_count: item.view_count ?? item.view ?? 0,
652
- reel_source: item.reel_source ?? item.source ?? 'IG',
653
- is_verified: !!(item.is_verified || item.verified),
654
- __typename: 'GenAIReelPrimitive'
655
- })),
656
- __typename: 'GenAIHScrollLayoutViewModel'
490
+ if (sub.product !== undefined) {
491
+ const isMultiple = Array.isArray(sub.product)
492
+ const products = isMultiple ? sub.product : [sub.product]
493
+ return [
494
+ { messageType: RichSubMessageType.TEXT, messageText: '[ PRODUCT - PontaCT ]' },
495
+ { messageType: RichSubMessageType.PRODUCT, productMetadata: { isMultiple, products } }
496
+ ]
657
497
  }
658
- }
659
- };
660
- };
661
-
662
- /** Buat unified section untuk PRODUCT (single: Single layout, array: HScroll) */
663
- const makeProductSection = (data) => {
664
- const items = Array.isArray(data) ? data : [data];
665
- const primitives = items.map(item => ({
666
- title: item.title ?? '',
667
- brand: item.brand ?? '',
668
- price: item.price ?? '',
669
- sale_price: item.sale_price ?? item.salePrice ?? '',
670
- product_url: (() => {
671
- const u = item.product_url ?? item.url ?? '';
672
- if (!u || !/^https?:\/\//i.test(u)) {
673
- console.warn('[baileys] richProduct: product_url/url kosong atau tidak diawali http(s):// — tautan produk ini kemungkinan besar tidak akan bisa dibuka di WhatsApp. Nilai yang diterima:', u || '(kosong)');
498
+ if (sub.post !== undefined) {
499
+ const isMultiple = Array.isArray(sub.post)
500
+ const posts = isMultiple ? sub.post : [sub.post]
501
+ return [
502
+ { messageType: RichSubMessageType.TEXT, messageText: '[ POST - PontaCT ]' },
503
+ { messageType: RichSubMessageType.POST, postMetadata: { isMultiple, posts } }
504
+ ]
674
505
  }
675
- return u;
676
- })(),
677
- image: { url: item.image_url ?? item.image ?? '' },
678
- additional_images: [{ url: item.icon_url ?? item.icon ?? '' }],
679
- __typename: 'GenAIProductItemCardPrimitive'
680
- }));
681
- const isMultiple = Array.isArray(data);
682
- return {
683
- sub: {
684
- messageType: RichSubMessageType.TEXT,
685
- messageText: ''
686
- },
687
- section: isMultiple
688
- ? {
689
- view_model: {
690
- primitives,
691
- __typename: 'GenAIHScrollLayoutViewModel'
506
+ if (sub.reels !== undefined) {
507
+ const reelItems = Array.isArray(sub.reels) ? sub.reels : [sub.reels]
508
+ return {
509
+ messageType: RichSubMessageType.REELS,
510
+ contentItemsMetadata: {
511
+ contentType: 1,
512
+ itemsMetadata: reelItems.map(i => ({
513
+ reelItem: { title: i.title, profileIconUrl: i.profileIconUrl, thumbnailUrl: i.thumbnailUrl, videoUrl: i.videoUrl }
514
+ }))
515
+ },
516
+ _reelItems: reelItems
692
517
  }
693
518
  }
694
- : {
695
- view_model: {
696
- primitive: primitives[0],
697
- __typename: 'GenAISingleLayoutViewModel'
698
- }
519
+ if (sub.sources !== undefined) {
520
+ const arr = Array.isArray(sub.sources) ? sub.sources : [sub.sources]
521
+ return { messageType: RichSubMessageType.SOURCES, sourcesMetadata: { sources: arr } }
699
522
  }
700
- };
701
- };
702
-
703
- /** Buat unified section untuk POST (HScroll) */
704
- const makePostSection = (data) => {
705
- const posts = Array.isArray(data) ? data : [data];
706
- const primitives = posts.map(p => ({
707
- title: p.title ?? '',
708
- subtitle: p.subtitle ?? '',
709
- username: p.username ?? '',
710
- profile_picture_url: p.profile_picture_url ?? p.profile_url ?? '',
711
- is_verified: !!(p.is_verified || p.verified),
712
- thumbnail_url: p.thumbnail_url ?? p.thumbnail ?? '',
713
- post_caption: p.post_caption ?? p.caption ?? '',
714
- likes_count: p.likes_count ?? p.like ?? 0,
715
- comments_count: p.comments_count ?? p.comment ?? 0,
716
- shares_count: p.shares_count ?? p.share ?? 0,
717
- post_url: p.post_url ?? p.url ?? '',
718
- post_deeplink: p.post_deeplink ?? p.deeplink ?? '',
719
- source_app: p.source_app ?? p.source ?? 'INSTAGRAM',
720
- footer_label: p.footer_label ?? p.footer ?? '',
721
- footer_icon: p.footer_icon ?? p.icon ?? '',
722
- is_carousel: posts.length > 1,
723
- orientation: p.orientation ?? 'LANDSCAPE',
724
- post_type: p.post_type ?? 'VIDEO',
725
- __typename: 'GenAIPostPrimitive'
726
- }));
727
- return {
728
- sub: {
729
- messageType: RichSubMessageType.TEXT,
730
- messageText: ''
731
- },
732
- section: {
733
- view_model: {
734
- primitives,
735
- __typename: 'GenAIHScrollLayoutViewModel'
523
+ if (sub.tip !== undefined) {
524
+ return { messageType: RichSubMessageType.TIP, tipText: sub.tip }
736
525
  }
737
- }
738
- };
739
- };
740
-
741
- /** Buat submessage + unified section untuk TIP (metadata text) */
742
- const makeTipSub = (text) => ({
743
- sub: {
744
- messageType: RichSubMessageType.TEXT,
745
- messageText: text
746
- },
747
- section: {
748
- view_model: {
749
- primitive: {
750
- text,
751
- __typename: 'GenAIMetadataTextPrimitive'
526
+ if (sub.footer !== undefined) {
527
+ return { messageType: RichSubMessageType.FOOTER, footerText: sub.footer }
528
+ }
529
+ // passthrough — kalau ada tipe lain langsung lewat
530
+ return sub
531
+ })
532
+ } else if ('richImages' in content) {
533
+ // ── richImages shorthand ──────────────────────────────────────────────
534
+ const raw = content.richImages
535
+ const urls = typeof raw === 'string' ? [raw] : (Array.isArray(raw) ? raw : [raw])
536
+ submessages = [{
537
+ messageType: RichSubMessageType.IMAGES,
538
+ gridImageMetadata: {
539
+ gridImageUrl: { imagePreviewUrl: urls[0] },
540
+ imageUrls: urls.map(url => ({ imagePreviewUrl: url, imageHighResUrl: url, sourceUrl: url }))
752
541
  },
753
- __typename: 'GenAISingleLayoutViewModel'
754
- }
755
- }
756
- });
757
-
758
- /** Buat unified section untuk SUGGEST (ActionRow pill buttons) — tidak ada submessage proto */
759
- const makeSuggestSection = (suggestion) => {
760
- const suggestions = Array.isArray(suggestion) ? suggestion : [suggestion];
761
- return {
762
- view_model: {
763
- primitives: suggestions.map(text => ({
764
- prompt_text: text,
765
- prompt_type: 'SUGGESTED_PROMPT',
766
- __typename: 'GenAIFollowUpSuggestionPillPrimitive'
767
- })),
768
- __typename: 'GenAIActionRowLayoutViewModel'
542
+ _imageUrls: urls,
543
+ _mimeType: 'image/jpeg'
544
+ }]
545
+ } else if ('richVideo' in content) {
546
+ // ── richVideo shorthand ───────────────────────────────────────────────
547
+ const rawVideo = content.richVideo
548
+ const url = typeof rawVideo === 'string' ? rawVideo : rawVideo.url
549
+ const mimeType = (typeof rawVideo === 'object' && rawVideo.mimeType) || 'video/mp4'
550
+ const duration = (typeof rawVideo === 'object' && rawVideo.duration) || 10
551
+ const fallbackText = (typeof rawVideo === 'object' && rawVideo.fallbackText) || '[ VIDEO - PontaCT ]'
552
+ submessages = [
553
+ { messageType: RichSubMessageType.TEXT, messageText: fallbackText },
554
+ { messageType: RichSubMessageType.VIDEO, videoMetadata: { url, mimeType, duration } }
555
+ ]
556
+ } else if ('richSuggestions' in content) {
557
+ // ── richSuggestions shorthand ─────────────────────────────────────────
558
+ const arr = Array.isArray(content.richSuggestions) ? content.richSuggestions : [content.richSuggestions]
559
+ submessages = [{ messageType: RichSubMessageType.SUGGESTIONS, suggestions: arr }]
560
+ } else if ('richLatex' in content) {
561
+ // ── richLatex shorthand ───────────────────────────────────────────────
562
+ const raw = content.richLatex
563
+ const url = typeof raw === 'string' ? raw : raw.url
564
+ const text = (typeof raw === 'object' && raw.text) || ''
565
+ submessages = [
566
+ { messageType: RichSubMessageType.TEXT, messageText: text || '[latex]' },
567
+ {
568
+ messageType: RichSubMessageType.LATEX,
569
+ latexMetadata: {
570
+ url,
571
+ text,
572
+ width: Number((typeof raw === 'object' && raw.width) || 100),
573
+ height: Number((typeof raw === 'object' && raw.height) || 100),
574
+ font_height: Number((typeof raw === 'object' && raw.font_height) || 83.33),
575
+ padding: Number((typeof raw === 'object' && raw.padding) || 15)
576
+ }
577
+ }
578
+ ]
579
+ } else if ('richProduct' in content) {
580
+ // ── richProduct shorthand ─────────────────────────────────────────────
581
+ const isMultiple = Array.isArray(content.richProduct)
582
+ const products = isMultiple ? content.richProduct : [content.richProduct]
583
+ submessages = [
584
+ { messageType: RichSubMessageType.TEXT, messageText: '[ PRODUCT - PontaCT ]' },
585
+ { messageType: RichSubMessageType.PRODUCT, productMetadata: { isMultiple, products } }
586
+ ]
587
+ } else if ('richPost' in content) {
588
+ // ── richPost shorthand ────────────────────────────────────────────────
589
+ const isMultiple = Array.isArray(content.richPost)
590
+ const posts = isMultiple ? content.richPost : [content.richPost]
591
+ submessages = [
592
+ { messageType: RichSubMessageType.TEXT, messageText: '[ POST - PontaCT ]' },
593
+ { messageType: RichSubMessageType.POST, postMetadata: { isMultiple, posts } }
594
+ ]
595
+ } else if ('richReels' in content) {
596
+ // ── richReels shorthand ───────────────────────────────────────────────
597
+ const reelItems = Array.isArray(content.richReels) ? content.richReels : [content.richReels]
598
+ submessages = [{
599
+ messageType: RichSubMessageType.REELS,
600
+ contentItemsMetadata: {
601
+ contentType: 1,
602
+ itemsMetadata: reelItems.map(i => ({
603
+ reelItem: { title: i.title, profileIconUrl: i.profileIconUrl, thumbnailUrl: i.thumbnailUrl, videoUrl: i.videoUrl }
604
+ }))
605
+ },
606
+ _reelItems: reelItems
607
+ }]
608
+ } else if ('richSources' in content) {
609
+ // ── richSources shorthand ─────────────────────────────────────────────
610
+ const arr = Array.isArray(content.richSources) ? content.richSources : [content.richSources]
611
+ submessages = [{ messageType: RichSubMessageType.SOURCES, sourcesMetadata: { sources: arr } }]
612
+ } else if ('richTip' in content) {
613
+ // ── richTip shorthand ─────────────────────────────────────────────────
614
+ submessages = [{ messageType: RichSubMessageType.TIP, tipText: content.richTip }]
615
+ } else if ('richFooter' in content) {
616
+ // ── richFooter shorthand ──────────────────────────────────────────────
617
+ submessages = [{ messageType: RichSubMessageType.FOOTER, footerText: content.richFooter }]
618
+ } else {
619
+ // ── shorthand / items mode ────────────────────────────────────────────
620
+ let items = []
621
+
622
+ if (content.items) {
623
+ items = content.items
624
+ } else if (content.codes) {
625
+ items = content.codes.map(c => ({ code: c.code, language: c.language }))
626
+ } else if (content.table) {
627
+ items = [{ table: content.table }]
628
+ } else if ('richText' in content) {
629
+ items = Array.isArray(content.richText)
630
+ ? content.richText.map(t => ({ text: t }))
631
+ : [{ text: content.richText }]
632
+ } else {
633
+ items = [{ code: content.code, language: content.language }]
769
634
  }
770
- };
771
- };
772
-
773
- /* ─────────────────────────────────────────────────────────────
774
- MAIN BUILDER — prepareRichResponseMessage
775
- ───────────────────────────────────────────────────────────── */
776
635
 
777
- export const prepareRichResponseMessage = (content) => {
778
- const {
779
- code, contentText, aiNotice: disclaimerText, bottomText: footerText, topText: headerText,
780
- language, links, noHeading, aiBlocks: richResponse, table, title,
781
- // sub-types lama
782
- gridImage, inlineImage, dynamic: dynamicContent,
783
- map: mapContent, latex, contentItems,
784
- // sub-types baru (prefixed 'rich' agar tidak konflik dengan media biasa)
785
- richImage, richVideo, reels, source, richProduct, richPost, tip, suggest
786
- } = content;
636
+ submessages = items.flatMap(item => {
637
+ // ── TEXT ──────────────────────────────────────────────────────────
638
+ if ('text' in item) {
639
+ return {
640
+ messageType: RichSubMessageType.TEXT,
641
+ messageText: item.text,
642
+ ...(item.inlineEntities ? { inlineEntities: item.inlineEntities } : {})
643
+ }
644
+ }
787
645
 
788
- let submessages = [];
789
- // extraSections: section-section unified yang tidak punya proto submessage 1:1
790
- // (source, product, post, suggest) atau punya struktur multi-section (image, video, reels)
791
- let extraSections = null;
646
+ // ── IMAGES ────────────────────────────────────────────────────────
647
+ if ('images' in item) {
648
+ const raw = item.images
649
+ const urls = typeof raw === 'string' ? [raw] : (Array.isArray(raw) ? raw : [raw])
650
+ return {
651
+ messageType: RichSubMessageType.IMAGES,
652
+ gridImageMetadata: {
653
+ gridImageUrl: { imagePreviewUrl: urls[0] },
654
+ imageUrls: urls.map(url => ({ imagePreviewUrl: url, imageHighResUrl: url, sourceUrl: url }))
655
+ },
656
+ _imageUrls: urls,
657
+ _mimeType: 'image/jpeg'
658
+ }
659
+ }
792
660
 
793
- /**
794
- * Helper: push ke submessages dan optionally ke extraSections.
795
- * Untuk tipe yang punya make*Sub yang mengembalikan { sub, sections[] } atau { sub, section }
796
- */
797
- const pushRich = (built) => {
798
- if (!built) return;
799
- if (built.sub) submessages.push(built.sub);
800
- if (!extraSections) extraSections = [];
801
- if (built.sections) extraSections.push(...built.sections);
802
- else if (built.section) extraSections.push(built.section);
803
- };
661
+ // ── VIDEO ─────────────────────────────────────────────────────────
662
+ if ('videoUrl' in item) {
663
+ const fallbackText = item.fallbackText || '[ VIDEO - PontaCT ]'
664
+ return [
665
+ { messageType: RichSubMessageType.TEXT, messageText: fallbackText },
666
+ {
667
+ messageType: RichSubMessageType.VIDEO,
668
+ videoMetadata: {
669
+ url: item.videoUrl,
670
+ mimeType: item.mimeType || 'video/mp4',
671
+ duration: item.duration || 10
672
+ }
673
+ }
674
+ ]
675
+ }
804
676
 
805
- /* ── mode array (aiBlocks) — multi-section campuran ── */
806
- if (Array.isArray(richResponse)) {
807
- if (headerText) submessages.push(makeTextSub(headerText));
808
- richResponse.forEach(sub => {
809
- if (sub.text != null) { submessages.push(makeTextSub(sub.text, sub.inlineEntities)); return; }
810
- if (sub.code != null) { submessages.push(makeCodeSub(sub.code, sub.language || 'javascript')); return; }
811
- if (sub.table != null) { submessages.push(makeTableSub(sub.table, sub.title, sub.noHeading)); return; }
812
- if (sub.gridImage != null) { submessages.push(makeGridImageSub(sub.gridImage.gridImageUrl, sub.gridImage.imageUrls)); return; }
813
- if (sub.inlineImage != null) { submessages.push(makeInlineImageSub(sub.inlineImage.imageUrl, sub.inlineImage.imageText, sub.inlineImage.alignment, sub.inlineImage.tapLinkUrl)); return; }
814
- if (sub.dynamic != null) { submessages.push(makeDynamicSub(sub.dynamic.url, sub.dynamic.type, sub.dynamic.version, sub.dynamic.loopCount)); return; }
815
- if (sub.map != null) { submessages.push(makeMapSub(sub.map.centerLatitude, sub.map.centerLongitude, sub.map)); return; }
816
- if (sub.latex != null) { submessages.push(makeLatexSub(sub.latex.text, sub.latex.expressions)); return; }
817
- if (sub.contentItems != null) { submessages.push(makeContentItemsSub(sub.contentItems.items, sub.contentItems.contentType)); return; }
818
- // tipe baru
819
- if (sub.richImage != null) { pushRich(makeImageSub(sub.richImage)); return; }
820
- if (sub.richVideo != null) { pushRich(makeVideoSub(sub.richVideo)); return; }
821
- if (sub.reels != null) { pushRich(makeReelsSub(sub.reels)); return; }
822
- if (sub.source != null) { if (!extraSections) extraSections = []; extraSections.push(makeSourceSection(sub.source)); return; }
823
- if (sub.richProduct != null) { pushRich(makeProductSection(sub.richProduct)); return; }
824
- if (sub.richPost != null) { pushRich(makePostSection(sub.richPost)); return; }
825
- if (sub.tip != null) { pushRich(makeTipSub(sub.tip)); return; }
826
- if (sub.suggest != null) { if (!extraSections) extraSections = []; extraSections.push(makeSuggestSection(sub.suggest)); return; }
827
- submessages.push(sub); // passthrough kalau sudah bentuk proto
828
- });
829
- if (footerText) submessages.push(makeTextSub(footerText));
677
+ // ── SUGGESTIONS ───────────────────────────────────────────────────
678
+ if ('suggestions' in item) {
679
+ const arr = Array.isArray(item.suggestions) ? item.suggestions : [item.suggestions]
680
+ return { messageType: RichSubMessageType.SUGGESTIONS, suggestions: arr }
681
+ }
830
682
 
831
- /* ── mode flat (convenience fields) ── */
832
- } else {
833
- if (headerText) submessages.push(makeTextSub(headerText));
683
+ // ── LATEX ─────────────────────────────────────────────────────────
684
+ if ('latexUrl' in item) {
685
+ const text = item.latexText || ''
686
+ return [
687
+ { messageType: RichSubMessageType.TEXT, messageText: text || '[latex]' },
688
+ {
689
+ messageType: RichSubMessageType.LATEX,
690
+ latexMetadata: {
691
+ url: item.latexUrl,
692
+ text,
693
+ width: Number(item.latexWidth || 100),
694
+ height: Number(item.latexHeight || 100),
695
+ font_height: Number(item.latexFontHeight || 83.33),
696
+ padding: Number(item.latexPadding || 15)
697
+ }
698
+ }
699
+ ]
700
+ }
834
701
 
835
- if (code) submessages.push(makeCodeSub(code, language || 'javascript'));
836
- if (table) submessages.push(makeTableSub(table, title, noHeading));
837
- if (gridImage) submessages.push(makeGridImageSub(gridImage.gridImageUrl, gridImage.imageUrls));
838
- if (inlineImage) submessages.push(makeInlineImageSub(inlineImage.imageUrl, inlineImage.imageText, inlineImage.alignment, inlineImage.tapLinkUrl));
839
- if (dynamicContent) submessages.push(makeDynamicSub(dynamicContent.url, dynamicContent.type, dynamicContent.version, dynamicContent.loopCount));
840
- if (mapContent) submessages.push(makeMapSub(mapContent.centerLatitude, mapContent.centerLongitude, mapContent));
841
- if (latex) submessages.push(makeLatexSub(latex.text, latex.expressions));
842
- if (contentItems) submessages.push(makeContentItemsSub(contentItems.items, contentItems.contentType));
702
+ // ── PRODUCT ───────────────────────────────────────────────────────
703
+ if ('product' in item) {
704
+ const isMultiple = Array.isArray(item.product)
705
+ const products = isMultiple ? item.product : [item.product]
706
+ return [
707
+ { messageType: RichSubMessageType.TEXT, messageText: '[ PRODUCT - PontaCT ]' },
708
+ { messageType: RichSubMessageType.PRODUCT, productMetadata: { isMultiple, products } }
709
+ ]
710
+ }
843
711
 
844
- // ── tipe baru ──
845
- if (richImage) pushRich(makeImageSub(richImage));
846
- if (richVideo) pushRich(makeVideoSub(richVideo));
847
- if (reels) pushRich(makeReelsSub(reels));
848
- if (source) { if (!extraSections) extraSections = []; extraSections.push(makeSourceSection(source)); }
849
- if (richProduct) pushRich(makeProductSection(richProduct));
850
- if (richPost) pushRich(makePostSection(richPost));
851
- if (contentText) submessages.push(makeTextSub(contentText)); // ← setelah produk/post, tampil di bawah card
852
- if (tip) pushRich(makeTipSub(tip));
853
- if (suggest) { if (!extraSections) extraSections = []; extraSections.push(makeSuggestSection(suggest)); }
712
+ // ── POST ──────────────────────────────────────────────────────────
713
+ if ('post' in item) {
714
+ const isMultiple = Array.isArray(item.post)
715
+ const posts = isMultiple ? item.post : [item.post]
716
+ return [
717
+ { messageType: RichSubMessageType.TEXT, messageText: '[ POST - PontaCT ]' },
718
+ { messageType: RichSubMessageType.POST, postMetadata: { isMultiple, posts } }
719
+ ]
720
+ }
854
721
 
855
- /* links bisa dikombinasi dengan tipe lain di atas */
856
- if (links && Array.isArray(links)) {
857
- links.forEach((linkField, index) => {
858
- const prefix = 'SS_' + index;
859
- const url = (linkField.url && /^https?:\/\//i.test(linkField.url)) ? linkField.url : DONATE_URL;
860
- if (url === DONATE_URL) {
861
- console.warn('[baileys] links[].url kosong/tidak valid (harus http:// atau https://) — dipakai DONATE_URL sebagai gantinya. Cek nilai url yang dikirim ke field "links".');
722
+ // ── REELS ─────────────────────────────────────────────────────────
723
+ if ('reels' in item) {
724
+ const reelItems = Array.isArray(item.reels) ? item.reels : [item.reels]
725
+ return {
726
+ messageType: RichSubMessageType.REELS,
727
+ contentItemsMetadata: {
728
+ contentType: 1,
729
+ itemsMetadata: reelItems.map(i => ({
730
+ reelItem: { title: i.title, profileIconUrl: i.profileIconUrl, thumbnailUrl: i.thumbnailUrl, videoUrl: i.videoUrl }
731
+ }))
732
+ },
733
+ _reelItems: reelItems
862
734
  }
863
- const sources = (linkField.sources || []).map(s => ({
864
- source_type: 'THIRD_PARTY',
865
- source_display_name: s.displayName || 'Source',
866
- source_subtitle: s.subtitle || '',
867
- source_url: s.url || url
868
- }));
869
- submessages.push(makeTextSub(
870
- linkField.text + ` {{${prefix}}}¹{{/${prefix}}} `,
871
- [{
872
- key: prefix,
873
- metadata: {
874
- reference_id: index + 1,
875
- reference_url: url,
876
- reference_title: linkField.title || url,
877
- reference_display_name: linkField.displayName || url,
878
- sources,
879
- __typename: 'GenAISearchCitationItem'
880
- }
881
- }]
882
- ));
883
- });
884
- }
735
+ }
885
736
 
886
- if (footerText) submessages.push(makeTextSub(footerText));
887
- }
737
+ // ── SOURCES ───────────────────────────────────────────────────────
738
+ if ('sources' in item) {
739
+ const arr = Array.isArray(item.sources) ? item.sources : [item.sources]
740
+ return { messageType: RichSubMessageType.SOURCES, sourcesMetadata: { sources: arr } }
741
+ }
888
742
 
889
- /* build unifiedResponse JSON — gabungkan sections dari submessages + extraSections */
890
- // Pisahkan footerSubs (teks setelah media) agar selalu muncul SETELAH extraSections (video/image)
891
- const footerSubMessages = footerText ? [makeTextSub(footerText)] : [];
892
- // Hapus footerText dari submessages jika sudah ada (sudah di-push sebelumnya)
893
- const mainSubmessages = footerText
894
- ? submessages.filter(s => !(s.messageType === RichSubMessageType.TEXT && s.messageText === footerText))
895
- : submessages;
896
- const baseUnified = toUnified(mainSubmessages);
897
- const footerSections = footerSubMessages.map(buildUnifiedSection);
898
- const unified = extraSections && extraSections.length > 0
899
- ? { ...baseUnified, sections: [...baseUnified.sections, ...extraSections, ...footerSections] }
900
- : { ...baseUnified, sections: [...baseUnified.sections, ...footerSections] };
743
+ // ── TIP ───────────────────────────────────────────────────────────
744
+ if ('tip' in item) {
745
+ return { messageType: RichSubMessageType.TIP, tipText: item.tip }
746
+ }
901
747
 
902
- const richResponseMessage = proto.AIRichResponseMessage.create({
903
- submessages,
904
- messageType: proto.AIRichResponseMessageType.AI_RICH_RESPONSE_TYPE_STANDARD,
905
- unifiedResponse: {
906
- data: Buffer.from(JSON.stringify(unified), 'utf-8')
907
- },
908
- contextInfo: {
909
- isForwarded: true,
910
- forwardingScore: 1,
911
- forwardOrigin: 4
912
- }
913
- });
748
+ // ── FOOTER ────────────────────────────────────────────────────────
749
+ if ('footer' in item) {
750
+ return { messageType: RichSubMessageType.FOOTER, footerText: item.footer }
751
+ }
914
752
 
915
- const message = wrapToBotForwardedMessage(richResponseMessage);
916
- const botMetadata = message.messageContextInfo.botMetadata;
753
+ // ── TABLE ─────────────────────────────────────────────────────────
754
+ if (item.table) {
755
+ const { title, headers, rows } = item.table
756
+ const allRows = []
757
+ if (headers && headers.length) {
758
+ allRows.push({ items: headers, isHeading: true })
759
+ }
760
+ for (const row of (rows || [])) {
761
+ allRows.push({ items: row, isHeading: false })
762
+ }
763
+ const tableSubmessage = {
764
+ messageType: RichSubMessageType.TABLE,
765
+ tableMetadata: { rows: allRows }
766
+ }
767
+ if (title) {
768
+ return [
769
+ {
770
+ messageType: RichSubMessageType.TEXT,
771
+ messageText: title
772
+ },
773
+ tableSubmessage
774
+ ]
775
+ }
776
+ return tableSubmessage
777
+ }
917
778
 
918
- if (disclaimerText) {
919
- botMetadata.messageDisclaimerText = disclaimerText;
779
+ // ── CODE (default) ────────────────────────────────────────────────
780
+ const lang = item.language || 'javascript'
781
+ const codeBlocks = tokenizeCode(item.code, lang)
782
+ return {
783
+ messageType: RichSubMessageType.CODE,
784
+ codeMetadata: {
785
+ codeLanguage: lang,
786
+ codeBlocks: codeBlocks.map(b => ({
787
+ highlightType: b.highlightType,
788
+ codeContent: b.codeContent
789
+ }))
790
+ }
791
+ }
792
+ })
920
793
  }
921
- botMetadata.botResponseId = unified.response_id;
922
-
923
- return message;
924
- };
925
794
 
926
- /* ─────────────────────────────────────────────────────────────
927
- BOT WRAPPER HELPERS
928
- ───────────────────────────────────────────────────────────── */
795
+ const uuid = crypto_1.randomUUID()
796
+ const unified = toUnified(submessages, uuid)
797
+ const aiForwarded = content.aiForwarded !== false
798
+
799
+ const pontaSources = submessages
800
+ .filter(s => s.messageType === RichSubMessageType.REELS && s._reelItems)
801
+ .flatMap(s => (s._reelItems || []).map((i, idx) => ({
802
+ provider: 'PontaCT',
803
+ thumbnailCDNURL: i.thumbnailUrl,
804
+ sourceProviderURL: i.videoUrl,
805
+ sourceQuery: '',
806
+ faviconCDNURL: i.profileIconUrl,
807
+ citationNumber: idx + 1,
808
+ sourceTitle: i.title
809
+ })))
929
810
 
930
- export const botMetadataSignature = () => {
931
- const signature = new Uint8Array(64);
932
- getRandomValues(signature);
933
- return signature;
934
- };
935
-
936
- export const botMetadataCertificate = (length = 685) => {
937
- const certificate = new Uint8Array(length);
938
- certificate[0] = 48;
939
- certificate[1] = 130;
940
- getRandomValues(certificate.subarray(2));
941
- return certificate;
942
- };
943
-
944
- export const wrapToBotForwardedMessage = (richResponseMessage) => ({
945
- messageContextInfo: {
946
- botMetadata: {
947
- verificationMetadata: {
948
- proofs: [
949
- {
950
- certificateChain: [
951
- botMetadataCertificate(),
952
- botMetadataCertificate(892)
953
- ],
811
+ return {
812
+ messageContextInfo: {
813
+ botMetadata: {
814
+ ...(pontaSources.length ? { richResponseSourcesMetadata: { sources: pontaSources } } : {}),
815
+ verificationMetadata: {
816
+ proofs: [{
817
+ certificateChain: [botMetadataCertificate(), botMetadataCertificate(892)],
954
818
  version: 1,
955
819
  useCase: 1,
956
820
  signature: botMetadataSignature()
821
+ }]
822
+ },
823
+ botResponseId: uuid
824
+ }
825
+ },
826
+ botForwardedMessage: {
827
+ message: {
828
+ richResponseMessage: {
829
+ messageType: 1,
830
+ submessages,
831
+ unifiedResponse: {
832
+ data: Buffer.from(JSON.stringify(unified))
833
+ },
834
+ contextInfo: {
835
+ isForwarded: true,
836
+ forwardingScore: 1,
837
+ ...(aiForwarded ? { forwardedAiBotMessageInfo: { botJid: '867051314767696@bot' } } : {}),
838
+ forwardOrigin: 4
957
839
  }
958
- ]
840
+ }
959
841
  }
960
842
  }
961
- },
962
- botForwardedMessage: {
963
- message: { richResponseMessage }
964
- }
965
- });
966
-
967
- /* ─────────────────────────────────────────────────────────────
968
- STANDALONE GENERATORS (kompatibel dengan baileys upstream)
969
- Semua fungsi di bawah ini di-port dari baileys-main/src/Utils/rich-messages.js
970
- agar API nya 100% kompatibel dengan baileys — termasuk sendTable, sendLatex, dll.
971
- ───────────────────────────────────────────────────────────── */
972
-
973
- /* helper: bangun contextInfo dari quoted */
974
- const buildRichContextInfo = (quoted) => {
975
- const ctxInfo = {
976
- isForwarded: true,
977
- forwardingScore: 1,
978
- forwardOrigin: 4,
979
- };
980
- if (quoted?.key) {
981
- ctxInfo.stanzaId = quoted.key.id;
982
- ctxInfo.participant = quoted.key.participant || quoted.sender || quoted.key.remoteJid;
983
- ctxInfo.quotedMessage = quoted.message;
984
- }
985
- return ctxInfo;
986
- };
987
-
988
- /* helper: wrap submessages + contextInfo ke botForwardedMessage (format lama/V1) */
989
- const buildBotForwardedMessage = (submessages, contextInfo, unifiedResponse) => {
990
- const richResponse = { messageType: 1, submessages, contextInfo };
991
- if (unifiedResponse) richResponse.unifiedResponse = unifiedResponse;
992
- return { botForwardedMessage: { message: { richResponseMessage: richResponse } } };
993
- };
994
-
995
- /* helper: wrap proto AIRichResponseMessage ke botForwardedMessage dengan messageContextInfo */
996
- const _wrapProtoToResult = (submessages, quoted) => {
997
- const unified = toUnified(submessages);
998
- const richResponseMessage = proto.AIRichResponseMessage.create({
999
- submessages,
1000
- messageType: proto.AIRichResponseMessageType.AI_RICH_RESPONSE_TYPE_STANDARD,
1001
- unifiedResponse: { data: Buffer.from(JSON.stringify(unified), 'utf-8') },
1002
- contextInfo: buildRichContextInfo(quoted),
1003
- });
1004
- return { message: wrapToBotForwardedMessage(richResponseMessage), messageId: generateMessageIDV2() };
1005
- };
1006
-
1007
- // ── Table ──────────────────────────────────────────────────
1008
-
1009
- export const generateTableContent = (title, headers, rows, quoted, options = {}) => {
1010
- const { footer, headerText } = options;
1011
- const tableRows = [{ items: headers, isHeading: true }, ...rows.map(row => ({ items: row.map(String) }))];
1012
- const subs = [];
1013
- if (headerText) subs.push({ messageType: RichSubMessageType.TEXT, messageText: headerText });
1014
- subs.push({ messageType: RichSubMessageType.TABLE, tableMetadata: { title, rows: tableRows } });
1015
- if (footer) subs.push({ messageType: RichSubMessageType.TEXT, messageText: footer });
1016
- return _wrapProtoToResult(subs, quoted);
1017
- };
1018
-
1019
- export const generateListContent = (title, items, quoted, options = {}) => {
1020
- const { footer, headerText } = options;
1021
- const tableRows = items.map(item => ({ items: Array.isArray(item) ? item.map(String) : [String(item)] }));
1022
- const subs = [];
1023
- if (headerText) subs.push({ messageType: RichSubMessageType.TEXT, messageText: headerText });
1024
- subs.push({ messageType: RichSubMessageType.TABLE, tableMetadata: { title, rows: tableRows } });
1025
- if (footer) subs.push({ messageType: RichSubMessageType.TEXT, messageText: footer });
1026
- return _wrapProtoToResult(subs, quoted);
1027
- };
1028
-
1029
- export const toTableMetadataV2 = (arr) => {
1030
- if (!Array.isArray(arr) || arr.length === 0) throw new Error('Input must be a non-empty array');
1031
- const [title, headerStr, ...rest] = arr;
1032
- const splitCols = (str) => typeof str !== 'string' ? [] : str.includes('|') ? str.split('|').map(s => s.trim()) : str.split(',').map(s => s.trim());
1033
- const splitRows = (str) => typeof str !== 'string' ? [] : str.split(';;').map(row => splitCols(row));
1034
- const header = splitCols(headerStr);
1035
- const parsedRows = rest.flatMap(splitRows);
1036
- const maxLen = Math.max(header.length, ...parsedRows.map(r => r.length));
1037
- const unified_rows = [
1038
- { is_header: true, cells: [...header, ...Array(maxLen - header.length).fill('')] },
1039
- ...parsedRows.map(cells => ({ is_header: false, cells: [...cells, ...Array(maxLen - cells.length).fill('')] }))
1040
- ];
1041
- const rows = unified_rows.map(r => ({ items: r.cells, ...(r.is_header ? { isHeading: true } : {}) }));
1042
- return { title, rows, unified_rows };
1043
- };
1044
-
1045
- export const generateTableContentV2 = (table, quoted, options = {}) => {
1046
- const { title, footer, headerText, text } = options;
1047
- const { unified_rows } = toTableMetadataV2(table);
1048
- const subs = [];
1049
- const sections = [];
1050
- if (headerText || title) sections.push({ view_model: { primitive: { text: headerText || title, __typename: 'GenAIMarkdownTextUXPrimitive' }, __typename: 'GenAISingleLayoutViewModel' } });
1051
- if (text) sections.push({ view_model: { primitive: { text, __typename: 'GenAIMarkdownTextUXPrimitive' }, __typename: 'GenAISingleLayoutViewModel' } });
1052
- sections.push({ view_model: { primitive: { rows: unified_rows, __typename: 'GenATableUXPrimitive' }, __typename: 'GenAISingleLayoutViewModel' } });
1053
- if (footer) sections.push({ view_model: { primitive: { text: footer, __typename: 'GenAIMarkdownTextUXPrimitive' }, __typename: 'GenAISingleLayoutViewModel' } });
1054
- // using randomUUID and randomBytes from top-level import
1055
- const base64Data = Buffer.from(JSON.stringify({ response_id: randomUUID(), sections })).toString('base64');
1056
- const ctxInfo = buildRichContextInfo(quoted);
1057
- ctxInfo.forwardingScore = 2;
1058
- ctxInfo.botMessageSharingInfo = { botEntryPointOrigin: 1, forwardScore: 2 };
1059
- const content = {
1060
- messageContextInfo: { threadId: [], deviceListMetadata: { senderKeyIndexes: [], recipientKeyIndexes: [], recipientKeyHash: '', recipientTimestamp: Math.floor(Date.now() / 1000) }, deviceListMetadataVersion: 2, messageSecret: randomBytes(32) },
1061
- botForwardedMessage: { message: { richResponseMessage: { submessages: subs, messageType: 1, unifiedResponse: { data: base64Data }, contextInfo: ctxInfo } } }
1062
- };
1063
- return { message: content, messageId: generateMessageIDV2() };
1064
- };
1065
-
1066
- // ── Code Block ────────────────────────────────────────────
1067
-
1068
- export const generateCodeBlockContent = (code, quoted, options = {}) => {
1069
- const { title, footer, language = 'javascript' } = options;
1070
- const subs = [];
1071
- if (title) subs.push({ messageType: RichSubMessageType.TEXT, messageText: title });
1072
- subs.push({ messageType: RichSubMessageType.CODE, codeMetadata: { codeLanguage: language, codeBlocks: tokenizeCode(code, language) } });
1073
- if (footer) subs.push({ messageType: RichSubMessageType.TEXT, messageText: footer });
1074
- return _wrapProtoToResult(subs, quoted);
1075
- };
1076
-
1077
- export const generateCodeBlockContentV2 = (code, quoted, options = {}) => {
1078
- const { title, footer, language = 'javascript', text } = options;
1079
- const { unified_codeBlock } = tokenizeCodeV2(code, language);
1080
- const sections = [];
1081
- if (text) sections.push({ view_model: { primitive: { text, __typename: 'GenAIMarkdownTextUXPrimitive' }, __typename: 'GenAISingleLayoutViewModel' } });
1082
- sections.push({ view_model: { primitive: { language, code_blocks: unified_codeBlock, __typename: 'GenAICodeUXPrimitive' }, __typename: 'GenAISingleLayoutViewModel' } });
1083
- if (footer) sections.push({ view_model: { primitive: { text: footer, __typename: 'GenAIMarkdownTextUXPrimitive' }, __typename: 'GenAISingleLayoutViewModel' } });
1084
- // using randomUUID and randomBytes from top-level import
1085
- const base64Data = Buffer.from(JSON.stringify({ response_id: randomUUID(), sections })).toString('base64');
1086
- const ctxInfo = buildRichContextInfo(quoted);
1087
- ctxInfo.forwardingScore = 2;
1088
- ctxInfo.mentionedJid = [];
1089
- ctxInfo.groupMentions = [];
1090
- ctxInfo.statusAttributions = [];
1091
- ctxInfo.botMessageSharingInfo = { botEntryPointOrigin: 1, forwardScore: 2 };
1092
- const content = {
1093
- messageContextInfo: { threadId: [], deviceListMetadata: { senderKeyIndexes: [], recipientKeyIndexes: [], recipientKeyHash: '', recipientTimestamp: Math.floor(Date.now() / 1000) }, deviceListMetadataVersion: 2, messageSecret: randomBytes(32) },
1094
- botForwardedMessage: { message: { richResponseMessage: { submessages: [], messageType: 1, unifiedResponse: { data: base64Data }, contextInfo: ctxInfo } } }
1095
- };
1096
- return { message: content, messageId: generateMessageIDV2() };
1097
- };
1098
-
1099
- // ── Rich Message (generic) ────────────────────────────────
1100
-
1101
- export const generateRichMessageContent = (submessages, quoted) => {
1102
- return _wrapProtoToResult(submessages, quoted);
1103
- };
1104
-
1105
- export const generateUnifiedResponseContent = (quoted, captured) => {
1106
- const richResponseMessage = proto.AIRichResponseMessage.create({
1107
- submessages: captured.submessages,
1108
- messageType: proto.AIRichResponseMessageType.AI_RICH_RESPONSE_TYPE_STANDARD,
1109
- unifiedResponse: captured.unifiedResponse,
1110
- contextInfo: buildRichContextInfo(quoted),
1111
- });
1112
- return { message: wrapToBotForwardedMessage(richResponseMessage), messageId: generateMessageIDV2() };
1113
- };
1114
-
1115
- export const captureUnifiedResponse = (msg) => {
1116
- const botFwd = msg?.botForwardedMessage?.message;
1117
- if (!botFwd) return null;
1118
- const rich = botFwd.richResponseMessage;
1119
- if (!rich?.unifiedResponse?.data) return null;
1120
- return { unifiedResponse: { data: rich.unifiedResponse.data }, submessages: rich.submessages || [], contextInfo: rich.contextInfo || {} };
1121
- };
1122
-
1123
- // ── Link ──────────────────────────────────────────────────
1124
-
1125
- export const generateLinkContent = (text, links, quoted, options = {}) => {
1126
- const { footer, forwardingScore = 3, citations = [], proofs = [] } = options;
1127
- const subs = [];
1128
- const fullText = footer ? `${text}${footer}` : text;
1129
- subs.push({ messageType: RichSubMessageType.TEXT, messageText: fullText });
1130
- const sections = [];
1131
- const inlineEntities = links.map((link, i) => {
1132
- const url = typeof link === 'string' ? link : link.url;
1133
- const displayName = typeof link === 'object' && link.displayName ? link.displayName : citations[i]?.sourceTitle || `Link ${i + 1}`;
1134
- return { key: `IE_${i}`, metadata: { display_name: displayName, is_trusted: false, url, __typename: 'GenAIInlineLinkItem' } };
1135
- });
1136
- sections.push({ view_model: { primitive: { text, inline_entities: inlineEntities, __typename: 'GenAIMarkdownTextUXPrimitive' }, __typename: 'GenAISingleLayoutViewModel' } });
1137
- if (footer) sections.push({ view_model: { primitive: { text: footer, __typename: 'GenAIMarkdownTextUXPrimitive' }, __typename: 'GenAISingleLayoutViewModel' } });
1138
- // using randomUUID and randomBytes from top-level import
1139
- const base64Data = Buffer.from(JSON.stringify({ response_id: randomUUID(), sections })).toString('base64');
1140
- const ctxInfo = buildRichContextInfo(quoted);
1141
- ctxInfo.forwardingScore = forwardingScore;
1142
- ctxInfo.botMessageSharingInfo = { forwardScore: forwardingScore };
1143
- const messageContextInfo = { messageSecret: randomBytes(32) };
1144
- if (citations.length > 0 || proofs.length > 0) {
1145
- const botMetadata = {};
1146
- 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 || '' })) };
1147
- if (proofs.length > 0) botMetadata.verificationMetadata = { proofs: proofs.map(p => ({ version: p.version || 1, useCase: p.useCase || 1, signature: p.signature || '', certificateChain: p.certificateChain || [] })) };
1148
- messageContextInfo.botMetadata = botMetadata;
1149
- }
1150
- const content = { messageContextInfo, botForwardedMessage: { message: { richResponseMessage: { messageType: 1, submessages: subs, unifiedResponse: { data: base64Data }, contextInfo: ctxInfo } } } };
1151
- return { message: content, messageId: generateMessageIDV2() };
1152
- };
1153
-
1154
- export const generateLinkContentV2 = (text, links, quoted, options = {}) => {
1155
- const { footer, searchEngine = 'MAME' } = options;
1156
- const subs = [];
1157
- const fullText = footer ? `${text}${footer}` : text;
1158
- subs.push({ messageType: RichSubMessageType.TEXT, messageText: fullText });
1159
- const sections = [];
1160
- const inlineEntities = links.map((link, i) => {
1161
- const url = typeof link === 'string' ? link : link.url;
1162
- const displayName = typeof link === 'object' && link.displayName ? link.displayName : `Link ${i + 1}`;
1163
- const sourceDisplayName = typeof link === 'object' && link.sourceDisplayName ? link.sourceDisplayName : `Source ${i + 1}`;
1164
- const sourceSubtitle = typeof link === 'object' && link.sourceSubtitle ? link.sourceSubtitle : '';
1165
- 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' } };
1166
- });
1167
- sections.push({ view_model: { primitive: { text, inline_entities: inlineEntities, __typename: 'GenAIMarkdownTextUXPrimitive' }, __typename: 'GenAISingleLayoutViewModel' } });
1168
- const searchSources = links.map((link, i) => {
1169
- const url = typeof link === 'string' ? link : link.url;
1170
- const sourceDisplayName = typeof link === 'object' && link.sourceDisplayName ? link.sourceDisplayName : `Source ${i + 1}`;
1171
- const sourceSubtitle = typeof link === 'object' && link.sourceSubtitle ? link.sourceSubtitle : '';
1172
- return { source_type: 'THIRD_PARTY', source_display_name: sourceDisplayName, source_subtitle: sourceSubtitle, source_url: url };
1173
- });
1174
- sections.push({ view_model: { primitive: { sources: searchSources, search_engine: searchEngine, __typename: 'GenAISearchResultPrimitive' }, __typename: 'GenAISingleLayoutViewModel' } });
1175
- if (footer) sections.push({ view_model: { primitive: { text: footer, __typename: 'GenAIMarkdownTextUXPrimitive' }, __typename: 'GenAISingleLayoutViewModel' } });
1176
- // using randomUUID and randomBytes from top-level import
1177
- const base64Data = Buffer.from(JSON.stringify({ response_id: randomUUID(), sections })).toString('base64');
1178
- const ctxInfo = { isForwarded: true, forwardOrigin: 4 };
1179
- if (quoted?.key) { ctxInfo.participant = quoted.key.participant || quoted.sender || quoted.key.remoteJid; ctxInfo.quotedMessage = quoted.message; }
1180
- const content = { messageContextInfo: { threadId: [], messageSecret: randomBytes(32) }, botForwardedMessage: { message: { richResponseMessage: { messageType: 1, submessages: subs, unifiedResponse: { data: base64Data }, contextInfo: ctxInfo } } } };
1181
- return { message: content, messageId: generateMessageIDV2() };
1182
- };
1183
-
1184
- // ── LaTeX (3 varian, port dari baileys) ──────────────────
1185
-
1186
- /**
1187
- * generateLatexContent — kirim LaTeX dengan URL gambar yang sudah ada (pre-rendered).
1188
- * Jika `expr.url` tidak diisi, otomatis di-generate via buildLatexUrl (codecogs).
1189
- *
1190
- * @param {object} quoted - pesan yang di-quote (boleh null)
1191
- * @param {object} options - { text, expressions, headerText, footer }
1192
- * expressions: Array<{ latexExpression, url?, width?, height?,
1193
- * fontHeight?, imageTopPadding?, imageLeadingPadding?,
1194
- * imageBottomPadding?, imageTrailingPadding? }>
1195
- */
1196
- export const generateLatexContent = (quoted, options) => {
1197
- const { text, expressions, headerText, footer } = options;
1198
- const subs = [];
1199
- if (headerText) subs.push({ messageType: RichSubMessageType.TEXT, messageText: headerText });
1200
- const latexExpressions = expressions.map((expr) => {
1201
- const entry = {
1202
- latexExpression: expr.latexExpression,
1203
- url: expr.url || buildLatexUrl(expr.latexExpression),
1204
- width: expr.width ?? 120,
1205
- height: expr.height ?? 40,
1206
- };
1207
- if (expr.fontHeight !== undefined) entry.fontHeight = expr.fontHeight;
1208
- if (expr.imageTopPadding !== undefined) entry.imageTopPadding = expr.imageTopPadding;
1209
- if (expr.imageLeadingPadding !== undefined) entry.imageLeadingPadding = expr.imageLeadingPadding;
1210
- if (expr.imageBottomPadding !== undefined) entry.imageBottomPadding = expr.imageBottomPadding;
1211
- if (expr.imageTrailingPadding !== undefined) entry.imageTrailingPadding = expr.imageTrailingPadding;
1212
- return entry;
1213
- });
1214
- subs.push({ messageType: RichSubMessageType.LATEX, latexMetadata: { text: text || '', expressions: latexExpressions } });
1215
- if (footer) subs.push({ messageType: RichSubMessageType.TEXT, messageText: footer });
1216
- return _wrapProtoToResult(subs, quoted);
1217
- };
1218
-
1219
- /**
1220
- * generateLatexImageContent — render LaTeX ke PNG via renderLatexToPng,
1221
- * upload via uploadFn, kirim sebagai LaTeX message dengan URL hasil upload.
1222
- *
1223
- * @param {object} quoted - pesan yang di-quote (boleh null)
1224
- * @param {object} options - { text, expressions, headerText, footer }
1225
- * @param {Function} uploadFn - async (buffer, type) => { url, directPath }
1226
- * @param {Function} renderLatexToPng - async (latexExpression) => { buffer, width, height }
1227
- */
1228
- export const generateLatexImageContent = async (quoted, options, uploadFn, renderLatexToPng) => {
1229
- const { text, expressions, headerText, footer } = options;
1230
- const subs = [];
1231
- if (headerText) subs.push({ messageType: RichSubMessageType.TEXT, messageText: headerText });
1232
- const latexExpressions = await Promise.all(
1233
- expressions.map(async (expr) => {
1234
- const { buffer, width, height } = await renderLatexToPng(expr.latexExpression);
1235
- const uploadResult = await uploadFn(buffer, 'image');
1236
- const imageUrl = uploadResult.url || uploadResult.directPath;
1237
- return { latexExpression: expr.latexExpression, url: imageUrl, width, height };
1238
- }),
1239
- );
1240
- subs.push({ messageType: RichSubMessageType.LATEX, latexMetadata: { text: text || '', expressions: latexExpressions } });
1241
- if (footer) subs.push({ messageType: RichSubMessageType.TEXT, messageText: footer });
1242
- return _wrapProtoToResult(subs, quoted);
1243
- };
1244
-
1245
- /**
1246
- * generateLatexInlineImageContent — render LaTeX ke PNG, upload, kirim sebagai
1247
- * deretan INLINE_IMAGE (satu per expression). Fallback untuk klien tanpa LaTeX native.
1248
- *
1249
- * @param {object} quoted - pesan yang di-quote (boleh null)
1250
- * @param {object} options - { text, expressions, headerText, footer }
1251
- * @param {Function} uploadFn - async (buffer, type) => { url, directPath }
1252
- * @param {Function} renderLatexToPng - async (latexExpression) => { buffer, width, height }
1253
- */
1254
- export const generateLatexInlineImageContent = async (quoted, options, uploadFn, renderLatexToPng) => {
1255
- const { text, expressions, headerText, footer } = options;
1256
- const subs = [];
1257
- if (headerText) subs.push({ messageType: RichSubMessageType.TEXT, messageText: headerText });
1258
- if (text) subs.push({ messageType: RichSubMessageType.TEXT, messageText: text });
1259
- for (const expr of expressions) {
1260
- const { buffer, width, height } = await renderLatexToPng(expr.latexExpression);
1261
- const uploadResult = await uploadFn(buffer, 'image');
1262
- const imageUrl = uploadResult.url || uploadResult.directPath;
1263
- subs.push({
1264
- messageType: RichSubMessageType.INLINE_IMAGE,
1265
- imageMetadata: {
1266
- imageUrl: { imagePreviewUrl: imageUrl, imageHighResUrl: imageUrl },
1267
- imageText: expr.latexExpression,
1268
- alignment: 2,
1269
- },
1270
- });
1271
- }
1272
- if (footer) subs.push({ messageType: RichSubMessageType.TEXT, messageText: footer });
1273
- return _wrapProtoToResult(subs, quoted);
1274
- };
1275
-
1276
- // ── tokenizeCodeV2 (dipakai oleh generateCodeBlockContentV2) ─
1277
-
1278
- export const tokenizeCodeV2 = (code, language = 'javascript') => {
1279
- const keywords = LANGUAGE_KEYWORDS[language] || LANGUAGE_KEYWORDS['javascript'] || new Set();
1280
- const tokens = [];
1281
- let i = 0;
1282
- const n = code.length;
1283
- const push = (codeContent, highlightType) => {
1284
- if (!codeContent) return;
1285
- const last = tokens[tokens.length - 1];
1286
- if (last && last.highlightType === highlightType) last.codeContent += codeContent;
1287
- else tokens.push({ codeContent, highlightType });
1288
- };
1289
- const isWordStart = (c) => /[a-zA-Z_$]/.test(c);
1290
- const isWord = (c) => /[a-zA-Z0-9_$]/.test(c);
1291
- const isNum = (c) => /[0-9]/.test(c);
1292
- const HIGHLIGHT_TYPE_MAP = { 0: 'DEFAULT', 1: 'KEYWORD', 2: 'METHOD', 3: 'STR', 4: 'NUMBER', 5: 'COMMENT' };
1293
- const isPyBash = ['python','py','bash','sh','shell'].includes(language);
1294
- while (i < n) {
1295
- const c = code[i];
1296
- if (/\s/.test(c)) {
1297
- let s = i; while (i < n && /\s/.test(code[i])) i++;
1298
- push(code.slice(s, i), 0); continue;
1299
- }
1300
- if (c === '/' && code[i + 1] === '/') {
1301
- let s = i; i += 2; while (i < n && code[i] !== '\n') i++;
1302
- push(code.slice(s, i), 5); continue;
1303
- }
1304
- if (c === '/' && code[i + 1] === '*') {
1305
- let s = i; i += 2;
1306
- while (i < n - 1 && !(code[i] === '*' && code[i + 1] === '/')) i++;
1307
- i += 2; push(code.slice(s, i), 5); continue;
1308
- }
1309
- if (c === '#' && isPyBash) {
1310
- let s = i; i++; while (i < n && code[i] !== '\n') i++;
1311
- push(code.slice(s, i), 5); continue;
1312
- }
1313
- if (c === '"' || c === "'" || c === '`') {
1314
- let s = i; const q = c; i++;
1315
- while (i < n) { if (code[i] === '\\' && i + 1 < n) i += 2; else if (code[i] === q) { i++; break; } else i++; }
1316
- push(code.slice(s, i), 3); continue;
1317
- }
1318
- if (isNum(c)) {
1319
- let s = i; while (i < n && /[0-9.xXa-fA-FeEbBoO_]/.test(code[i])) i++;
1320
- push(code.slice(s, i), 4); continue;
1321
- }
1322
- if (isWordStart(c)) {
1323
- let s = i; while (i < n && isWord(code[i])) i++;
1324
- const word = code.slice(s, i);
1325
- let type = 0;
1326
- if (keywords.has(word)) type = 1;
1327
- else { let j = i; while (j < n && /\s/.test(code[j])) j++; if (code[j] === '(') type = 2; }
1328
- push(word, type); continue;
1329
- }
1330
- push(c, 0); i++;
1331
843
  }
1332
- return {
1333
- codeBlock: tokens,
1334
- unified_codeBlock: tokens.map(t => ({ content: t.codeContent, type: HIGHLIGHT_TYPE_MAP[t.highlightType] || 'DEFAULT' }))
1335
- };
1336
- };
1337
-
1338
- /* Re-export LANGUAGE_KEYWORDS dari WABinary/constants.js agar kompatibel dengan baileys export */
1339
- export { LANGUAGE_KEYWORDS } from '../WABinary/constants.js';
1340
-
1341
- //# sourceMappingURL=rich-message-utils.js.map
844
+ }
845
+ exports.prepareRichResponseMessage = prepareRichResponseMessage