ruby-baileys 0.0.0-stage → 1.5.17

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 (296) hide show
  1. package/EXPERIMENTAL.md +350 -0
  2. package/LICENSE +23 -0
  3. package/NOTICE.md +14 -0
  4. package/README.md +6265 -2
  5. package/WAProto/GenerateStatics.sh +3 -0
  6. package/WAProto/WAProto.proto +5479 -0
  7. package/WAProto/fix-imports.js +81 -0
  8. package/WAProto/index.d.ts +16877 -0
  9. package/WAProto/index.js +122159 -0
  10. package/assets/ruby_aspirant.jpg +0 -0
  11. package/assets/ruby_aspirant_4k.jpg +88 -0
  12. package/assets/ruby_aspirant_full.jpg +0 -0
  13. package/assets/ruby_aspirant_render.png +1 -0
  14. package/assets/ruby_aspirants_hd.jpg +0 -0
  15. package/assets/thumb.jpg +0 -0
  16. package/engine-requirements.js +14 -0
  17. package/index.cjs +29 -0
  18. package/index.d.ts +2 -0
  19. package/package.json +178 -4
  20. package/src/Defaults/index.d.ts +153 -0
  21. package/src/Defaults/index.js +165 -0
  22. package/src/MessageBuilder/bot-signature.d.ts +22 -0
  23. package/src/MessageBuilder/bot-signature.js +123 -0
  24. package/src/MessageBuilder/extras.d.ts +396 -0
  25. package/src/MessageBuilder/extras.js +1229 -0
  26. package/src/MessageBuilder/index.d.ts +210 -0
  27. package/src/MessageBuilder/index.js +3521 -0
  28. package/src/MessageBuilder/metaai.d.ts +438 -0
  29. package/src/MessageBuilder/metaai.js +891 -0
  30. package/src/Signal/Group/ciphertext-message.d.ts +10 -0
  31. package/src/Signal/Group/ciphertext-message.js +12 -0
  32. package/src/Signal/Group/group-session-builder.d.ts +8 -0
  33. package/src/Signal/Group/group-session-builder.js +28 -0
  34. package/src/Signal/Group/group_cipher.d.ts +11 -0
  35. package/src/Signal/Group/group_cipher.js +79 -0
  36. package/src/Signal/Group/index.d.ts +12 -0
  37. package/src/Signal/Group/index.js +12 -0
  38. package/src/Signal/Group/keyhelper.d.ts +7 -0
  39. package/src/Signal/Group/keyhelper.js +18 -0
  40. package/src/Signal/Group/sender-chain-key.d.ts +14 -0
  41. package/src/Signal/Group/sender-chain-key.js +26 -0
  42. package/src/Signal/Group/sender-key-distribution-message.d.ts +17 -0
  43. package/src/Signal/Group/sender-key-distribution-message.js +63 -0
  44. package/src/Signal/Group/sender-key-message.d.ts +19 -0
  45. package/src/Signal/Group/sender-key-message.js +66 -0
  46. package/src/Signal/Group/sender-key-name.d.ts +12 -0
  47. package/src/Signal/Group/sender-key-name.js +48 -0
  48. package/src/Signal/Group/sender-key-record.d.ts +13 -0
  49. package/src/Signal/Group/sender-key-record.js +41 -0
  50. package/src/Signal/Group/sender-key-state.d.ts +17 -0
  51. package/src/Signal/Group/sender-key-state.js +84 -0
  52. package/src/Signal/Group/sender-message-key.d.ts +12 -0
  53. package/src/Signal/Group/sender-message-key.js +26 -0
  54. package/src/Signal/libsignal.d.ts +55 -0
  55. package/src/Signal/libsignal.js +429 -0
  56. package/src/Signal/lid-mapping.d.ts +21 -0
  57. package/src/Signal/lid-mapping.js +275 -0
  58. package/src/Socket/Client/index.d.ts +3 -0
  59. package/src/Socket/Client/index.js +3 -0
  60. package/src/Socket/Client/types.d.ts +6 -0
  61. package/src/Socket/Client/types.js +10 -0
  62. package/src/Socket/Client/websocket.d.ts +13 -0
  63. package/src/Socket/Client/websocket.js +54 -0
  64. package/src/Socket/business.d.ts +432 -0
  65. package/src/Socket/business.js +380 -0
  66. package/src/Socket/chats.d.ts +248 -0
  67. package/src/Socket/chats.js +1485 -0
  68. package/src/Socket/communities.d.ts +599 -0
  69. package/src/Socket/communities.js +422 -0
  70. package/src/Socket/groups.d.ts +299 -0
  71. package/src/Socket/groups.js +340 -0
  72. package/src/Socket/index.d.ts +582 -0
  73. package/src/Socket/index.js +48 -0
  74. package/src/Socket/messages-recv.d.ts +371 -0
  75. package/src/Socket/messages-recv.js +1803 -0
  76. package/src/Socket/messages-send.d.ts +391 -0
  77. package/src/Socket/messages-send.js +1575 -0
  78. package/src/Socket/mex.d.ts +2 -0
  79. package/src/Socket/mex.js +45 -0
  80. package/src/Socket/newsletter.d.ts +469 -0
  81. package/src/Socket/newsletter.js +643 -0
  82. package/src/Socket/socket.d.ts +78 -0
  83. package/src/Socket/socket.js +1088 -0
  84. package/src/Store/index.d.ts +4 -0
  85. package/src/Store/index.js +4 -0
  86. package/src/Store/make-in-memory-store.d.ts +63 -0
  87. package/src/Store/make-in-memory-store.js +398 -0
  88. package/src/Store/make-ordered-dictionary.d.ts +13 -0
  89. package/src/Store/make-ordered-dictionary.js +79 -0
  90. package/src/Store/object-repository.d.ts +11 -0
  91. package/src/Store/object-repository.js +24 -0
  92. package/src/Types/Auth.d.ts +2 -0
  93. package/src/Types/Auth.js +2 -0
  94. package/src/Types/Bussines.d.ts +2 -0
  95. package/src/Types/Bussines.js +2 -0
  96. package/src/Types/Call.d.ts +2 -0
  97. package/src/Types/Call.js +2 -0
  98. package/src/Types/Chat.d.ts +3 -0
  99. package/src/Types/Chat.js +25 -0
  100. package/src/Types/Contact.d.ts +2 -0
  101. package/src/Types/Contact.js +2 -0
  102. package/src/Types/Events.d.ts +2 -0
  103. package/src/Types/Events.js +1 -0
  104. package/src/Types/GroupMetadata.d.ts +6 -0
  105. package/src/Types/GroupMetadata.js +7 -0
  106. package/src/Types/Label.d.ts +3 -0
  107. package/src/Types/Label.js +25 -0
  108. package/src/Types/LabelAssociation.d.ts +3 -0
  109. package/src/Types/LabelAssociation.js +7 -0
  110. package/src/Types/Message.d.ts +14 -0
  111. package/src/Types/Message.js +24 -0
  112. package/src/Types/Mex.d.ts +3 -0
  113. package/src/Types/Mex.js +96 -0
  114. package/src/Types/Product.d.ts +2 -0
  115. package/src/Types/Product.js +2 -0
  116. package/src/Types/Signal.d.ts +2 -0
  117. package/src/Types/Signal.js +1 -0
  118. package/src/Types/Socket.d.ts +2 -0
  119. package/src/Types/Socket.js +1 -0
  120. package/src/Types/State.d.ts +6 -0
  121. package/src/Types/State.js +55 -0
  122. package/src/Types/USync.d.ts +2 -0
  123. package/src/Types/USync.js +1 -0
  124. package/src/Types/index.d.ts +14 -0
  125. package/src/Types/index.js +26 -0
  126. package/src/Utils/auth-store.d.ts +30 -0
  127. package/src/Utils/auth-store.js +77 -0
  128. package/src/Utils/auth-utils.d.ts +59 -0
  129. package/src/Utils/auth-utils.js +274 -0
  130. package/src/Utils/browser-utils.d.ts +10 -0
  131. package/src/Utils/browser-utils.js +29 -0
  132. package/src/Utils/business.d.ts +50 -0
  133. package/src/Utils/business.js +227 -0
  134. package/src/Utils/chat-utils.d.ts +409 -0
  135. package/src/Utils/chat-utils.js +847 -0
  136. package/src/Utils/companion-reg-client-utils.d.ts +5 -0
  137. package/src/Utils/companion-reg-client-utils.js +40 -0
  138. package/src/Utils/crypto.d.ts +40 -0
  139. package/src/Utils/crypto.js +111 -0
  140. package/src/Utils/decode-wa-message.d.ts +100 -0
  141. package/src/Utils/decode-wa-message.js +374 -0
  142. package/src/Utils/event-buffer.d.ts +23 -0
  143. package/src/Utils/event-buffer.js +625 -0
  144. package/src/Utils/generics.d.ts +64 -0
  145. package/src/Utils/generics.js +376 -0
  146. package/src/Utils/group-metadata-cache.d.ts +5 -0
  147. package/src/Utils/group-metadata-cache.js +26 -0
  148. package/src/Utils/history.d.ts +76 -0
  149. package/src/Utils/history.js +132 -0
  150. package/src/Utils/html-app.d.ts +19 -0
  151. package/src/Utils/html-app.js +97 -0
  152. package/src/Utils/html-multiplayer.d.ts +10 -0
  153. package/src/Utils/html-multiplayer.js +95 -0
  154. package/src/Utils/identity-change-handler.d.ts +14 -0
  155. package/src/Utils/identity-change-handler.js +49 -0
  156. package/src/Utils/index.d.ts +45 -0
  157. package/src/Utils/index.js +45 -0
  158. package/src/Utils/link-preview-metadata.d.ts +47 -0
  159. package/src/Utils/link-preview-metadata.js +89 -0
  160. package/src/Utils/link-preview.d.ts +13 -0
  161. package/src/Utils/link-preview.js +80 -0
  162. package/src/Utils/logger.d.ts +3 -0
  163. package/src/Utils/logger.js +3 -0
  164. package/src/Utils/lt-hash.d.ts +7 -0
  165. package/src/Utils/lt-hash.js +4 -0
  166. package/src/Utils/make-mutex.d.ts +7 -0
  167. package/src/Utils/make-mutex.js +33 -0
  168. package/src/Utils/message-retry-manager.d.ts +80 -0
  169. package/src/Utils/message-retry-manager.js +256 -0
  170. package/src/Utils/messages-media.d.ts +137 -0
  171. package/src/Utils/messages-media.js +815 -0
  172. package/src/Utils/messages.d.ts +63 -0
  173. package/src/Utils/messages.js +2149 -0
  174. package/src/Utils/modern-messages.d.ts +150 -0
  175. package/src/Utils/modern-messages.js +496 -0
  176. package/src/Utils/native-flow.d.ts +11 -0
  177. package/src/Utils/native-flow.js +116 -0
  178. package/src/Utils/newsletter-status.d.ts +158 -0
  179. package/src/Utils/newsletter-status.js +651 -0
  180. package/src/Utils/noise-handler.d.ts +21 -0
  181. package/src/Utils/noise-handler.js +201 -0
  182. package/src/Utils/offline-node-processor.d.ts +10 -0
  183. package/src/Utils/offline-node-processor.js +34 -0
  184. package/src/Utils/optional-media.d.ts +3 -0
  185. package/src/Utils/optional-media.js +46 -0
  186. package/src/Utils/payment-messages.d.ts +66 -0
  187. package/src/Utils/payment-messages.js +161 -0
  188. package/src/Utils/pre-key-manager.d.ts +26 -0
  189. package/src/Utils/pre-key-manager.js +96 -0
  190. package/src/Utils/process-message.d.ts +61 -0
  191. package/src/Utils/process-message.js +689 -0
  192. package/src/Utils/reporting-utils.d.ts +13 -0
  193. package/src/Utils/reporting-utils.js +257 -0
  194. package/src/Utils/scheduled-message.d.ts +51 -0
  195. package/src/Utils/scheduled-message.js +113 -0
  196. package/src/Utils/sender-key-memory.d.ts +20 -0
  197. package/src/Utils/sender-key-memory.js +52 -0
  198. package/src/Utils/signal.d.ts +83 -0
  199. package/src/Utils/signal.js +194 -0
  200. package/src/Utils/stanza-ack.d.ts +17 -0
  201. package/src/Utils/stanza-ack.js +31 -0
  202. package/src/Utils/status-stickers.d.ts +127 -0
  203. package/src/Utils/status-stickers.js +261 -0
  204. package/src/Utils/sync-action-utils.d.ts +3 -0
  205. package/src/Utils/sync-action-utils.js +45 -0
  206. package/src/Utils/tc-token-utils.d.ts +30 -0
  207. package/src/Utils/tc-token-utils.js +156 -0
  208. package/src/Utils/ttl-cache.d.ts +29 -0
  209. package/src/Utils/ttl-cache.js +121 -0
  210. package/src/Utils/use-mongo-auth-state.d.ts +22 -0
  211. package/src/Utils/use-mongo-auth-state.js +68 -0
  212. package/src/Utils/use-multi-file-auth-state.d.ts +12 -0
  213. package/src/Utils/use-multi-file-auth-state.js +107 -0
  214. package/src/Utils/use-mysql-auth-state.d.ts +20 -0
  215. package/src/Utils/use-mysql-auth-state.js +80 -0
  216. package/src/Utils/use-postgres-auth-state.d.ts +20 -0
  217. package/src/Utils/use-postgres-auth-state.js +80 -0
  218. package/src/Utils/use-redis-auth-state.d.ts +20 -0
  219. package/src/Utils/use-redis-auth-state.js +90 -0
  220. package/src/Utils/use-single-file-auth-state.d.ts +11 -0
  221. package/src/Utils/use-single-file-auth-state.js +109 -0
  222. package/src/Utils/use-sqlite-auth-state.d.ts +13 -0
  223. package/src/Utils/use-sqlite-auth-state.js +121 -0
  224. package/src/Utils/useNekoDBAuth.d.ts +19 -0
  225. package/src/Utils/useNekoDBAuth.js +111 -0
  226. package/src/Utils/username.d.ts +12 -0
  227. package/src/Utils/username.js +59 -0
  228. package/src/Utils/validate-connection.d.ts +44 -0
  229. package/src/Utils/validate-connection.js +208 -0
  230. package/src/Utils/voice-recognition.d.ts +53 -0
  231. package/src/Utils/voice-recognition.js +219 -0
  232. package/src/Voip/audio-feeder.d.ts +25 -0
  233. package/src/Voip/audio-feeder.js +269 -0
  234. package/src/Voip/index.d.ts +89 -0
  235. package/src/Voip/index.js +937 -0
  236. package/src/Voip/relay-transport.d.ts +6 -0
  237. package/src/Voip/relay-transport.js +825 -0
  238. package/src/Voip/signaling.d.ts +12 -0
  239. package/src/Voip/signaling.js +1100 -0
  240. package/src/Voip/types.d.ts +3 -0
  241. package/src/Voip/types.js +13 -0
  242. package/src/Voip/video-feeder.d.ts +25 -0
  243. package/src/Voip/video-feeder.js +269 -0
  244. package/src/Voip/wasm-engine.d.ts +27 -0
  245. package/src/Voip/wasm-engine.js +1308 -0
  246. package/src/Voip/worker-bootstrap.js +1024 -0
  247. package/src/WABinary/constants.d.ts +23 -0
  248. package/src/WABinary/constants.js +1301 -0
  249. package/src/WABinary/decode.d.ts +10 -0
  250. package/src/WABinary/decode.js +262 -0
  251. package/src/WABinary/encode.d.ts +3 -0
  252. package/src/WABinary/encode.js +220 -0
  253. package/src/WABinary/generic-utils.d.ts +77 -0
  254. package/src/WABinary/generic-utils.js +214 -0
  255. package/src/WABinary/index.d.ts +6 -0
  256. package/src/WABinary/index.js +6 -0
  257. package/src/WABinary/jid-utils.d.ts +31 -0
  258. package/src/WABinary/jid-utils.js +98 -0
  259. package/src/WABinary/types.d.ts +2 -0
  260. package/src/WABinary/types.js +1 -0
  261. package/src/WAM/BinaryInfo.d.ts +8 -0
  262. package/src/WAM/BinaryInfo.js +10 -0
  263. package/src/WAM/constants.d.ts +34927 -0
  264. package/src/WAM/constants.js +22853 -0
  265. package/src/WAM/encode.d.ts +2 -0
  266. package/src/WAM/encode.js +149 -0
  267. package/src/WAM/index.d.ts +4 -0
  268. package/src/WAM/index.js +4 -0
  269. package/src/WAUSync/Protocols/USyncContactProtocol.d.ts +37 -0
  270. package/src/WAUSync/Protocols/USyncContactProtocol.js +51 -0
  271. package/src/WAUSync/Protocols/USyncDeviceProtocol.d.ts +23 -0
  272. package/src/WAUSync/Protocols/USyncDeviceProtocol.js +52 -0
  273. package/src/WAUSync/Protocols/USyncDisappearingModeProtocol.d.ts +13 -0
  274. package/src/WAUSync/Protocols/USyncDisappearingModeProtocol.js +27 -0
  275. package/src/WAUSync/Protocols/USyncStatusProtocol.d.ts +13 -0
  276. package/src/WAUSync/Protocols/USyncStatusProtocol.js +38 -0
  277. package/src/WAUSync/Protocols/USyncUsernameProtocol.d.ts +10 -0
  278. package/src/WAUSync/Protocols/USyncUsernameProtocol.js +24 -0
  279. package/src/WAUSync/Protocols/UsyncBotProfileProtocol.d.ts +39 -0
  280. package/src/WAUSync/Protocols/UsyncBotProfileProtocol.js +50 -0
  281. package/src/WAUSync/Protocols/UsyncLIDProtocol.d.ts +15 -0
  282. package/src/WAUSync/Protocols/UsyncLIDProtocol.js +29 -0
  283. package/src/WAUSync/Protocols/index.d.ts +6 -0
  284. package/src/WAUSync/Protocols/index.js +6 -0
  285. package/src/WAUSync/USyncQuery.d.ts +21 -0
  286. package/src/WAUSync/USyncQuery.js +94 -0
  287. package/src/WAUSync/USyncUser.d.ts +17 -0
  288. package/src/WAUSync/USyncUser.js +31 -0
  289. package/src/WAUSync/index.d.ts +4 -0
  290. package/src/WAUSync/index.js +4 -0
  291. package/src/assets/wasm/README.md +76 -0
  292. package/src/assets/wasm/loader.js +5 -0
  293. package/src/assets/wasm/whatsapp.wasm +0 -0
  294. package/src/assets/wasm/worker-modules.js +273 -0
  295. package/src/index.d.ts +49 -0
  296. package/src/index.js +54 -0
@@ -0,0 +1,1229 @@
1
+ /* Elaina Baileys maintained distribution. Upstream notices and license are preserved in LICENSE and NOTICE.md. */
2
+ import { randomUUID } from 'crypto'
3
+ import { AIRich } from './index.js'
4
+ import { extractMessageContent, generateWAMessageFromContent, prepareWAMessageMedia } from '../Utils/messages.js'
5
+ import { checkHtmlApp } from '../Utils/html-app.js'
6
+
7
+ export const AI_RICH_LAYOUTS = Object.freeze([
8
+ 'Single',
9
+ 'HScroll',
10
+ 'ActionRow',
11
+ 'VStack',
12
+ 'Grid',
13
+ 'FlexibleCountGrid',
14
+ 'RichListItem',
15
+ 'AddonAction',
16
+ 'MultipleResponse',
17
+ 'IGSuggestedBloomCard'
18
+ ])
19
+
20
+ export const AI_RICH_PRIMITIVES = Object.freeze([
21
+ 'GenAIMarkdownTextUXPrimitive',
22
+ 'GenAICodeUXPrimitive',
23
+ 'GenATableUXPrimitive',
24
+ 'GenAIMetadataTextPrimitive',
25
+ 'GenAISearchResultPrimitive',
26
+ 'GenAIReelPrimitive',
27
+ 'GenAIPostPrimitive',
28
+ 'GenAIProductItemCardPrimitive',
29
+ 'GenAIImaginePrimitive',
30
+ 'GenAIFollowUpSuggestionPillPrimitive',
31
+ 'FOATextPrimitive',
32
+ 'FOABloksPrimitive',
33
+ 'GenAIImagePrimitive',
34
+ 'GenAIDividerPrimitive',
35
+ 'GenAISpacerPrimitive',
36
+ 'GenAITaskPrimitive',
37
+ 'GenAILatexUXPrimitive',
38
+ 'GenAIBotThinkingStatusPrimitive',
39
+ 'GenAIBotProgressStatusPrimitive',
40
+ 'GenAIMetaSubsQuotaUpsellPrimitive',
41
+ 'GenAIMapPrimitive',
42
+ 'GenAIVideoPrimitive',
43
+ 'GenAIReminderPrimitive',
44
+ 'GenAICommentPrimitive',
45
+ 'GenAICompactEntityPrimitive',
46
+ 'GenAIActionListPrimitive',
47
+ 'GenAISportsWidgetPrimitive',
48
+ 'GenAISearchAdPrimitive',
49
+ 'GenAISearchResultV2Primitive',
50
+ 'GenAISearchPlannerStepsPrimitive',
51
+ 'GenAISearchPlannerStepSnippetPrimitive',
52
+ 'GenAIChainOfThoughtStepPrimitive',
53
+ 'GenAIChainingSuggestionPrimitive',
54
+ 'GenAILocationPermissionPrimitive',
55
+ 'GenAITimestampPlaceholderPrimitive',
56
+ 'GenAIP13NTransparencyPrimitive',
57
+ 'GenAIProfessionalConsentPrimitive',
58
+ 'GenAI3PAccountLinkingUpsellPrimitive',
59
+ 'GenAI3PExtWidgetPrimitive',
60
+ 'GenAIFilePrimitive',
61
+ 'GenAIFileLinkPrimitive',
62
+ 'GenAIFooterActionPrimitive',
63
+ 'GenAIClippyWidgetPrimitive',
64
+ 'GenAIClippyFollowUpPrimitive',
65
+ 'GenAIaeacdsnwHtmlPrimitive',
66
+ 'GenAIQuizPrimitive',
67
+ 'GenAIActionGroupPrimitive',
68
+ 'GenAIFusedComparisonTablePrimitive'
69
+ ])
70
+
71
+ export const AI_RICH_PRIMITIVE_INTERFACE = 'GenAIUXPrimitive'
72
+
73
+ export const AI_RICH_PRIMITIVES_WITHOUT_SCHEMA = Object.freeze([
74
+ 'GenAIClippyWidgetPrimitive',
75
+ 'GenAIClippyFollowUpPrimitive'
76
+ ])
77
+
78
+ export const AI_RICH_ITEMS = Object.freeze([
79
+ 'GenAIMediaItem',
80
+ 'GenAIPlaceDetailsItem',
81
+ 'GenAIPlaceEntityItem',
82
+ 'GenAISocialEntityItem',
83
+ 'GenAIProductEntityItem',
84
+ 'GenAISourcedItem',
85
+ 'GenAITopicLinkItem',
86
+ 'GenAIThreadSurfingItem',
87
+ 'GenAISideBySideSurveyItem',
88
+ 'GenAIIGCoachSuggestedPromptItem',
89
+ 'GenAI3PExtCalendarEventItem',
90
+ 'GenAI3PAccountLinkingBottomsheetAppItem',
91
+ 'GenAIActionListRow',
92
+ 'GenAISportsTeam',
93
+ 'GenAISportsVenue',
94
+ 'GenAISportsGroup',
95
+ 'GenAIContextualSourcesViewModel'
96
+ ])
97
+
98
+ export const AI_RICH_HTML_PRIMITIVE = 'GenAIaeacdsnwHtmlPrimitive'
99
+ export const AI_RICH_HTML_PRIMITIVE_CLASS = 'FOAHtmlPrimitive'
100
+
101
+ export const AI_RICH_INLINE_ENTITIES = Object.freeze([
102
+ 'GenAIInlineLinkItem',
103
+ 'GenAISearchCitationItem',
104
+ 'GenAILatexItem',
105
+ 'GenAIDeeplinkItem'
106
+ ])
107
+
108
+ export const AI_RICH_PRIMITIVES_WEB_RENDERED = Object.freeze([
109
+ 'GenAIMarkdownTextUXPrimitive',
110
+ 'GenAICodeUXPrimitive',
111
+ 'GenAILatexUXPrimitive',
112
+ 'GenATableUXPrimitive',
113
+ 'GenAIMetadataTextPrimitive',
114
+ 'GenAIDividerPrimitive',
115
+ 'GenAISpacerPrimitive',
116
+ 'GenAIImagePrimitive',
117
+ 'GenAIImaginePrimitive',
118
+ 'GenAIReelPrimitive',
119
+ 'GenAIPostPrimitive',
120
+ 'GenAIProductItemCardPrimitive',
121
+ 'GenAISearchResultPrimitive',
122
+ 'GenAIBotThinkingStatusPrimitive',
123
+ 'GenAIBotProgressStatusPrimitive',
124
+ 'GenAIMetaSubsQuotaUpsellPrimitive',
125
+ 'FOATextPrimitive',
126
+ 'FOABloksPrimitive'
127
+ ])
128
+
129
+ export const DividerType = Object.freeze({ DOT: 'DOT', HORIZONTAL_LINE: 'HORIZONTAL_LINE' })
130
+ export const ImagineType = Object.freeze({ IMAGINE: 'IMAGINE', ANIMATE: 'ANIMATE', MEMU: 'MEMU' })
131
+ export const ImagineStatus = Object.freeze({ GENERATING: 'GENERATING', READY: 'READY', FAILED: 'FAILED' })
132
+ export const ThinkingIcon = Object.freeze({ THINKING: 'THINKING', WEB_SEARCH: 'WEB_SEARCH', META_SEARCH: 'META_SEARCH' })
133
+ export const TaskStatus = Object.freeze({ PENDING: 'PENDING', RUNNING: 'RUNNING', DONE: 'DONE' })
134
+ export const FooterActionType = Object.freeze({
135
+ OPEN_FULL_VIEW: 'OPEN_FULL_VIEW',
136
+ DOWNLOAD_MEDIA: 'DOWNLOAD_MEDIA',
137
+ GENERATE_IMAGE: 'GENERATE_IMAGE',
138
+ CANCEL_REASONING: 'CANCEL_REASONING',
139
+ UPGRADE_TO_SUBS: 'UPGRADE_TO_SUBS',
140
+ COPY_LINK: 'COPY_LINK',
141
+ REMIX_MEDIA: 'REMIX_MEDIA',
142
+ USE_TEMPLATE: 'USE_TEMPLATE'
143
+ })
144
+ export const AddonActionType = Object.freeze({
145
+ COPY_TO_CLIPBOARD: 'COPY_TO_CLIPBOARD',
146
+ SEND_TO_CHAT: 'SEND_TO_CHAT',
147
+ FOLLOW_UP_PROMPT: 'FOLLOW_UP_PROMPT'
148
+ })
149
+
150
+ const trimEmpty = (object) => {
151
+ for (const key of Object.keys(object)) {
152
+ if (object[key] === undefined) {
153
+ delete object[key]
154
+ }
155
+ }
156
+ return object
157
+ }
158
+
159
+ export const dividerSection = ({ dividerType = DividerType.HORIZONTAL_LINE } = {}) =>
160
+ AIRich.newLayout('Single', {
161
+ divider_type: dividerType,
162
+ __typename: 'GenAIDividerPrimitive'
163
+ })
164
+
165
+ export const spacerSection = ({ spacing = 1 } = {}) =>
166
+ AIRich.newLayout('Single', {
167
+ spacing,
168
+ __typename: 'GenAISpacerPrimitive'
169
+ })
170
+
171
+ export const imageSection = (url, { fallbackUrl, previewUrl, previewFallbackUrl } = {}) =>
172
+ AIRich.newLayout('Single', {
173
+ full_image: { url, url_fallback: fallbackUrl ?? '' },
174
+ preview_image: { url: previewUrl ?? url, url_fallback: previewFallbackUrl ?? fallbackUrl ?? '' },
175
+ __typename: 'GenAIImagePrimitive'
176
+ })
177
+
178
+ export const taskSection = ({ taskId, title = '', subtitle = '', status = TaskStatus.PENDING }) => {
179
+ if (!taskId) {
180
+ throw new TypeError('taskSection requires taskId, an empty id makes WhatsApp drop the item')
181
+ }
182
+ return AIRich.newLayout('Single', {
183
+ task_id: String(taskId),
184
+ title,
185
+ subtitle,
186
+ status,
187
+ __typename: 'GenAITaskPrimitive'
188
+ })
189
+ }
190
+
191
+ export const latexSection = (expression, { image, width = 100, height = 100, fontHeight = 83.333333333333, padding = 15 } = {}) =>
192
+ AIRich.newLayout('Single', {
193
+ latex_expression: expression,
194
+ ...(image
195
+ ? {
196
+ latex_image: { url: image, width, height },
197
+ font_height: fontHeight,
198
+ padding
199
+ }
200
+ : {}),
201
+ __typename: 'GenAILatexUXPrimitive'
202
+ })
203
+
204
+ const statusSection = (typename) => (title, { icon = ThinkingIcon.THINKING, inProgress = true, metaSearchApps = [], thoughtDurationSec } = {}) =>
205
+ AIRich.newLayout('Single', trimEmpty({
206
+ title,
207
+ icon,
208
+ is_in_progress: inProgress,
209
+ meta_search_apps: metaSearchApps,
210
+ thought_duration_sec: thoughtDurationSec === undefined ? undefined : Number(thoughtDurationSec),
211
+ __typename: typename
212
+ }))
213
+
214
+ export const thinkingSection = statusSection('GenAIBotThinkingStatusPrimitive')
215
+ export const progressSection = statusSection('GenAIBotProgressStatusPrimitive')
216
+
217
+ export const lockHeight = (height) => {
218
+ const px = Number(height)
219
+ if (!Number.isFinite(px) || px <= 0) {
220
+ throw new TypeError('height must be a positive number of pixels')
221
+ }
222
+ return '<style>html,body{margin:0;padding:0;height:' + px + 'px;max-height:' + px + 'px;overflow:hidden}'
223
+ + '#__wrap{height:' + px + 'px;overflow-y:auto;-webkit-overflow-scrolling:touch;touch-action:pan-y}</style>'
224
+ + '<script>document.addEventListener("DOMContentLoaded",function(){'
225
+ + 'var w=document.createElement("div");w.id="__wrap";'
226
+ + 'while(document.body.firstChild)w.appendChild(document.body.firstChild);'
227
+ + 'document.body.appendChild(w)});<' + '/script>'
228
+ }
229
+
230
+ export const HTML_APP_BRIDGE = 'AndroidBridge'
231
+
232
+ export const dataUri = (bytes, mimetype) => {
233
+ if (typeof mimetype !== 'string' || !mimetype.includes('/')) {
234
+ throw new TypeError('dataUri needs a mimetype like video/mp4')
235
+ }
236
+ const buffer = Buffer.isBuffer(bytes) ? bytes : Buffer.from(bytes)
237
+ if (!buffer.length) {
238
+ throw new TypeError('dataUri needs a non-empty buffer')
239
+ }
240
+ return 'data:' + mimetype + ';base64,' + buffer.toString('base64')
241
+ }
242
+
243
+ const MEDIA_TAGS = new Set(['video', 'audio', 'img'])
244
+
245
+ export const htmlMedia = (bytes, { mimetype, tag = 'video', label = 'Tap untuk memuat media', poster, id = 'm' + randomUUID().slice(0, 8), attributes = '' } = {}) => {
246
+ if (!MEDIA_TAGS.has(tag)) {
247
+ throw new TypeError('htmlMedia tag must be one of ' + [...MEDIA_TAGS].join(', '))
248
+ }
249
+ if (typeof id !== 'string' || !/^[A-Za-z][A-Za-z0-9_-]*$/.test(id)) {
250
+ throw new TypeError('htmlMedia id must start with a letter and stay alphanumeric')
251
+ }
252
+
253
+ const uri = dataUri(bytes, mimetype)
254
+ const controls = tag === 'img' ? '' : ' controls preload="none" playsinline'
255
+ const cover = poster && tag === 'video' ? ' poster="' + String(poster).replace(/"/g, '&quot;') + '"' : ''
256
+ const json = JSON.stringify(uri).replace(/</g, '\\u003c').replace(/>/g, '\\u003e')
257
+
258
+ return '<' + tag + ' id="' + id + '"' + controls + cover + (attributes ? ' ' + attributes : '') + '></' + tag + '>'
259
+ + '<button id="' + id + '_go" type="button">' + String(label).replace(/[<>&]/g, c => ({ '<': '&lt;', '>': '&gt;', '&': '&amp;' })[c]) + '</button>'
260
+ + '<script>(function(){'
261
+ + 'var src=' + json + ',el=document.getElementById(' + JSON.stringify(id) + '),go=document.getElementById(' + JSON.stringify(id + '_go') + ');'
262
+ + 'go.addEventListener("click",function(){'
263
+ + 'if(el.src)return;el.src=src;go.disabled=true;go.textContent="\\u2713";'
264
+ + 'if(el.play)el.play().catch(function(){})});'
265
+ + '})();<' + '/script>'
266
+ }
267
+
268
+ export const scrollControls = ({ target = '#__wrap', step = 140, holdMs = 70, size = 36, side = 'right', inset = 6 } = {}) => {
269
+ const px = Number(step)
270
+ const hold = Number(holdMs)
271
+ const box = Number(size)
272
+ const edge = Number(inset)
273
+ if (typeof target !== 'string' || target.trim() === '') {
274
+ throw new TypeError('scrollControls target must be a CSS selector')
275
+ }
276
+ if (!Number.isFinite(px) || px <= 0) {
277
+ throw new TypeError('scrollControls step must be a positive number of pixels')
278
+ }
279
+ if (!Number.isFinite(hold) || hold < 16) {
280
+ throw new TypeError('scrollControls holdMs must be at least 16')
281
+ }
282
+ if (!Number.isFinite(box) || box < 24) {
283
+ throw new TypeError('scrollControls size must be at least 24 pixels')
284
+ }
285
+ if (side !== 'right' && side !== 'left') {
286
+ throw new TypeError('scrollControls side must be "right" or "left"')
287
+ }
288
+ if (!Number.isFinite(edge) || edge < 0) {
289
+ throw new TypeError('scrollControls inset must not be negative')
290
+ }
291
+ return '<style>'
292
+ + '#__sc{position:fixed;' + side + ':' + edge + 'px;bottom:' + edge + 'px;z-index:2147483647;'
293
+ + 'display:none;flex-direction:column;gap:' + Math.round(box / 6) + 'px}'
294
+ + '#__sc button{width:' + box + 'px;height:' + box + 'px;padding:0;border:0;'
295
+ + 'border-radius:' + Math.round(box / 4) + 'px;background:rgba(0,0,0,.6);color:#fff;'
296
+ + 'font:' + Math.round(box / 2) + 'px/1 system-ui;touch-action:none;cursor:pointer;'
297
+ + '-webkit-tap-highlight-color:transparent}'
298
+ + '#__sc button:disabled{opacity:.3}'
299
+ + '</style>'
300
+ + '<script>(function(){'
301
+ + 'var STEP=' + px + ',HOLD=' + hold + ',SEL=' + JSON.stringify(target) + ';'
302
+ + 'var box,up,down,timer=null;'
303
+ + 'function pane(){return document.querySelector(SEL)||document.scrollingElement||document.body}'
304
+ + 'function room(){var p=pane();return p?p.scrollHeight-p.clientHeight:0}'
305
+ + 'function sync(){if(!box)return;var p=pane(),r=room();'
306
+ + 'box.style.display=r>4?"flex":"none";if(r<=4)return;'
307
+ + 'up.disabled=p.scrollTop<=0;down.disabled=p.scrollTop>=r-1}'
308
+ + 'function move(by){var p=pane();if(!p)return;p.scrollTop+=by;sync()}'
309
+ + 'function stop(){if(timer!==null){clearInterval(timer);timer=null}}'
310
+ + 'function press(e,by){e.preventDefault();e.stopPropagation();stop();move(by);'
311
+ + 'timer=setInterval(function(){move(by)},HOLD)}'
312
+ + 'function make(label,by){var b=document.createElement("button");b.type="button";'
313
+ + 'b.textContent=label;b.setAttribute("aria-label",by<0?"scroll up":"scroll down");'
314
+ + 'b.addEventListener("touchstart",function(e){press(e,by)},{passive:false});'
315
+ + 'b.addEventListener("touchend",function(e){e.preventDefault();stop()},{passive:false});'
316
+ + 'b.addEventListener("touchcancel",stop);'
317
+ + 'b.addEventListener("mousedown",function(e){press(e,by)});'
318
+ + 'b.addEventListener("mouseup",stop);b.addEventListener("mouseleave",stop);'
319
+ + 'b.addEventListener("click",function(e){e.preventDefault();e.stopPropagation()});'
320
+ + 'return b}'
321
+ + 'function start(){if(box)return;'
322
+ + 'box=document.createElement("div");box.id="__sc";'
323
+ + 'up=make("\\u25b2",-STEP);down=make("\\u25bc",STEP);'
324
+ + 'box.appendChild(up);box.appendChild(down);document.body.appendChild(box);'
325
+ + 'var p=pane();if(p&&p.addEventListener)p.addEventListener("scroll",sync,{passive:true});'
326
+ + 'if(typeof ResizeObserver==="function"){try{new ResizeObserver(sync).observe(document.body)}catch(e){}}'
327
+ + 'addEventListener("load",sync);sync()}'
328
+ + 'if(document.readyState==="loading")document.addEventListener("DOMContentLoaded",start);else start();'
329
+ + 'window.__scrollControls={by:move,sync:sync};'
330
+ + '})();<' + '/script>'
331
+ }
332
+
333
+ export const autoHeight = ({ min = 60, max = 900, settleMs = 120, maxReports = 24 } = {}) => {
334
+ const low = Number(min)
335
+ const high = Number(max)
336
+ const wait = Number(settleMs)
337
+ const cap = Number(maxReports)
338
+ if (!Number.isFinite(low) || low <= 0) {
339
+ throw new TypeError('autoHeight min must be a positive number of pixels')
340
+ }
341
+ if (!Number.isFinite(high) || high <= low) {
342
+ throw new TypeError('autoHeight max must be greater than min')
343
+ }
344
+ if (!Number.isFinite(wait) || wait < 0) {
345
+ throw new TypeError('autoHeight settleMs must not be negative')
346
+ }
347
+ if (!Number.isFinite(cap) || cap < 1) {
348
+ throw new TypeError('autoHeight maxReports must be at least 1')
349
+ }
350
+ return '<style>html,body{margin:0;padding:0}</style>'
351
+ + '<script>(function(){'
352
+ + 'var LOW=' + low + ',HIGH=' + high + ',WAIT=' + wait + ',CAP=' + cap + ';'
353
+ + 'var sent=[],timer=null,stopped=false;'
354
+ + 'function measure(){var d=document.documentElement,b=document.body;'
355
+ + 'var h=Math.max(d?d.scrollHeight:0,b?b.scrollHeight:0,b?b.offsetHeight:0);'
356
+ + 'return Math.min(HIGH,Math.max(LOW,Math.ceil(h)))}'
357
+ + 'function report(){if(stopped)return;var h=measure();'
358
+ + 'if(sent.length&&sent[sent.length-1]===h)return;'
359
+ + 'if(sent.length>=2&&sent[sent.length-2]===h){stopped=true;return}'
360
+ + 'sent.push(h);if(sent.length>=CAP)stopped=true;'
361
+ + 'try{window.AndroidBridge.updateSize(h)}catch(e){stopped=true}}'
362
+ + 'function schedule(){if(stopped)return;'
363
+ + 'if(timer!==null)clearTimeout(timer);timer=setTimeout(function(){timer=null;report()},WAIT)}'
364
+ + 'function start(){report();'
365
+ + 'if(typeof ResizeObserver==="function"&&document.body){'
366
+ + 'try{new ResizeObserver(schedule).observe(document.body)}catch(e){}}'
367
+ + 'addEventListener("load",schedule)}'
368
+ + 'if(document.readyState==="loading")document.addEventListener("DOMContentLoaded",start);else start();'
369
+ + '})();<' + '/script>'
370
+ }
371
+
372
+ export const htmlSection = (html, { trustedSources = [], height, typename = AI_RICH_HTML_PRIMITIVE } = {}) => {
373
+ if (typeof html !== 'string' || html.trim() === '') {
374
+ throw new TypeError('htmlSection requires a non-empty HTML string')
375
+ }
376
+ if (!Array.isArray(trustedSources)) {
377
+ throw new TypeError('htmlSection trustedSources must be an array of strings')
378
+ }
379
+ if (typeof typename !== 'string' || typename.trim() === '') {
380
+ throw new TypeError('htmlSection typename must be a non-empty string')
381
+ }
382
+ return AIRich.newLayout('Single', {
383
+ payload: height === undefined ? html : lockHeight(height) + html,
384
+ trusted_sources: trustedSources.map(String),
385
+ __typename: typename
386
+ })
387
+ }
388
+
389
+ export const sendHtmlApp = async (sock, jid, html, { title = '', label, trustedSources, height, autoHeight: auto, scrollButtons, typename, id, bypassDownload = false, guard = 'warn', ...options } = {}) => {
390
+ if (!sock) {
391
+ throw new TypeError('sendHtmlApp requires a socket as the first argument')
392
+ }
393
+ if (!jid) {
394
+ throw new TypeError('sendHtmlApp requires a target jid')
395
+ }
396
+ if (auto && height !== undefined) {
397
+ throw new TypeError('sendHtmlApp takes either height or autoHeight, not both')
398
+ }
399
+
400
+ if (auto) {
401
+ html = autoHeight(auto === true ? undefined : auto) + html
402
+ }
403
+
404
+ if (scrollButtons) {
405
+ html += scrollControls(scrollButtons === true ? undefined : scrollButtons)
406
+ }
407
+
408
+ if (guard !== false) {
409
+ const report = checkHtmlApp(html, { height })
410
+ if (!report.ok && guard === true) {
411
+ throw new TypeError('this html app will not behave in the WebView:\n ' + report.problems.join('\n '))
412
+ }
413
+ if (guard === 'warn') {
414
+ for (const problem of report.problems) {
415
+ sock.logger?.warn({ jid }, 'html app: ' + problem)
416
+ }
417
+ for (const warning of report.warnings) {
418
+ sock.logger?.warn({ jid }, 'html app: ' + warning)
419
+ }
420
+ }
421
+ }
422
+
423
+ const rich = new AIRich(sock)
424
+
425
+ if (title) {
426
+ rich.setTitle(title)
427
+ }
428
+
429
+ rich._addContent(
430
+ htmlSection(html, { trustedSources, height, ...(typename ? { typename } : {}) }),
431
+ label ? { messageType: 2, messageText: String(label) } : undefined,
432
+ id ? { id } : {}
433
+ )
434
+
435
+ return rich.send(jid, { bypassDownload, ...options })
436
+ }
437
+
438
+ export const HTML_MIME_TYPE = 'text/html'
439
+
440
+ export const sendHtmlDocument = async (sock, jid, html, { fileName = 'app.html', caption, ...options } = {}) => {
441
+ if (!sock) {
442
+ throw new TypeError('sendHtmlDocument requires a socket as the first argument')
443
+ }
444
+ if (!jid) {
445
+ throw new TypeError('sendHtmlDocument requires a target jid')
446
+ }
447
+ if (typeof html !== 'string' || html.trim() === '') {
448
+ throw new TypeError('sendHtmlDocument requires a non-empty HTML string')
449
+ }
450
+ const name = String(fileName)
451
+ if (!/\.html?$/i.test(name)) {
452
+ throw new TypeError('sendHtmlDocument fileName must end with .html or .htm')
453
+ }
454
+ return sock.sendMessage(jid, {
455
+ document: Buffer.from(html, 'utf-8'),
456
+ mimetype: HTML_MIME_TYPE,
457
+ fileName: name,
458
+ ...(caption ? { caption: String(caption) } : {}),
459
+ ...options
460
+ })
461
+ }
462
+
463
+ const fileArtifact = (typename) => (url, { title = '', fileExtension = 'html', fileLength = 0, pageCount, previewImage } = {}) => {
464
+ if (typeof url !== 'string' || url.trim() === '') {
465
+ throw new TypeError('a file section requires a non-empty url')
466
+ }
467
+ return AIRich.newLayout('Single', trimEmpty({
468
+ title: String(title),
469
+ url,
470
+ file_extension: String(fileExtension).replace(/^\./, ''),
471
+ file_length: Number(fileLength) || 0,
472
+ page_count: pageCount === undefined ? undefined : Number(pageCount) || 0,
473
+ preview_image: previewImage,
474
+ __typename: typename
475
+ }))
476
+ }
477
+
478
+ export const fileSection = fileArtifact('GenAIFilePrimitive')
479
+ export const fileLinkSection = fileArtifact('GenAIFileLinkPrimitive')
480
+
481
+ const b64 = (value) => (value === undefined || value === null ? undefined : Buffer.isBuffer(value) || value instanceof Uint8Array ? Buffer.from(value).toString('base64') : String(value))
482
+
483
+ export const botMediaMetadata = (documentMessage) => {
484
+ if (!documentMessage) {
485
+ throw new TypeError('botMediaMetadata requires a prepared document message')
486
+ }
487
+ return trimEmpty({
488
+ fileSha256: b64(documentMessage.fileSha256),
489
+ mediaKey: b64(documentMessage.mediaKey),
490
+ fileEncSha256: b64(documentMessage.fileEncSha256),
491
+ directPath: documentMessage.directPath,
492
+ mediaKeyTimestamp: documentMessage.mediaKeyTimestamp ? Number(documentMessage.mediaKeyTimestamp) : undefined,
493
+ mimetype: documentMessage.mimetype
494
+ })
495
+ }
496
+
497
+ export const prepareFileArtifact = async (sock, content, { mimetype = HTML_MIME_TYPE, fileName = 'app.html', title, id } = {}) => {
498
+ if (!sock) {
499
+ throw new TypeError('prepareFileArtifact requires a socket as the first argument')
500
+ }
501
+ const body = typeof content === 'string' ? Buffer.from(content, 'utf-8') : content
502
+ if (!Buffer.isBuffer(body) && !(body instanceof Uint8Array)) {
503
+ throw new TypeError('prepareFileArtifact content must be a string or a buffer')
504
+ }
505
+
506
+ const prepared = await prepareWAMessageMedia(
507
+ { document: Buffer.from(body), mimetype, fileName },
508
+ { upload: sock.waUploadToServer }
509
+ )
510
+ const documentMessage = prepared.documentMessage
511
+ const media = botMediaMetadata(documentMessage)
512
+ const mediaId = id ?? randomUUID()
513
+
514
+ return {
515
+ mediaId,
516
+ media,
517
+ documentMessage,
518
+ section: AIRich.newLayout('Single', {
519
+ title: String(title || fileName),
520
+ url: documentMessage.url ?? '',
521
+ file_extension: fileName.includes('.') ? fileName.split('.').pop() : '',
522
+ file_length: Number(documentMessage.fileLength) || 0,
523
+ preview_image: {
524
+ media_id: mediaId,
525
+ mime_type: mimetype,
526
+ url: documentMessage.url ?? '',
527
+ url_fallback: ''
528
+ },
529
+ __typename: 'GenAIFilePrimitive'
530
+ }),
531
+ mediaDetails: { id: mediaId, previewMedia: media, highResMedia: media }
532
+ }
533
+ }
534
+
535
+ export const sendHtmlArtifact = async (sock, jid, html, { fileName = 'app.html', title, label, id, bypassDownload = false, ...options } = {}) => {
536
+ if (!jid) {
537
+ throw new TypeError('sendHtmlArtifact requires a target jid')
538
+ }
539
+
540
+ const artifact = await prepareFileArtifact(sock, html, { mimetype: HTML_MIME_TYPE, fileName, title, id })
541
+ const rich = new AIRich(sock)
542
+
543
+ if (title) {
544
+ rich.setTitle(title)
545
+ }
546
+ rich.setBotMetadata({ unifiedResponseMutation: { mediaDetailsMetadataList: [artifact.mediaDetails] } })
547
+ rich._addContent(
548
+ artifact.section,
549
+ label ? { messageType: 2, messageText: String(label) } : undefined,
550
+ {}
551
+ )
552
+
553
+ const message = await rich.send(jid, { bypassDownload, ...options })
554
+ return { message, mediaId: artifact.mediaId }
555
+ }
556
+
557
+ export const SourceProvider = Object.freeze({ UNKNOWN: 0, BING: 1, GOOGLE: 2, SUPPORT: 3, OTHER: 4 })
558
+
559
+ export const botSourcesMetadata = (sources) => {
560
+ if (!Array.isArray(sources) || sources.length === 0) {
561
+ throw new TypeError('botSourcesMetadata requires a non-empty array of sources')
562
+ }
563
+ return {
564
+ sources: sources.map((source, index) => {
565
+ if (!source || typeof source !== 'object' || Array.isArray(source)) {
566
+ throw new TypeError('each source must be a plain object')
567
+ }
568
+ if (!source.url) {
569
+ throw new TypeError('each source requires a url')
570
+ }
571
+ return trimEmpty({
572
+ provider: source.provider ?? SourceProvider.OTHER,
573
+ sourceProviderUrl: String(source.url),
574
+ sourceTitle: source.title === undefined ? undefined : String(source.title),
575
+ sourceQuery: source.query === undefined ? undefined : String(source.query),
576
+ faviconCdnUrl: source.favicon === undefined ? undefined : String(source.favicon),
577
+ thumbnailCdnUrl: source.thumbnail === undefined ? undefined : String(source.thumbnail),
578
+ citationNumber: source.citation === undefined ? index + 1 : Number(source.citation)
579
+ })
580
+ })
581
+ }
582
+ }
583
+
584
+ export const EMBEDDED_SCREEN_PRESENTATION = Object.freeze({
585
+ HALF_HEIGHT: 'HALF_HEIGHT',
586
+ FULL_HEIGHT: 'FULL_HEIGHT'
587
+ })
588
+
589
+ export const EMBEDDED_SCREEN_TYPENAME = 'FOAUnifiedResponseEmbeddedScreen'
590
+ export const EMBEDDED_SCREEN_TABBED_TYPENAME = 'FOAEmbeddedScreenContentTabbed'
591
+ export const EMBEDDED_SCREEN_SINGLE_TYPENAME = 'FOAEmbeddedSingleScreen'
592
+ export const EMBEDDED_SCREEN_CONTEXTUAL_TYPENAME = 'GenAIEmbeddedContextualScreenContent'
593
+ export const EMBEDDED_SCREEN_TAB_TYPENAME = 'FOAUnifiedResponseTab'
594
+ export const AI_RICH_SECTION_TYPENAME = AIRich.SECTION_TYPENAME
595
+ export const AI_RICH_UNIFIED_RESPONSE_TYPENAME = AIRich.UNIFIED_RESPONSE_TYPENAME
596
+ export const AI_RICH_UNIFIED_RESPONSE_TYPENAME_APP = 'GenAIUnifiedResponse'
597
+ export const AI_RICH_NESTED_UNIFIED_RESPONSE_TYPENAME = 'GenAINestedUnifiedResponse'
598
+
599
+ export const AI_RICH_NODES = Object.freeze([
600
+ 'GenAIQuizQuestion',
601
+ 'GenAIActionButton',
602
+ 'GenAIFusedComparisonColumn',
603
+ 'GenAIFusedComparisonRow',
604
+ 'GenAIFusedComparisonCTA',
605
+ 'FOAEmbeddedSingleScreen',
606
+ 'GenAI3PAccountLinkingBottomsheet',
607
+ 'GenAI3PExtArtifactConfirmation',
608
+ 'GenAI3PExtCalendarAttendee',
609
+ 'GenAI3PExtCalendarDateSection',
610
+ 'GenAI3PExtCalendarEventList',
611
+ 'GenAI3PExtConnectorPendingToolCall',
612
+ 'GenAI3PExtWidgetCTA',
613
+ 'GenAI3PExtWidgetStandardHeader',
614
+ 'GenAI3PExtWidgetToast',
615
+ 'GenAIAmericanFootballGameContent',
616
+ 'GenAIChainOfThoughtStepMarkdownText',
617
+ 'GenAICodeBlockData',
618
+ 'GenAIEmbeddedContextualScreenContent',
619
+ 'GenAIImaginePrimitiveStatusUpdate',
620
+ 'GenAIImagineThumbnail',
621
+ 'GenAIMapItemLocation',
622
+ 'GenAIMapItemStaticMap',
623
+ 'GenAIMarketplaceMetadata',
624
+ 'GenAIMetaSubsQuotaUpsellButton',
625
+ 'GenAINestedUnifiedResponse',
626
+ 'GenAIP13nUiSignalValue',
627
+ 'GenAIP13nUiSignals',
628
+ 'GenAIPlaceDetailsItemCategory',
629
+ 'GenAIPlaceDetailsItemOpeningHours',
630
+ 'GenAIPlaceDetailsItemOpeningHoursTime',
631
+ 'GenAIPlaceDetailsItemRating',
632
+ 'GenAISearchPlannerInstruction',
633
+ 'GenAISearchPlannerStep',
634
+ 'GenAISoccerGameContent',
635
+ 'GenAISportsTeamIcon',
636
+ 'GenAISportsTeamRecord',
637
+ 'GenAIStepDeepResearchEntry',
638
+ 'GenAIStepSkillEntry',
639
+ 'GenAIStepSubagentEntry',
640
+ 'GenAIStepThoughtEntry',
641
+ 'GenAIStepWebLinkSource',
642
+ 'GenAITableCell',
643
+ 'GenAITableRow',
644
+ 'GenAITextInlineEntity',
645
+ 'GenAIThreadSurfingPrompt',
646
+ 'GenAIUnifiedResponse',
647
+ 'GenAIVideoMusicClipInfo',
648
+ 'GenAIVideoTextToSpeechInfo',
649
+ 'GenAIWidgetPlacesItemAddress'
650
+ ])
651
+
652
+ const readTypename = (label, value) => {
653
+ if (value === undefined) {
654
+ return undefined
655
+ }
656
+ if (typeof value !== 'string' || value.trim() === '') {
657
+ throw new TypeError(label + ' typename must be a non-empty string')
658
+ }
659
+ return value
660
+ }
661
+
662
+ export const embeddedTab = ({ id, header, tabHeader, sections = [], typename } = {}) => {
663
+ if (!Array.isArray(sections)) {
664
+ throw new TypeError('embeddedTab sections must be an array of sections')
665
+ }
666
+ return trimEmpty({
667
+ __typename: readTypename('embeddedTab', typename),
668
+ id: id ?? randomUUID(),
669
+ header,
670
+ tab_header: tabHeader,
671
+ sections
672
+ })
673
+ }
674
+
675
+ export const embeddedTabbedContent = (tabs, { typename = EMBEDDED_SCREEN_TABBED_TYPENAME } = {}) => {
676
+ if (!Array.isArray(tabs)) {
677
+ throw new TypeError('embeddedTabbedContent tabs must be an array of tabs')
678
+ }
679
+ return { __typename: readTypename('embeddedTabbedContent', typename), tabs }
680
+ }
681
+
682
+ export const AI_RICH_RESPONSE_KEYS = Object.freeze([
683
+ 'chunk_id', 'surface', 'product', 'thread_type', 'is_forwarded', 'is_optimistic', 'nested_responses'
684
+ ])
685
+
686
+ export const embeddedScreen = ({ id, title, content, tabs, tabsTypename, typename, header, body, artifacts, steps, stepEntries, sources, pollId } = {}) => {
687
+ if (content !== undefined && !Array.isArray(content)) {
688
+ throw new TypeError('embeddedScreen content must be an array of sections')
689
+ }
690
+ if (tabs !== undefined && !Array.isArray(tabs)) {
691
+ throw new TypeError('embeddedScreen tabs must be an array of tabs')
692
+ }
693
+ for (const [name, value] of [['artifacts', artifacts], ['steps', steps], ['stepEntries', stepEntries], ['sources', sources]]) {
694
+ if (value !== undefined && !Array.isArray(value)) {
695
+ throw new TypeError('embeddedScreen ' + name + ' must be an array')
696
+ }
697
+ }
698
+ const entries = content === undefined ? [] : [...content]
699
+ if (tabs !== undefined) {
700
+ entries.push(embeddedTabbedContent(tabs, { typename: tabsTypename ?? EMBEDDED_SCREEN_TABBED_TYPENAME }))
701
+ }
702
+ return trimEmpty({
703
+ __typename: readTypename('embeddedScreen', typename),
704
+ id: id ?? randomUUID(),
705
+ title: title === undefined ? undefined : String(title),
706
+ content: entries.length ? entries : undefined,
707
+ header,
708
+ body,
709
+ artifacts,
710
+ steps,
711
+ step_entries: stepEntries,
712
+ sources,
713
+ poll_id: pollId
714
+ })
715
+ }
716
+
717
+ export const readEmbeddedTabs = (screen) => {
718
+ const tabs = []
719
+ if (Array.isArray(screen?.tabs)) {
720
+ tabs.push(...screen.tabs)
721
+ }
722
+ for (const entry of Array.isArray(screen?.content) ? screen.content : []) {
723
+ if (Array.isArray(entry?.tabs)) {
724
+ tabs.push(...entry.tabs)
725
+ }
726
+ }
727
+ return tabs
728
+ }
729
+
730
+ export const readEmbeddedSections = (screen) => {
731
+ const sections = []
732
+ for (const entry of Array.isArray(screen?.content) ? screen.content : []) {
733
+ if (entry?.view_model) {
734
+ sections.push(entry)
735
+ continue
736
+ }
737
+ for (const section of Array.isArray(entry?.sections) ? entry.sections : []) {
738
+ sections.push(section)
739
+ }
740
+ }
741
+ for (const tab of readEmbeddedTabs(screen)) {
742
+ for (const section of Array.isArray(tab?.sections) ? tab.sections : []) {
743
+ sections.push(section)
744
+ }
745
+ }
746
+ return sections
747
+ }
748
+
749
+ export const footerActionSection = (actionType, { buttonText = '', url } = {}) => {
750
+ if (!Object.values(FooterActionType).includes(actionType)) {
751
+ throw new TypeError('footerActionSection actionType must be one of ' + Object.values(FooterActionType).join(', '))
752
+ }
753
+ return AIRich.newLayout('Single', {
754
+ cta_text: String(buttonText),
755
+ cta_type: actionType,
756
+ ...(url ? { cta_url: String(url) } : {}),
757
+ __typename: 'GenAIFooterActionPrimitive'
758
+ })
759
+ }
760
+
761
+ export const BLOKS_A2UI_TYPE = 'im_a2ui'
762
+ export const BLOKS_A2UI_REPLY_ACTION = 'a2ui_reply_action'
763
+ export const BLOKS_A2UI_SUPPORTED_ELEMENTS = Object.freeze(['info_card', 'list_card'])
764
+
765
+ const bloksPayloadData = (data) => {
766
+ if (data === undefined || data === null) {
767
+ return ''
768
+ }
769
+ if (typeof data === 'string') {
770
+ return data
771
+ }
772
+ if (typeof data !== 'object' || Array.isArray(data)) {
773
+ throw new TypeError('bloks data must be a JSON string or a plain object')
774
+ }
775
+ return JSON.stringify(data)
776
+ }
777
+
778
+ const bloksType = (type, caller) => {
779
+ if (typeof type !== 'string' || type.trim() === '') {
780
+ throw new TypeError(caller + ' requires a non-empty bloks type')
781
+ }
782
+ return type
783
+ }
784
+
785
+ export const bloksSection = (type, data, { uuid, initialResponse = '', versioningId = '' } = {}) =>
786
+ AIRich.newLayout('Single', {
787
+ type: bloksType(type, 'bloksSection'),
788
+ data: bloksPayloadData(data),
789
+ uuid: uuid ?? randomUUID(),
790
+ initial_response: String(initialResponse),
791
+ versioning_id: String(versioningId),
792
+ __typename: 'FOABloksPrimitive'
793
+ })
794
+
795
+ export const bloksWidget = ({ type, data, uuid, fallback = '' } = {}) => ({
796
+ type: bloksType(type, 'bloksWidget'),
797
+ data: bloksPayloadData(data),
798
+ uuid: uuid ?? randomUUID(),
799
+ fallback: String(fallback)
800
+ })
801
+
802
+ export const A2UI_VERSION = 'v0.9'
803
+ export const A2UI_BASIC_CATALOG = 'https://a2ui.org/specification/v0_9/catalogs/basic/catalog.json'
804
+ export const A2UI_ROOT_ID = 'root'
805
+
806
+ const a2uiNode = (component) => (id, extra = {}) => {
807
+ if (typeof id !== 'string' || id.trim() === '') {
808
+ throw new TypeError('every a2ui component needs a non-empty id')
809
+ }
810
+ return trimEmpty({ id, component, ...extra })
811
+ }
812
+
813
+ export const a2uiText = (id, text, { variant = 'body' } = {}) =>
814
+ a2uiNode('Text')(id, { text: String(text), variant })
815
+
816
+ export const a2uiImage = (id, url, { variant = 'header', fit = 'cover' } = {}) => {
817
+ if (typeof url !== 'string' || url.trim() === '') {
818
+ throw new TypeError('a2uiImage requires a url')
819
+ }
820
+ return a2uiNode('Image')(id, { url, variant, fit })
821
+ }
822
+
823
+ const a2uiContainer = (component) => (id, children = []) => {
824
+ if (!Array.isArray(children)) {
825
+ throw new TypeError('a2ui container children must be an array of component ids')
826
+ }
827
+ return a2uiNode(component)(id, { children: children.map(String) })
828
+ }
829
+
830
+ export const a2uiColumn = a2uiContainer('Column')
831
+ export const a2uiRow = a2uiContainer('Row')
832
+
833
+ export const a2uiCard = (id, child) => {
834
+ if (typeof child !== 'string' || child.trim() === '') {
835
+ throw new TypeError('a2uiCard takes the id of one child component, not an array')
836
+ }
837
+ return a2uiNode('Card')(id, { child })
838
+ }
839
+
840
+ const a2uiElement = (type) => (element = {}) => {
841
+ if (element === null || typeof element !== 'object' || Array.isArray(element)) {
842
+ throw new TypeError('a2ui ' + type + ' element must be a plain object')
843
+ }
844
+ return { type, ...element }
845
+ }
846
+
847
+ export const a2uiInfoCard = a2uiElement('info_card')
848
+ export const a2uiListCard = a2uiElement('list_card')
849
+
850
+ const A2UI_FALLBACK_KEYS = Object.freeze(['text', 'title', 'label', 'body'])
851
+
852
+ export const a2uiFallback = (components) => {
853
+ const list = Array.isArray(components)
854
+ ? components
855
+ : (components && typeof components === 'object' ? [components] : [])
856
+ return list
857
+ .map(component => A2UI_FALLBACK_KEYS
858
+ .map(key => component?.[key])
859
+ .find(value => typeof value === 'string' && value.trim() !== '') ?? '')
860
+ .filter(Boolean)
861
+ .join('\n')
862
+ }
863
+
864
+ export const a2uiSurface = (components, { surfaceId, root = A2UI_ROOT_ID, catalogId, sendDataModel, version = A2UI_VERSION, type, title } = {}) => {
865
+ if (!Array.isArray(components) || components.length === 0) {
866
+ throw new TypeError('a2uiSurface requires at least one component')
867
+ }
868
+ if (!components.some(component => component?.id === root)) {
869
+ throw new TypeError('a2ui components must include one with id "' + root + '" to serve as the root')
870
+ }
871
+ return trimEmpty({
872
+ version,
873
+ type,
874
+ title,
875
+ createSurface: trimEmpty({
876
+ surfaceId: surfaceId ?? 'card-' + randomUUID(),
877
+ root,
878
+ catalogId,
879
+ sendDataModel: sendDataModel === undefined ? undefined : !!sendDataModel,
880
+ components
881
+ })
882
+ })
883
+ }
884
+
885
+ export const a2uiWidget = (components, { uuid, surfaceId, root, catalogId, sendDataModel, version, type, title, fallback, data } = {}) => {
886
+ const id = uuid ?? randomUUID()
887
+ const payload = data ?? a2uiSurface(components, { surfaceId: surfaceId ?? 'card-' + id, root, catalogId, sendDataModel, version, type, title })
888
+ return bloksWidget({
889
+ type: BLOKS_A2UI_TYPE,
890
+ uuid: id,
891
+ fallback: fallback === undefined ? a2uiFallback(Array.isArray(components) && components.length ? components : payload) : fallback,
892
+ data: payload
893
+ })
894
+ }
895
+
896
+ export const sendA2UI = async (sock, jid, components, { buttons = [], contextInfo, messageId, additionalNodes = [], ...options } = {}) => {
897
+ if (!sock) {
898
+ throw new TypeError('sendA2UI requires a socket as the first argument')
899
+ }
900
+ if (!jid) {
901
+ throw new TypeError('sendA2UI requires a target jid')
902
+ }
903
+ if (!Array.isArray(buttons)) {
904
+ throw new TypeError('sendA2UI buttons must be an array of native flow buttons')
905
+ }
906
+
907
+ const widget = a2uiWidget(components, options)
908
+ const text = widget.fallback
909
+
910
+ const msg = generateWAMessageFromContent(
911
+ jid,
912
+ {
913
+ interactiveMessage: trimEmpty({
914
+ nativeFlowMessage: { buttons, messageParamsJson: JSON.stringify({}), messageVersion: 1 },
915
+ bloksWidget: widget,
916
+ ...(text ? { body: { text } } : {}),
917
+ contextInfo
918
+ })
919
+ },
920
+ { messageId, ...options }
921
+ )
922
+
923
+ await sock.relayMessage(msg.key.remoteJid, msg.message, {
924
+ messageId: msg.key.id,
925
+ additionalNodes: [
926
+ {
927
+ tag: 'biz',
928
+ attrs: {},
929
+ content: [{ tag: 'interactive', attrs: { type: 'native_flow', v: '1' }, content: [{ tag: 'native_flow', attrs: { v: '9', name: 'mixed' } }] }]
930
+ },
931
+ ...additionalNodes
932
+ ],
933
+ ...options
934
+ })
935
+
936
+ return msg
937
+ }
938
+
939
+ export const buildA2UISection = (components, { uuid, surfaceId, root, catalogId, sendDataModel, version, type, title, data, initialResponse, versioningId } = {}) =>
940
+ bloksSection(
941
+ BLOKS_A2UI_TYPE,
942
+ data ?? a2uiSurface(components, { surfaceId, root, catalogId, sendDataModel, version, type, title }),
943
+ { uuid, initialResponse, versioningId }
944
+ )
945
+
946
+ export const sendA2UIBloks = async (sock, jid, components, { uuid, surfaceId, root, catalogId, sendDataModel, version, type, title, data, initialResponse, versioningId, ...options } = {}) => {
947
+ if (!sock) {
948
+ throw new TypeError('sendA2UIBloks requires a socket as the first argument')
949
+ }
950
+ if (!jid) {
951
+ throw new TypeError('sendA2UIBloks requires a target jid')
952
+ }
953
+
954
+ const rich = new AIRich(sock)
955
+ rich.addSection(buildA2UISection(components, { uuid, surfaceId, root, catalogId, sendDataModel, version, type, title, data, initialResponse, versioningId }))
956
+
957
+ return rich.send(jid, options)
958
+ }
959
+
960
+ export const sendBloksWidget = async (sock, jid, { type, data, uuid, fallback = '', body, contextInfo, messageId, additionalNodes = [], ...options } = {}) => {
961
+ if (!sock) {
962
+ throw new TypeError('sendBloksWidget requires a socket as the first argument')
963
+ }
964
+ if (!jid) {
965
+ throw new TypeError('sendBloksWidget requires a target jid')
966
+ }
967
+
968
+ const widget = bloksWidget({ type, data, uuid, fallback })
969
+ const text = body === undefined ? widget.fallback : String(body)
970
+
971
+ const msg = generateWAMessageFromContent(
972
+ jid,
973
+ {
974
+ interactiveMessage: {
975
+ bloksWidget: widget,
976
+ ...(text ? { body: { text } } : {}),
977
+ ...(contextInfo ? { contextInfo } : {})
978
+ }
979
+ },
980
+ { messageId, ...options }
981
+ )
982
+
983
+ await sock.relayMessage(msg.key.remoteJid, msg.message, {
984
+ messageId: msg.key.id,
985
+ additionalNodes: [
986
+ {
987
+ tag: 'biz',
988
+ attrs: {},
989
+ content: [{ tag: 'interactive', attrs: { type: 'native_flow', v: '1' }, content: [{ tag: 'native_flow', attrs: { v: '9', name: 'mixed' } }] }]
990
+ },
991
+ ...additionalNodes
992
+ ],
993
+ ...options
994
+ })
995
+
996
+ return msg
997
+ }
998
+
999
+ export const decodeBloksWidget = (msg) => {
1000
+ const message = msg?.message ?? msg
1001
+ const interactive =
1002
+ message?.interactiveMessage ??
1003
+ message?.viewOnceMessage?.message?.interactiveMessage ??
1004
+ message?.viewOnceMessageV2?.message?.interactiveMessage
1005
+
1006
+ const widget = interactive?.bloksWidget
1007
+ if (!widget) {
1008
+ return null
1009
+ }
1010
+
1011
+ let params = null
1012
+ try {
1013
+ params = widget.data ? JSON.parse(widget.data) : null
1014
+ }
1015
+ catch {
1016
+ params = null
1017
+ }
1018
+
1019
+ return {
1020
+ type: widget.type ?? '',
1021
+ uuid: widget.uuid ?? '',
1022
+ fallback: widget.fallback ?? '',
1023
+ data: widget.data ?? '',
1024
+ params
1025
+ }
1026
+ }
1027
+
1028
+ export const collectTypenames = (root, { limit = 20000 } = {}) => {
1029
+ const found = new Set()
1030
+ const stack = [root]
1031
+ let seen = 0
1032
+
1033
+ while (stack.length && seen < limit) {
1034
+ const node = stack.pop()
1035
+ seen++
1036
+ if (Array.isArray(node)) {
1037
+ for (const entry of node) {
1038
+ if (entry && typeof entry === 'object') {
1039
+ stack.push(entry)
1040
+ }
1041
+ }
1042
+ continue
1043
+ }
1044
+ if (!node || typeof node !== 'object') {
1045
+ continue
1046
+ }
1047
+ if (typeof node.__typename === 'string' && node.__typename) {
1048
+ found.add(node.__typename)
1049
+ }
1050
+ for (const value of Object.values(node)) {
1051
+ if (value && typeof value === 'object') {
1052
+ stack.push(value)
1053
+ }
1054
+ }
1055
+ }
1056
+
1057
+ return [...found].sort()
1058
+ }
1059
+
1060
+ export const decodeAIRich = (msg) => {
1061
+ const message = msg?.message ?? msg
1062
+ const rich =
1063
+ message?.botForwardedMessage?.message?.richResponseMessage ??
1064
+ message?.botForwardedMessage?.richResponseMessage ??
1065
+ message?.richResponseMessage
1066
+
1067
+ if (!rich) {
1068
+ return null
1069
+ }
1070
+
1071
+ let unified = null
1072
+ const data = rich.unifiedResponse?.data
1073
+ if (data) {
1074
+ try {
1075
+ unified = JSON.parse(Buffer.from(data, 'base64').toString('utf-8'))
1076
+ }
1077
+ catch {
1078
+ unified = null
1079
+ }
1080
+ }
1081
+
1082
+ const sections = Array.isArray(unified?.sections) ? unified.sections : []
1083
+ const footerSections = Array.isArray(unified?.footer_sections) ? unified.footer_sections : []
1084
+ const embeddedScreens = Array.isArray(unified?.embedded_screens) ? unified.embedded_screens : []
1085
+
1086
+ const readPrimitives = (section) => {
1087
+ const view = section?.view_model
1088
+ if (Array.isArray(view?.primitives)) return view.primitives
1089
+ if (view?.primitive) return [view.primitive]
1090
+ return []
1091
+ }
1092
+
1093
+ return {
1094
+ responseId: unified?.response_id,
1095
+ layouts: sections.map(section => String(section?.view_model?.__typename ?? '').replace(/^GenAI(.*)LayoutViewModel$/, '$1')),
1096
+ typenames: [...new Set(sections.flatMap(section => readPrimitives(section).map(primitive => primitive?.__typename).filter(Boolean)))],
1097
+ footerTypenames: [...new Set(footerSections.flatMap(section => readPrimitives(section).map(primitive => primitive?.__typename).filter(Boolean)))],
1098
+ allTypenames: collectTypenames(unified),
1099
+ sections,
1100
+ footerSections,
1101
+ embeddedScreens,
1102
+ embeddedTabs: embeddedScreens.flatMap(readEmbeddedTabs),
1103
+ embeddedSections: embeddedScreens.flatMap(readEmbeddedSections),
1104
+ submessages: rich.submessages ?? [],
1105
+ unified
1106
+ }
1107
+ }
1108
+
1109
+ const A2UI_TEXT_KEYS = Object.freeze(['text', 'label', 'title'])
1110
+
1111
+ const readPrimitiveText = (primitive) => {
1112
+ if (!primitive || typeof primitive !== 'object') {
1113
+ return ''
1114
+ }
1115
+ if (typeof primitive.text === 'string') {
1116
+ return primitive.text
1117
+ }
1118
+ if (typeof primitive.title === 'string') {
1119
+ return primitive.title
1120
+ }
1121
+ return ''
1122
+ }
1123
+
1124
+ const readA2UIText = (components) => components
1125
+ .filter(component => component?.component === 'Text' || component?.component === 'Heading')
1126
+ .map(component => A2UI_TEXT_KEYS.map(key => component[key]).find(value => typeof value === 'string') ?? '')
1127
+ .filter(Boolean)
1128
+
1129
+ const parseBloksParams = (data) => {
1130
+ if (!data || typeof data !== 'string') {
1131
+ return null
1132
+ }
1133
+ try {
1134
+ return JSON.parse(data)
1135
+ }
1136
+ catch {
1137
+ return null
1138
+ }
1139
+ }
1140
+
1141
+ export const readRichMessage = (msg) => {
1142
+ const raw = msg?.message ?? msg
1143
+ if (!raw || typeof raw !== 'object') {
1144
+ return null
1145
+ }
1146
+
1147
+ const content = extractMessageContent(raw) ?? raw
1148
+ const rich = decodeAIRich(raw)
1149
+ const widget = decodeBloksWidget(raw)
1150
+ const interactive =
1151
+ content?.interactiveMessage ??
1152
+ raw?.interactiveMessage ??
1153
+ raw?.viewOnceMessage?.message?.interactiveMessage ??
1154
+ raw?.viewOnceMessageV2?.message?.interactiveMessage
1155
+
1156
+ if (!rich && !widget && !interactive) {
1157
+ return null
1158
+ }
1159
+
1160
+ const buttons = (interactive?.nativeFlowMessage?.buttons ?? []).map(button => {
1161
+ let params = null
1162
+ try {
1163
+ params = button?.buttonParamsJson ? JSON.parse(button.buttonParamsJson) : null
1164
+ }
1165
+ catch {
1166
+ params = null
1167
+ }
1168
+ return { name: button?.name ?? '', params }
1169
+ })
1170
+
1171
+ const isA2UIWidget = widget?.type === BLOKS_A2UI_TYPE
1172
+
1173
+ const sectionPrimitives = sections => sections.flatMap(section => {
1174
+ const view = section?.view_model
1175
+ if (Array.isArray(view?.primitives)) return view.primitives
1176
+ return view?.primitive ? [view.primitive] : []
1177
+ })
1178
+
1179
+ const primitives = sectionPrimitives(rich?.sections ?? [])
1180
+ const embeddedPrimitives = sectionPrimitives(rich?.embeddedSections ?? [])
1181
+
1182
+ const richBloks = primitives.find(primitive => primitive?.__typename === 'FOABloksPrimitive' && primitive?.type === BLOKS_A2UI_TYPE)
1183
+ const a2uiParams = isA2UIWidget ? widget.params : parseBloksParams(richBloks?.data)
1184
+ const surface = a2uiParams?.createSurface
1185
+ const a2uiComponents = Array.isArray(surface?.components) ? surface.components : []
1186
+ const a2uiElement = !surface && a2uiParams && typeof a2uiParams === 'object' ? a2uiParams : undefined
1187
+ const isA2UI = isA2UIWidget || !!richBloks
1188
+
1189
+ const a2uiTextLines = readA2UIText(a2uiComponents)
1190
+ const bodyText = interactive?.body?.text ?? ''
1191
+ const widgetFallback = widget?.fallback ?? ''
1192
+ const bodyDuplicatesWidget = bodyText !== '' && bodyText === widgetFallback
1193
+
1194
+ const lines = [
1195
+ ...primitives.filter(primitive => primitive !== richBloks).map(readPrimitiveText),
1196
+ ...a2uiTextLines,
1197
+ bodyDuplicatesWidget && a2uiTextLines.length ? '' : bodyText,
1198
+ interactive?.footer?.text ?? ''
1199
+ ].filter(Boolean)
1200
+
1201
+ const kind = isA2UI ? 'a2ui' : widget ? 'bloks' : rich ? 'airich' : 'interactive'
1202
+
1203
+ return trimEmpty({
1204
+ kind,
1205
+ text: lines.join('\n'),
1206
+ title: raw?.messageContextInfo?.botMetadata?.messageDisclaimerText || interactive?.header?.title || '',
1207
+ buttons,
1208
+ html: [...primitives, ...embeddedPrimitives].filter(p => typeof p?.payload === 'string').map(p => p.payload),
1209
+ typenames: rich?.typenames ?? [],
1210
+ sections: rich?.sections ?? [],
1211
+ footerSections: rich?.footerSections ?? [],
1212
+ embeddedScreens: rich?.embeddedScreens ?? [],
1213
+ embeddedTabs: rich?.embeddedTabs ?? [],
1214
+ submessages: rich?.submessages ?? [],
1215
+ responseId: rich?.responseId,
1216
+ a2ui: isA2UI
1217
+ ? trimEmpty({
1218
+ surfaceId: surface?.surfaceId,
1219
+ root: surface?.root,
1220
+ catalogId: surface?.catalogId,
1221
+ version: a2uiParams?.version,
1222
+ type: a2uiParams?.type,
1223
+ components: a2uiComponents.length ? a2uiComponents : undefined,
1224
+ element: a2uiElement
1225
+ })
1226
+ : undefined,
1227
+ bloks: widget ? { type: widget.type, uuid: widget.uuid, fallback: widget.fallback, params: widget.params } : undefined
1228
+ })
1229
+ }