@pontalabs/baileys 1.0.4 → 1.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (447) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +267 -839
  3. package/WAProto/GenerateStatics.sh +3 -4
  4. package/WAProto/WAProto.proto +1215 -511
  5. package/WAProto/fix-imports.js +85 -0
  6. package/WAProto/index.d.ts +14017 -55
  7. package/WAProto/index.js +97689 -29
  8. package/engine-requirements.js +10 -0
  9. package/lib/Defaults/index.d.ts +72 -74
  10. package/lib/Defaults/index.d.ts.map +1 -0
  11. package/lib/Defaults/index.js +94 -108
  12. package/lib/Defaults/index.js.map +1 -0
  13. package/lib/Signal/Group/ciphertext-message.d.ts +8 -7
  14. package/lib/Signal/Group/ciphertext-message.d.ts.map +1 -0
  15. package/lib/Signal/Group/ciphertext-message.js +9 -16
  16. package/lib/Signal/Group/ciphertext-message.js.map +1 -0
  17. package/lib/Signal/Group/group-session-builder.d.ts +11 -13
  18. package/lib/Signal/Group/group-session-builder.d.ts.map +1 -0
  19. package/lib/Signal/Group/group-session-builder.js +19 -61
  20. package/lib/Signal/Group/group-session-builder.js.map +1 -0
  21. package/lib/Signal/Group/group_cipher.d.ts +14 -16
  22. package/lib/Signal/Group/group_cipher.d.ts.map +1 -0
  23. package/lib/Signal/Group/group_cipher.js +41 -70
  24. package/lib/Signal/Group/group_cipher.js.map +1 -0
  25. package/lib/Signal/Group/index.d.ts +12 -11
  26. package/lib/Signal/Group/index.d.ts.map +1 -0
  27. package/lib/Signal/Group/index.js +12 -61
  28. package/lib/Signal/Group/index.js.map +1 -0
  29. package/lib/Signal/Group/keyhelper.d.ts +9 -14
  30. package/lib/Signal/Group/keyhelper.d.ts.map +1 -0
  31. package/lib/Signal/Group/keyhelper.js +10 -58
  32. package/lib/Signal/Group/keyhelper.js.map +1 -0
  33. package/lib/Signal/Group/sender-chain-key.d.ts +13 -13
  34. package/lib/Signal/Group/sender-chain-key.d.ts.map +1 -0
  35. package/lib/Signal/Group/sender-chain-key.js +13 -34
  36. package/lib/Signal/Group/sender-chain-key.js.map +1 -0
  37. package/lib/Signal/Group/sender-key-distribution-message.d.ts +16 -16
  38. package/lib/Signal/Group/sender-key-distribution-message.d.ts.map +1 -0
  39. package/lib/Signal/Group/sender-key-distribution-message.js +28 -36
  40. package/lib/Signal/Group/sender-key-distribution-message.js.map +1 -0
  41. package/lib/Signal/Group/sender-key-message.d.ts +18 -18
  42. package/lib/Signal/Group/sender-key-message.d.ts.map +1 -0
  43. package/lib/Signal/Group/sender-key-message.js +38 -45
  44. package/lib/Signal/Group/sender-key-message.js.map +1 -0
  45. package/lib/Signal/Group/sender-key-name.d.ts +14 -15
  46. package/lib/Signal/Group/sender-key-name.d.ts.map +1 -0
  47. package/lib/Signal/Group/sender-key-name.js +20 -31
  48. package/lib/Signal/Group/sender-key-name.js.map +1 -0
  49. package/lib/Signal/Group/sender-key-record.d.ts +24 -25
  50. package/lib/Signal/Group/sender-key-record.d.ts.map +1 -0
  51. package/lib/Signal/Group/sender-key-record.js +18 -35
  52. package/lib/Signal/Group/sender-key-record.js.map +1 -0
  53. package/lib/Signal/Group/sender-key-state.d.ts +29 -34
  54. package/lib/Signal/Group/sender-key-state.d.ts.map +1 -0
  55. package/lib/Signal/Group/sender-key-state.js +37 -100
  56. package/lib/Signal/Group/sender-key-state.js.map +1 -0
  57. package/lib/Signal/Group/sender-message-key.d.ts +11 -10
  58. package/lib/Signal/Group/sender-message-key.d.ts.map +1 -0
  59. package/lib/Signal/Group/sender-message-key.js +15 -22
  60. package/lib/Signal/Group/sender-message-key.js.map +1 -0
  61. package/lib/Signal/libsignal.d.ts +5 -8
  62. package/lib/Signal/libsignal.d.ts.map +1 -0
  63. package/lib/Signal/libsignal.js +279 -121
  64. package/lib/Signal/libsignal.js.map +1 -0
  65. package/lib/Signal/lid-mapping.d.ts +23 -28
  66. package/lib/Signal/lid-mapping.d.ts.map +1 -0
  67. package/lib/Signal/lid-mapping.js +277 -184
  68. package/lib/Signal/lid-mapping.js.map +1 -0
  69. package/lib/Socket/Client/index.d.ts +3 -2
  70. package/lib/Socket/Client/index.d.ts.map +1 -0
  71. package/lib/Socket/Client/index.js +3 -22
  72. package/lib/Socket/Client/index.js.map +1 -0
  73. package/lib/Socket/Client/types.d.ts +15 -15
  74. package/lib/Socket/Client/types.d.ts.map +1 -0
  75. package/lib/Socket/Client/types.js +8 -15
  76. package/lib/Socket/Client/types.js.map +1 -0
  77. package/lib/Socket/Client/websocket.d.ts +12 -12
  78. package/lib/Socket/Client/websocket.d.ts.map +1 -0
  79. package/lib/Socket/Client/websocket.js +29 -37
  80. package/lib/Socket/Client/websocket.js.map +1 -0
  81. package/lib/Socket/business.d.ts +208 -178
  82. package/lib/Socket/business.d.ts.map +1 -0
  83. package/lib/Socket/business.js +91 -127
  84. package/lib/Socket/business.js.map +1 -0
  85. package/lib/Socket/chats.d.ts +120 -93
  86. package/lib/Socket/chats.d.ts.map +1 -0
  87. package/lib/Socket/chats.js +727 -652
  88. package/lib/Socket/chats.js.map +1 -0
  89. package/lib/Socket/communities.d.ts +273 -0
  90. package/lib/Socket/communities.d.ts.map +1 -0
  91. package/lib/Socket/communities.js +431 -0
  92. package/lib/Socket/communities.js.map +1 -0
  93. package/lib/Socket/dugong.js +261 -0
  94. package/lib/Socket/dugong.js.bak +204 -0
  95. package/lib/Socket/groups.d.ts +143 -111
  96. package/lib/Socket/groups.d.ts.map +1 -0
  97. package/lib/Socket/groups.js +165 -177
  98. package/lib/Socket/groups.js.map +1 -0
  99. package/lib/Socket/index.d.ts +252 -183
  100. package/lib/Socket/index.d.ts.map +1 -0
  101. package/lib/Socket/index.js +38 -18
  102. package/lib/Socket/index.js.bak +44 -0
  103. package/lib/Socket/index.js.map +1 -0
  104. package/lib/Socket/messages-recv.d.ts +196 -167
  105. package/lib/Socket/messages-recv.d.ts.map +1 -0
  106. package/lib/Socket/messages-recv.js +1443 -1563
  107. package/lib/Socket/messages-recv.js.map +1 -0
  108. package/lib/Socket/messages-send.d.ts +192 -158
  109. package/lib/Socket/messages-send.d.ts.map +1 -0
  110. package/lib/Socket/messages-send.js +1102 -1208
  111. package/lib/Socket/messages-send.js.bak +1400 -0
  112. package/lib/Socket/messages-send.js.map +1 -0
  113. package/lib/Socket/mex.d.ts +3 -0
  114. package/lib/Socket/mex.d.ts.map +1 -0
  115. package/lib/Socket/mex.js +42 -0
  116. package/lib/Socket/mex.js.map +1 -0
  117. package/lib/Socket/newsletter.d.ts +163 -138
  118. package/lib/Socket/newsletter.d.ts.map +1 -0
  119. package/lib/Socket/newsletter.js +153 -249
  120. package/lib/Socket/newsletter.js.map +1 -0
  121. package/lib/Socket/socket.d.ts +50 -36
  122. package/lib/Socket/socket.d.ts.map +1 -0
  123. package/lib/Socket/socket.js +749 -820
  124. package/lib/Socket/socket.js.map +1 -0
  125. package/lib/Store/index.d.ts +4 -4
  126. package/lib/Store/index.d.ts.map +1 -0
  127. package/lib/Store/index.js +4 -24
  128. package/lib/Store/index.js.map +1 -0
  129. package/lib/Store/make-in-memory-store.d.ts +44 -104
  130. package/lib/Store/make-in-memory-store.d.ts.map +1 -0
  131. package/lib/Store/make-in-memory-store.js +199 -213
  132. package/lib/Store/make-in-memory-store.js.map +1 -0
  133. package/lib/Store/make-ordered-dictionary.d.ts +13 -12
  134. package/lib/Store/make-ordered-dictionary.d.ts.map +1 -0
  135. package/lib/Store/make-ordered-dictionary.js +38 -45
  136. package/lib/Store/make-ordered-dictionary.js.map +1 -0
  137. package/lib/Store/object-repository.d.ts +11 -10
  138. package/lib/Store/object-repository.d.ts.map +1 -0
  139. package/lib/Store/object-repository.js +9 -16
  140. package/lib/Store/object-repository.js.map +1 -0
  141. package/lib/Types/Auth.d.ts +93 -97
  142. package/lib/Types/Auth.d.ts.map +1 -0
  143. package/lib/Types/Auth.js +2 -3
  144. package/lib/Types/Auth.js.map +1 -0
  145. package/lib/Types/Bussines.d.ts +25 -0
  146. package/lib/Types/Bussines.d.ts.map +1 -0
  147. package/lib/Types/Bussines.js +2 -3
  148. package/lib/Types/Bussines.js.map +1 -0
  149. package/lib/Types/Call.d.ts +14 -13
  150. package/lib/Types/Call.d.ts.map +1 -0
  151. package/lib/Types/Call.js +2 -3
  152. package/lib/Types/Call.js.map +1 -0
  153. package/lib/Types/Chat.d.ts +78 -97
  154. package/lib/Types/Chat.d.ts.map +1 -0
  155. package/lib/Types/Chat.js +8 -9
  156. package/lib/Types/Chat.js.map +1 -0
  157. package/lib/Types/Contact.d.ts +12 -9
  158. package/lib/Types/Contact.d.ts.map +1 -0
  159. package/lib/Types/Contact.js +2 -3
  160. package/lib/Types/Contact.js.map +1 -0
  161. package/lib/Types/Events.d.ts +206 -176
  162. package/lib/Types/Events.d.ts.map +1 -0
  163. package/lib/Types/Events.js +2 -3
  164. package/lib/Types/Events.js.map +1 -0
  165. package/lib/Types/GroupMetadata.d.ts +53 -48
  166. package/lib/Types/GroupMetadata.d.ts.map +1 -0
  167. package/lib/Types/GroupMetadata.js +2 -3
  168. package/lib/Types/GroupMetadata.js.map +1 -0
  169. package/lib/Types/Label.d.ts +13 -14
  170. package/lib/Types/Label.d.ts.map +1 -0
  171. package/lib/Types/Label.js +24 -30
  172. package/lib/Types/Label.js.map +1 -0
  173. package/lib/Types/LabelAssociation.d.ts +15 -20
  174. package/lib/Types/LabelAssociation.d.ts.map +1 -0
  175. package/lib/Types/LabelAssociation.js +6 -12
  176. package/lib/Types/LabelAssociation.js.map +1 -0
  177. package/lib/Types/Message.d.ts +248 -412
  178. package/lib/Types/Message.d.ts.map +1 -0
  179. package/lib/Types/Message.js +17 -19
  180. package/lib/Types/Message.js.map +1 -0
  181. package/lib/Types/Mex.d.ts +141 -0
  182. package/lib/Types/Mex.d.ts.map +1 -0
  183. package/lib/Types/Mex.js +37 -0
  184. package/lib/Types/Mex.js.map +1 -0
  185. package/lib/Types/Product.d.ts +58 -71
  186. package/lib/Types/Product.d.ts.map +1 -0
  187. package/lib/Types/Product.js +2 -3
  188. package/lib/Types/Product.js.map +1 -0
  189. package/lib/Types/RichType.d.ts +3 -0
  190. package/lib/Types/RichType.d.ts.map +1 -0
  191. package/lib/Types/RichType.js +23 -0
  192. package/lib/Types/RichType.js.bak +23 -0
  193. package/lib/Types/RichType.js.map +1 -0
  194. package/lib/Types/Signal.d.ts +71 -82
  195. package/lib/Types/Signal.d.ts.map +1 -0
  196. package/lib/Types/Signal.js +2 -3
  197. package/lib/Types/Signal.js.map +1 -0
  198. package/lib/Types/Socket.d.ts +76 -81
  199. package/lib/Types/Socket.d.ts.map +1 -0
  200. package/lib/Types/Socket.js +3 -3
  201. package/lib/Types/Socket.js.map +1 -0
  202. package/lib/Types/State.d.ts +75 -19
  203. package/lib/Types/State.d.ts.map +1 -0
  204. package/lib/Types/State.js +56 -14
  205. package/lib/Types/State.js.map +1 -0
  206. package/lib/Types/USync.d.ts +8 -8
  207. package/lib/Types/USync.d.ts.map +1 -0
  208. package/lib/Types/USync.js +2 -3
  209. package/lib/Types/USync.js.map +1 -0
  210. package/lib/Types/index.d.ts +47 -62
  211. package/lib/Types/index.d.ts.map +1 -0
  212. package/lib/Types/index.js +26 -50
  213. package/lib/Types/index.js.map +1 -0
  214. package/lib/Utils/auth-utils.d.ts +13 -10
  215. package/lib/Utils/auth-utils.d.ts.map +1 -0
  216. package/lib/Utils/auth-utils.js +213 -454
  217. package/lib/Utils/auth-utils.js.map +1 -0
  218. package/lib/Utils/browser-utils.d.ts +4 -0
  219. package/lib/Utils/browser-utils.d.ts.map +1 -0
  220. package/lib/Utils/browser-utils.js +28 -0
  221. package/lib/Utils/browser-utils.js.map +1 -0
  222. package/lib/Utils/business.d.ts +14 -20
  223. package/lib/Utils/business.d.ts.map +1 -0
  224. package/lib/Utils/business.js +110 -134
  225. package/lib/Utils/business.js.map +1 -0
  226. package/lib/Utils/chat-utils.d.ts +88 -70
  227. package/lib/Utils/chat-utils.d.ts.map +1 -0
  228. package/lib/Utils/chat-utils.js +466 -403
  229. package/lib/Utils/chat-utils.js.map +1 -0
  230. package/lib/Utils/companion-reg-client-utils.d.ts +17 -0
  231. package/lib/Utils/companion-reg-client-utils.d.ts.map +1 -0
  232. package/lib/Utils/companion-reg-client-utils.js +35 -0
  233. package/lib/Utils/companion-reg-client-utils.js.map +1 -0
  234. package/lib/Utils/crypto.d.ts +27 -46
  235. package/lib/Utils/crypto.d.ts.map +1 -0
  236. package/lib/Utils/crypto.js +117 -188
  237. package/lib/Utils/crypto.js.map +1 -0
  238. package/lib/Utils/decode-wa-message.d.ts +58 -45
  239. package/lib/Utils/decode-wa-message.d.ts.map +1 -0
  240. package/lib/Utils/decode-wa-message.js +213 -303
  241. package/lib/Utils/decode-wa-message.js.map +1 -0
  242. package/lib/Utils/event-buffer.d.ts +14 -17
  243. package/lib/Utils/event-buffer.d.ts.map +1 -0
  244. package/lib/Utils/event-buffer.js +321 -296
  245. package/lib/Utils/event-buffer.js.map +1 -0
  246. package/lib/Utils/generics.d.ts +66 -92
  247. package/lib/Utils/generics.d.ts.map +1 -0
  248. package/lib/Utils/generics.js +270 -436
  249. package/lib/Utils/generics.js.map +1 -0
  250. package/lib/Utils/history.d.ts +24 -23
  251. package/lib/Utils/history.d.ts.map +1 -0
  252. package/lib/Utils/history.js +106 -76
  253. package/lib/Utils/history.js.map +1 -0
  254. package/lib/Utils/identity-change-handler.d.ts +44 -0
  255. package/lib/Utils/identity-change-handler.d.ts.map +1 -0
  256. package/lib/Utils/identity-change-handler.js +50 -0
  257. package/lib/Utils/identity-change-handler.js.map +1 -0
  258. package/lib/Utils/index.d.ts +24 -21
  259. package/lib/Utils/index.d.ts.map +1 -0
  260. package/lib/Utils/index.js +24 -41
  261. package/lib/Utils/index.js.map +1 -0
  262. package/lib/Utils/link-preview.d.ts +12 -14
  263. package/lib/Utils/link-preview.d.ts.map +1 -0
  264. package/lib/Utils/link-preview.js +40 -75
  265. package/lib/Utils/link-preview.js.map +1 -0
  266. package/lib/Utils/logger.d.ts +10 -11
  267. package/lib/Utils/logger.d.ts.map +1 -0
  268. package/lib/Utils/logger.js +3 -7
  269. package/lib/Utils/logger.js.map +1 -0
  270. package/lib/Utils/lt-hash.d.ts +8 -14
  271. package/lib/Utils/lt-hash.d.ts.map +1 -0
  272. package/lib/Utils/lt-hash.js +3 -53
  273. package/lib/Utils/lt-hash.js.map +1 -0
  274. package/lib/Utils/make-mutex.d.ts +7 -7
  275. package/lib/Utils/make-mutex.d.ts.map +1 -0
  276. package/lib/Utils/make-mutex.js +28 -44
  277. package/lib/Utils/make-mutex.js.map +1 -0
  278. package/lib/Utils/message-retry-manager.d.ts +115 -88
  279. package/lib/Utils/message-retry-manager.d.ts.map +1 -0
  280. package/lib/Utils/message-retry-manager.js +265 -160
  281. package/lib/Utils/message-retry-manager.js.map +1 -0
  282. package/lib/Utils/messages-media.d.ts +105 -107
  283. package/lib/Utils/messages-media.d.ts.map +1 -0
  284. package/lib/Utils/messages-media.js +548 -619
  285. package/lib/Utils/messages-media.js.bak +786 -0
  286. package/lib/Utils/messages-media.js.map +1 -0
  287. package/lib/Utils/messages.d.ts +52 -66
  288. package/lib/Utils/messages.d.ts.map +1 -0
  289. package/lib/Utils/messages.js +1730 -1558
  290. package/lib/Utils/messages.js.backup +1768 -0
  291. package/lib/Utils/messages.js.bak +1809 -0
  292. package/lib/Utils/messages.js.bak2 +893 -0
  293. package/lib/Utils/messages.js.bak3 +1183 -0
  294. package/lib/Utils/messages.js.map +1 -0
  295. package/lib/Utils/noise-handler.d.ts +18 -18
  296. package/lib/Utils/noise-handler.d.ts.map +1 -0
  297. package/lib/Utils/noise-handler.js +176 -130
  298. package/lib/Utils/noise-handler.js.map +1 -0
  299. package/lib/Utils/offline-node-processor.d.ts +17 -0
  300. package/lib/Utils/offline-node-processor.d.ts.map +1 -0
  301. package/lib/Utils/offline-node-processor.js +40 -0
  302. package/lib/Utils/offline-node-processor.js.map +1 -0
  303. package/lib/Utils/pre-key-manager.d.ts +28 -0
  304. package/lib/Utils/pre-key-manager.d.ts.map +1 -0
  305. package/lib/Utils/pre-key-manager.js +106 -0
  306. package/lib/Utils/pre-key-manager.js.map +1 -0
  307. package/lib/Utils/process-message.d.ts +43 -32
  308. package/lib/Utils/process-message.d.ts.map +1 -0
  309. package/lib/Utils/process-message.js +484 -282
  310. package/lib/Utils/process-message.js.map +1 -0
  311. package/lib/Utils/reporting-utils.d.ts +11 -0
  312. package/lib/Utils/reporting-utils.d.ts.map +1 -0
  313. package/lib/Utils/reporting-utils.js +258 -0
  314. package/lib/Utils/reporting-utils.js.map +1 -0
  315. package/lib/Utils/rich-message-utils.d.ts +49 -0
  316. package/lib/Utils/rich-message-utils.d.ts.map +1 -0
  317. package/lib/Utils/rich-message-utils.js +1220 -724
  318. package/lib/Utils/rich-message-utils.js.backup +706 -0
  319. package/lib/Utils/rich-message-utils.js.bak +246 -0
  320. package/lib/Utils/rich-message-utils.js.map +1 -0
  321. package/lib/Utils/rich-message-utils.js.original +1332 -0
  322. package/lib/Utils/signal.d.ts +37 -32
  323. package/lib/Utils/signal.d.ts.map +1 -0
  324. package/lib/Utils/signal.js +140 -105
  325. package/lib/Utils/signal.js.map +1 -0
  326. package/lib/Utils/stanza-ack.d.ts +11 -0
  327. package/lib/Utils/stanza-ack.d.ts.map +1 -0
  328. package/lib/Utils/stanza-ack.js +38 -0
  329. package/lib/Utils/stanza-ack.js.map +1 -0
  330. package/lib/Utils/sync-action-utils.d.ts +19 -0
  331. package/lib/Utils/sync-action-utils.d.ts.map +1 -0
  332. package/lib/Utils/sync-action-utils.js +49 -0
  333. package/lib/Utils/sync-action-utils.js.map +1 -0
  334. package/lib/Utils/tc-token-utils.d.ts +37 -0
  335. package/lib/Utils/tc-token-utils.d.ts.map +1 -0
  336. package/lib/Utils/tc-token-utils.js +163 -0
  337. package/lib/Utils/tc-token-utils.js.map +1 -0
  338. package/lib/Utils/use-multi-file-auth-state.d.ts +5 -5
  339. package/lib/Utils/use-multi-file-auth-state.d.ts.map +1 -0
  340. package/lib/Utils/use-multi-file-auth-state.js +70 -120
  341. package/lib/Utils/use-multi-file-auth-state.js.map +1 -0
  342. package/lib/Utils/use-single-file-auth-state.d.ts +11 -13
  343. package/lib/Utils/use-single-file-auth-state.d.ts.map +1 -0
  344. package/lib/Utils/use-single-file-auth-state.js +92 -66
  345. package/lib/Utils/use-single-file-auth-state.js.map +1 -0
  346. package/lib/Utils/use-sqlite-auth-state.d.ts +11 -0
  347. package/lib/Utils/use-sqlite-auth-state.d.ts.map +1 -0
  348. package/lib/Utils/use-sqlite-auth-state.js +109 -0
  349. package/lib/Utils/use-sqlite-auth-state.js.map +1 -0
  350. package/lib/Utils/validate-connection.d.ts +11 -13
  351. package/lib/Utils/validate-connection.d.ts.map +1 -0
  352. package/lib/Utils/validate-connection.js +128 -113
  353. package/lib/Utils/validate-connection.js.map +1 -0
  354. package/lib/WABinary/constants.d.ts +25 -27
  355. package/lib/WABinary/constants.d.ts.map +1 -0
  356. package/lib/WABinary/constants.js +1451 -1292
  357. package/lib/WABinary/constants.js.bak +1373 -0
  358. package/lib/WABinary/constants.js.map +1 -0
  359. package/lib/WABinary/decode.d.ts +7 -9
  360. package/lib/WABinary/decode.d.ts.map +1 -0
  361. package/lib/WABinary/decode.js +163 -189
  362. package/lib/WABinary/decode.js.map +1 -0
  363. package/lib/WABinary/encode.d.ts +3 -3
  364. package/lib/WABinary/encode.d.ts.map +1 -0
  365. package/lib/WABinary/encode.js +110 -155
  366. package/lib/WABinary/encode.js.map +1 -0
  367. package/lib/WABinary/generic-utils.d.ts +14 -27
  368. package/lib/WABinary/generic-utils.d.ts.map +1 -0
  369. package/lib/WABinary/generic-utils.js +177 -105
  370. package/lib/WABinary/generic-utils.js.map +1 -0
  371. package/lib/WABinary/index.d.ts +6 -5
  372. package/lib/WABinary/index.d.ts.map +1 -0
  373. package/lib/WABinary/index.js +6 -25
  374. package/lib/WABinary/index.js.map +1 -0
  375. package/lib/WABinary/jid-utils.d.ts +41 -51
  376. package/lib/WABinary/jid-utils.d.ts.map +1 -0
  377. package/lib/WABinary/jid-utils.js +83 -120
  378. package/lib/WABinary/jid-utils.js.map +1 -0
  379. package/lib/WABinary/types.d.ts +10 -13
  380. package/lib/WABinary/types.d.ts.map +1 -0
  381. package/lib/WABinary/types.js +2 -3
  382. package/lib/WABinary/types.js.map +1 -0
  383. package/lib/WAM/BinaryInfo.d.ts +8 -15
  384. package/lib/WAM/BinaryInfo.d.ts.map +1 -0
  385. package/lib/WAM/BinaryInfo.js +7 -14
  386. package/lib/WAM/BinaryInfo.js.map +1 -0
  387. package/lib/WAM/constants.d.ts +30 -37
  388. package/lib/WAM/constants.d.ts.map +1 -0
  389. package/lib/WAM/constants.js +19193 -11711
  390. package/lib/WAM/constants.js.map +1 -0
  391. package/lib/WAM/encode.d.ts +3 -3
  392. package/lib/WAM/encode.d.ts.map +1 -0
  393. package/lib/WAM/encode.js +95 -109
  394. package/lib/WAM/encode.js.map +1 -0
  395. package/lib/WAM/index.d.ts +4 -3
  396. package/lib/WAM/index.d.ts.map +1 -0
  397. package/lib/WAM/index.js +4 -23
  398. package/lib/WAM/index.js.map +1 -0
  399. package/lib/WAUSync/Protocols/USyncContactProtocol.d.ts +9 -9
  400. package/lib/WAUSync/Protocols/USyncContactProtocol.d.ts.map +1 -0
  401. package/lib/WAUSync/Protocols/USyncContactProtocol.js +37 -21
  402. package/lib/WAUSync/Protocols/USyncContactProtocol.js.map +1 -0
  403. package/lib/WAUSync/Protocols/USyncDeviceProtocol.d.ts +19 -22
  404. package/lib/WAUSync/Protocols/USyncDeviceProtocol.d.ts.map +1 -0
  405. package/lib/WAUSync/Protocols/USyncDeviceProtocol.js +18 -26
  406. package/lib/WAUSync/Protocols/USyncDeviceProtocol.js.map +1 -0
  407. package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.d.ts +11 -12
  408. package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.d.ts.map +1 -0
  409. package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.js +12 -20
  410. package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.js.map +1 -0
  411. package/lib/WAUSync/Protocols/USyncStatusProtocol.d.ts +11 -12
  412. package/lib/WAUSync/Protocols/USyncStatusProtocol.d.ts.map +1 -0
  413. package/lib/WAUSync/Protocols/USyncStatusProtocol.js +16 -24
  414. package/lib/WAUSync/Protocols/USyncStatusProtocol.js.map +1 -0
  415. package/lib/WAUSync/Protocols/USyncUsernameProtocol.d.ts +10 -0
  416. package/lib/WAUSync/Protocols/USyncUsernameProtocol.d.ts.map +1 -0
  417. package/lib/WAUSync/Protocols/USyncUsernameProtocol.js +25 -0
  418. package/lib/WAUSync/Protocols/USyncUsernameProtocol.js.map +1 -0
  419. package/lib/WAUSync/Protocols/UsyncBotProfileProtocol.d.ts +26 -0
  420. package/lib/WAUSync/Protocols/UsyncBotProfileProtocol.d.ts.map +1 -0
  421. package/lib/WAUSync/Protocols/UsyncBotProfileProtocol.js +51 -0
  422. package/lib/WAUSync/Protocols/UsyncBotProfileProtocol.js.map +1 -0
  423. package/lib/WAUSync/Protocols/UsyncLIDProtocol.d.ts +10 -0
  424. package/lib/WAUSync/Protocols/UsyncLIDProtocol.d.ts.map +1 -0
  425. package/lib/WAUSync/Protocols/UsyncLIDProtocol.js +29 -0
  426. package/lib/WAUSync/Protocols/UsyncLIDProtocol.js.map +1 -0
  427. package/lib/WAUSync/Protocols/index.d.ts +6 -6
  428. package/lib/WAUSync/Protocols/index.d.ts.map +1 -0
  429. package/lib/WAUSync/Protocols/index.js +6 -26
  430. package/lib/WAUSync/Protocols/index.js.map +1 -0
  431. package/lib/WAUSync/USyncQuery.d.ts +27 -28
  432. package/lib/WAUSync/USyncQuery.d.ts.map +1 -0
  433. package/lib/WAUSync/USyncQuery.js +68 -62
  434. package/lib/WAUSync/USyncQuery.js.map +1 -0
  435. package/lib/WAUSync/USyncUser.d.ts +15 -10
  436. package/lib/WAUSync/USyncUser.d.ts.map +1 -0
  437. package/lib/WAUSync/USyncUser.js +20 -19
  438. package/lib/WAUSync/USyncUser.js.map +1 -0
  439. package/lib/WAUSync/index.d.ts +4 -3
  440. package/lib/WAUSync/index.d.ts.map +1 -0
  441. package/lib/WAUSync/index.js +4 -23
  442. package/lib/WAUSync/index.js.map +1 -0
  443. package/lib/index.d.ts +11 -12
  444. package/lib/index.d.ts.map +1 -0
  445. package/lib/index.js +12 -39
  446. package/lib/index.js.map +1 -0
  447. package/package.json +86 -42
@@ -1,21 +1,18 @@
1
- "use strict"
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- })
6
-
7
- const boom_1 = require("@hapi/boom")
8
- const crypto_1 = require("crypto")
9
- const fs_1 = require("fs")
10
- const WAProto_1 = require("../../WAProto")
11
- const Defaults_1 = require("../Defaults")
12
- const Types_1 = require("../Types")
13
- const WABinary_1 = require("../WABinary")
14
- const crypto_2 = require("./crypto")
15
- const generics_1 = require("./generics")
16
- const messages_media_1 = require("./messages-media")
17
- const rich_message_utils_1 = require("./rich-message-utils")
18
-
1
+ import { Boom } from '@hapi/boom';
2
+ import { randomBytes } from 'crypto';
3
+ import { zip } from 'fflate';
4
+ import { promises as fs } from 'fs';
5
+ import {} from 'stream';
6
+ import { proto } from '../../WAProto/index.js';
7
+ import { CALL_AUDIO_PREFIX, CALL_VIDEO_PREFIX, DONATE_URL, LIBRARY_NAME, MEDIA_KEYS, URL_REGEX, WA_DEFAULT_EPHEMERAL } from '../Defaults/index.js';
8
+ import { AssociationType, ButtonHeaderType, ButtonType, CarouselCardType, ListType, ProtocolType, WAMessageStatus, WAProto } from '../Types/index.js';
9
+ import { isJidGroup, isJidNewsletter, isJidStatusBroadcast, isLidUser, isPnUser, jidNormalizedUser } from '../WABinary/index.js';
10
+ import { sha256 } from './crypto.js';
11
+ import { generateMessageIDV2, getKeyAuthor, unixTimestampSeconds } from './generics.js';
12
+ import { downloadContentFromMessage, encryptedStream, generateThumbnail, getAudioDuration, getAudioWaveform, getImageProcessingLibrary, getRawMediaUploadData, getStream, toBuffer } from './messages-media.js';
13
+ import { prepareRichResponseMessage } from './rich-message-utils.js';
14
+ import { shouldIncludeReportingToken } from './reporting-utils.js';
15
+ const CONCURRENCY_LIMIT = 10;
19
16
  const MIMETYPE_MAP = {
20
17
  image: 'image/jpeg',
21
18
  video: 'video/mp4',
@@ -23,117 +20,111 @@ const MIMETYPE_MAP = {
23
20
  audio: 'audio/ogg; codecs=opus',
24
21
  sticker: 'image/webp',
25
22
  'product-catalog-image': 'image/jpeg'
26
- }
27
-
23
+ };
28
24
  const MessageTypeProto = {
29
- 'image': Types_1.WAProto.Message.ImageMessage,
30
- 'video': Types_1.WAProto.Message.VideoMessage,
31
- 'audio': Types_1.WAProto.Message.AudioMessage,
32
- 'sticker': Types_1.WAProto.Message.StickerMessage,
33
- 'document': Types_1.WAProto.Message.DocumentMessage,
34
- }
35
-
36
- const extractUrlFromText = (text) => text.match(Defaults_1.URL_REGEX)?.[0]
37
-
38
- const generateLinkPreviewIfRequired = async (text, getUrlInfo, logger) => {
39
- const url = extractUrlFromText(text)
25
+ image: WAProto.Message.ImageMessage,
26
+ video: WAProto.Message.VideoMessage,
27
+ audio: WAProto.Message.AudioMessage,
28
+ sticker: WAProto.Message.StickerMessage,
29
+ document: WAProto.Message.DocumentMessage
30
+ };
31
+ /**
32
+ * Uses a regex to test whether the string contains a URL, and returns the URL if it does.
33
+ * @param text eg. hello https://google.com
34
+ * @returns the URL, eg. https://google.com
35
+ */
36
+ export const extractUrlFromText = (text) => text.match(URL_REGEX)?.[0];
37
+ export const generateLinkPreviewIfRequired = async (text, getUrlInfo, logger) => {
38
+ const url = extractUrlFromText(text);
40
39
  if (!!getUrlInfo && url) {
41
40
  try {
42
- const urlInfo = await getUrlInfo(url)
43
- return urlInfo
44
- } catch (error) {
45
- logger?.warn({
46
- trace: error.stack
47
- }, 'url generation failed')
41
+ const urlInfo = await getUrlInfo(url);
42
+ return urlInfo;
43
+ }
44
+ catch (error) {
45
+ // ignore if fails
46
+ logger?.warn({ trace: error.stack }, 'url generation failed');
48
47
  }
49
48
  }
50
- }
51
-
49
+ };
52
50
  const assertColor = async (color) => {
53
- let assertedColor
51
+ let assertedColor;
54
52
  if (typeof color === 'number') {
55
- assertedColor = color > 0 ? color : 0xffffffff + Number(color) + 1
56
- } else {
57
- let hex = color.trim().replace('#', '')
53
+ assertedColor = color > 0 ? color : 0xffffffff + Number(color) + 1;
54
+ }
55
+ else {
56
+ let hex = color.trim().replace('#', '');
58
57
  if (hex.length <= 6) {
59
- hex = 'FF' + hex.padStart(6, '0')
58
+ hex = 'FF' + hex.padStart(6, '0');
60
59
  }
61
- assertedColor = parseInt(hex, 16)
62
- return assertedColor
60
+ assertedColor = parseInt(hex, 16);
61
+ return assertedColor;
63
62
  }
64
- }
65
-
66
- const prepareWAMessageMedia = async (message, options) => {
67
- const logger = options.logger
68
- let mediaType
69
-
70
- for (const key of Defaults_1.MEDIA_KEYS) {
63
+ };
64
+ export const prepareWAMessageMedia = async (message, options) => {
65
+ const logger = options.logger;
66
+ let mediaType;
67
+ for (const key of MEDIA_KEYS) {
71
68
  if (key in message) {
72
- mediaType = key
69
+ mediaType = key;
73
70
  }
74
71
  }
75
-
76
72
  if (!mediaType) {
77
- throw new boom_1.Boom('Invalid media type', {
78
- statusCode: 400
79
- });
73
+ throw new Boom('Invalid media type', { statusCode: 400 });
80
74
  }
81
-
82
75
  const uploadData = {
83
76
  ...message,
84
77
  media: message[mediaType]
85
- }
86
- delete uploadData[mediaType]
87
- const cacheableKey = typeof uploadData.media === 'object' && 'url' in uploadData.media && !!uploadData.media.url && !!options.mediaCache && mediaType + ':' + uploadData.media.url.toString()
88
-
78
+ };
79
+ delete uploadData[mediaType];
80
+ // check if cacheable + generate cache key
81
+ const cacheableKey = typeof uploadData.media === 'object' &&
82
+ 'url' in uploadData.media &&
83
+ !!uploadData.media.url &&
84
+ !!options.mediaCache &&
85
+ mediaType + ':' + uploadData.media.url.toString();
89
86
  if (mediaType === 'document' && !uploadData.fileName) {
90
- uploadData.fileName = 'file'
87
+ uploadData.fileName = 'file';
91
88
  }
92
-
93
89
  if (!uploadData.mimetype) {
94
- uploadData.mimetype = MIMETYPE_MAP[mediaType]
90
+ uploadData.mimetype = MIMETYPE_MAP[mediaType];
95
91
  }
96
-
97
92
  if (cacheableKey) {
98
- const mediaBuff = await options.mediaCache.get(cacheableKey)
93
+ const mediaBuff = await options.mediaCache.get(cacheableKey);
99
94
  if (mediaBuff) {
100
- logger?.debug({
101
- cacheableKey
102
- }, 'got media cache hit')
103
- const obj = Types_1.WAProto.Message.decode(mediaBuff)
104
- const key = `${mediaType}Message`
105
- Object.assign(obj[key], {
106
- ...uploadData,
107
- media: undefined
108
- })
109
- return obj
95
+ logger?.debug({ cacheableKey }, 'got media cache hit');
96
+ const obj = proto.Message.decode(mediaBuff);
97
+ const key = `${mediaType}Message`;
98
+ Object.assign(obj[key], { ...uploadData, media: undefined });
99
+ return obj;
110
100
  }
111
101
  }
112
-
113
- const isNewsletter = !!options.jid && WABinary_1.isJidNewsletter(options.jid)
114
-
102
+ const isNewsletter = !!options.jid && isJidNewsletter(options.jid);
115
103
  if (isNewsletter) {
116
- logger?.info({
117
- key: cacheableKey
118
- }, 'Preparing raw media for newsletter')
119
- const {
120
- filePath,
121
- fileSha256,
122
- fileLength
123
- } = await messages_media_1.getRawMediaUploadData(uploadData.media, options.mediaTypeOverride || mediaType, logger)
124
- const fileSha256B64 = fileSha256.toString('base64')
125
- const {
126
- mediaUrl,
127
- directPath
128
- } = await options.upload(filePath, {
104
+ logger?.info({ key: cacheableKey }, 'Preparing raw media for newsletter');
105
+ const { filePath, fileSha256, fileLength } = await getRawMediaUploadData(uploadData.media, options.mediaTypeOverride || mediaType, logger);
106
+ const fileSha256B64 = fileSha256.toString('base64');
107
+ // Generate thumbnail for image/video before uploading (filePath still exists)
108
+ if ((mediaType === 'image' || mediaType === 'video') && !uploadData.jpegThumbnail) {
109
+ try {
110
+ const { thumbnail, originalImageDimensions } = await generateThumbnail(filePath, mediaType, options);
111
+ uploadData.jpegThumbnail = thumbnail;
112
+ if (!uploadData.width && originalImageDimensions) {
113
+ uploadData.width = originalImageDimensions.width;
114
+ uploadData.height = originalImageDimensions.height;
115
+ }
116
+ } catch (e) {
117
+ logger?.warn({ err: e }, 'failed to generate thumbnail for newsletter media');
118
+ }
119
+ }
120
+ const { mediaUrl, directPath } = await options.upload(filePath, {
129
121
  fileEncSha256B64: fileSha256B64,
130
122
  mediaType: mediaType,
131
123
  timeoutMs: options.mediaUploadTimeoutMs
132
- })
133
-
134
- await fs_1.promises.unlink(filePath)
135
-
136
- const obj = Types_1.WAProto.Message.fromObject({
124
+ });
125
+ await fs.unlink(filePath);
126
+ const obj = WAProto.Message.fromObject({
127
+ // todo: add more support here
137
128
  [`${mediaType}Message`]: MessageTypeProto[mediaType].fromObject({
138
129
  url: mediaUrl,
139
130
  directPath,
@@ -142,114 +133,83 @@ const prepareWAMessageMedia = async (message, options) => {
142
133
  ...uploadData,
143
134
  media: undefined
144
135
  })
145
- })
146
-
136
+ });
147
137
  if (uploadData.ptv) {
148
- obj.ptvMessage = obj.videoMessage
149
- delete obj.videoMessage
138
+ obj.ptvMessage = obj.videoMessage;
139
+ delete obj.videoMessage;
140
+ }
141
+ if (obj.stickerMessage) {
142
+ obj.stickerMessage.stickerSentTs = Date.now();
150
143
  }
151
-
152
144
  if (cacheableKey) {
153
- logger?.debug({
154
- cacheableKey
155
- }, 'set cache');
156
- await options.mediaCache.set(cacheableKey, Types_1.WAProto.Message.encode(obj).finish())
157
- }
158
-
159
- return obj
160
- }
161
-
162
- const requiresDurationComputation = mediaType === 'audio' && typeof uploadData.seconds === 'undefined'
163
- const requiresThumbnailComputation = (mediaType === 'image' || mediaType === 'video') && typeof uploadData['jpegThumbnail'] === 'undefined'
164
- const requiresWaveformProcessing = mediaType === 'audio' && uploadData.ptt === true && typeof uploadData.waveform === 'undefined'
165
- const requiresAudioBackground = options.backgroundColor && mediaType === 'audio' && uploadData.ptt === true
166
- const requiresOriginalForSomeProcessing = requiresDurationComputation || requiresThumbnailComputation
167
-
168
- const {
169
- mediaKey,
170
- encFilePath,
171
- originalFilePath,
172
- fileEncSha256,
173
- fileSha256,
174
- fileLength
175
- } = await messages_media_1.encryptedStream(uploadData.media, options.mediaTypeOverride || mediaType, {
145
+ logger?.debug({ cacheableKey }, 'set cache');
146
+ await options.mediaCache.set(cacheableKey, WAProto.Message.encode(obj).finish());
147
+ }
148
+ return obj;
149
+ }
150
+ const requiresDurationComputation = mediaType === 'audio' && typeof uploadData.seconds === 'undefined';
151
+ const requiresThumbnailComputation = (mediaType === 'image' || mediaType === 'video') && typeof uploadData['jpegThumbnail'] === 'undefined';
152
+ const requiresWaveformProcessing = mediaType === 'audio' && uploadData.ptt === true && typeof uploadData.waveform === 'undefined';
153
+ const requiresAudioBackground = options.backgroundColor && mediaType === 'audio' && uploadData.ptt === true;
154
+ const requiresOriginalForSomeProcessing = requiresDurationComputation || requiresThumbnailComputation;
155
+ const { mediaKey, encFilePath, originalFilePath, fileEncSha256, fileSha256, fileLength } = await encryptedStream(uploadData.media, options.mediaTypeOverride || mediaType, {
176
156
  logger,
177
157
  saveOriginalFileIfRequired: requiresOriginalForSomeProcessing,
178
158
  opts: options.options
179
- })
180
-
181
- const fileEncSha256B64 = fileEncSha256.toString('base64')
182
-
183
- const [{
184
- mediaUrl,
185
- directPath
186
- }] = await Promise.all([
159
+ });
160
+ const fileEncSha256B64 = fileEncSha256.toString('base64');
161
+ const [{ mediaUrl, directPath }] = await Promise.all([
187
162
  (async () => {
188
163
  const result = await options.upload(encFilePath, {
189
164
  fileEncSha256B64,
190
165
  mediaType,
191
166
  timeoutMs: options.mediaUploadTimeoutMs
192
- })
193
- logger?.debug({
194
- mediaType,
195
- cacheableKey
196
- }, 'uploaded media')
197
- return result
167
+ });
168
+ logger?.debug({ mediaType, cacheableKey }, 'uploaded media');
169
+ return result;
198
170
  })(),
199
-
200
171
  (async () => {
201
172
  try {
202
173
  if (requiresThumbnailComputation) {
203
- const {
204
- thumbnail,
205
- originalImageDimensions
206
- } = await messages_media_1.generateThumbnail(originalFilePath, mediaType, options)
207
- uploadData.jpegThumbnail = thumbnail
174
+ const { thumbnail, originalImageDimensions } = await generateThumbnail(originalFilePath, mediaType, options);
175
+ uploadData.jpegThumbnail = thumbnail;
208
176
  if (!uploadData.width && originalImageDimensions) {
209
- uploadData.width = originalImageDimensions.width
210
- uploadData.height = originalImageDimensions.height
211
-
212
- logger?.debug('set dimensions')
177
+ uploadData.width = originalImageDimensions.width;
178
+ uploadData.height = originalImageDimensions.height;
179
+ logger?.debug('set dimensions');
213
180
  }
214
-
215
181
  logger?.debug('generated thumbnail');
216
182
  }
217
-
218
183
  if (requiresDurationComputation) {
219
- uploadData.seconds = await messages_media_1.getAudioDuration(originalFilePath)
220
- logger?.debug('computed audio duration')
184
+ uploadData.seconds = await getAudioDuration(originalFilePath);
185
+ logger?.debug('computed audio duration');
221
186
  }
222
-
223
187
  if (requiresWaveformProcessing) {
224
- uploadData.waveform = await messages_media_1.getAudioWaveform(originalFilePath, logger)
225
- logger?.debug('processed waveform')
188
+ uploadData.waveform = await getAudioWaveform(originalFilePath, logger);
189
+ logger?.debug('processed waveform');
226
190
  }
227
-
228
191
  if (requiresAudioBackground) {
229
- uploadData.backgroundArgb = await assertColor(options.backgroundColor)
230
- logger?.debug('computed backgroundColor audio status')
192
+ uploadData.backgroundArgb = await assertColor(options.backgroundColor);
193
+ logger?.debug('computed backgroundColor audio status');
231
194
  }
232
- } catch (error) {
233
- logger?.warn({
234
- trace: error.stack
235
- }, 'failed to obtain extra info')
195
+ }
196
+ catch (error) {
197
+ logger?.warn({ trace: error.stack }, 'failed to obtain extra info');
236
198
  }
237
199
  })()
238
200
  ]).finally(async () => {
239
201
  try {
240
- await fs_1.promises.unlink(encFilePath)
241
-
202
+ await fs.unlink(encFilePath);
242
203
  if (originalFilePath) {
243
- await fs_1.promises.unlink(originalFilePath)
204
+ await fs.unlink(originalFilePath);
244
205
  }
245
-
246
- logger?.debug('removed tmp files')
247
- } catch (error) {
248
- logger?.warn('failed to remove tmp file')
249
- }
250
- })
251
-
252
- const obj = Types_1.WAProto.Message.fromObject({
206
+ logger?.debug('removed tmp files');
207
+ }
208
+ catch (error) {
209
+ logger?.warn('failed to remove tmp file');
210
+ }
211
+ });
212
+ const obj = WAProto.Message.fromObject({
253
213
  [`${mediaType}Message`]: MessageTypeProto[mediaType].fromObject({
254
214
  url: mediaUrl,
255
215
  directPath,
@@ -257,850 +217,827 @@ const prepareWAMessageMedia = async (message, options) => {
257
217
  fileEncSha256,
258
218
  fileSha256,
259
219
  fileLength,
260
- mediaKeyTimestamp: generics_1.unixTimestampSeconds(),
220
+ mediaKeyTimestamp: unixTimestampSeconds(),
261
221
  ...uploadData,
262
222
  media: undefined
263
223
  })
264
- })
265
-
224
+ });
266
225
  if (uploadData.ptv) {
267
- obj.ptvMessage = obj.videoMessage
268
- delete obj.videoMessage
226
+ obj.ptvMessage = obj.videoMessage;
227
+ delete obj.videoMessage;
269
228
  }
270
-
271
229
  if (cacheableKey) {
272
- logger?.debug({
273
- cacheableKey
274
- }, 'set cache');
275
- await options.mediaCache.set(cacheableKey, Types_1.WAProto.Message.encode(obj).finish())
230
+ logger?.debug({ cacheableKey }, 'set cache');
231
+ await options.mediaCache.set(cacheableKey, WAProto.Message.encode(obj).finish());
276
232
  }
277
-
278
- return obj
279
- }
280
-
281
- const prepareAlbumMessageContent = async (jid, albums, options) => {
282
- let mediaHandle
283
- let mediaMsg
284
- let message = []
285
-
286
- const albumMsg = generateWAMessageFromContent(jid, {
287
- albumMessage: {
288
- expectedImageCount: albums.filter(item => 'image' in item).length,
289
- expectedVideoCount: albums.filter(item => 'video' in item).length
290
- }
291
- }, options)
292
-
293
- await options.sock.relayMessage(jid, albumMsg.message, {
294
- messageId: albumMsg.key.id
295
- })
296
-
297
- for (const i in albums) {
298
- const media = albums[i]
299
-
300
- if ('image' in media) {
301
- mediaMsg = await generateWAMessage(jid, {
302
- image: media.image,
303
- ...media,
304
- ...options
305
- }, {
306
- userJid: options.userJid,
307
- upload: async (encFilePath, opts) => {
308
- const up = await options.sock.waUploadToServer(encFilePath, {
309
- ...opts,
310
- newsletter: WABinary_1.isJidNewsletter(jid)
311
- })
312
- mediaHandle = up.handle
313
- return up
314
- },
315
- ...options
316
- })
317
- } else if ('video' in media) {
318
- mediaMsg = await generateWAMessage(jid, {
319
- video: media.video,
320
- ...media,
321
- ...options
322
- }, {
323
- userJid: options.userJid,
324
- upload: async (encFilePath, opts) => {
325
- const up = await options.sock.waUploadToServer(encFilePath, {
326
- ...opts,
327
- newsletter: WABinary_1.isJidNewsletter(jid)
328
- })
329
- mediaHandle = up.handle
330
- return up
331
- },
332
- ...options
333
- })
334
- }
335
-
336
- if (mediaMsg) {
337
- mediaMsg.message.messageContextInfo = {
338
- messageSecret: crypto_1.randomBytes(32),
339
- messageAssociation: {
340
- associationType: 1,
341
- parentMessageKey: albumMsg.key
342
- }
343
- }
344
- }
345
-
346
- message.push(mediaMsg)
347
- }
348
-
349
- return message
350
- }
351
-
352
- const prepareDisappearingMessageSettingContent = (expiration) => {
233
+ return obj;
234
+ };
235
+ export const prepareDisappearingMessageSettingContent = (ephemeralExpiration) => {
236
+ ephemeralExpiration = ephemeralExpiration || 0;
353
237
  const content = {
354
238
  ephemeralMessage: {
355
239
  message: {
356
240
  protocolMessage: {
357
- type: Types_1.WAProto.Message.ProtocolMessage.Type.EPHEMERAL_SETTING,
358
- ephemeralExpiration: expiration ? expiration : 0
241
+ type: WAProto.Message.ProtocolMessage.Type.EPHEMERAL_SETTING,
242
+ ephemeralExpiration
359
243
  }
360
244
  }
361
245
  }
362
- }
363
-
364
- return Types_1.WAProto.Message.fromObject(content)
365
- }
366
-
367
- const generateForwardMessageContent = (message, forceForward) => {
368
- let content = message.message
246
+ };
247
+ return WAProto.Message.fromObject(content);
248
+ };
249
+ /**
250
+ * Generate forwarded message content like WA does
251
+ * @param message the message to forward
252
+ * @param options.forceForward will show the message as forwarded even if it is from you
253
+ */
254
+ export const generateForwardMessageContent = (message, forceForward) => {
255
+ let content = message.message;
369
256
  if (!content) {
370
- throw new boom_1.Boom('no content in message', {
371
- statusCode: 400
372
- })
257
+ throw new Boom('no content in message', { statusCode: 400 });
373
258
  }
374
-
375
- content = normalizeMessageContent(content)
376
- content = WAProto_1.proto.Message.decode(WAProto_1.proto.Message.encode(content).finish())
377
-
378
- let key = Object.keys(content)[0]
379
- let score = content?.[key]?.contextInfo?.forwardingScore || 0
380
-
381
- score += message.key.fromMe && !forceForward ? 0 : 1
382
-
259
+ // hacky copy
260
+ content = normalizeMessageContent(content);
261
+ content = proto.Message.decode(proto.Message.encode(content).finish());
262
+ let key = Object.keys(content)[0];
263
+ let score = content?.[key]?.contextInfo?.forwardingScore || 0;
264
+ score += message.key.fromMe && !forceForward ? 0 : 1;
383
265
  if (key === 'conversation') {
384
- content.extendedTextMessage = {
385
- text: content[key]
386
- }
387
- delete content.conversation
388
- key = 'extendedTextMessage'
266
+ content.extendedTextMessage = { text: content[key] };
267
+ delete content.conversation;
268
+ key = 'extendedTextMessage';
389
269
  }
390
-
270
+ const key_ = content?.[key];
391
271
  if (score > 0) {
392
- content[key].contextInfo = {
393
- forwardingScore: score,
394
- isForwarded: true
272
+ key_.contextInfo = { forwardingScore: score, isForwarded: true };
273
+ }
274
+ else {
275
+ key_.contextInfo = {};
276
+ }
277
+ return content;
278
+ };
279
+ export const hasNonNullishProperty = (message, key) => {
280
+ return message != null &&
281
+ typeof message === 'object' &&
282
+ key in message &&
283
+ message[key] != null;
284
+ };
285
+ export const hasOptionalProperty = (obj, key) => {
286
+ return obj != null &&
287
+ typeof obj === 'object' &&
288
+ key in obj &&
289
+ obj[key] != null;
290
+ };
291
+ // Validate album message media to avoid bug
292
+ export const hasValidAlbumMedia = (message) => {
293
+ return message.imageMessage ||
294
+ message.videoMessage;
295
+ };
296
+ export const hasValidInteractiveHeader = (message) => {
297
+ return message.imageMessage ||
298
+ message.videoMessage ||
299
+ message.documentMessage ||
300
+ message.productMessage ||
301
+ message.locationMessage;
302
+ };
303
+ // Validate carousel cards header to avoid bug
304
+ export const hasValidCarouselHeader = (message) => {
305
+ return message.imageMessage ||
306
+ message.videoMessage ||
307
+ message.productMessage;
308
+ };
309
+ // Extract product message into a standalone function so it can also be reused as the header for interactive messages
310
+ const prepareProductMessage = async (message, options) => {
311
+ if (!message.businessOwnerJid) {
312
+ throw new Boom('"businessOwnerJid" is missing from the content', { statusCode: 400 });
313
+ }
314
+ const { imageMessage } = await prepareWAMessageMedia({ image: message.image || message.product.productImage }, options);
315
+ const content = {
316
+ ...message,
317
+ product: {
318
+ currencyCode: 'IDR',
319
+ priceAmount1000: 1000,
320
+ title: LIBRARY_NAME,
321
+ ...message.product,
322
+ productImage: imageMessage
323
+ }
324
+ };
325
+ delete content.image;
326
+ return content;
327
+ };
328
+ const prepareStickerPackMessage = async (message, options) => {
329
+ const { cover, stickers = [], name = '📦 Sticker Pack', publisher = 'GitHub: pontalabs', description = '🏷️ pontalabs/baileys' } = message;
330
+ if (stickers.length > 60) {
331
+ throw new Boom('Sticker pack exceeds the maximum limit of 60 stickers', { statusCode: 400 });
332
+ }
333
+ if (stickers.length === 0) {
334
+ throw new Boom('Sticker pack must contain at least one sticker', { statusCode: 400 });
335
+ }
336
+ if (!cover) {
337
+ throw new Boom('Sticker pack must contain a cover', { statusCode: 400 });
338
+ }
339
+ const logger = options.logger;
340
+ let cacheableKey = false;
341
+ if (Array.isArray(stickers) && stickers.length && options.mediaCache) {
342
+ const urls = [];
343
+ for (let i = 0; i < stickers.length; i++) {
344
+ const data = stickers[i].data;
345
+ if (typeof data === 'object' && data?.url) {
346
+ urls.push(data.url);
347
+ }
348
+ }
349
+ if (urls.length > 0) {
350
+ cacheableKey = 'sticker:' + urls.join('@');
395
351
  }
396
- } else {
397
- content[key].contextInfo = {}
398
352
  }
399
-
400
- return content
401
- }
402
-
403
- const generateWAMessageContent = async (message, options) => {
404
- let m = {}
405
- if ('code' in message || 'codes' in message || 'table' in message || 'items' in message || 'richText' in message || 'richResponse' in message || 'richImages' in message || 'richVideo' in message || 'richSuggestions' in message || 'richQuestion' in message || 'richLatex' in message || 'richProduct' in message || 'richPost' in message || 'richReels' in message || 'richSources' in message || 'richTip' in message || 'richFooter' in message) {
406
- return rich_message_utils_1.prepareRichResponseMessage(message)
407
- } else if ('text' in message) {
408
- const extContent = {
409
- text: message.text
410
- }
411
- let urlInfo = message.linkPreview
412
-
353
+ if (cacheableKey) {
354
+ const mediaBuff = await options.mediaCache.get(cacheableKey);
355
+ if (mediaBuff) {
356
+ logger?.debug({ cacheableKey }, 'got media cache hit');
357
+ return proto.Message.StickerPackMessage.decode(mediaBuff);
358
+ }
359
+ }
360
+ const lib = await getImageProcessingLibrary();
361
+ const hasSharp = 'sharp' in lib && !!lib.sharp?.default;
362
+ const hasImage = 'image' in lib && !!lib.image?.Transformer;
363
+ const hasJimp = 'jimp' in lib && !!lib.jimp?.Jimp;
364
+ if (!hasSharp && !hasImage) {
365
+ throw new Boom('No image processing library (sharp or @napi-rs/image) available for converting sticker to WebP.');
366
+ }
367
+ const stickerPackIdValue = generateMessageIDV2();
368
+ const stickerData = {};
369
+ const stickerMetadata = new Array(stickers.length);
370
+ for (let i = 0; i < stickers.length; i += CONCURRENCY_LIMIT) {
371
+ const promises = [];
372
+ const chunkEnd = Math.min(i + CONCURRENCY_LIMIT, stickers.length);
373
+ for (let j = i; j < chunkEnd; j++) {
374
+ promises.push((async (index) => {
375
+ const sticker = stickers[index];
376
+ const { stream } = await getStream(sticker.data);
377
+ const buffer = await toBuffer(stream);
378
+ let webpBuffer;
379
+ let isAnimated = false;
380
+ if (isWebPBuffer(buffer)) {
381
+ webpBuffer = buffer;
382
+ isAnimated = isAnimatedWebP(buffer);
383
+ }
384
+ else if (hasSharp) {
385
+ webpBuffer = await lib.sharp.default(buffer)
386
+ .resize(512, 512, { fit: 'inside' })
387
+ .webp({ quality: 80 })
388
+ .toBuffer();
389
+ }
390
+ else {
391
+ webpBuffer = await new lib.image.Transformer(buffer)
392
+ .resize(512, 512)
393
+ .webp(80);
394
+ }
395
+ if (webpBuffer.length > 1024 * 1024) {
396
+ throw new Boom(`Sticker at index ${index} exceeds the 1MB size limit`, { statusCode: 400 });
397
+ }
398
+ const hash = sha256(webpBuffer).toString('base64').replace(/\//g, '-');
399
+ const fileName = `${hash}.webp`;
400
+ stickerData[fileName] = [new Uint8Array(webpBuffer), { level: 0 }];
401
+ stickerMetadata[index] = {
402
+ fileName,
403
+ mimetype: 'image/webp',
404
+ isAnimated,
405
+ emojis: sticker.emojis || ['✨'],
406
+ accessibilityLabel: sticker.accessibilityLabel || '‎'
407
+ };
408
+ })(j));
409
+ }
410
+ await Promise.all(promises);
411
+ }
412
+ const trayIconFileName = `${stickerPackIdValue}.webp`;
413
+ const { stream: coverStream } = await getStream(cover);
414
+ const coverBuffer = await toBuffer(coverStream);
415
+ let coverWebpBuffer;
416
+ if (isWebPBuffer(coverBuffer)) {
417
+ coverWebpBuffer = coverBuffer;
418
+ }
419
+ else if (hasSharp) {
420
+ coverWebpBuffer = await lib.sharp.default(coverBuffer)
421
+ .resize(512, 512, { fit: 'inside' })
422
+ .webp({ quality: 80 })
423
+ .toBuffer();
424
+ }
425
+ else {
426
+ coverWebpBuffer = await new lib.image.Transformer(coverBuffer)
427
+ .resize(512, 512)
428
+ .webp(80);
429
+ }
430
+ stickerData[trayIconFileName] = [new Uint8Array(coverWebpBuffer), { level: 0 }];
431
+ const zipBuffer = await new Promise((resolve, reject) => {
432
+ zip(stickerData, (error, data) => error ? reject(error) : resolve(Buffer.from(data)));
433
+ });
434
+ const stickerPackUpload = await encryptedStream(zipBuffer, 'sticker-pack', {
435
+ logger,
436
+ opts: options.options
437
+ });
438
+ let stickerPackUploadResult;
439
+ try {
440
+ stickerPackUploadResult = await options.upload(stickerPackUpload.encFilePath, {
441
+ fileEncSha256B64: stickerPackUpload.fileEncSha256.toString('base64'),
442
+ mediaType: 'sticker-pack',
443
+ timeoutMs: options.mediaUploadTimeoutMs
444
+ });
445
+ }
446
+ finally {
447
+ fs.unlink(stickerPackUpload.encFilePath).catch(() => logger?.warn('failed to remove tmp file'));
448
+ }
449
+ const obj = {
450
+ name,
451
+ publisher,
452
+ stickerPackId: stickerPackIdValue,
453
+ packDescription: description,
454
+ stickerPackOrigin: proto.Message.StickerPackMessage.StickerPackOrigin.USER_CREATED,
455
+ stickerPackSize: zipBuffer.length,
456
+ stickers: stickerMetadata,
457
+ fileSha256: stickerPackUpload.fileSha256,
458
+ fileEncSha256: stickerPackUpload.fileEncSha256,
459
+ mediaKey: stickerPackUpload.mediaKey,
460
+ directPath: stickerPackUploadResult.directPath,
461
+ fileLength: stickerPackUpload.fileLength,
462
+ mediaKeyTimestamp: unixTimestampSeconds(),
463
+ trayIconFileName
464
+ };
465
+ try {
466
+ let thumbnailBuffer;
467
+ if (hasSharp) {
468
+ thumbnailBuffer = await lib.sharp.default(coverBuffer).resize(252, 252).jpeg().toBuffer();
469
+ }
470
+ else if (hasImage) {
471
+ thumbnailBuffer = await new lib.image.Transformer(coverBuffer).resize(252, 252).jpeg();
472
+ }
473
+ else if (hasJimp) {
474
+ const jimpImage = await lib.jimp.Jimp.read(coverBuffer);
475
+ thumbnailBuffer = await jimpImage.resize({ w: 252, h: 252 }).getBuffer('image/jpeg');
476
+ }
477
+ else {
478
+ throw new Error('No image processing library available for thumbnail generation');
479
+ }
480
+ if (!thumbnailBuffer || thumbnailBuffer.length === 0) {
481
+ throw new Error('Failed to generate thumbnail buffer');
482
+ }
483
+ const thumbUpload = await encryptedStream(thumbnailBuffer, 'thumbnail-sticker-pack', {
484
+ logger,
485
+ opts: options.options,
486
+ mediaKey: stickerPackUpload.mediaKey
487
+ });
488
+ let thumbUploadResult;
489
+ try {
490
+ thumbUploadResult = await options.upload(thumbUpload.encFilePath, {
491
+ fileEncSha256B64: thumbUpload.fileEncSha256.toString('base64'),
492
+ mediaType: 'thumbnail-sticker-pack',
493
+ timeoutMs: options.mediaUploadTimeoutMs
494
+ });
495
+ }
496
+ finally {
497
+ fs.unlink(thumbUpload.encFilePath).catch(() => logger?.warn('failed to remove tmp file'));
498
+ }
499
+ Object.assign(obj, {
500
+ thumbnailDirectPath: thumbUploadResult.directPath,
501
+ thumbnailSha256: thumbUpload.fileSha256,
502
+ thumbnailEncSha256: thumbUpload.fileEncSha256,
503
+ thumbnailHeight: 252,
504
+ thumbnailWidth: 252,
505
+ imageDataHash: sha256(thumbnailBuffer).toString('base64')
506
+ });
507
+ }
508
+ catch (error) {
509
+ logger?.warn(`Thumbnail generation failed: ${error}`);
510
+ }
511
+ if (cacheableKey) {
512
+ logger?.debug({ cacheableKey }, 'set cache (background)');
513
+ options.mediaCache.set(cacheableKey, WAProto.Message.StickerPackMessage.encode(obj).finish());
514
+ }
515
+ return WAProto.Message.StickerPackMessage.fromObject(obj);
516
+ };
517
+ const prepareNativeFlowButtons = (message) => {
518
+ const buttons = message.nativeFlow;
519
+ const isButtonsFieldArray = Array.isArray(buttons);
520
+ const correctedField = isButtonsFieldArray ? buttons : buttons.buttons;
521
+ const messageParamsJson = {};
522
+ if (hasOptionalProperty(message, 'offerText') && !!message.offerText) {
523
+ Object.assign(messageParamsJson, {
524
+ limited_time_offer: {
525
+ text: message.offerText || LIBRARY_NAME,
526
+ url: (message.offerUrl && /^https?:\/\//i.test(message.offerUrl)) ? message.offerUrl : DONATE_URL,
527
+ copy_code: message.offerCode,
528
+ expiration_time: message.offerExpiration
529
+ }
530
+ });
531
+ }
532
+ if (hasOptionalProperty(message, 'optionText') && !!message.optionText) {
533
+ Object.assign(messageParamsJson, {
534
+ bottom_sheet: {
535
+ in_thread_buttons_limit: 1,
536
+ divider_indices: Array.from({ length: correctedField.length }, (_, index) => index),
537
+ list_title: message.optionTitle || '📄 Select Options',
538
+ button_title: message.optionText
539
+ }
540
+ });
541
+ }
542
+ return {
543
+ buttons: correctedField.map(button => {
544
+ const buttonText = button.text || button.buttonText;
545
+ const buttonIcon = button.icon?.toUpperCase();
546
+ if (hasOptionalProperty(button, 'id') && !!button.id) {
547
+ return { name: 'quick_reply', buttonParamsJson: JSON.stringify({ display_text: buttonText || '👉🏻 Click', id: button.id, icon: buttonIcon }) };
548
+ }
549
+ else if (hasOptionalProperty(button, 'copy') && !!button.copy) {
550
+ return { name: 'cta_copy', buttonParamsJson: JSON.stringify({ display_text: buttonText || '📋 Copy', copy_code: button.copy, icon: buttonIcon }) };
551
+ }
552
+ else if (hasOptionalProperty(button, 'url') && !!button.url) {
553
+ return { name: 'cta_url', buttonParamsJson: JSON.stringify({ display_text: buttonText || '🌐 Visit', url: button.url, merchant_url: button.url, webview_interaction: button.useWebview, icon: buttonIcon }) };
554
+ }
555
+ else if (hasOptionalProperty(button, 'call') && !!button.call) {
556
+ return { name: 'cta_call', buttonParamsJson: JSON.stringify({ display_text: buttonText || '📞 Call', phone_number: button.call, icon: buttonIcon }) };
557
+ }
558
+ else if (hasOptionalProperty(button, 'sections') && !!button.sections) {
559
+ return { name: 'single_select', buttonParamsJson: JSON.stringify({ title: buttonText || '📋 Select', sections: button.sections, icon: buttonIcon }) };
560
+ }
561
+ return button;
562
+ }),
563
+ messageParamsJson: JSON.stringify(messageParamsJson),
564
+ messageVersion: 3
565
+ };
566
+ };
567
+ export const generateWAMessageContent = async (message, options) => {
568
+ var _a, _b;
569
+ let m = {};
570
+ if (hasNonNullishProperty(message, 'raw')) {
571
+ delete message.raw;
572
+ return message;
573
+ }
574
+ else if (hasNonNullishProperty(message, 'code') ||
575
+ hasNonNullishProperty(message, 'links') ||
576
+ hasNonNullishProperty(message, 'table') ||
577
+ hasNonNullishProperty(message, 'aiBlocks') ||
578
+ hasNonNullishProperty(message, 'latex') ||
579
+ hasNonNullishProperty(message, 'gridImage') ||
580
+ hasNonNullishProperty(message, 'inlineImage') ||
581
+ hasNonNullishProperty(message, 'dynamic') ||
582
+ hasNonNullishProperty(message, 'map') ||
583
+ hasNonNullishProperty(message, 'contentItems') ||
584
+ // tipe baru (nama prefixed 'rich' agar tidak konflik dengan media biasa)
585
+ hasNonNullishProperty(message, 'richImage') ||
586
+ hasNonNullishProperty(message, 'richVideo') ||
587
+ hasNonNullishProperty(message, 'reels') ||
588
+ hasNonNullishProperty(message, 'source') ||
589
+ hasNonNullishProperty(message, 'richProduct') ||
590
+ hasNonNullishProperty(message, 'richPost') ||
591
+ hasNonNullishProperty(message, 'tip') ||
592
+ hasNonNullishProperty(message, 'suggest')) {
593
+ m = prepareRichResponseMessage(message);
594
+ }
595
+ else if (hasNonNullishProperty(message, 'text')) {
596
+ const extContent = { text: message.text };
597
+ let urlInfo = message.linkPreview;
413
598
  if (typeof urlInfo === 'undefined') {
414
- urlInfo = await generateLinkPreviewIfRequired(message.text, options.getUrlInfo, options.logger)
599
+ urlInfo = await generateLinkPreviewIfRequired(message.text, options.getUrlInfo, options.logger);
415
600
  }
416
-
417
601
  if (urlInfo) {
418
- extContent.canonicalUrl = urlInfo['canonical-url']
419
- extContent.matchedText = urlInfo['matched-text']
420
- extContent.jpegThumbnail = urlInfo.jpegThumbnail
421
- extContent.description = urlInfo.description
422
- extContent.title = urlInfo.title
423
- extContent.previewType = 0
424
- const img = urlInfo.highQualityThumbnail
425
-
602
+ extContent.matchedText = urlInfo['matched-text'];
603
+ extContent.jpegThumbnail = urlInfo.jpegThumbnail;
604
+ extContent.description = urlInfo.description;
605
+ extContent.title = urlInfo.title;
606
+ extContent.previewType = 0;
607
+ const img = urlInfo.highQualityThumbnail;
426
608
  if (img) {
427
- extContent.thumbnailDirectPath = img.directPath
428
- extContent.mediaKey = img.mediaKey
429
- extContent.mediaKeyTimestamp = img.mediaKeyTimestamp
430
- extContent.thumbnailWidth = img.width
431
- extContent.thumbnailHeight = img.height
432
- extContent.thumbnailSha256 = img.fileSha256
433
- extContent.thumbnailEncSha256 = img.fileEncSha256
609
+ extContent.thumbnailDirectPath = img.directPath;
610
+ extContent.mediaKey = img.mediaKey;
611
+ extContent.mediaKeyTimestamp = img.mediaKeyTimestamp;
612
+ extContent.thumbnailWidth = img.width;
613
+ extContent.thumbnailHeight = img.height;
614
+ extContent.thumbnailSha256 = img.fileSha256;
615
+ extContent.thumbnailEncSha256 = img.fileEncSha256;
434
616
  }
435
617
  }
436
-
437
618
  if (options.backgroundColor) {
438
- extContent.backgroundArgb = await assertColor(options.backgroundColor)
619
+ extContent.backgroundArgb = await assertColor(options.backgroundColor);
439
620
  }
440
-
441
- if (options.textColor) {
442
- extContent.textArgb = await assertColor(options.textColor)
443
- }
444
-
445
621
  if (options.font) {
446
- extContent.font = options.font
447
- }
448
-
449
- extContent.contextInfo = {
450
- ...(message.contextInfo || {}),
451
- ...(message.mentions ? {
452
- mentionedJid: message.mentions
453
- } : {})
454
- }
455
-
456
- m.extendedTextMessage = extContent
457
- } else if ('contacts' in message) {
458
- const contactLen = message.contacts.contacts.length
459
-
460
- let contactMessage
461
-
622
+ extContent.font = options.font;
623
+ }
624
+ m.extendedTextMessage = extContent;
625
+ }
626
+ else if (hasNonNullishProperty(message, 'contacts')) {
627
+ const contactLen = message.contacts.contacts.length;
462
628
  if (!contactLen) {
463
- throw new boom_1.Boom('require atleast 1 contact', {
464
- statusCode: 400
465
- })
629
+ throw new Boom('require atleast 1 contact', { statusCode: 400 });
466
630
  }
467
-
468
631
  if (contactLen === 1) {
469
- contactMessage = {
470
- contactMessage: Types_1.WAProto.Message.ContactMessage.fromObject(message.contacts.contacts[0])
471
- }
472
- } else {
473
- contactMessage = {
474
- contactsArrayMessage: Types_1.WAProto.Message.ContactsArrayMessage.fromObject(message.contacts)
475
- }
632
+ m.contactMessage = WAProto.Message.ContactMessage.create(message.contacts.contacts[0]);
476
633
  }
477
-
478
- const [type] = Object.keys(contactMessage)
479
-
480
- contactMessage[type].contextInfo = {
481
- ...(message.contextInfo || {}),
482
- ...(message.mentions ? {
483
- mentionedJid: message.mentions
484
- } : {})
485
- }
486
-
487
- m = contactMessage
488
- } else if ('location' in message) {
489
- let locationMessage
490
-
491
- if (message.live) {
492
- locationMessage = {
493
- liveLocationMessage: Types_1.WAProto.Message.LiveLocationMessage.fromObject(message.location)
494
- }
495
- } else {
496
- locationMessage = {
497
- locationMessage: Types_1.WAProto.Message.LocationMessage.fromObject(message.location)
498
- }
634
+ else {
635
+ m.contactsArrayMessage = WAProto.Message.ContactsArrayMessage.create(message.contacts);
499
636
  }
500
-
501
- const [type] = Object.keys(locationMessage)
502
-
503
- locationMessage[type].contextInfo = {
504
- ...(message.contextInfo || {}),
505
- ...(message.mentions ? {
506
- mentionedJid: message.mentions
507
- } : {})
508
- }
509
-
510
- m = locationMessage
511
- } else if ('react' in message) {
637
+ }
638
+ else if (hasNonNullishProperty(message, 'location')) {
639
+ m.locationMessage = WAProto.Message.LocationMessage.create(message.location);
640
+ }
641
+ else if (hasNonNullishProperty(message, 'react')) {
512
642
  if (!message.react.senderTimestampMs) {
513
- message.react.senderTimestampMs = Date.now()
643
+ message.react.senderTimestampMs = Date.now();
514
644
  }
515
-
516
- m.reactionMessage = Types_1.WAProto.Message.ReactionMessage.fromObject(message.react)
517
- } else if ('delete' in message) {
645
+ m.reactionMessage = WAProto.Message.ReactionMessage.create(message.react);
646
+ }
647
+ else if (hasNonNullishProperty(message, 'delete')) {
518
648
  m.protocolMessage = {
519
649
  key: message.delete,
520
- type: Types_1.WAProto.Message.ProtocolMessage.Type.REVOKE
521
- }
522
- } else if ('forward' in message) {
523
- const mess = generateForwardMessageContent(message.forward, message.force)
524
- const [type] = Object.keys(mess)
525
-
526
- mess[type].contextInfo = {
527
- ...(message.contextInfo || {}),
528
- ...(message.mentions ? {
529
- mentionedJid: message.mentions
530
- } : {})
531
- }
532
-
533
- m = mess
534
- } else if ('disappearingMessagesInChat' in message) {
535
- const exp = typeof message.disappearingMessagesInChat === 'boolean' ? (message.disappearingMessagesInChat ? Defaults_1.WA_DEFAULT_EPHEMERAL : 0) : message.disappearingMessagesInChat
536
- m = prepareDisappearingMessageSettingContent(exp)
537
- } else if ('groupInvite' in message) {
538
- m.groupInviteMessage = {}
539
-
540
- m.groupInviteMessage.inviteCode = message.groupInvite.code
541
- m.groupInviteMessage.inviteExpiration = message.groupInvite.expiration
542
- m.groupInviteMessage.caption = message.groupInvite.caption
543
- m.groupInviteMessage.groupJid = message.groupInvite.jid
544
- m.groupInviteMessage.groupName = message.groupInvite.name
545
- m.groupInviteMessage.contextInfo = message.contextInfo
546
-
547
- if (options.getProfilePicUrl) {
548
- const pfpUrl = await options.getProfilePicUrl(message.groupInvite.jid)
549
- const { thumbnail } = await messages_media_1.generateThumbnail(pfpUrl, 'image')
550
- m.groupInviteMessage.jpegThumbnail = thumbnail
551
- }
552
-
553
- m.groupInviteMessage.contextInfo = {
554
- ...(message.contextInfo || {}),
555
- ...(message.mentions ? {
556
- mentionedJid: message.mentions
557
- } : {})
558
- }
559
- } else if ('adminInvite' in message) {
560
- m.newsletterAdminInviteMessage = {}
561
-
562
- m.newsletterAdminInviteMessage.newsletterJid = message.adminInvite.jid
563
- m.newsletterAdminInviteMessage.newsletterName = message.adminInvite.name
564
- m.newsletterAdminInviteMessage.caption = message.adminInvite.caption
565
- m.newsletterAdminInviteMessage.inviteExpiration = message.adminInvite.expiration
566
- m.newsletterAdminInviteMessage.contextInfo = message.contextInfo
567
-
650
+ type: WAProto.Message.ProtocolMessage.Type.REVOKE
651
+ };
652
+ }
653
+ else if (hasNonNullishProperty(message, 'forward')) {
654
+ m = generateForwardMessageContent(message.forward, message.force);
655
+ }
656
+ else if (hasNonNullishProperty(message, 'disappearingMessagesInChat')) {
657
+ const exp = typeof message.disappearingMessagesInChat === 'boolean'
658
+ ? message.disappearingMessagesInChat
659
+ ? WA_DEFAULT_EPHEMERAL
660
+ : 0
661
+ : message.disappearingMessagesInChat;
662
+ m = prepareDisappearingMessageSettingContent(exp);
663
+ }
664
+ else if (hasNonNullishProperty(message, 'groupInvite')) {
665
+ m.groupInviteMessage = {};
666
+ m.groupInviteMessage.inviteCode = message.groupInvite.inviteCode;
667
+ m.groupInviteMessage.inviteExpiration = message.groupInvite.inviteExpiration;
668
+ m.groupInviteMessage.caption = message.groupInvite.text;
669
+ m.groupInviteMessage.groupJid = message.groupInvite.jid;
670
+ m.groupInviteMessage.groupName = message.groupInvite.subject;
671
+ //TODO: use built-in interface and get disappearing mode info etc.
672
+ //TODO: cache / use store!?
568
673
  if (options.getProfilePicUrl) {
569
- const pfpUrl = await options.getProfilePicUrl(message.adminInvite.jid)
570
- const {
571
- thumbnail
572
- } = await messages_media_1.generateThumbnail(pfpUrl, 'image')
573
- m.newsletterAdminInviteMessage.jpegThumbnail = thumbnail
574
- }
575
-
576
- m.newsletterAdminInviteMessage.contextInfo = {
577
- ...(message.contextInfo || {}),
578
- ...(message.mentions ? {
579
- mentionedJid: message.mentions
580
- } : {})
581
- }
582
- } else if ('pin' in message) {
583
- m.pinInChatMessage = {}
584
- m.messageContextInfo = {}
585
-
586
- m.pinInChatMessage.key = message.pin.key
587
- m.pinInChatMessage.type = message.pin?.type || 1
588
- m.pinInChatMessage.senderTimestampMs = message.pin?.time || Date.now()
589
- m.messageContextInfo.messageAddOnDurationInSecs = message.pin.type === 1 ? message.pin.time || 86400 : 0
590
- m.messageContextInfo.messageAddOnExpiryType = WAProto_1.proto.MessageContextInfo.MessageAddonExpiryType.STATIC
591
- } else if ('keep' in message) {
592
- m.keepInChatMessage = {}
593
-
594
- m.keepInChatMessage.key = message.keep.key
595
- m.keepInChatMessage.keepType = message.keep?.type || 1
596
- m.keepInChatMessage.timestampMs = message.keep?.time || Date.now()
597
- } else if ('call' in message) {
598
- m.scheduledCallCreationMessage = {}
599
-
600
- m.scheduledCallCreationMessage.scheduledTimestampMs = message.call?.time || Date.now()
601
- m.scheduledCallCreationMessage.callType = message.call?.type || 1
602
- m.scheduledCallCreationMessage.title = message.call?.name || 'Call Creation'
603
-
604
- m.scheduledCallCreationMessage.contextInfo = {
605
- ...(message.contextInfo || {}),
606
- ...(message.mentions ? {
607
- mentionedJid: message.mentions
608
- } : {})
609
- }
610
- } else if ('paymentInvite' in message) {
611
- m.messageContextInfo = {}
612
- m.paymentInviteMessage = {}
613
-
614
- m.paymentInviteMessage.expiryTimestamp = message.paymentInvite?.expiry || 0
615
- m.paymentInviteMessage.serviceType = message.paymentInvite?.type || 2
616
-
617
- m.paymentInviteMessage.contextInfo = {
618
- ...(message.contextInfo || {}),
619
- ...(message.mentions ? {
620
- mentionedJid: message.mentions
621
- } : {})
622
- }
623
- } else if ('buttonLocation' in message) {
624
- const bl = message.buttonLocation
625
- const thumbInput = bl.jpegThumbnail || bl.thumbnail
626
- if (!thumbInput) throw new Error('buttonLocation requires jpegThumbnail or thumbnail')
627
- let thumb
628
- if (Buffer.isBuffer(thumbInput)) {
629
- thumb = thumbInput
630
- } else if (typeof thumbInput === 'string' && (thumbInput.startsWith('http://') || thumbInput.startsWith('https://'))) {
631
- const { stream } = await messages_media_1.getStream(thumbInput)
632
- thumb = await messages_media_1.toBuffer(stream)
633
- } else if (typeof thumbInput === 'string') {
634
- thumb = await fs_1.promises.readFile(thumbInput)
635
- } else {
636
- throw new Error('buttonLocation thumbnail must be Buffer, URL, or file path')
637
- }
638
- let listButton = []
639
- if (bl.listMenu && bl.listMenu.length) {
640
- const nativeParams = JSON.stringify({
641
- title: bl.listButtonText || 'Menu',
642
- sections: [{
643
- title: bl.listSectionTitle || 'Pilihan',
644
- highlight_label: '',
645
- rows: bl.listMenu.map(row => ({
646
- header: row.header || '',
647
- title: row.title || '',
648
- description: row.description || '',
649
- id: row.id || ''
650
- }))
651
- }]
652
- })
653
- listButton.push({
654
- buttonId: 'list_menu',
655
- buttonText: { displayText: bl.listButtonText || 'Pilih Menu' },
656
- type: 1,
657
- nativeFlowInfo: {
658
- name: 'single_select',
659
- paramsJson: nativeParams,
660
- buttonParamsJson: nativeParams
674
+ const pfpUrl = await options.getProfilePicUrl(message.groupInvite.jid, 'preview');
675
+ if (pfpUrl) {
676
+ const resp = await fetch(pfpUrl, { method: 'GET', dispatcher: options?.options?.dispatcher });
677
+ if (resp.ok) {
678
+ const buf = Buffer.from(await resp.arrayBuffer());
679
+ m.groupInviteMessage.jpegThumbnail = buf;
661
680
  }
662
- })
663
- }
664
- const extraButtons = (bl.extraButtons || []).map(btn => ({
665
- buttonId: btn.id || btn.buttonId || 'btn',
666
- buttonText: { displayText: btn.displayText || btn.name || 'Button' },
667
- type: 1
668
- }))
669
- m = {
670
- buttonsMessage: {
671
- locationMessage: {
672
- degreesLatitude: bl.latitude || 0,
673
- degreesLongitude: bl.longitude || 0,
674
- name: bl.name || 'Location',
675
- address: bl.address || '',
676
- jpegThumbnail: thumb
677
- },
678
- contentText: bl.text || '',
679
- footerText: bl.footer || '',
680
- buttons: [...listButton, ...extraButtons],
681
- headerType: 6,
682
- viewOnce: true
683
681
  }
684
682
  }
685
- } else if ('groupStatus' in message) {
686
- const gs = message.groupStatus
687
- if (!gs.message) throw new Error('groupStatus.message is required')
688
- const audienceType = gs.audienceType || 0
689
- const backgroundColor = gs.backgroundArgb
690
- const textColor = gs.textArgb
691
- const font = gs.font
692
- let messageContent = gs.message
693
- if (typeof messageContent === 'string') {
694
- messageContent = {
695
- extendedTextMessage: {
696
- text: messageContent,
697
- contextInfo: { statusAudienceMetadata: { audienceType } },
698
- ...(backgroundColor && { backgroundArgb: backgroundColor }),
699
- ...(textColor && { textArgb: textColor }),
700
- ...(font && { font })
701
- }
702
- }
703
- } else if (messageContent.extendedTextMessage) {
704
- if (backgroundColor) messageContent.extendedTextMessage.backgroundArgb = backgroundColor
705
- if (textColor) messageContent.extendedTextMessage.textArgb = textColor
706
- if (font) messageContent.extendedTextMessage.font = font
707
- messageContent.extendedTextMessage.contextInfo = {
708
- ...messageContent.extendedTextMessage.contextInfo,
709
- statusAudienceMetadata: { audienceType }
710
- }
711
- } else if (
712
- messageContent.imageMessage || messageContent.videoMessage ||
713
- messageContent.audioMessage || messageContent.documentMessage
714
- ) {
715
- const key = Object.keys(messageContent)[0]
716
- if (!messageContent[key].contextInfo) messageContent[key].contextInfo = {}
717
- messageContent[key].contextInfo.statusAudienceMetadata = { audienceType }
718
- }
719
- m = { groupStatusMessageV2: { message: messageContent } }
720
- } else if ('orderStatus' in message) {
721
- const os = message.orderStatus
722
- if (!os.image) throw new Error('orderStatus requires image')
723
- let imageInput = os.image
724
- if (typeof imageInput === 'string' && !imageInput.startsWith('http://') && !imageInput.startsWith('https://')) {
725
- imageInput = await fs_1.promises.readFile(imageInput)
726
- }
727
- const media = await prepareWAMessageMedia({ image: imageInput }, { upload: options.upload, ...options })
728
- m = {
729
- viewOnceMessage: {
730
- message: {
731
- messageContextInfo: {
732
- deviceListMetadata: {},
733
- deviceListMetadataVersion: 2
734
- },
735
- interactiveMessage: WAProto_1.proto.Message.InteractiveMessage.create({
736
- header: {
737
- title: os.title || 'Order Status',
738
- hasMediaAttachment: true,
739
- ...media
740
- },
741
- body: { text: os.text || 'Silakan cek status pesanan Anda.' },
742
- footer: { text: os.footer || 'Powered by PontaCT' },
743
- nativeFlowMessage: {
744
- buttons: [{
745
- name: 'order_status',
746
- buttonParamsJson: JSON.stringify({
747
- reference_id: os.referenceId || 'PT-001',
748
- order: {
749
- status: os.status || 'PROCESSING',
750
- subtotal: {
751
- value: os.subtotalValue || 0,
752
- offset: os.subtotalOffset || 100
753
- },
754
- tax: {
755
- value: os.taxValue || 0,
756
- offset: os.taxOffset || 100
757
- },
758
- currency: os.currency || 'IDR'
759
- }
760
- })
761
- }]
762
- }
763
- })
764
- }
683
+ }
684
+ else if (hasNonNullishProperty(message, 'stickers')) {
685
+ m.stickerPackMessage = await prepareStickerPackMessage(message, options);
686
+ }
687
+ else if (hasNonNullishProperty(message, 'pin')) {
688
+ m.pinInChatMessage = {};
689
+ m.messageContextInfo = {};
690
+ m.pinInChatMessage.key = message.pin;
691
+ m.pinInChatMessage.type = message.type;
692
+ m.pinInChatMessage.senderTimestampMs = Date.now();
693
+ m.messageContextInfo.messageAddOnDurationInSecs = message.type === 1 ? message.time || 86400 : 0;
694
+ }
695
+ else if (hasNonNullishProperty(message, 'keep')) {
696
+ m.keepInChatMessage = {};
697
+ m.keepInChatMessage.key = message.keep;
698
+ m.keepInChatMessage.keepType = message.type;
699
+ m.keepInChatMessage.timestampMs = Date.now();
700
+ }
701
+ else if (hasNonNullishProperty(message, 'flowReply')) {
702
+ m.interactiveResponseMessage = {
703
+ body: {
704
+ format: message.flowReply.format || proto.Message.InteractiveResponseMessage.Body.Format.DEFAULT,
705
+ text: message.flowReply.text
706
+ },
707
+ nativeFlowResponseMessage: {
708
+ name: message.flowReply.name,
709
+ paramsJson: message.flowReply.paramsJson || '{}',
710
+ version: message.flowReply.version || 1
765
711
  }
766
- }
767
- } else if ('buttonReply' in message) {
712
+ };
713
+ }
714
+ else if (hasNonNullishProperty(message, 'buttonReply')) {
768
715
  switch (message.type) {
769
- case 'list':
770
- m.listResponseMessage = {
771
- title: message.buttonReply.title,
772
- description: message.buttonReply.description,
773
- singleSelectReply: {
774
- selectedRowId: message.buttonReply.rowId
775
- },
776
- lisType: WAProto_1.proto.Message.ListResponseMessage.ListType.SINGLE_SELECT
777
- }
778
- break
779
716
  case 'template':
780
717
  m.templateButtonReplyMessage = {
781
718
  selectedDisplayText: message.buttonReply.displayText,
782
719
  selectedId: message.buttonReply.id,
783
720
  selectedIndex: message.buttonReply.index
784
- }
785
- break
721
+ };
722
+ break;
786
723
  case 'plain':
787
724
  m.buttonsResponseMessage = {
788
725
  selectedButtonId: message.buttonReply.id,
789
726
  selectedDisplayText: message.buttonReply.displayText,
790
- type: WAProto_1.proto.Message.ButtonsResponseMessage.Type.DISPLAY_TEXT
791
- }
792
- break
793
- case 'interactive':
794
- m.interactiveResponseMessage = {
795
- body: {
796
- text: message.buttonReply.displayText,
797
- format: WAProto_1.proto.Message.InteractiveResponseMessage.Body.Format.EXTENSIONS_1
798
- },
799
- nativeFlowResponseMessage: {
800
- name: message.buttonReply.nativeFlows.name,
801
- paramsJson: message.buttonReply.nativeFlows.paramsJson,
802
- version: message.buttonReply.nativeFlows.version
803
- }
804
- }
805
- break
806
- }
807
- } else if ('ptv' in message && message.ptv) {
808
- const {
809
- videoMessage
810
- } = await prepareWAMessageMedia({
811
- video: message.video
812
- }, options)
813
-
814
- m.ptvMessage = videoMessage
815
- } else if ('album' in message) {
816
- const imageMessages = message.album.filter(item => 'image' in item)
817
- const videoMessages = message.album.filter(item => 'video' in item)
818
-
819
- m.albumMessage = Types_1.WAProto.Message.AlbumMessage.fromObject({
820
- expectedImageCount: imageMessages.length,
821
- expectedVideoCount: videoMessages.length
822
- })
823
- } else if ('order' in message) {
824
- m.orderMessage = Types_1.WAProto.Message.OrderMessage.fromObject(message.order)
825
-
826
- m.orderMessage.contextInfo = {
827
- ...(message.contextInfo || {}),
828
- ...(message.mentions ? {
829
- mentionedJid: message.mentions
830
- } : {})
831
- }
832
- } else if ('event' in message) {
833
- m.eventMessage = Types_1.WAProto.Message.EventMessage.fromObject(message.event)
834
-
835
- if (!message.event.startTime) {
836
- m.eventMessage.startTime = generics_1.unixTimestampSeconds() + 86400
837
- }
838
-
839
- if (options.getCallLink && message.event.call) {
840
- const link = await options.getCallLink(message.event.call, m.eventMessage.startTime)
841
- m.eventMessage.joinLink = link
842
- }
843
-
844
- m.eventMessage.contextInfo = {
845
- ...(message.contextInfo || {}),
846
- ...(message.mentions ? {
847
- mentionedJid: message.mentions
848
- } : {})
849
- }
850
- } else if ('product' in message) {
851
- const { imageMessage } = await prepareWAMessageMedia({
852
- image: message.product.productImage
853
- }, options)
854
-
855
- m.productMessage = Types_1.WAProto.Message.ProductMessage.fromObject({
727
+ type: proto.Message.ButtonsResponseMessage.Type.DISPLAY_TEXT
728
+ };
729
+ break;
730
+ }
731
+ }
732
+ else if (hasOptionalProperty(message, 'ptv') && message.ptv) {
733
+ const { videoMessage } = await prepareWAMessageMedia({ video: message.video }, options);
734
+ m.ptvMessage = videoMessage;
735
+ }
736
+ else if (hasNonNullishProperty(message, 'product')) {
737
+ if (!message.businessOwnerJid) {
738
+ throw new Boom('"businessOwnerJid" is missing from the content', { statusCode: 400 });
739
+ }
740
+ const { imageMessage } = await prepareWAMessageMedia({ image: message.image || message.product.productImage }, options);
741
+ const content = {
856
742
  ...message,
857
743
  product: {
744
+ currencyCode: 'IDR',
745
+ priceAmount1000: 1000,
746
+ title: LIBRARY_NAME,
858
747
  ...message.product,
859
- productImage: imageMessage,
748
+ productImage: imageMessage
860
749
  }
861
- })
862
-
863
- m.productMessage.contextInfo = {
864
- ...(message.contextInfo || {}),
865
- ...(message.mentions ? {
866
- mentionedJid: message.mentions
867
- } : {})
868
- }
869
- } else if ('pollResult' in message) {
870
- if (!Array.isArray(message.pollResult.values)) {
871
- throw new boom_1.Boom('Invalid pollResult values', {
872
- statusCode: 400
873
- })
874
- }
875
-
876
- const pollResultSnapshotMessage = {
877
- name: message.pollResult.name,
878
- pollVotes: message.pollResult.values.map(([optionName, optionVoteCount]) => ({
879
- optionName,
880
- optionVoteCount
881
- }))
882
- }
883
-
884
- pollResultSnapshotMessage.contextInfo = {
885
- ...(message.contextInfo || {}),
886
- ...(message.mentions ? {
887
- mentionedJid: message.mentions
888
- } : {})
889
- }
890
-
891
- m.pollResultSnapshotMessage = pollResultSnapshotMessage
892
- } else if ('poll' in message) {
750
+ };
751
+ delete content.image;
752
+ m.productMessage = WAProto.Message.ProductMessage.create(content);
753
+ }
754
+ else if (hasNonNullishProperty(message, 'listReply')) {
755
+ m.listResponseMessage = {
756
+ description: message.listReply.description,
757
+ listType: proto.Message.ListResponseMessage.ListType.SINGLE_SELECT,
758
+ singleSelectReply: {
759
+ selectedRowId: message.listReply.id
760
+ },
761
+ title: message.listReply.title
762
+ };
763
+ }
764
+ else if (hasNonNullishProperty(message, 'event')) {
765
+ m.eventMessage = {};
766
+ const startTime = Math.floor(message.event.startDate.getTime() / 1000);
767
+ if (message.event.call && options.getCallLink) {
768
+ const token = await options.getCallLink(message.event.call, { startTime });
769
+ m.eventMessage.joinLink = (message.event.call === 'audio' ? CALL_AUDIO_PREFIX : CALL_VIDEO_PREFIX) + token;
770
+ }
771
+ m.messageContextInfo = {
772
+ // encKey
773
+ messageSecret: message.event.messageSecret || randomBytes(32)
774
+ };
775
+ m.eventMessage.name = message.event.name;
776
+ m.eventMessage.description = message.event.description;
777
+ m.eventMessage.startTime = startTime;
778
+ m.eventMessage.endTime = message.event.endDate ? message.event.endDate.getTime() / 1000 : undefined;
779
+ m.eventMessage.isCanceled = message.event.isCancelled ?? false;
780
+ m.eventMessage.extraGuestsAllowed = message.event.extraGuestsAllowed;
781
+ m.eventMessage.isScheduleCall = message.event.isScheduleCall ?? false;
782
+ m.eventMessage.location = message.event.location;
783
+ }
784
+ else if (hasNonNullishProperty(message, 'poll')) {
785
+ (_a = message.poll).selectableCount || (_a.selectableCount = 0);
786
+ (_b = message.poll).toAnnouncementGroup || (_b.toAnnouncementGroup = false);
893
787
  if (!Array.isArray(message.poll.values)) {
894
- throw new boom_1.Boom('Invalid poll values', {
895
- statusCode: 400
896
- })
788
+ throw new Boom('Invalid poll values', { statusCode: 400 });
897
789
  }
898
-
899
- if (message.poll.selectableCount < 0 ||
900
- message.poll.selectableCount > message.poll.values.length) {
901
- throw new boom_1.Boom(`poll.selectableCount in poll should be >= 0 and <= ${message.poll.values.length}`, {
790
+ if (message.poll.selectableCount < 0 || message.poll.selectableCount > message.poll.values.length) {
791
+ throw new Boom(`poll.selectableCount in poll should be >= 0 and <= ${message.poll.values.length}`, {
902
792
  statusCode: 400
903
- })
793
+ });
904
794
  }
905
-
906
795
  const pollCreationMessage = {
907
796
  name: message.poll.name,
908
- selectableOptionsCount: message.poll?.selectableCount || 0,
909
- options: message.poll.values.map(optionName => ({
910
- optionName
911
- })),
912
- }
913
-
914
- pollCreationMessage.contextInfo = {
915
- ...(message.contextInfo || {}),
916
- ...(message.mentions ? {
917
- mentionedJid: message.mentions
918
- } : {})
919
- }
920
-
921
- if (message.poll?.toAnnouncementGroup) {
922
- m.pollCreationMessageV2 = pollCreationMessage
923
- } else {
924
- if (message.poll.selectableCount > 0) {
925
- m.pollCreationMessageV3 = pollCreationMessage
926
- } else {
927
- m.pollCreationMessage = pollCreationMessage
928
- }
929
- }
930
- } else if ('payment' in message) {
931
- const requestPaymentMessage = {
932
- amount: {
933
- currencyCode: message.payment?.currency || 'IDR',
934
- offset: message.payment?.offset || 0,
935
- value: message.payment?.amount || 999999999
936
- },
937
- expiryTimestamp: message.payment?.expiry || 0,
938
- amount1000: message.payment?.amount || 999999999 * 1000,
939
- currencyCodeIso4217: message.payment?.currency || 'IDR',
940
- requestFrom: message.payment?.from || '0@s.whatsapp.net',
941
- noteMessage: {
942
- extendedTextMessage: {
943
- text: message.payment?.note || 'Notes'
797
+ selectableOptionsCount: message.poll.selectableCount,
798
+ options: message.poll.values.map(optionName => ({ optionName })),
799
+ endTime: message.poll.endDate ? message.poll.endDate.getTime() : undefined,
800
+ hideParticipantName: message.poll.hideVoter ?? false,
801
+ allowAddOption: message.poll.canAddOption ?? false
802
+ };
803
+ if (message.poll.toAnnouncementGroup) {
804
+ // poll v2 is for community announcement groups (single select and multiple)
805
+ m.pollCreationMessageV2 = pollCreationMessage;
806
+ }
807
+ else {
808
+ // Add quiz message support
809
+ if (message.poll.pollType === 1) {
810
+ if (!message.poll.correctAnswer) {
811
+ throw new Boom('No "correctAnswer" provided for quiz', { statusCode: 400 });
944
812
  }
945
- },
946
- background: {
947
- placeholderArgb: message.payment?.image?.placeholderArgb || 4278190080,
948
- textArgb: message.payment?.image?.textArgb || 4294967295,
949
- subtextArgb: message.payment?.image?.subtextArgb || 4294967295,
950
- type: 1
813
+ m.pollCreationMessageV5 = {
814
+ // quiz for newsletter only
815
+ ...pollCreationMessage,
816
+ correctAnswer: {
817
+ optionName: message.poll.correctAnswer.toString()
818
+ },
819
+ pollType: 1,
820
+ selectableOptionsCount: 1
821
+ };
822
+ }
823
+ else if (message.poll.selectableCount === 1) {
824
+ //poll v3 is for single select polls
825
+ m.pollCreationMessageV3 = pollCreationMessage;
826
+ }
827
+ else {
828
+ // poll for multiple choice polls
829
+ m.pollCreationMessage = pollCreationMessage;
951
830
  }
952
831
  }
953
-
954
- requestPaymentMessage.noteMessage.extendedTextMessage.contextInfo = {
955
- ...(message.contextInfo || {}),
956
- ...(message.mentions ? {
957
- mentionedJid: message.mentions
958
- } : {})
959
- }
960
-
961
- m.requestPaymentMessage = requestPaymentMessage
962
- } else if ('stickerPack' in message) {
963
- const {
964
- stickers,
965
- cover,
966
- name,
967
- publisher,
968
- packId,
969
- description
970
- } = message.stickerPack
971
-
972
- const { zip } = require('fflate')
973
- const stickerData = {}
974
- const stickerPromises = stickers.map(async (s, i) => {
975
- const { stream } = await messages_media_1.getStream(s.sticker)
976
- const buffer = await messages_media_1.toBuffer(stream)
977
- const hash = crypto_2.sha256(buffer).toString('base64url')
978
- const fileName = `${i.toString().padStart(2, '0')}_${hash}.webp`
979
- stickerData[fileName] = [new Uint8Array(buffer), {
980
- level: 0
981
- }]
982
-
983
- return {
984
- fileName,
985
- mimetype: 'image/webp',
986
- isAnimated: s.isAnimated || false,
987
- isLottie: s.isLottie || false,
988
- emojis: s.emojis || [],
989
- accessibilityLabel: s.accessibilityLabel || ''
832
+ m.messageContextInfo = {
833
+ // encKey
834
+ messageSecret: message.poll.messageSecret || randomBytes(32)
835
+ };
836
+ }
837
+ else if (hasNonNullishProperty(message, 'pollResult')) {
838
+ const pollResultSnapshotMessage = {
839
+ name: message.pollResult.name,
840
+ pollVotes: message.pollResult.votes.map(vote => ({
841
+ optionName: vote.name,
842
+ optionVoteCount: parseInt(vote.voteCount)
843
+ }))
844
+ };
845
+ if (message.pollResult.pollType === 1) {
846
+ pollResultSnapshotMessage.pollType = proto.Message.PollType.QUIZ;
847
+ m.pollResultSnapshotMessageV3 = pollResultSnapshotMessage;
848
+ }
849
+ else {
850
+ pollResultSnapshotMessage.pollType = proto.Message.PollType.POLL;
851
+ m.pollResultSnapshotMessage = pollResultSnapshotMessage;
852
+ }
853
+ }
854
+ else if (hasNonNullishProperty(message, 'pollUpdate')) {
855
+ if (!message.pollUpdate.key) {
856
+ throw new Boom('Message key is required', { statusCode: 400 });
857
+ }
858
+ if (!message.pollUpdate.vote) {
859
+ throw new Boom('Encrypted vote payload is required', { statusCode: 400 });
860
+ }
861
+ m.pollUpdateMessage = {
862
+ metadata: message.pollUpdate.metadata,
863
+ pollCreationMessageKey: message.pollUpdate.key,
864
+ senderTimestampMs: Date.now(),
865
+ vote: message.pollUpdate.vote
866
+ };
867
+ }
868
+ else if (hasNonNullishProperty(message, 'paymentInviteServiceType')) {
869
+ m.paymentInviteMessage = {
870
+ expiryTimestamp: Date.now(),
871
+ serviceType: message.paymentInviteServiceType
872
+ };
873
+ }
874
+ else if (hasNonNullishProperty(message, 'orderText')) {
875
+ if (!Buffer.isBuffer(message.thumbnail)) {
876
+ throw new Boom('Must provide thumbnail buffer in order message', { statusCode: 400 });
877
+ }
878
+ m.orderMessage = {
879
+ itemCount: 1,
880
+ messageVersion: 1,
881
+ orderTitle: LIBRARY_NAME,
882
+ status: proto.Message.OrderMessage.OrderStatus.INQUIRY,
883
+ surface: proto.Message.OrderMessage.OrderSurface.CATALOG,
884
+ token: generateMessageIDV2(),
885
+ totalAmount1000: 1000,
886
+ totalCurrencyCode: 'IDR',
887
+ ...message,
888
+ message: message.orderText
889
+ };
890
+ delete m.orderMessage.orderText;
891
+ }
892
+ else if (hasNonNullishProperty(message, 'album')) {
893
+ if (!Array.isArray(message.album)) {
894
+ throw new Boom('Invalid album type. Expected an array.', { statusCode: 400 });
895
+ }
896
+ let videoCount = 0;
897
+ for (let i = 0; i < message.album.length; i++) {
898
+ if (message.album[i].video)
899
+ videoCount++;
900
+ }
901
+ ;
902
+ let imageCount = 0;
903
+ for (let i = 0; i < message.album.length; i++) {
904
+ if (message.album[i].image)
905
+ imageCount++;
906
+ }
907
+ ;
908
+ if ((videoCount + imageCount) < 2) {
909
+ throw new Boom('Minimum provide 2 media to upload album message', { statusCode: 400 });
910
+ }
911
+ m.albumMessage = {
912
+ expectedImageCount: imageCount,
913
+ expectedVideoCount: videoCount
914
+ };
915
+ }
916
+ else if (hasNonNullishProperty(message, 'sharePhoneNumber')) {
917
+ m.protocolMessage = {
918
+ type: proto.Message.ProtocolMessage.Type.SHARE_PHONE_NUMBER
919
+ };
920
+ }
921
+ else if (hasNonNullishProperty(message, 'requestPhoneNumber')) {
922
+ m.requestPhoneNumberMessage = {};
923
+ }
924
+ else if (hasNonNullishProperty(message, 'limitSharing')) {
925
+ m.protocolMessage = {
926
+ type: proto.Message.ProtocolMessage.Type.LIMIT_SHARING,
927
+ limitSharing: {
928
+ sharingLimited: message.limitSharing === true,
929
+ trigger: 1,
930
+ limitSharingSettingTimestamp: Date.now(),
931
+ initiatedByMe: true
990
932
  }
991
- })
992
-
993
- const stickerMetadata = await Promise.all(stickerPromises)
994
-
995
- const zipBuffer = await new Promise((resolve, reject) => {
996
- zip(stickerData, (err, data) => {
997
- if (err) {
998
- reject(err)
999
- } else {
1000
- resolve(Buffer.from(data))
933
+ };
934
+ }
935
+ else if (hasNonNullishProperty(message, 'buttonsMessage')) {
936
+ // Direct buttonsMessage passthrough — supports headerType 6 (locationMessage), etc.
937
+ const btnMsg = { ...message.buttonsMessage };
938
+ if (btnMsg.locationMessage?.jpegThumbnail && !Buffer.isBuffer(btnMsg.locationMessage.jpegThumbnail)) {
939
+ const lib = await getImageProcessingLibrary();
940
+ const hasSharp = 'sharp' in lib && !!lib.sharp?.default;
941
+ if (hasSharp) {
942
+ const rawBuf = typeof btnMsg.locationMessage.jpegThumbnail === 'string'
943
+ ? (await fs.readFile(btnMsg.locationMessage.jpegThumbnail))
944
+ : btnMsg.locationMessage.jpegThumbnail;
945
+ btnMsg.locationMessage.jpegThumbnail = await lib.sharp.default(rawBuf)
946
+ .resize(300, 300, { fit: 'inside', withoutEnlargement: true })
947
+ .jpeg({ quality: 80 })
948
+ .toBuffer();
949
+ }
950
+ }
951
+ m = { buttonsMessage: btnMsg };
952
+ }
953
+ else {
954
+ m = await prepareWAMessageMedia(message, options);
955
+ }
956
+ if (hasNonNullishProperty(message, 'buttons')) {
957
+ const buttonsMessage = {
958
+ buttons: message.buttons.map(button => {
959
+ const buttonText = button.text || button.buttonText;
960
+ if (hasOptionalProperty(button, 'sections')) {
961
+ return {
962
+ nativeFlowInfo: {
963
+ name: 'single_select',
964
+ paramsJson: JSON.stringify({
965
+ title: buttonText,
966
+ sections: button.sections
967
+ })
968
+ },
969
+ type: ButtonType.NATIVE_FLOW
970
+ };
1001
971
  }
972
+ else if (hasOptionalProperty(button, 'name')) {
973
+ return {
974
+ nativeFlowInfo: {
975
+ name: button.name,
976
+ paramsJson: button.paramsJson
977
+ },
978
+ type: ButtonType.NATIVE_FLOW
979
+ };
980
+ }
981
+ return {
982
+ buttonId: button.id || button.buttonId,
983
+ buttonText: typeof buttonText === 'string' ? { displayText: buttonText } : buttonText,
984
+ type: button.type || ButtonType.RESPONSE
985
+ };
1002
986
  })
1003
- })
1004
-
1005
- const coverBuffer = await messages_media_1.toBuffer((await messages_media_1.getStream(cover)).stream)
1006
-
1007
- const [stickerPackUpload, coverUpload] = await Promise.all([
1008
- messages_media_1.encryptedStream(zipBuffer, 'sticker-pack', {
1009
- logger: options.logger,
1010
- opts: options.options
1011
- }),
1012
- prepareWAMessageMedia({
1013
- image: coverBuffer
1014
- }, {
1015
- ...options,
1016
- mediaTypeOverride: 'image'
1017
- })
1018
- ])
1019
-
1020
- const stickerPackUploadResult = await options.upload(stickerPackUpload.encFilePath, {
1021
- fileEncSha256B64: stickerPackUpload.fileEncSha256.toString('base64'),
1022
- mediaType: 'sticker-pack',
1023
- timeoutMs: options.mediaUploadTimeoutMs
1024
- })
1025
-
1026
-
1027
- const coverImage = coverUpload.imageMessage
1028
- const imageDataHash = crypto_2.sha256(coverBuffer).toString('base64')
1029
- const stickerPackId = packId || generics_1.generateMessageID()
1030
-
1031
- m.stickerPackMessage = {
1032
- name,
1033
- publisher,
1034
- stickerPackId,
1035
- packDescription: description,
1036
- stickerPackOrigin: WAProto_1.proto.Message.StickerPackMessage.StickerPackOrigin.THIRD_PARTY,
1037
- stickerPackSize: stickerPackUpload.fileLength,
1038
- stickers: stickerMetadata,
1039
- fileSha256: stickerPackUpload.fileSha256,
1040
- fileEncSha256: stickerPackUpload.fileEncSha256,
1041
- mediaKey: stickerPackUpload.mediaKey,
1042
- directPath: stickerPackUploadResult.directPath,
1043
- fileLength: stickerPackUpload.fileLength,
1044
- mediaKeyTimestamp: generics_1.unixTimestampSeconds(),
1045
- trayIconFileName: `${stickerPackId}.webp`,
1046
- imageDataHash,
1047
- thumbnailDirectPath: coverImage.directPath,
1048
- thumbnailFileSha256: coverImage.fileSha256,
1049
- thumbnailFileEncSha256: coverImage.fileEncSha256,
1050
- thumbnailHeight: coverImage.height,
1051
- thumbnailWidth: coverImage.width
1052
- }
1053
-
1054
- m.stickerPackMessage.contextInfo = {
1055
- ...(message.contextInfo || {}),
1056
- ...(message.mentions ? {
1057
- mentionedJid: message.mentions
1058
- } : {})
1059
- }
1060
- } else if ('sharePhoneNumber' in message) {
1061
- m.protocolMessage = {
1062
- type: Types_1.WAProto.Message.ProtocolMessage.Type.SHARE_PHONE_NUMBER
1063
- }
1064
- } else if ('requestPhoneNumber' in message) {
1065
- m.requestPhoneNumberMessage = {}
1066
- } else {
1067
- const mess = await prepareWAMessageMedia(message, options)
1068
- const [type] = Object.keys(mess)
1069
-
1070
- mess[type].contextInfo = {
1071
- ...(message.contextInfo || {}),
1072
- ...(message.mentions ? {
1073
- mentionedJid: message.mentions
1074
- } : {})
1075
- }
1076
-
1077
- m = mess
1078
- }
1079
-
1080
- if ('sections' in message && !!message.sections) {
987
+ };
988
+ if (hasOptionalProperty(message, 'text')) {
989
+ buttonsMessage.contentText = message.text;
990
+ buttonsMessage.headerType = ButtonHeaderType.EMPTY;
991
+ }
992
+ else {
993
+ if (hasOptionalProperty(message, 'caption')) {
994
+ buttonsMessage.contentText = message.caption;
995
+ }
996
+ const type = Object.keys(m)[0].replace('Message', '').toUpperCase();
997
+ buttonsMessage.headerType = ButtonHeaderType[type];
998
+ Object.assign(buttonsMessage, m);
999
+ }
1000
+ if (hasOptionalProperty(message, 'footer')) {
1001
+ buttonsMessage.footerText = message.footer;
1002
+ }
1003
+ m = { buttonsMessage };
1004
+ }
1005
+ else if (hasNonNullishProperty(message, 'sections')) {
1081
1006
  const listMessage = {
1082
- title: message.title,
1007
+ sections: message.sections,
1083
1008
  buttonText: message.buttonText,
1009
+ title: message.title,
1084
1010
  footerText: message.footer,
1085
1011
  description: message.text,
1086
- sections: message.sections,
1087
- listType: WAProto_1.proto.Message.ListMessage.ListType.SINGLE_SELECT
1088
- }
1089
-
1090
- listMessage.contextInfo = {
1091
- ...(message.contextInfo || {}),
1092
- ...(message.mentions ? {
1093
- mentionedJid: message.mentions
1094
- } : {})
1012
+ listType: ListType.SINGLE_SELECT
1013
+ };
1014
+ m = { listMessage };
1015
+ }
1016
+ else if (hasNonNullishProperty(message, 'productList')) {
1017
+ // --- validasi 100% ---
1018
+ if (!message.businessOwnerJid) {
1019
+ throw new Boom('"businessOwnerJid" wajib diisi untuk mengirim productList', { statusCode: 400 });
1095
1020
  }
1096
-
1097
- m = {
1098
- listMessage
1021
+ if (!Array.isArray(message.productList) || message.productList.length === 0) {
1022
+ throw new Boom('"productList" harus berupa array section yang tidak kosong', { statusCode: 400 });
1099
1023
  }
1100
- } else if ('productList' in message && !!message.productList) {
1101
- const thumbnail = message.thumbnail ? await messages_media_1.generateThumbnail(message.thumbnail, 'image') : null
1102
-
1103
- const listMessage = {
1024
+ message.productList.forEach((section, i) => {
1025
+ if (!section || typeof section !== 'object') {
1026
+ throw new Boom(`productList[${i}] tidak valid, harus berupa object section`, { statusCode: 400 });
1027
+ }
1028
+ if (!Array.isArray(section.products) || section.products.length === 0) {
1029
+ throw new Boom(`productList[${i}].products harus berupa array produk yang tidak kosong`, { statusCode: 400 });
1030
+ }
1031
+ section.products.forEach((product, j) => {
1032
+ if (!product || !product.productId) {
1033
+ throw new Boom(`productList[${i}].products[${j}].productId wajib diisi`, { statusCode: 400 });
1034
+ }
1035
+ });
1036
+ });
1037
+ const thumbnail = message.thumbnail
1038
+ ? await generateThumbnail(message.thumbnail, 'image')
1039
+ : null;
1040
+ m.listMessage = {
1104
1041
  title: message.title,
1105
1042
  buttonText: message.buttonText,
1106
1043
  footerText: message.footer,
@@ -1113,763 +1050,998 @@ const generateWAMessageContent = async (message, options) => {
1113
1050
  },
1114
1051
  businessOwnerJid: message.businessOwnerJid
1115
1052
  },
1116
- listType: WAProto_1.proto.Message.ListMessage.ListType.PRODUCT_LIST
1117
- }
1118
-
1119
- listMessage.contextInfo = {
1120
- ...(message.contextInfo || {}),
1121
- ...(message.mentions ? {
1122
- mentionedJid: message.mentions
1123
- } : {})
1124
- }
1125
-
1126
- m = {
1127
- listMessage
1128
- }
1129
- } else if ('buttons' in message && !!message.buttons) {
1130
- const buttonsMessage = {
1131
- buttons: message.buttons.map(b => ({
1132
- ...b,
1133
- type: WAProto_1.proto.Message.ButtonsMessage.Button.Type.RESPONSE
1134
- }))
1053
+ listType: proto.Message.ListMessage.ListType.PRODUCT_LIST
1054
+ };
1055
+ }
1056
+ else if (hasNonNullishProperty(message, 'payment')) {
1057
+ // --- validasi 100% ---
1058
+ const p = message.payment;
1059
+ if (!p || typeof p !== 'object') {
1060
+ throw new Boom('"payment" harus berupa object', { statusCode: 400 });
1135
1061
  }
1136
-
1137
- if ('text' in message) {
1138
- buttonsMessage.contentText = message.text
1139
- buttonsMessage.headerType = WAProto_1.proto.Message.ButtonsMessage.HeaderType.EMPTY
1140
- } else {
1141
- if ('caption' in message) {
1142
- buttonsMessage.contentText = message.caption
1143
- }
1144
-
1145
- const type = Object.keys(m)[0].replace('Message', '').toUpperCase()
1146
-
1147
- buttonsMessage.headerType = WAProto_1.proto.Message.ButtonsMessage.HeaderType[type]
1148
-
1149
- Object.assign(buttonsMessage, m)
1150
- }
1151
-
1152
- if ('footer' in message && !!message.footer) {
1153
- buttonsMessage.footerText = message.footer
1154
- }
1155
-
1156
- if ('title' in message && !!message.title) {
1157
- buttonsMessage.text = message.title
1158
- buttonsMessage.headerType = WAProto_1.proto.Message.ButtonsMessage.HeaderType.TEXT
1159
- }
1160
-
1161
- buttonsMessage.contextInfo = {
1162
- ...(message.contextInfo || {}),
1163
- ...(message.mentions ? {
1164
- mentionedJid: message.mentions
1165
- } : {})
1166
- }
1167
-
1168
- m = {
1169
- buttonsMessage
1062
+ if (!p.from) {
1063
+ throw new Boom('"payment.from" (JID tujuan pembayaran) wajib diisi', { statusCode: 400 });
1170
1064
  }
1171
- } else if ('template' in message && !!message.templateButtons) {
1172
- const hydratedTemplate = {
1173
- hydratedButtons: message.templateButtons
1174
- }
1175
-
1176
- if ('text' in message) {
1177
- hydratedTemplate.hydratedContentText = message.text
1178
- } else {
1179
- if ('caption' in message) {
1180
- hydratedTemplate.hydratedContentText = message.caption
1181
- }
1182
-
1183
- Object.assign(msg, m)
1065
+ if (typeof p.amount !== 'number' || !(p.amount > 0)) {
1066
+ throw new Boom('"payment.amount" wajib berupa angka lebih dari 0', { statusCode: 400 });
1184
1067
  }
1185
-
1186
- if ('footer' in message && !!message.footer) {
1187
- hydratedTemplate.hydratedFooterText = message.footer
1068
+ if (p.currency !== undefined && typeof p.currency !== 'string') {
1069
+ throw new Boom('"payment.currency" harus berupa string (mis. "IDR")', { statusCode: 400 });
1188
1070
  }
1189
-
1190
- hydratedTemplate.contextInfo = {
1191
- ...(message.contextInfo || {}),
1192
- ...(message.mentions ? {
1193
- mentionedJid: message.mentions
1194
- } : {})
1071
+ if (p.offset !== undefined && typeof p.offset !== 'number') {
1072
+ throw new Boom('"payment.offset" harus berupa angka', { statusCode: 400 });
1195
1073
  }
1196
-
1197
- m = {
1198
- templateMessage: {
1199
- hydratedTemplate
1200
- }
1074
+ if (p.expiry !== undefined && typeof p.expiry !== 'number') {
1075
+ throw new Boom('"payment.expiry" harus berupa angka (unix timestamp)', { statusCode: 400 });
1201
1076
  }
1202
- } else if ('interactive' in message && !!message.interactive) {
1203
- const interactiveMessage = {
1204
- nativeFlowMessage: {
1205
- buttons: message.interactive
1077
+ const currency = p.currency || 'IDR';
1078
+ m.requestPaymentMessage = {
1079
+ amount: {
1080
+ currencyCode: currency,
1081
+ offset: p.offset ?? 0,
1082
+ value: p.amount
1083
+ },
1084
+ expiryTimestamp: p.expiry ?? 0,
1085
+ amount1000: p.amount * 1000,
1086
+ currencyCodeIso4217: currency,
1087
+ requestFrom: p.from,
1088
+ noteMessage: {
1089
+ extendedTextMessage: { text: p.note || '' }
1090
+ },
1091
+ background: {
1092
+ placeholderArgb: p.image?.placeholderArgb ?? 4278190080,
1093
+ textArgb: p.image?.textArgb ?? 4294967295,
1094
+ subtextArgb: p.image?.subtextArgb ?? 4294967295,
1095
+ type: 1
1206
1096
  }
1207
- }
1208
-
1209
- if ('text' in message) {
1210
- interactiveMessage.body = {
1211
- text: message.text
1212
- },
1213
- interactiveMessage.header = {
1214
- title: message.title,
1215
- subtitle: message.subtitle,
1216
- hasMediaAttachment: false
1217
- }
1218
- } else {
1219
- if ('caption' in message) {
1220
- interactiveMessage.body = {
1221
- text: message.caption
1097
+ };
1098
+ }
1099
+ else if (hasNonNullishProperty(message, 'buttonLocation')) {
1100
+ // --- validasi 100% ---
1101
+ const bl = message.buttonLocation;
1102
+ if (!bl || typeof bl !== 'object') {
1103
+ throw new Boom('"buttonLocation" harus berupa object', { statusCode: 400 });
1104
+ }
1105
+ if (typeof bl.latitude !== 'number' || typeof bl.longitude !== 'number') {
1106
+ throw new Boom('"buttonLocation.latitude" dan "buttonLocation.longitude" wajib berupa angka', { statusCode: 400 });
1107
+ }
1108
+ if (bl.sections !== undefined && !Array.isArray(bl.sections)) {
1109
+ throw new Boom('"buttonLocation.sections" harus berupa array', { statusCode: 400 });
1110
+ }
1111
+ if (bl.extraButtons !== undefined && !Array.isArray(bl.extraButtons)) {
1112
+ throw new Boom('"buttonLocation.extraButtons" harus berupa array', { statusCode: 400 });
1113
+ }
1114
+ let thumb;
1115
+ if (bl.thumbnail) {
1116
+ const t = await generateThumbnail(bl.thumbnail, 'image');
1117
+ thumb = t?.thumbnail;
1118
+ }
1119
+ const listButton = [];
1120
+ if (Array.isArray(bl.sections) && bl.sections.length > 0) {
1121
+ bl.sections.forEach((section, i) => {
1122
+ if (!Array.isArray(section.rows) || section.rows.length === 0) {
1123
+ throw new Boom(`buttonLocation.sections[${i}].rows harus berupa array yang tidak kosong`, { statusCode: 400 });
1222
1124
  }
1223
-
1224
- interactiveMessage.header = {
1225
- title: message.title,
1226
- subtitle: message.subtitle,
1227
- hasMediaAttachment: message.hasMediaAttachment ? message.hasMediaAttachment : false,
1228
- ...Object.assign(interactiveMessage, m)
1125
+ section.rows.forEach((row, j) => {
1126
+ if (!row || !row.id || !row.title) {
1127
+ throw new Boom(`buttonLocation.sections[${i}].rows[${j}] wajib punya "id" dan "title"`, { statusCode: 400 });
1128
+ }
1129
+ });
1130
+ });
1131
+ const nativeParams = JSON.stringify({
1132
+ sections: bl.sections.map(section => ({
1133
+ title: section.title || '',
1134
+ rows: section.rows.map(row => ({
1135
+ title: row.title || '',
1136
+ description: row.description || '',
1137
+ id: row.id || ''
1138
+ }))
1139
+ }))
1140
+ });
1141
+ listButton.push({
1142
+ buttonId: 'list_menu',
1143
+ buttonText: { displayText: bl.listButtonText || '📄 Pilih Menu' },
1144
+ type: 1,
1145
+ nativeFlowInfo: {
1146
+ name: 'single_select',
1147
+ paramsJson: nativeParams,
1148
+ buttonParamsJson: nativeParams
1229
1149
  }
1150
+ });
1151
+ }
1152
+ const extraButtons = (bl.extraButtons || []).map((btn, i) => {
1153
+ if (!btn || (!btn.id && !btn.buttonId)) {
1154
+ throw new Boom(`buttonLocation.extraButtons[${i}].id wajib diisi`, { statusCode: 400 });
1230
1155
  }
1156
+ return {
1157
+ buttonId: btn.id || btn.buttonId,
1158
+ buttonText: { displayText: btn.displayText || btn.name || 'Button' },
1159
+ type: 1
1160
+ };
1161
+ });
1162
+ if (listButton.length === 0 && extraButtons.length === 0) {
1163
+ throw new Boom('"buttonLocation" membutuhkan minimal 1 "sections" atau 1 "extraButtons"', { statusCode: 400 });
1231
1164
  }
1232
-
1233
- if ('footer' in message && !!message.footer) {
1234
- interactiveMessage.footer = {
1235
- text: message.footer
1165
+ m = {
1166
+ buttonsMessage: {
1167
+ locationMessage: {
1168
+ degreesLatitude: bl.latitude,
1169
+ degreesLongitude: bl.longitude,
1170
+ name: bl.name || 'Location',
1171
+ address: bl.address || '',
1172
+ jpegThumbnail: thumb
1173
+ },
1174
+ contentText: bl.text || '',
1175
+ footerText: bl.footer || '',
1176
+ buttons: [...listButton, ...extraButtons],
1177
+ headerType: ButtonHeaderType.LOCATION,
1178
+ viewOnce: true
1236
1179
  }
1180
+ };
1181
+ }
1182
+ else if (hasNonNullishProperty(message, 'orderStatus')) {
1183
+ // --- validasi 100% ---
1184
+ const os = message.orderStatus;
1185
+ if (!os || typeof os !== 'object') {
1186
+ throw new Boom('"orderStatus" harus berupa object', { statusCode: 400 });
1237
1187
  }
1238
-
1239
- interactiveMessage.contextInfo = {
1240
- ...(message.contextInfo || {}),
1241
- ...(message.mentions ? {
1242
- mentionedJid: message.mentions
1243
- } : {})
1188
+ if (!os.image) {
1189
+ throw new Boom('"orderStatus.image" wajib diisi (path file, URL, atau Buffer)', { statusCode: 400 });
1244
1190
  }
1245
-
1246
- m = {
1247
- interactiveMessage
1191
+ if (!os.referenceId) {
1192
+ throw new Boom('"orderStatus.referenceId" wajib diisi', { statusCode: 400 });
1248
1193
  }
1249
- } else if ('shop' in message && !!message.shop) {
1250
- const interactiveMessage = {
1251
- shopStorefrontMessage: {
1252
- surface: message.shop.surface,
1253
- id: message.shop.id
1254
- }
1194
+ if (os.status !== undefined && typeof os.status !== 'string') {
1195
+ throw new Boom('"orderStatus.status" harus berupa string', { statusCode: 400 });
1255
1196
  }
1256
-
1257
- if ('text' in message) {
1258
- interactiveMessage.body = {
1259
- text: message.text
1260
- },
1261
- interactiveMessage.header = {
1262
- title: message.title,
1263
- subtitle: message.subtitle,
1264
- hasMediaAttachment: false
1265
- }
1266
- } else {
1267
- if ('caption' in message) {
1268
- interactiveMessage.body = {
1269
- text: message.caption
1270
- }
1271
-
1272
- interactiveMessage.header = {
1273
- title: message.title,
1274
- subtitle: message.subtitle,
1275
- hasMediaAttachment: message.hasMediaAttachment ? message.hasMediaAttachment : false,
1276
- ...Object.assign(interactiveMessage, m)
1277
- }
1278
- }
1197
+ if (os.subtotalValue !== undefined && typeof os.subtotalValue !== 'number') {
1198
+ throw new Boom('"orderStatus.subtotalValue" harus berupa angka', { statusCode: 400 });
1279
1199
  }
1280
-
1281
- if ('footer' in message && !!message.footer) {
1282
- interactiveMessage.footer = {
1283
- text: message.footer
1284
- }
1200
+ if (os.taxValue !== undefined && typeof os.taxValue !== 'number') {
1201
+ throw new Boom('"orderStatus.taxValue" harus berupa angka', { statusCode: 400 });
1285
1202
  }
1286
-
1287
- interactiveMessage.contextInfo = {
1288
- ...(message.contextInfo || {}),
1289
- ...(message.mentions ? {
1290
- mentionedJid: message.mentions
1291
- } : {})
1203
+ let imageInput = os.image;
1204
+ if (typeof imageInput === 'string' && !/^https?:\/\//i.test(imageInput)) {
1205
+ imageInput = await fs.readFile(imageInput);
1292
1206
  }
1293
-
1207
+ const media = await prepareWAMessageMedia({ image: imageInput }, options);
1294
1208
  m = {
1295
- interactiveMessage
1296
- }
1297
- } else if ('collection' in message && !!message.collection) {
1298
- const interactiveMessage = {
1299
- collectionMessage: {
1300
- bizJid: message.collection.bizJid,
1301
- id: message.collection.id,
1302
- messageVersion: message?.collection?.version
1209
+ viewOnceMessage: {
1210
+ message: {
1211
+ messageContextInfo: {
1212
+ deviceListMetadata: {},
1213
+ deviceListMetadataVersion: 2
1214
+ },
1215
+ interactiveMessage: proto.Message.InteractiveMessage.create({
1216
+ header: {
1217
+ title: os.title || 'Order Status',
1218
+ hasMediaAttachment: true,
1219
+ ...media
1220
+ },
1221
+ body: {
1222
+ text: os.text || 'Silakan cek status pesanan Anda.'
1223
+ },
1224
+ footer: {
1225
+ text: os.footer || 'Powered by @pontalabs/baileys'
1226
+ },
1227
+ nativeFlowMessage: {
1228
+ buttons: [
1229
+ {
1230
+ name: 'order_status',
1231
+ buttonParamsJson: JSON.stringify({
1232
+ reference_id: os.referenceId,
1233
+ order: {
1234
+ status: os.status || 'PROCESSING',
1235
+ subtotal: {
1236
+ value: os.subtotalValue || 0,
1237
+ offset: os.subtotalOffset || 100
1238
+ },
1239
+ tax: {
1240
+ value: os.taxValue || 0,
1241
+ offset: os.taxOffset || 100
1242
+ },
1243
+ currency: os.currency || 'IDR'
1244
+ }
1245
+ })
1246
+ }
1247
+ ]
1248
+ }
1249
+ })
1250
+ }
1303
1251
  }
1304
- }
1305
-
1306
- if ('text' in message) {
1307
- interactiveMessage.body = {
1308
- text: message.text
1309
- },
1310
- interactiveMessage.header = {
1311
- title: message.title,
1312
- subtitle: message.subtitle,
1313
- hasMediaAttachment: false
1252
+ };
1253
+ }
1254
+ else if (hasNonNullishProperty(message, 'templateButtons')) {
1255
+ const hydratedTemplate = {
1256
+ hydratedButtons: message.templateButtons.map((button, i) => {
1257
+ const buttonText = button.text || button.buttonText;
1258
+ if (hasOptionalProperty(button, 'id')) {
1259
+ return {
1260
+ index: i,
1261
+ quickReplyButton: {
1262
+ displayText: buttonText || '👉🏻 Click',
1263
+ id: button.id
1264
+ }
1265
+ };
1314
1266
  }
1315
- } else {
1316
- if ('caption' in message) {
1317
- interactiveMessage.body = {
1318
- text: message.caption
1267
+ else if (hasOptionalProperty(button, 'url')) {
1268
+ return {
1269
+ index: i,
1270
+ urlButton: {
1271
+ displayText: buttonText || '🌐 Visit',
1272
+ url: button.url
1273
+ }
1274
+ };
1319
1275
  }
1320
- interactiveMessage.header = {
1321
- title: message.title,
1322
- subtitle: message.subtitle,
1323
- hasMediaAttachment: message.hasMediaAttachment ? message.hasMediaAttachment : false,
1324
- ...Object.assign(interactiveMessage, m)
1276
+ else if (hasOptionalProperty(button, 'call')) {
1277
+ return {
1278
+ index: i,
1279
+ callButton: {
1280
+ displayText: buttonText || '📞 Call',
1281
+ phoneNumber: button.call
1282
+ }
1283
+ };
1325
1284
  }
1285
+ button.index = button.index || i;
1286
+ return button;
1287
+ })
1288
+ };
1289
+ if (hasOptionalProperty(message, 'text')) {
1290
+ hydratedTemplate.hydratedContentText = message.text;
1291
+ }
1292
+ else {
1293
+ if (hasOptionalProperty(message, 'caption')) {
1294
+ hydratedTemplate.hydratedTitleText = message.title;
1295
+ hydratedTemplate.hydratedContentText = message.caption;
1326
1296
  }
1297
+ ;
1298
+ Object.assign(hydratedTemplate, m);
1327
1299
  }
1328
-
1329
- if ('footer' in message && !!message.footer) {
1330
- interactiveMessage.footer = {
1331
- text: message.footer
1332
- }
1333
- }
1334
-
1335
- interactiveMessage.contextInfo = {
1336
- ...(message.contextInfo || {}),
1337
- ...(message.mentions ? {
1338
- mentionedJid: message.mentions
1339
- } : {})
1300
+ if (hasOptionalProperty(message, 'footer')) {
1301
+ hydratedTemplate.hydratedFooterText = message.footer;
1340
1302
  }
1341
-
1303
+ hydratedTemplate.templateId = message.id || 'template-' + Date.now();
1342
1304
  m = {
1343
- interactiveMessage
1344
- }
1345
- } else if ('cards' in message && !!message.cards) {
1346
- const slides = await Promise.all(message.cards.map(async (slide) => {
1347
- const {
1348
- image,
1349
- video,
1350
- product,
1351
- title,
1352
- body,
1353
- footer,
1354
- buttons
1355
- } = slide
1356
- let header
1357
-
1358
- if (product) {
1359
- const { imageMessage } = await prepareWAMessageMedia({
1360
- image: product.productImage,
1361
- ...options
1362
- }, options)
1363
- header = {
1364
- productMessage: {
1365
- product: {
1366
- ...product,
1367
- productImage: imageMessage,
1368
- },
1369
- ...slide
1370
- }
1371
- }
1372
- } else if (image) {
1373
- header = await prepareWAMessageMedia({
1374
- image: image,
1375
- ...options
1376
- }, options)
1377
- } else if (video) {
1378
- header = await prepareWAMessageMedia({
1379
- video: video,
1380
- ...options
1381
- }, options)
1305
+ templateMessage: {
1306
+ hydratedFourRowTemplate: hydratedTemplate,
1307
+ hydratedTemplate: hydratedTemplate
1382
1308
  }
1383
-
1384
- const msg = {
1385
- header: {
1386
- title,
1387
- hasMediaAttachment: true,
1388
- ...header
1389
- },
1390
- body: {
1391
- text: body
1392
- },
1393
- footer: {
1394
- text: footer
1395
- },
1396
- nativeFlowMessage: {
1397
- buttons,
1309
+ };
1310
+ }
1311
+ else if (hasNonNullishProperty(message, 'nativeFlow')) {
1312
+ const interactiveMessage = {
1313
+ nativeFlowMessage: prepareNativeFlowButtons(message)
1314
+ };
1315
+ if (hasOptionalProperty(message, 'bizJid')) {
1316
+ interactiveMessage.collectionMessage = {
1317
+ bizJid: message.bizJid,
1318
+ id: message.id,
1319
+ messageVersion: 1
1320
+ };
1321
+ }
1322
+ else if (hasOptionalProperty(message, 'shopSurface')) {
1323
+ interactiveMessage.shopStorefrontMessage = {
1324
+ surface: message.shopSurface,
1325
+ id: message.id,
1326
+ messageVersion: 1
1327
+ };
1328
+ }
1329
+ if (hasOptionalProperty(message, 'text')) {
1330
+ interactiveMessage.body = { text: message.text };
1331
+ }
1332
+ else {
1333
+ if (hasOptionalProperty(message, 'caption')) {
1334
+ const isValidHeader = hasValidInteractiveHeader(m);
1335
+ if (!isValidHeader) {
1336
+ throw new Boom('Invalid media type for interactive message header', { statusCode: 400 });
1398
1337
  }
1338
+ interactiveMessage.header = {
1339
+ title: message.title || '',
1340
+ subtitle: message.subtitle || '',
1341
+ hasMediaAttachment: isValidHeader
1342
+ };
1343
+ interactiveMessage.body = { text: message.caption };
1399
1344
  }
1400
-
1401
- return msg
1402
- }))
1403
-
1404
- const interactiveMessage = {
1405
- carouselMessage: {
1406
- cards: slides
1345
+ if (hasOptionalProperty(message, 'thumbnail') && !!message.thumbnail) {
1346
+ interactiveMessage.jpegThumbnail = message.thumbnail;
1407
1347
  }
1348
+ Object.assign(interactiveMessage.header, m);
1408
1349
  }
1409
-
1410
- if ('text' in message) {
1411
- interactiveMessage.body = {
1412
- text: message.text
1413
- },
1414
- interactiveMessage.header = {
1415
- title: message.title,
1416
- subtitle: message.subtitle,
1417
- hasMediaAttachment: false
1418
- }
1419
- }
1420
-
1421
- if ('footer' in message && !!message.footer) {
1350
+ if (hasOptionalProperty(message, 'audioFooter')) {
1351
+ const { audioMessage } = await prepareWAMessageMedia({
1352
+ audio: message.audioFooter
1353
+ }, options);
1422
1354
  interactiveMessage.footer = {
1423
- text: message.footer
1355
+ audioMessage,
1356
+ hasMediaAttachment: true
1357
+ };
1358
+ }
1359
+ else if (hasOptionalProperty(message, 'footer')) {
1360
+ interactiveMessage.footer = { text: message.footer };
1361
+ }
1362
+ m = { interactiveMessage };
1363
+ }
1364
+ else if (hasNonNullishProperty(message, 'cards')) {
1365
+ const interactiveMessage = {
1366
+ carouselMessage: {
1367
+ cards: await Promise.all(message.cards.map(async (card) => {
1368
+ let carouselHeader = {};
1369
+ if (hasNonNullishProperty(card, 'product')) {
1370
+ carouselHeader.productMessage = await prepareProductMessage(card, options);
1371
+ }
1372
+ else {
1373
+ carouselHeader = await prepareWAMessageMedia(card, options).catch(() => ({}));
1374
+ }
1375
+ const isValidHeader = hasValidCarouselHeader(carouselHeader);
1376
+ if (!isValidHeader) {
1377
+ throw new Boom('Invalid media type for carousel card', { statusCode: 400 });
1378
+ }
1379
+ const carouselCard = {
1380
+ nativeFlowMessage: prepareNativeFlowButtons(card.nativeFlow ? card : [])
1381
+ };
1382
+ if (hasOptionalProperty(card, 'text')) {
1383
+ carouselCard.body = { text: card.text };
1384
+ }
1385
+ else {
1386
+ if (hasOptionalProperty(card, 'caption')) {
1387
+ carouselCard.header = {
1388
+ title: card.title || '',
1389
+ subtitle: card.subtitle || '',
1390
+ hasMediaAttachment: isValidHeader
1391
+ };
1392
+ carouselCard.body = { text: card.caption };
1393
+ }
1394
+ if (hasOptionalProperty(card, 'thumbnail') && !!card.thumbnail) {
1395
+ carouselCard.jpegThumbnail = card.thumbnail;
1396
+ }
1397
+ Object.assign(carouselCard.header, carouselHeader);
1398
+ }
1399
+ if (hasOptionalProperty(card, 'audioFooter')) {
1400
+ const { audioMessage } = await prepareWAMessageMedia({
1401
+ audio: card.audioFooter
1402
+ }, options);
1403
+ carouselCard.footer = {
1404
+ audioMessage,
1405
+ hasMediaAttachment: true
1406
+ };
1407
+ }
1408
+ else if (hasOptionalProperty(card, 'footer')) {
1409
+ carouselCard.footer = { text: card.footer };
1410
+ }
1411
+ return carouselCard;
1412
+ })),
1413
+ carouselCardType: CarouselCardType.UNKNOWN,
1414
+ messageVersion: 1
1424
1415
  }
1416
+ };
1417
+ if (hasOptionalProperty(message, 'text')) {
1418
+ interactiveMessage.body = { text: message.text };
1425
1419
  }
1426
-
1427
- interactiveMessage.contextInfo = {
1428
- ...(message.contextInfo || {}),
1429
- ...(message.mentions ? {
1430
- mentionedJid: message.mentions
1431
- } : {})
1420
+ if (hasOptionalProperty(message, 'footer')) {
1421
+ interactiveMessage.footer = { text: message.footer };
1432
1422
  }
1433
-
1434
- m = {
1435
- interactiveMessage
1423
+ m = { interactiveMessage };
1424
+ }
1425
+ else if (hasNonNullishProperty(message, 'requestPaymentFrom')) {
1426
+ const requestPaymentMessage = {
1427
+ amount: {
1428
+ currencyCode: 'IDR',
1429
+ offset: 1000,
1430
+ value: 1000
1431
+ },
1432
+ amount1000: 1000,
1433
+ currencyCodeIso4217: 'IDR',
1434
+ expiryTimestamp: Date.now(),
1435
+ noteMessage: m,
1436
+ requestFrom: message.requestPaymentFrom,
1437
+ ...message
1438
+ };
1439
+ delete requestPaymentMessage.requestPaymentFrom;
1440
+ if (hasNonNullishProperty(m, 'extendedTextMessage') || hasNonNullishProperty(m, 'stickerMessage')) {
1441
+ Object.assign(requestPaymentMessage.noteMessage, m);
1442
+ }
1443
+ else {
1444
+ throw new Boom('Invalid message type for request payment note message', { statusCode: 400 });
1445
+ }
1446
+ m = { requestPaymentMessage };
1447
+ }
1448
+ else if (hasNonNullishProperty(message, 'invoiceNote')) {
1449
+ const attachment = m.imageMessage || m.documentMessage;
1450
+ const type = Object.keys(m)[0].replace('Message', '').toUpperCase();
1451
+ const invoiceMessage = {
1452
+ attachmentType: proto.Message.InvoiceMessage.AttachmentType[type === 'DOCUMENT' ? 'PDF' : 'IMAGE'],
1453
+ note: message.invoiceNote
1454
+ };
1455
+ if (attachment) {
1456
+ const { directPath, fileEncSha256, fileSha256, jpegThumbnail = undefined, mediaKey, mediaKeyTimestamp, mimetype } = attachment;
1457
+ Object.assign(invoiceMessage, {
1458
+ attachmentDirectPath: directPath,
1459
+ attachmentFileEncSha256: fileEncSha256,
1460
+ attachmentFileSha256: fileSha256,
1461
+ attachmentJpegThumbnail: jpegThumbnail,
1462
+ attachmentMediaKey: mediaKey,
1463
+ attachmentMediaKeyTimestamp: mediaKeyTimestamp,
1464
+ attachmentMimetype: mimetype,
1465
+ token: generateMessageIDV2()
1466
+ });
1467
+ }
1468
+ else {
1469
+ throw new Boom('Invalid media type for invoice message', { statusCode: 400 });
1470
+ }
1471
+ m = { invoiceMessage };
1472
+ }
1473
+ if (hasOptionalProperty(message, 'externalAdReply') && !!message.externalAdReply) {
1474
+ const messageType = Object.keys(m)[0];
1475
+ const key = m[messageType];
1476
+ const content = message.externalAdReply;
1477
+ if ('thumbnail' in content && !Buffer.isBuffer(content.thumbnail)) {
1478
+ throw new Boom('Thumbnail must in buffer type', { statusCode: 400 });
1479
+ }
1480
+ if (!content.url || typeof content.url !== 'string' || !/^https?:\/\//i.test(content.url)) {
1481
+ options.logger?.warn({ givenUrl: content.url }, 'externalAdReply: field "url" kosong/tidak valid (harus diawali http:// atau https://) — fallback ke DONATE_URL, link produk asli TIDAK terkirim. Cek kode yang mengisi `url` di externalAdReply.');
1482
+ content.url = DONATE_URL;
1483
+ }
1484
+ const externalAdReply = {
1485
+ ...content,
1486
+ body: content.body,
1487
+ mediaType: content.mediaType || 1,
1488
+ mediaUrl: content.url,
1489
+ renderLargerThumbnail: content.largeThumbnail,
1490
+ sourceUrl: content.url,
1491
+ thumbnail: content.thumbnail,
1492
+ thumbnailUrl: content.url + '?update=' + Date.now(),
1493
+ title: content.title || LIBRARY_NAME
1494
+ };
1495
+ delete externalAdReply.subTitle;
1496
+ delete externalAdReply.largeThumbnail;
1497
+ delete externalAdReply.url;
1498
+ if ('contextInfo' in key && !!key.contextInfo) {
1499
+ key.contextInfo.externalAdReply = { ...key.contextInfo.externalAdReply, ...externalAdReply };
1500
+ }
1501
+ else if (key) {
1502
+ key.contextInfo = { externalAdReply };
1436
1503
  }
1437
1504
  }
1438
-
1439
- if ('ephemeral' in message && !!message.ephemeral) {
1440
- m = {
1441
- ephemeralMessage: {
1442
- message: m
1505
+ if ((hasOptionalProperty(message, 'mentions') && message.mentions?.length) ||
1506
+ (hasOptionalProperty(message, 'mentionAll') && message.mentionAll)) {
1507
+ const messageType = Object.keys(m)[0];
1508
+ const key = m[messageType];
1509
+ if (key && 'contextInfo' in key) {
1510
+ key.contextInfo = key.contextInfo || {};
1511
+ if (message.mentions?.length) {
1512
+ key.contextInfo.mentionedJid = message.mentions;
1443
1513
  }
1514
+ if (message.mentionAll) {
1515
+ key.contextInfo.nonJidMentions = 1;
1516
+ }
1517
+ }
1518
+ else if (key) {
1519
+ key.contextInfo = {
1520
+ mentionedJid: message.mentions,
1521
+ nonJidMentions: message.mentionAll ? 1 : 0
1522
+ };
1444
1523
  }
1445
1524
  }
1446
-
1447
- if ('viewOnce' in message && !!message.viewOnce) {
1448
- m = {
1449
- viewOnceMessageV2: {
1450
- message: m
1451
- }
1525
+ if (hasOptionalProperty(message, 'contextInfo') && !!message.contextInfo) {
1526
+ const messageType = Object.keys(m)[0];
1527
+ const key = m[messageType];
1528
+ if ('contextInfo' in key && !!key.contextInfo) {
1529
+ key.contextInfo = { ...key.contextInfo, ...message.contextInfo };
1530
+ }
1531
+ else if (key) {
1532
+ key.contextInfo = message.contextInfo;
1452
1533
  }
1453
1534
  }
1454
-
1455
- if ('viewOnceExt' in message && !!message.viewOnceExt) {
1535
+ if (hasOptionalProperty(message, 'groupStatus') && !!message.groupStatus) {
1536
+ const messageType = Object.keys(m)[0];
1537
+ const key = m[messageType];
1538
+ if ('contextInfo' in key && !!key.contextInfo) {
1539
+ key.contextInfo.isGroupStatus = message.groupStatus;
1540
+ }
1541
+ else if (key) {
1542
+ key.contextInfo = {
1543
+ isGroupStatus: message.groupStatus
1544
+ };
1545
+ }
1546
+ m = { groupStatusMessageV2: { message: m } };
1547
+ delete message.groupStatus;
1548
+ }
1549
+ if (hasOptionalProperty(message, 'spoiler') && !!message.spoiler) {
1550
+ const messageType = Object.keys(m)[0];
1551
+ const key = m[messageType];
1552
+ if ('contextInfo' in key && !!key.contextInfo) {
1553
+ key.contextInfo.isSpoiler = message.spoiler;
1554
+ }
1555
+ else if (key) {
1556
+ key.contextInfo = {
1557
+ isSpoiler: message.spoiler
1558
+ };
1559
+ }
1560
+ m = { spoilerMessage: { message: m } };
1561
+ delete message.spoiler;
1562
+ }
1563
+ else if (hasOptionalProperty(message, 'interactiveAsTemplate') && !!message.interactiveAsTemplate) {
1564
+ if (!m.interactiveMessage) {
1565
+ throw new Boom('Invalid message type for template', { statusCode: 400 });
1566
+ }
1456
1567
  m = {
1457
- viewOnceMessageV2Extension: {
1458
- message: m
1568
+ templateMessage: {
1569
+ interactiveMessageTemplate: m.interactiveMessage,
1570
+ templateId: message.id || 'template-' + Date.now()
1459
1571
  }
1460
- }
1572
+ };
1573
+ delete message.interactiveAsTemplate;
1574
+ }
1575
+ if (hasOptionalProperty(message, 'ephemeral') && !!message.ephemeral) {
1576
+ m = { ephemeralMessage: { message: m } };
1577
+ delete message.ephemeral;
1578
+ }
1579
+ if (hasOptionalProperty(message, 'isLottie') && !!message.isLottie) {
1580
+ m = { lottieStickerMessage: { message: m } };
1461
1581
  }
1462
-
1463
- if ('edit' in message) {
1582
+ else if (hasOptionalProperty(message, 'viewOnce') && !!message.viewOnce) {
1583
+ m = { viewOnceMessage: { message: m } };
1584
+ }
1585
+ else if (hasOptionalProperty(message, 'viewOnceV2') && !!message.viewOnceV2) {
1586
+ m = { viewOnceMessageV2: { message: m } };
1587
+ delete message.viewOnceV2;
1588
+ }
1589
+ else if (hasOptionalProperty(message, 'viewOnceV2Extension') && !!message.viewOnceV2Extension) {
1590
+ m = { viewOnceMessageV2Extension: { message: m } };
1591
+ delete message.viewOnceV2Extension;
1592
+ }
1593
+ if (hasOptionalProperty(message, 'edit')) {
1464
1594
  m = {
1465
1595
  protocolMessage: {
1466
1596
  key: message.edit,
1467
1597
  editedMessage: m,
1468
1598
  timestampMs: Date.now(),
1469
- type: Types_1.WAProto.Message.ProtocolMessage.Type.MESSAGE_EDIT
1599
+ type: WAProto.Message.ProtocolMessage.Type.MESSAGE_EDIT
1470
1600
  }
1601
+ };
1602
+ }
1603
+ if (shouldIncludeReportingToken(m)) {
1604
+ m.messageContextInfo = m.messageContextInfo || {};
1605
+ if (!m.messageContextInfo.messageSecret) {
1606
+ m.messageContextInfo.messageSecret = randomBytes(32);
1471
1607
  }
1472
1608
  }
1473
-
1474
- return Types_1.WAProto.Message.fromObject(m)
1475
- }
1476
-
1477
- const generateWAMessageFromContent = (jid, message, options) => {
1609
+ return WAProto.Message.create(m);
1610
+ };
1611
+ export const generateWAMessageFromContent = (jid, message, options) => {
1612
+ // set timestamp to now
1613
+ // if not specified
1478
1614
  if (!options.timestamp) {
1479
- options.timestamp = new Date()
1480
- }
1481
-
1482
- const innerMessage = normalizeMessageContent(message)
1483
- const key = getContentType(innerMessage)
1484
- const timestamp = generics_1.unixTimestampSeconds(options.timestamp)
1485
- const {
1486
- quoted,
1487
- userJid
1488
- } = options
1489
-
1490
- if (quoted && !WABinary_1.isJidNewsletter(jid)) {
1491
- const participant = quoted.key.fromMe ? userJid : quoted.participant || quoted.key.participant || quoted.key.remoteJid
1492
- let quotedMsg = normalizeMessageContent(quoted.message)
1493
- const msgType = getContentType(quotedMsg)
1494
-
1495
- quotedMsg = WAProto_1.proto.Message.fromObject({
1496
- [msgType]: quotedMsg[msgType]
1497
- })
1498
-
1499
- const quotedContent = quotedMsg[msgType]
1615
+ options.timestamp = new Date();
1616
+ }
1617
+ const innerMessage = normalizeMessageContent(message);
1618
+ const messageContextInfo = message.messageContextInfo;
1619
+ const key = getContentType(innerMessage);
1620
+ const timestamp = unixTimestampSeconds(options.timestamp);
1621
+ const isNewsletter = isJidNewsletter(jid);
1622
+ const { quoted, userJid } = options;
1623
+ if (quoted) {
1624
+ const participant = quoted.key.fromMe
1625
+ ? userJid // TODO: Add support for LIDs
1626
+ : quoted.participant || quoted.key.participant || quoted.key.remoteJid;
1627
+ let quotedMsg = normalizeMessageContent(quoted.message);
1628
+ const msgType = getContentType(quotedMsg);
1629
+ // strip any redundant properties
1630
+ quotedMsg = proto.Message.create({ [msgType]: quotedMsg[msgType] });
1631
+ const quotedContent = quotedMsg[msgType];
1500
1632
  if (typeof quotedContent === 'object' && quotedContent && 'contextInfo' in quotedContent) {
1501
- delete quotedContent.contextInfo
1502
- }
1503
-
1504
- let requestPayment
1505
- if (key === 'requestPaymentMessage') {
1506
- if (innerMessage?.requestPaymentMessage && innerMessage?.requestPaymentMessage?.noteMessage?.extendedTextMessage) {
1507
- requestPayment = innerMessage?.requestPaymentMessage?.noteMessage?.extendedTextMessage
1508
- } else if (innerMessage?.requestPaymentMessage && innerMessage?.requestPaymentMessage?.noteMessage?.stickerMessage) {
1509
- requestPayment = innerMessage.requestPaymentMessage?.noteMessage?.stickerMessage
1510
- }
1633
+ delete quotedContent.contextInfo;
1511
1634
  }
1512
-
1513
- const contextInfo = (key === 'requestPaymentMessage' ? requestPayment?.contextInfo : innerMessage[key].contextInfo) || {}
1514
- contextInfo.participant = WABinary_1.jidNormalizedUser(participant)
1515
- contextInfo.stanzaId = quoted.key.id
1516
- contextInfo.quotedMessage = quotedMsg
1517
-
1518
- if (jid !== quoted.key.remoteJid) {
1519
- contextInfo.remoteJid = quoted.key.remoteJid
1635
+ const contextInfo = ('contextInfo' in innerMessage[key] && innerMessage[key]?.contextInfo) || {};
1636
+ contextInfo.participant = jidNormalizedUser(participant);
1637
+ contextInfo.stanzaId = quoted.key.id;
1638
+ contextInfo.quotedMessage = quotedMsg;
1639
+ // if a participant is quoted, then it must be a group
1640
+ // hence, remoteJid of group must also be entered
1641
+ if (!isNewsletter && jid !== quoted.key.remoteJid) {
1642
+ contextInfo.remoteJid = quoted.key.remoteJid;
1520
1643
  }
1521
-
1522
- if (contextInfo.quotedMessage) {
1523
- contextInfo.quotedType = WAProto_1.proto.ContextInfo.QuotedType.EXPLICIT
1524
- }
1525
-
1526
- if (key === 'requestPaymentMessage' && requestPayment) {
1527
- requestPayment.contextInfo = contextInfo
1528
- } else {
1529
- innerMessage[key].contextInfo = contextInfo
1530
- }
1531
- }
1532
-
1533
- if (key !== 'protocolMessage' && key !== 'ephemeralMessage' && !WABinary_1.isJidNewsletter(jid)) {
1534
- message.messageContextInfo = {
1535
- messageSecret: crypto_1.randomBytes(32),
1536
- ...message.messageContextInfo
1644
+ if (contextInfo && innerMessage[key]) {
1645
+ /* @ts-ignore */
1646
+ innerMessage[key].contextInfo = contextInfo;
1537
1647
  }
1538
1648
  }
1539
-
1540
- if (!!options?.ephemeralExpiration && key !== 'protocolMessage' && key !== 'ephemeralMessage' && !WABinary_1.isJidNewsletter(jid)) {
1649
+ if (
1650
+ // if we want to send a disappearing message
1651
+ !!options?.ephemeralExpiration &&
1652
+ // and it's not a protocol message -- delete, toggle disappear message
1653
+ key !== 'protocolMessage' &&
1654
+ // already not converted to disappearing message
1655
+ key !== 'ephemeralMessage' &&
1656
+ // newsletters don't support ephemeral messages
1657
+ !isNewsletter) {
1658
+ /* @ts-ignore */
1541
1659
  innerMessage[key].contextInfo = {
1542
1660
  ...(innerMessage[key].contextInfo || {}),
1543
- expiration: options.ephemeralExpiration
1544
- }
1661
+ expiration: options.ephemeralExpiration || WA_DEFAULT_EPHEMERAL
1662
+ //ephemeralSettingTimestamp: options.ephemeralOptions.eph_setting_ts?.toString()
1663
+ };
1545
1664
  }
1546
-
1547
- message = Types_1.WAProto.Message.fromObject(message)
1548
-
1665
+ if (messageContextInfo?.messageSecret && (isPnUser(jid) || isLidUser(jid))) {
1666
+ messageContextInfo.deviceListMetadata = {
1667
+ recipientKeyHash: randomBytes(10),
1668
+ recipientTimestamp: unixTimestampSeconds()
1669
+ };
1670
+ messageContextInfo.deviceListMetadataVersion = 2;
1671
+ }
1672
+ message = WAProto.Message.create(message);
1549
1673
  const messageJSON = {
1550
1674
  key: {
1551
1675
  remoteJid: jid,
1552
1676
  fromMe: true,
1553
- id: options?.messageId || generics_1.generateMessageID()
1677
+ id: options?.messageId || generateMessageIDV2()
1554
1678
  },
1555
1679
  message: message,
1556
1680
  messageTimestamp: timestamp,
1557
1681
  messageStubParameters: [],
1558
- participant: WABinary_1.isJidGroup(jid) || WABinary_1.isJidStatusBroadcast(jid) ? userJid : undefined,
1559
- status: Types_1.WAMessageStatus.PENDING
1560
- }
1561
-
1562
- return Types_1.WAProto.WebMessageInfo.fromObject(messageJSON)
1563
- }
1564
-
1565
- const generateWAMessage = async (jid, content, options) => {
1566
- options.logger = options?.logger?.child({
1567
- msgId: options.messageId
1568
- })
1569
-
1682
+ participant: isJidGroup(jid) || isJidStatusBroadcast(jid) ? userJid : undefined, // TODO: Add support for LIDs
1683
+ status: WAMessageStatus.PENDING
1684
+ };
1685
+ return WAProto.WebMessageInfo.fromObject(messageJSON);
1686
+ };
1687
+ export const generateWAMessage = async (jid, content, options) => {
1688
+ // ensure msg ID is with every log
1689
+ options.logger = options?.logger?.child({ msgId: options.messageId });
1690
+ // Pass jid in the options to generateWAMessageContent
1570
1691
  if (jid) {
1571
- options.jid = jid
1692
+ options.jid = jid;
1572
1693
  }
1573
-
1574
- return generateWAMessageFromContent(jid, await generateWAMessageContent(content, {
1575
- newsletter: WABinary_1.isJidNewsletter(jid),
1576
- ...options
1577
- }), options)
1578
- }
1579
-
1580
- const getContentType = (content) => {
1694
+ return generateWAMessageFromContent(jid, await generateWAMessageContent(content, options), options);
1695
+ };
1696
+ /** Get the key to access the true type of content */
1697
+ export const getContentType = (content) => {
1581
1698
  if (content) {
1582
- const keys = Object.keys(content)
1583
- const key = keys.find(k => (k === 'conversation' || k.includes('Message')) && k !== 'senderKeyDistributionMessage' && k !== 'messageContextInfo')
1584
-
1585
- return key
1699
+ const keys = Object.keys(content);
1700
+ const key = keys.find(k => (k === 'conversation' || k.includes('Message')) && k !== 'senderKeyDistributionMessage');
1701
+ return key;
1586
1702
  }
1587
- }
1588
-
1589
- const normalizeMessageContent = (content) => {
1703
+ };
1704
+ export const normalizeMessageContent = (content) => {
1590
1705
  if (!content) {
1591
- return undefined
1706
+ return undefined;
1592
1707
  }
1593
-
1708
+ // set max iterations to prevent an infinite loop
1594
1709
  for (let i = 0; i < 5; i++) {
1595
- const inner = getFutureProofMessage(content)
1710
+ const inner = getFutureProofMessage(content);
1596
1711
  if (!inner) {
1597
- break
1712
+ break;
1598
1713
  }
1599
-
1600
- content = inner.message
1714
+ content = inner.message;
1601
1715
  }
1602
-
1603
- return content
1604
-
1716
+ return content;
1605
1717
  function getFutureProofMessage(message) {
1606
- return ((message === null || message === void 0 ? void 0 : message.editedMessage) || (message === null || message === void 0 ? void 0 : message.statusAddYours) || (message === null || message === void 0 ? void 0 : message.botTaskMessage) || (message === null || message === void 0 ? void 0 : message.eventCoverImage) || (message === null || message === void 0 ? void 0 : message.questionMessage) || (message === null || message === void 0 ? void 0 : message.questionReplyMessage) || (message === null || message === void 0 ? void 0 : message.viewOnceMessage) || (message === null || message === void 0 ? void 0 : message.botInvokeMessage) || (message === null || message === void 0 ? void 0 : message.ephemeralMessage) || (message === null || message === void 0 ? void 0 : message.lottieStickerMessage) || (message === null || message === void 0 ? void 0 : message.groupStatusMessage) || (message === null || message === void 0 ? void 0 : message.limitSharingMessage) || (message === null || message === void 0 ? void 0 : message.viewOnceMessageV2) || (message === null || message === void 0 ? void 0 : message.botForwardedMessage) || (message === null || message === void 0 ? void 0 : message.statusMentionMessage) || (message === null || message === void 0 ? void 0 : message.groupStatusMessageV2) || (message === null || message === void 0 ? void 0 : message.pollCreationMessageV4) || (message === null || message === void 0 ? void 0 : message.pollCreationMessageV5) || (message === null || message === void 0 ? void 0 : message.associatedChildMessage) || (message === null || message === void 0 ? void 0 : message.groupMentionedMessage) || (message === null || message === void 0 ? void 0 : message.groupStatusMentionMessage) || (message === null || message === void 0 ? void 0 : message.viewOnceMessageV2Extension) || (message === null || message === void 0 ? void 0 : message.documentWithCaptionMessage) || (message === null || message === void 0 ? void 0 : message.pollCreationOptionImageMessage) || (message === null || message === void 0 ? void 0 : message.spoilerMessage) || (message === null || message === void 0 ? void 0 : message.newsletterAdminProfileMessage) || (message === null || message === void 0 ? void 0 : message.newsletterAdminProfileMessageV2) || (message === null || message === void 0 ? void 0 : message.newsletterAdminProfileStatusMessage))
1718
+ return (message?.associatedChildMessage ||
1719
+ message?.botForwardedMessage ||
1720
+ message?.botInvokeMessage ||
1721
+ message?.botTaskMessage ||
1722
+ message?.documentWithCaptionMessage ||
1723
+ message?.editedMessage ||
1724
+ message?.ephemeralMessage ||
1725
+ message?.eventCoverImage ||
1726
+ message?.groupMentionedMessage ||
1727
+ message?.groupStatusMentionMessage ||
1728
+ message?.groupStatusMessage ||
1729
+ message?.groupStatusMessageV2 ||
1730
+ message?.limitSharingMessage ||
1731
+ message?.lottieStickerMessage ||
1732
+ message?.newsletterAdminProfileMessage ||
1733
+ message?.newsletterAdminProfileMessageV2 ||
1734
+ message?.newsletterAdminProfileStatusMessage ||
1735
+ message?.pollCreationMessageV4 ||
1736
+ message?.pollCreationOptionImageMessage ||
1737
+ message?.questionMessage ||
1738
+ message?.questionReplyMessage ||
1739
+ message?.spoilerMessage ||
1740
+ message?.statusAddYours ||
1741
+ message?.statusMentionMessage ||
1742
+ message?.viewOnceMessage ||
1743
+ message?.viewOnceMessageV2 ||
1744
+ message?.viewOnceMessageV2Extension);
1607
1745
  }
1608
- }
1609
-
1610
- const extractMessageContent = (content) => {
1611
- const extractFromButtonsMessage = (msg) => {
1612
- const header = typeof msg.header === 'object' && msg.header !== null
1613
-
1614
- if (header ? msg.header?.imageMessage : msg.imageMessage) {
1615
- return {
1616
- imageMessage: header ? msg.header.imageMessage : msg.imageMessage
1617
- }
1618
- } else if (header ? msg.header?.documentMessage : msg.documentMessage) {
1619
- return {
1620
- documentMessage: header ? msg.header.documentMessage : msg.documentMessage
1621
- }
1622
- } else if (header ? msg.header?.videoMessage : msg.videoMessage) {
1746
+ };
1747
+ /**
1748
+ * Extract the true message content from a message
1749
+ * Eg. extracts the inner message from a disappearing message/view once message
1750
+ */
1751
+ export const extractMessageContent = (content) => {
1752
+ const extractFromTemplateMessage = (msg) => {
1753
+ if (msg.imageMessage) {
1754
+ return { imageMessage: msg.imageMessage };
1755
+ }
1756
+ else if (msg.documentMessage) {
1757
+ return { documentMessage: msg.documentMessage };
1758
+ }
1759
+ else if (msg.videoMessage) {
1760
+ return { videoMessage: msg.videoMessage };
1761
+ }
1762
+ else if (msg.locationMessage) {
1763
+ return { locationMessage: msg.locationMessage };
1764
+ }
1765
+ else {
1623
1766
  return {
1624
- videoMessage: header ? msg.header.videoMessage : msg.videoMessage
1625
- }
1626
- } else if (header ? msg.header?.locationMessage : msg.locationMessage) {
1627
- return {
1628
- locationMessage: header ? msg.header.locationMessage : msg.locationMessage
1629
- }
1630
- } else if (header ? msg.header?.productMessage : msg.productMessage) {
1631
- return {
1632
- productMessage: header ? msg.header.productMessage : msg.productMessage
1633
- }
1634
- } else {
1635
- return {
1636
- conversation: 'contentText' in msg ? msg.contentText : ('hydratedContentText' in msg ? msg.hydratedContentText : 'body' in msg ? msg.body.text : '')
1637
- }
1767
+ conversation: 'contentText' in msg ? msg.contentText : 'hydratedContentText' in msg ? msg.hydratedContentText : ''
1768
+ };
1638
1769
  }
1639
- }
1640
-
1641
- content = normalizeMessageContent(content)
1770
+ };
1771
+ content = normalizeMessageContent(content);
1642
1772
  if (content?.buttonsMessage) {
1643
- return extractFromButtonsMessage(content.buttonsMessage)
1644
- }
1645
- if (content?.interactiveMessage) {
1646
- return extractFromButtonsMessage(content.interactiveMessage)
1647
- }
1648
- if (content?.templateMessage?.interactiveMessageTemplate) {
1649
- return extractFromButtonsMessage(content?.templateMessage?.interactiveMessageTemplate)
1773
+ return extractFromTemplateMessage(content.buttonsMessage);
1650
1774
  }
1651
1775
  if (content?.templateMessage?.hydratedFourRowTemplate) {
1652
- return extractFromButtonsMessage(content?.templateMessage?.hydratedFourRowTemplate)
1776
+ return extractFromTemplateMessage(content?.templateMessage?.hydratedFourRowTemplate);
1653
1777
  }
1654
1778
  if (content?.templateMessage?.hydratedTemplate) {
1655
- return extractFromButtonsMessage(content?.templateMessage?.hydratedTemplate)
1779
+ return extractFromTemplateMessage(content?.templateMessage?.hydratedTemplate);
1656
1780
  }
1657
1781
  if (content?.templateMessage?.fourRowTemplate) {
1658
- return extractFromButtonsMessage(content?.templateMessage?.fourRowTemplate)
1782
+ return extractFromTemplateMessage(content?.templateMessage?.fourRowTemplate);
1659
1783
  }
1660
- return content
1661
- }
1662
-
1663
- const getDevice = (id) => /^3A.{18}$/.test(id) ? 'ios' : /^3E.{20}$/.test(id) ? 'web' : /^(.{21}|.{32})$/.test(id) ? 'android' : /^(3F|.{18}$)/.test(id) ? 'desktop' : 'bots'
1664
-
1665
- const updateMessageWithReceipt = (msg, receipt) => {
1666
- msg.userReceipt = msg.userReceipt || []
1667
- const recp = msg.userReceipt.find(m => m.userJid === receipt.userJid)
1784
+ return content;
1785
+ };
1786
+ /**
1787
+ * Returns the device predicted by message ID
1788
+ */
1789
+ export const getDevice = (id) => /^3A.{18}$/.test(id)
1790
+ ? 'ios'
1791
+ : /^3E.{20}$/.test(id)
1792
+ ? 'web'
1793
+ : /^(.{21}|.{32})$/.test(id)
1794
+ ? 'android'
1795
+ : /^(3F|.{18}$)/.test(id)
1796
+ ? 'desktop'
1797
+ : 'unknown';
1798
+ /** Upserts a receipt in the message */
1799
+ export const updateMessageWithReceipt = (msg, receipt) => {
1800
+ msg.userReceipt = msg.userReceipt || [];
1801
+ const recp = msg.userReceipt.find(m => m.userJid === receipt.userJid);
1668
1802
  if (recp) {
1669
- Object.assign(recp, receipt)
1670
- } else {
1671
- msg.userReceipt.push(receipt)
1803
+ Object.assign(recp, receipt);
1672
1804
  }
1673
- }
1674
-
1675
- const updateMessageWithReaction = (msg, reaction) => {
1676
- const authorID = generics_1.getKeyAuthor(reaction.key)
1677
- const reactions = (msg.reactions || []).filter(r => generics_1.getKeyAuthor(r.key) !== authorID)
1678
- reaction.text = reaction.text || ''
1679
- reactions.push(reaction)
1680
- msg.reactions = reactions
1681
- }
1682
-
1683
- const updateMessageWithPollUpdate = (msg, update) => {
1684
- const authorID = generics_1.getKeyAuthor(update.pollUpdateMessageKey)
1685
- const votes = (msg.pollUpdates || []).filter(r => generics_1.getKeyAuthor(r.pollUpdateMessageKey) !== authorID)
1686
-
1805
+ else {
1806
+ msg.userReceipt.push(receipt);
1807
+ }
1808
+ };
1809
+ /** Update the message with a new reaction */
1810
+ export const updateMessageWithReaction = (msg, reaction) => {
1811
+ const authorID = getKeyAuthor(reaction.key);
1812
+ const reactions = (msg.reactions || []).filter(r => getKeyAuthor(r.key) !== authorID);
1813
+ reaction.text = reaction.text || '';
1814
+ reactions.push(reaction);
1815
+ msg.reactions = reactions;
1816
+ };
1817
+ /** Update the message with a new poll update */
1818
+ export const updateMessageWithPollUpdate = (msg, update) => {
1819
+ const authorID = getKeyAuthor(update.pollUpdateMessageKey);
1820
+ const reactions = (msg.pollUpdates || []).filter(r => getKeyAuthor(r.pollUpdateMessageKey) !== authorID);
1687
1821
  if (update.vote?.selectedOptions?.length) {
1688
- votes.push(update)
1822
+ reactions.push(update);
1689
1823
  }
1690
-
1691
- msg.pollUpdates = votes
1692
- }
1693
-
1694
- function getAggregateVotesInPollMessage({
1695
- message,
1696
- pollUpdates
1697
- }, meId) {
1698
- message = normalizeMessageContent(message)
1699
-
1700
- const opts = message?.pollCreationMessage?.options || message?.pollCreationMessageV2?.options || message?.pollCreationMessageV3?.options || []
1701
-
1824
+ msg.pollUpdates = reactions;
1825
+ };
1826
+ /** Update the message with a new event response */
1827
+ export const updateMessageWithEventResponse = (msg, update) => {
1828
+ const authorID = getKeyAuthor(update.eventResponseMessageKey);
1829
+ const responses = (msg.eventResponses || []).filter(r => getKeyAuthor(r.eventResponseMessageKey) !== authorID);
1830
+ responses.push(update);
1831
+ msg.eventResponses = responses;
1832
+ };
1833
+ /**
1834
+ * Aggregates all poll updates in a poll.
1835
+ * @param msg the poll creation message
1836
+ * @param meId your jid
1837
+ * @returns A list of options & their voters
1838
+ */
1839
+ export function getAggregateVotesInPollMessage({ message, pollUpdates }, meId) {
1840
+ const opts = message?.pollCreationMessage?.options ||
1841
+ message?.pollCreationMessageV2?.options ||
1842
+ message?.pollCreationMessageV3?.options ||
1843
+ [];
1702
1844
  const voteHashMap = opts.reduce((acc, opt) => {
1703
- const hash = crypto_2.sha256(Buffer.from(opt.optionName || '')).toString()
1845
+ const hash = sha256(Buffer.from(opt.optionName || '')).toString();
1704
1846
  acc[hash] = {
1705
1847
  name: opt.optionName || '',
1706
1848
  voters: []
1707
- }
1708
-
1709
- return acc
1710
- }, {})
1711
-
1849
+ };
1850
+ return acc;
1851
+ }, {});
1712
1852
  for (const update of pollUpdates || []) {
1713
- const { vote } = update
1853
+ const { vote } = update;
1714
1854
  if (!vote) {
1715
- continue
1855
+ continue;
1716
1856
  }
1717
-
1718
1857
  for (const option of vote.selectedOptions || []) {
1719
- const hash = option.toString()
1720
- let data = voteHashMap[hash]
1721
-
1858
+ const hash = option.toString();
1859
+ let data = voteHashMap[hash];
1722
1860
  if (!data) {
1723
1861
  voteHashMap[hash] = {
1724
1862
  name: 'Unknown',
1725
1863
  voters: []
1726
- }
1727
-
1728
- data = voteHashMap[hash]
1864
+ };
1865
+ data = voteHashMap[hash];
1729
1866
  }
1730
-
1731
- voteHashMap[hash].voters.push(generics_1.getKeyAuthor(update.pollUpdateMessageKey, meId))
1867
+ voteHashMap[hash].voters.push(getKeyAuthor(update.pollUpdateMessageKey, meId));
1732
1868
  }
1733
1869
  }
1734
-
1735
- return Object.values(voteHashMap)
1870
+ return Object.values(voteHashMap);
1736
1871
  }
1737
-
1738
- const aggregateMessageKeysNotFromMe = (keys) => {
1739
- const keyMap = {}
1740
-
1872
+ /**
1873
+ * Aggregates all event responses in an event message.
1874
+ * @param msg the event creation message
1875
+ * @param meId your jid
1876
+ * @returns A list of response types & their responders
1877
+ */
1878
+ export function getAggregateResponsesInEventMessage({ eventResponses }, meId) {
1879
+ const responseTypes = ['GOING', 'NOT_GOING', 'MAYBE'];
1880
+ const responseMap = {};
1881
+ for (const type of responseTypes) {
1882
+ responseMap[type] = {
1883
+ response: type,
1884
+ responders: []
1885
+ };
1886
+ }
1887
+ for (const update of eventResponses || []) {
1888
+ const responseType = update.eventResponse || 'UNKNOWN';
1889
+ if (responseType !== 'UNKNOWN' && responseMap[responseType]) {
1890
+ responseMap[responseType].responders.push(getKeyAuthor(update.eventResponseMessageKey, meId));
1891
+ }
1892
+ }
1893
+ return Object.values(responseMap);
1894
+ }
1895
+ /** Given a list of message keys, aggregates them by chat & sender. Useful for sending read receipts in bulk */
1896
+ export const aggregateMessageKeysNotFromMe = (keys) => {
1897
+ const keyMap = {};
1741
1898
  for (const { remoteJid, id, participant, fromMe } of keys) {
1742
1899
  if (!fromMe) {
1743
- const uqKey = `${remoteJid}:${participant || ''}`
1744
-
1900
+ const uqKey = `${remoteJid}:${participant || ''}`;
1745
1901
  if (!keyMap[uqKey]) {
1746
1902
  keyMap[uqKey] = {
1747
1903
  jid: remoteJid,
1748
1904
  participant: participant,
1749
1905
  messageIds: []
1750
- }
1906
+ };
1751
1907
  }
1752
-
1753
- keyMap[uqKey].messageIds.push(id)
1908
+ keyMap[uqKey].messageIds.push(id);
1754
1909
  }
1755
1910
  }
1756
-
1757
- return Object.values(keyMap)
1758
- }
1759
-
1760
- const REUPLOAD_REQUIRED_STATUS = [410, 404]
1761
-
1762
- const downloadMediaMessage = async (message, type, options, ctx) => {
1911
+ return Object.values(keyMap);
1912
+ };
1913
+ const REUPLOAD_REQUIRED_STATUS = [410, 404];
1914
+ /**
1915
+ * Downloads the given message. Throws an error if it's not a media message
1916
+ */
1917
+ export const downloadMediaMessage = async (message, type, options, ctx) => {
1763
1918
  const result = await downloadMsg().catch(async (error) => {
1764
- if (ctx && typeof error?.status === 'number' && REUPLOAD_REQUIRED_STATUS.includes(error.status)) {
1765
- ctx.logger.info({
1766
- key: message.key
1767
- }, 'sending reupload media request...')
1768
-
1769
- message = await ctx.reuploadRequest(message)
1770
- const result = await downloadMsg()
1771
- return result
1772
- }
1773
- throw error
1774
- })
1775
-
1776
- return result
1777
-
1919
+ if (ctx &&
1920
+ typeof error?.status === 'number' && // treat errors with status as HTTP failures requiring reupload
1921
+ REUPLOAD_REQUIRED_STATUS.includes(error.status)) {
1922
+ ctx.logger.info({ key: message.key }, 'sending reupload media request...');
1923
+ // request reupload
1924
+ message = await ctx.reuploadRequest(message);
1925
+ const result = await downloadMsg();
1926
+ return result;
1927
+ }
1928
+ throw error;
1929
+ });
1930
+ return result;
1778
1931
  async function downloadMsg() {
1779
- const mContent = extractMessageContent(message.message)
1932
+ const mContent = extractMessageContent(message.message);
1780
1933
  if (!mContent) {
1781
- throw new boom_1.Boom('No message present', {
1782
- statusCode: 400,
1783
- data: message
1784
- })
1934
+ throw new Boom('No message present', { statusCode: 400, data: message });
1785
1935
  }
1786
-
1787
- const contentType = getContentType(mContent)
1788
- let mediaType = contentType?.replace('Message', '')
1789
- const media = contentType === 'productMessage' ? mContent[contentType]?.product?.productImage : mContent[contentType]
1790
-
1936
+ const contentType = getContentType(mContent);
1937
+ let mediaType = contentType?.replace('Message', '');
1938
+ const media = mContent[contentType];
1791
1939
  if (!media || typeof media !== 'object' || (!('url' in media) && !('thumbnailDirectPath' in media))) {
1792
- throw new boom_1.Boom(`"${contentType}" message is not a media message`)
1940
+ throw new Boom(`"${contentType}" message is not a media message`);
1793
1941
  }
1794
-
1795
- let download
1796
-
1942
+ let download;
1797
1943
  if ('thumbnailDirectPath' in media && !('url' in media)) {
1798
1944
  download = {
1799
1945
  directPath: media.thumbnailDirectPath,
1800
1946
  mediaKey: media.mediaKey
1801
- }
1802
- mediaType = 'thumbnail-link'
1803
- } else {
1804
- download = media
1947
+ };
1948
+ mediaType = 'thumbnail-link';
1949
+ }
1950
+ else {
1951
+ download = media;
1805
1952
  }
1806
-
1807
- const stream = await messages_media_1.downloadContentFromMessage(download, mediaType, options)
1808
-
1953
+ const stream = await downloadContentFromMessage(download, mediaType, options);
1809
1954
  if (type === 'buffer') {
1810
- const bufferArray = []
1811
-
1955
+ const bufferArray = [];
1812
1956
  for await (const chunk of stream) {
1813
- bufferArray.push(chunk)
1957
+ bufferArray.push(chunk);
1814
1958
  }
1815
-
1816
- return Buffer.concat(bufferArray)
1959
+ return Buffer.concat(bufferArray);
1817
1960
  }
1818
-
1819
- return stream
1961
+ return stream;
1820
1962
  }
1821
- }
1822
-
1823
- const assertMediaContent = (content) => {
1824
- content = extractMessageContent(content)
1825
-
1826
- const mediaContent = content?.documentMessage || content?.imageMessage || content?.videoMessage || content?.audioMessage || content?.stickerMessage
1963
+ };
1964
+ /** Checks whether the given message is a media message; if it is returns the inner content */
1965
+ export const assertMediaContent = (content) => {
1966
+ content = extractMessageContent(content);
1967
+ const mediaContent = content?.documentMessage ||
1968
+ content?.imageMessage ||
1969
+ content?.videoMessage ||
1970
+ content?.audioMessage ||
1971
+ content?.stickerMessage;
1827
1972
  if (!mediaContent) {
1828
- throw new boom_1.Boom('given message is not a media message', {
1829
- statusCode: 400,
1830
- data: content
1831
- })
1973
+ throw new Boom('given message is not a media message', { statusCode: 400, data: content });
1832
1974
  }
1833
- return mediaContent
1834
- }
1835
-
1836
- const patchMessageForMdIfRequired = (message) => {
1837
- if (message?.buttonsMessage || message?.templateMessage || message?.listMessage || message?.interactiveMessage?.nativeFlowMesaage) {
1838
- message = {
1839
- viewOnceMessageV2Extension: {
1840
- message: {
1841
- messageContextInfo: {
1842
- deviceListMetadataVersion: 2,
1843
- deviceListMetadata: {}
1844
- },
1845
- ...message
1975
+ return mediaContent;
1976
+ };
1977
+ /**
1978
+ * Checks if a WebP buffer is animated by looking for VP8X chunk with animation flag
1979
+ * or ANIM/ANMF chunks
1980
+ */
1981
+ const isAnimatedWebP = (buffer) => {
1982
+ // WebP must start with RIFF....WEBP
1983
+ if (buffer.length < 12 ||
1984
+ buffer[0] !== 0x52 ||
1985
+ buffer[1] !== 0x49 ||
1986
+ buffer[2] !== 0x46 ||
1987
+ buffer[3] !== 0x46 ||
1988
+ buffer[8] !== 0x57 ||
1989
+ buffer[9] !== 0x45 ||
1990
+ buffer[10] !== 0x42 ||
1991
+ buffer[11] !== 0x50) {
1992
+ return false;
1993
+ }
1994
+ ;
1995
+ // Parse chunks starting after RIFF header (12 bytes)
1996
+ let offset = 12;
1997
+ while (offset < buffer.length - 8) {
1998
+ const chunkFourCC = buffer.toString('ascii', offset, offset + 4);
1999
+ const chunkSize = buffer.readUInt32LE(offset + 4);
2000
+ if (chunkFourCC === 'VP8X') {
2001
+ // VP8X extended header, check animation flag (bit 1 at offset+8)
2002
+ const flagsOffset = offset + 8;
2003
+ if (flagsOffset < buffer.length) {
2004
+ const flags = buffer[flagsOffset];
2005
+ if (flags & 0x02) {
2006
+ return true;
1846
2007
  }
2008
+ ;
1847
2009
  }
2010
+ ;
1848
2011
  }
2012
+ else if (chunkFourCC === 'ANIM' || chunkFourCC === 'ANMF') {
2013
+ // ANIM or ANMF chunks indicate animation
2014
+ return true;
2015
+ }
2016
+ ;
2017
+ // Move to next chunk (chunk size + 8 bytes header, padded to even)
2018
+ offset += 8 + chunkSize + (chunkSize % 2);
1849
2019
  }
1850
- return message
1851
- }
1852
-
1853
- module.exports = {
1854
- extractUrlFromText,
1855
- generateLinkPreviewIfRequired,
1856
- prepareWAMessageMedia,
1857
- prepareAlbumMessageContent,
1858
- prepareDisappearingMessageSettingContent,
1859
- generateForwardMessageContent,
1860
- generateWAMessageContent,
1861
- generateWAMessageFromContent,
1862
- generateWAMessage,
1863
- getContentType,
1864
- normalizeMessageContent,
1865
- extractMessageContent,
1866
- getDevice,
1867
- updateMessageWithReceipt,
1868
- updateMessageWithReaction,
1869
- updateMessageWithPollUpdate,
1870
- getAggregateVotesInPollMessage,
1871
- aggregateMessageKeysNotFromMe,
1872
- downloadMediaMessage,
1873
- assertMediaContent,
1874
- patchMessageForMdIfRequired
1875
- }
2020
+ ;
2021
+ return false;
2022
+ };
2023
+ /**
2024
+ * Checks if a buffer is a WebP file
2025
+ */
2026
+ const isWebPBuffer = (buffer) => {
2027
+ return (buffer.length >= 12 &&
2028
+ buffer[0] === 0x52 &&
2029
+ buffer[1] === 0x49 &&
2030
+ buffer[2] === 0x46 &&
2031
+ buffer[3] === 0x46 &&
2032
+ buffer[8] === 0x57 &&
2033
+ buffer[9] === 0x45 &&
2034
+ buffer[10] === 0x42 &&
2035
+ buffer[11] === 0x50);
2036
+ };
2037
+ export const shouldIncludeBizBinaryNode = (message) => {
2038
+ const hasValidInteractive = message.interactiveMessage &&
2039
+ !message.interactiveMessage.carouselMessage &&
2040
+ !message.interactiveMessage.collectionMessage &&
2041
+ !message.interactiveMessage.shopStorefrontMessage;
2042
+ return (message.buttonsMessage ||
2043
+ message.interactiveMessage ||
2044
+ message.listMessage ||
2045
+ hasValidInteractive);
2046
+ };
2047
+ //# sourceMappingURL=messages.js.map