violetics 7.0.6-alpha → 7.0.8-alpha

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 (407) hide show
  1. package/LICENSE +1 -2
  2. package/README.md +456 -978
  3. package/WAProto/GenerateStatics.sh +3 -0
  4. package/WAProto/WAProto.proto +5479 -0
  5. package/WAProto/fix-imports.js +81 -0
  6. package/WAProto/index.d.ts +14017 -0
  7. package/WAProto/index.js +2728 -7894
  8. package/engine-requirements.js +7 -10
  9. package/index.cjs +196 -0
  10. package/index.d.ts +30 -0
  11. package/lib/Defaults/index.d.ts +74 -0
  12. package/lib/Defaults/index.d.ts.map +1 -0
  13. package/lib/Defaults/index.js +16 -25
  14. package/lib/Defaults/index.js.map +1 -0
  15. package/lib/Defaults/phonenumber-mcc.json +223 -0
  16. package/lib/Signal/Group/ciphertext-message.d.ts +10 -0
  17. package/lib/Signal/Group/ciphertext-message.d.ts.map +1 -0
  18. package/lib/Signal/Group/ciphertext-message.js +2 -1
  19. package/lib/Signal/Group/ciphertext-message.js.map +1 -0
  20. package/lib/Signal/Group/group-session-builder.d.ts +15 -0
  21. package/lib/Signal/Group/group-session-builder.d.ts.map +1 -0
  22. package/lib/Signal/Group/group-session-builder.js +2 -1
  23. package/lib/Signal/Group/group-session-builder.js.map +1 -0
  24. package/lib/Signal/Group/group_cipher.d.ts +17 -0
  25. package/lib/Signal/Group/group_cipher.d.ts.map +1 -0
  26. package/lib/Signal/Group/group_cipher.js +2 -1
  27. package/lib/Signal/Group/group_cipher.js.map +1 -0
  28. package/lib/Signal/Group/index.d.ts +12 -0
  29. package/lib/Signal/Group/index.d.ts.map +1 -0
  30. package/lib/Signal/Group/index.js +2 -1
  31. package/lib/Signal/Group/index.js.map +1 -0
  32. package/lib/Signal/Group/keyhelper.d.ts +11 -0
  33. package/lib/Signal/Group/keyhelper.d.ts.map +1 -0
  34. package/lib/Signal/Group/keyhelper.js +2 -1
  35. package/lib/Signal/Group/keyhelper.js.map +1 -0
  36. package/lib/Signal/Group/sender-chain-key.d.ts +14 -0
  37. package/lib/Signal/Group/sender-chain-key.d.ts.map +1 -0
  38. package/lib/Signal/Group/sender-chain-key.js +2 -1
  39. package/lib/Signal/Group/sender-chain-key.js.map +1 -0
  40. package/lib/Signal/Group/sender-key-distribution-message.d.ts +17 -0
  41. package/lib/Signal/Group/sender-key-distribution-message.d.ts.map +1 -0
  42. package/lib/Signal/Group/sender-key-distribution-message.js +2 -1
  43. package/lib/Signal/Group/sender-key-distribution-message.js.map +1 -0
  44. package/lib/Signal/Group/sender-key-message.d.ts +19 -0
  45. package/lib/Signal/Group/sender-key-message.d.ts.map +1 -0
  46. package/lib/Signal/Group/sender-key-message.js +2 -1
  47. package/lib/Signal/Group/sender-key-message.js.map +1 -0
  48. package/lib/Signal/Group/sender-key-name.d.ts +18 -0
  49. package/lib/Signal/Group/sender-key-name.d.ts.map +1 -0
  50. package/lib/Signal/Group/sender-key-name.js +2 -1
  51. package/lib/Signal/Group/sender-key-name.js.map +1 -0
  52. package/lib/Signal/Group/sender-key-record.d.ts +31 -0
  53. package/lib/Signal/Group/sender-key-record.d.ts.map +1 -0
  54. package/lib/Signal/Group/sender-key-record.js +2 -1
  55. package/lib/Signal/Group/sender-key-record.js.map +1 -0
  56. package/lib/Signal/Group/sender-key-state.d.ts +39 -0
  57. package/lib/Signal/Group/sender-key-state.d.ts.map +1 -0
  58. package/lib/Signal/Group/sender-key-state.js +2 -1
  59. package/lib/Signal/Group/sender-key-state.js.map +1 -0
  60. package/lib/Signal/Group/sender-message-key.d.ts +12 -0
  61. package/lib/Signal/Group/sender-message-key.d.ts.map +1 -0
  62. package/lib/Signal/Group/sender-message-key.js +2 -1
  63. package/lib/Signal/Group/sender-message-key.js.map +1 -0
  64. package/lib/Signal/libsignal.d.ts +5 -0
  65. package/lib/Signal/libsignal.d.ts.map +1 -0
  66. package/lib/Signal/libsignal.js +44 -8
  67. package/lib/Signal/libsignal.js.map +1 -0
  68. package/lib/Signal/lid-mapping.d.ts +19 -0
  69. package/lib/Signal/lid-mapping.d.ts.map +1 -0
  70. package/lib/Signal/lid-mapping.js +2 -1
  71. package/lib/Signal/lid-mapping.js.map +1 -0
  72. package/lib/Socket/Client/index.d.ts +3 -0
  73. package/lib/Socket/Client/index.d.ts.map +1 -0
  74. package/lib/Socket/Client/index.js +2 -1
  75. package/lib/Socket/Client/index.js.map +1 -0
  76. package/lib/Socket/Client/types.d.ts +16 -0
  77. package/lib/Socket/Client/types.d.ts.map +1 -0
  78. package/lib/Socket/Client/types.js +2 -1
  79. package/lib/Socket/Client/types.js.map +1 -0
  80. package/lib/Socket/Client/websocket.d.ts +13 -0
  81. package/lib/Socket/Client/websocket.d.ts.map +1 -0
  82. package/lib/Socket/Client/websocket.js +3 -11
  83. package/lib/Socket/Client/websocket.js.map +1 -0
  84. package/lib/Socket/business.d.ts +202 -0
  85. package/lib/Socket/business.d.ts.map +1 -0
  86. package/lib/Socket/business.js +2 -1
  87. package/lib/Socket/business.js.map +1 -0
  88. package/lib/Socket/chats.d.ts +111 -0
  89. package/lib/Socket/chats.d.ts.map +1 -0
  90. package/lib/Socket/chats.js +155 -71
  91. package/lib/Socket/chats.js.map +1 -0
  92. package/lib/Socket/communities.d.ts +258 -0
  93. package/lib/Socket/communities.d.ts.map +1 -0
  94. package/lib/Socket/communities.js +2 -1
  95. package/lib/Socket/communities.js.map +1 -0
  96. package/lib/Socket/community.js +361 -0
  97. package/lib/Socket/groups.d.ts +150 -0
  98. package/lib/Socket/groups.d.ts.map +1 -0
  99. package/lib/Socket/groups.js +140 -9
  100. package/lib/Socket/groups.js.map +1 -0
  101. package/lib/Socket/index.d.ts +245 -0
  102. package/lib/Socket/index.d.ts.map +1 -0
  103. package/lib/Socket/index.js +2 -1
  104. package/lib/Socket/index.js.map +1 -0
  105. package/lib/Socket/messages-recv.d.ts +187 -0
  106. package/lib/Socket/messages-recv.d.ts.map +1 -0
  107. package/lib/Socket/messages-recv.js +481 -204
  108. package/lib/Socket/messages-recv.js.map +1 -0
  109. package/lib/Socket/messages-send.d.ts +183 -0
  110. package/lib/Socket/messages-send.d.ts.map +1 -0
  111. package/lib/Socket/messages-send.js +622 -233
  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 +2 -1
  116. package/lib/Socket/mex.js.map +1 -0
  117. package/lib/Socket/newsletter.d.ts +160 -0
  118. package/lib/Socket/newsletter.d.ts.map +1 -0
  119. package/lib/Socket/newsletter.js +107 -68
  120. package/lib/Socket/newsletter.js.map +1 -0
  121. package/lib/Socket/socket.d.ts +55 -0
  122. package/lib/Socket/socket.d.ts.map +1 -0
  123. package/lib/Socket/socket.js +25 -26
  124. package/lib/Socket/socket.js.map +1 -0
  125. package/lib/Socket/usync.js +76 -0
  126. package/lib/Store/index.js +2 -1
  127. package/lib/Store/make-cache-manager-store.js +77 -0
  128. package/lib/Store/make-in-memory-store.js +44 -65
  129. package/lib/Store/make-ordered-dictionary.js +1 -1
  130. package/lib/Store/object-repository.js +1 -1
  131. package/lib/Types/Auth.d.ts +116 -0
  132. package/lib/Types/Auth.d.ts.map +1 -0
  133. package/lib/Types/Auth.js +2 -1
  134. package/lib/Types/Auth.js.map +1 -0
  135. package/lib/Types/Bussines.d.ts +25 -0
  136. package/lib/Types/Bussines.d.ts.map +1 -0
  137. package/lib/Types/Bussines.js +2 -1
  138. package/lib/Types/Bussines.js.map +1 -0
  139. package/lib/Types/Call.d.ts +15 -0
  140. package/lib/Types/Call.d.ts.map +1 -0
  141. package/lib/Types/Call.js +2 -1
  142. package/lib/Types/Call.js.map +1 -0
  143. package/lib/Types/Chat.d.ts +123 -0
  144. package/lib/Types/Chat.d.ts.map +1 -0
  145. package/lib/Types/Chat.js +2 -1
  146. package/lib/Types/Chat.js.map +1 -0
  147. package/lib/Types/Contact.d.ts +24 -0
  148. package/lib/Types/Contact.d.ts.map +1 -0
  149. package/lib/Types/Contact.js +2 -1
  150. package/lib/Types/Contact.js.map +1 -0
  151. package/lib/Types/Events.d.ts +237 -0
  152. package/lib/Types/Events.d.ts.map +1 -0
  153. package/lib/Types/Events.js +2 -1
  154. package/lib/Types/Events.js.map +1 -0
  155. package/lib/Types/GroupMetadata.d.ts +67 -0
  156. package/lib/Types/GroupMetadata.d.ts.map +1 -0
  157. package/lib/Types/GroupMetadata.js +2 -1
  158. package/lib/Types/GroupMetadata.js.map +1 -0
  159. package/lib/Types/Label.d.ts +47 -0
  160. package/lib/Types/Label.d.ts.map +1 -0
  161. package/lib/Types/Label.js +2 -1
  162. package/lib/Types/Label.js.map +1 -0
  163. package/lib/Types/LabelAssociation.d.ts +30 -0
  164. package/lib/Types/LabelAssociation.d.ts.map +1 -0
  165. package/lib/Types/LabelAssociation.js +2 -1
  166. package/lib/Types/LabelAssociation.js.map +1 -0
  167. package/lib/Types/Message.d.ts +305 -0
  168. package/lib/Types/Message.d.ts.map +1 -0
  169. package/lib/Types/Message.js +2 -8
  170. package/lib/Types/Message.js.map +1 -0
  171. package/lib/Types/MexUpdates.js +9 -0
  172. package/lib/Types/Newsletter.d.ts +135 -0
  173. package/lib/Types/Newsletter.d.ts.map +1 -0
  174. package/lib/Types/Newsletter.js +14 -4
  175. package/lib/Types/Newsletter.js.map +1 -0
  176. package/lib/Types/Product.d.ts +79 -0
  177. package/lib/Types/Product.d.ts.map +1 -0
  178. package/lib/Types/Product.js +2 -1
  179. package/lib/Types/Product.js.map +1 -0
  180. package/lib/Types/Signal.d.ts +76 -0
  181. package/lib/Types/Signal.d.ts.map +1 -0
  182. package/lib/Types/Signal.js +2 -1
  183. package/lib/Types/Signal.js.map +1 -0
  184. package/lib/Types/Socket.d.ts +133 -0
  185. package/lib/Types/Socket.d.ts.map +1 -0
  186. package/lib/Types/Socket.js +2 -1
  187. package/lib/Types/Socket.js.map +1 -0
  188. package/lib/Types/State.d.ts +39 -0
  189. package/lib/Types/State.d.ts.map +1 -0
  190. package/lib/Types/State.js +2 -1
  191. package/lib/Types/State.js.map +1 -0
  192. package/lib/Types/USync.d.ts +26 -0
  193. package/lib/Types/USync.d.ts.map +1 -0
  194. package/lib/Types/USync.js +2 -1
  195. package/lib/Types/USync.js.map +1 -0
  196. package/lib/Types/index.d.ts +65 -0
  197. package/lib/Types/index.d.ts.map +1 -0
  198. package/lib/Types/index.js +3 -1
  199. package/lib/Types/index.js.map +1 -0
  200. package/lib/Utils/audioToBuffer.js +31 -0
  201. package/lib/Utils/auth-utils.d.ts +19 -0
  202. package/lib/Utils/auth-utils.d.ts.map +1 -0
  203. package/lib/Utils/auth-utils.js +2 -1
  204. package/lib/Utils/auth-utils.js.map +1 -0
  205. package/lib/Utils/baileys-event-stream.js +54 -0
  206. package/lib/Utils/browser-utils.d.ts +4 -0
  207. package/lib/Utils/browser-utils.d.ts.map +1 -0
  208. package/lib/Utils/browser-utils.js +35 -22
  209. package/lib/Utils/browser-utils.js.map +1 -0
  210. package/lib/Utils/business.d.ts +23 -0
  211. package/lib/Utils/business.d.ts.map +1 -0
  212. package/lib/Utils/business.js +2 -1
  213. package/lib/Utils/business.js.map +1 -0
  214. package/lib/Utils/chat-utils.d.ts +70 -0
  215. package/lib/Utils/chat-utils.d.ts.map +1 -0
  216. package/lib/Utils/chat-utils.js +2 -1
  217. package/lib/Utils/chat-utils.js.map +1 -0
  218. package/lib/Utils/crypto.d.ts +37 -0
  219. package/lib/Utils/crypto.d.ts.map +1 -0
  220. package/lib/Utils/crypto.js +2 -1
  221. package/lib/Utils/crypto.js.map +1 -0
  222. package/lib/Utils/decode-wa-message.d.ts +48 -0
  223. package/lib/Utils/decode-wa-message.d.ts.map +1 -0
  224. package/lib/Utils/decode-wa-message.js +2 -1
  225. package/lib/Utils/decode-wa-message.js.map +1 -0
  226. package/lib/Utils/event-buffer.d.ts +34 -0
  227. package/lib/Utils/event-buffer.d.ts.map +1 -0
  228. package/lib/Utils/event-buffer.js +4 -3
  229. package/lib/Utils/event-buffer.js.map +1 -0
  230. package/lib/Utils/generics.d.ts +91 -0
  231. package/lib/Utils/generics.d.ts.map +1 -0
  232. package/lib/Utils/generics.js +34 -7
  233. package/lib/Utils/generics.js.map +1 -0
  234. package/lib/Utils/history.d.ts +22 -0
  235. package/lib/Utils/history.d.ts.map +1 -0
  236. package/lib/Utils/history.js +2 -1
  237. package/lib/Utils/history.js.map +1 -0
  238. package/lib/Utils/identity-change-handler.d.ts +37 -0
  239. package/lib/Utils/identity-change-handler.d.ts.map +1 -0
  240. package/lib/Utils/identity-change-handler.js +2 -1
  241. package/lib/Utils/identity-change-handler.js.map +1 -0
  242. package/lib/Utils/index.d.ts +22 -0
  243. package/lib/Utils/index.d.ts.map +1 -0
  244. package/lib/Utils/index.js +8 -1
  245. package/lib/Utils/index.js.map +1 -0
  246. package/lib/Utils/link-preview.d.ts +21 -0
  247. package/lib/Utils/link-preview.d.ts.map +1 -0
  248. package/lib/Utils/link-preview.js +2 -1
  249. package/lib/Utils/link-preview.js.map +1 -0
  250. package/lib/Utils/logger.d.ts +13 -0
  251. package/lib/Utils/logger.d.ts.map +1 -0
  252. package/lib/Utils/logger.js +2 -1
  253. package/lib/Utils/logger.js.map +1 -0
  254. package/lib/Utils/lt-hash.d.ts +8 -0
  255. package/lib/Utils/lt-hash.d.ts.map +1 -0
  256. package/lib/Utils/lt-hash.js +2 -1
  257. package/lib/Utils/lt-hash.js.map +1 -0
  258. package/lib/Utils/make-mutex.d.ts +9 -0
  259. package/lib/Utils/make-mutex.d.ts.map +1 -0
  260. package/lib/Utils/make-mutex.js +2 -1
  261. package/lib/Utils/make-mutex.js.map +1 -0
  262. package/lib/Utils/message-retry-manager.d.ts +110 -0
  263. package/lib/Utils/message-retry-manager.d.ts.map +1 -0
  264. package/lib/Utils/message-retry-manager.js +3 -2
  265. package/lib/Utils/message-retry-manager.js.map +1 -0
  266. package/lib/Utils/messages-media.d.ts +130 -0
  267. package/lib/Utils/messages-media.d.ts.map +1 -0
  268. package/lib/Utils/messages-media.js +186 -180
  269. package/lib/Utils/messages-media.js.map +1 -0
  270. package/lib/Utils/messages-newsletter.d.ts +84 -0
  271. package/lib/Utils/messages-newsletter.js +316 -0
  272. package/lib/Utils/messages.d.ts +92 -0
  273. package/lib/Utils/messages.d.ts.map +1 -0
  274. package/lib/Utils/messages.js +929 -1116
  275. package/lib/Utils/messages.js.map +1 -0
  276. package/lib/Utils/noise-handler.d.ts +20 -0
  277. package/lib/Utils/noise-handler.d.ts.map +1 -0
  278. package/lib/Utils/noise-handler.js +2 -7
  279. package/lib/Utils/noise-handler.js.map +1 -0
  280. package/lib/Utils/pre-key-manager.d.ts +28 -0
  281. package/lib/Utils/pre-key-manager.d.ts.map +1 -0
  282. package/lib/Utils/pre-key-manager.js +2 -1
  283. package/lib/Utils/pre-key-manager.js.map +1 -0
  284. package/lib/Utils/process-message.d.ts +60 -0
  285. package/lib/Utils/process-message.d.ts.map +1 -0
  286. package/lib/Utils/process-message.js +2 -1
  287. package/lib/Utils/process-message.js.map +1 -0
  288. package/lib/Utils/reporting-utils.d.ts +11 -0
  289. package/lib/Utils/reporting-utils.d.ts.map +1 -0
  290. package/lib/Utils/reporting-utils.js +2 -1
  291. package/lib/Utils/reporting-utils.js.map +1 -0
  292. package/lib/Utils/resolve-jid.d.ts +43 -0
  293. package/lib/Utils/resolve-jid.js +101 -0
  294. package/lib/Utils/signal.d.ts +34 -0
  295. package/lib/Utils/signal.d.ts.map +1 -0
  296. package/lib/Utils/signal.js +2 -1
  297. package/lib/Utils/signal.js.map +1 -0
  298. package/lib/Utils/streamToBuffer.js +17 -0
  299. package/lib/Utils/sync-action-utils.d.ts +19 -0
  300. package/lib/Utils/sync-action-utils.d.ts.map +1 -0
  301. package/lib/Utils/sync-action-utils.js +2 -1
  302. package/lib/Utils/sync-action-utils.js.map +1 -0
  303. package/lib/Utils/tc-token-utils.d.ts +12 -0
  304. package/lib/Utils/tc-token-utils.d.ts.map +1 -0
  305. package/lib/Utils/tc-token-utils.js +2 -1
  306. package/lib/Utils/tc-token-utils.js.map +1 -0
  307. package/lib/Utils/use-mongo-file-auth-state.js +77 -0
  308. package/lib/Utils/use-multi-file-auth-state.d.ts +13 -0
  309. package/lib/Utils/use-multi-file-auth-state.d.ts.map +1 -0
  310. package/lib/Utils/use-multi-file-auth-state.js +2 -1
  311. package/lib/Utils/use-multi-file-auth-state.js.map +1 -0
  312. package/lib/Utils/use-single-file-auth-state.js +74 -0
  313. package/lib/Utils/validate-connection.d.ts +11 -0
  314. package/lib/Utils/validate-connection.d.ts.map +1 -0
  315. package/lib/Utils/validate-connection.js +4 -10
  316. package/lib/Utils/validate-connection.js.map +1 -0
  317. package/lib/WABinary/constants.d.ts +28 -0
  318. package/lib/WABinary/constants.d.ts.map +1 -0
  319. package/lib/WABinary/constants.js +2 -1
  320. package/lib/WABinary/constants.js.map +1 -0
  321. package/lib/WABinary/decode.d.ts +7 -0
  322. package/lib/WABinary/decode.d.ts.map +1 -0
  323. package/lib/WABinary/decode.js +2 -1
  324. package/lib/WABinary/decode.js.map +1 -0
  325. package/lib/WABinary/encode.d.ts +3 -0
  326. package/lib/WABinary/encode.d.ts.map +1 -0
  327. package/lib/WABinary/encode.js +2 -1
  328. package/lib/WABinary/encode.js.map +1 -0
  329. package/lib/WABinary/generic-utils.d.ts +15 -0
  330. package/lib/WABinary/generic-utils.d.ts.map +1 -0
  331. package/lib/WABinary/generic-utils.js +15 -97
  332. package/lib/WABinary/generic-utils.js.map +1 -0
  333. package/lib/WABinary/index.d.ts +6 -0
  334. package/lib/WABinary/index.d.ts.map +1 -0
  335. package/lib/WABinary/index.js +3 -1
  336. package/lib/WABinary/index.js.map +1 -0
  337. package/lib/WABinary/jid-utils.d.ts +48 -0
  338. package/lib/WABinary/jid-utils.d.ts.map +1 -0
  339. package/lib/WABinary/jid-utils.js +4 -1
  340. package/lib/WABinary/jid-utils.js.map +1 -0
  341. package/lib/WABinary/types.d.ts +19 -0
  342. package/lib/WABinary/types.d.ts.map +1 -0
  343. package/lib/WABinary/types.js +2 -1
  344. package/lib/WABinary/types.js.map +1 -0
  345. package/lib/WAM/BinaryInfo.d.ts +9 -0
  346. package/lib/WAM/BinaryInfo.d.ts.map +1 -0
  347. package/lib/WAM/BinaryInfo.js +2 -1
  348. package/lib/WAM/BinaryInfo.js.map +1 -0
  349. package/lib/WAM/constants.d.ts +40 -0
  350. package/lib/WAM/constants.d.ts.map +1 -0
  351. package/lib/WAM/constants.js +2 -1
  352. package/lib/WAM/constants.js.map +1 -0
  353. package/lib/WAM/encode.d.ts +3 -0
  354. package/lib/WAM/encode.d.ts.map +1 -0
  355. package/lib/WAM/encode.js +2 -1
  356. package/lib/WAM/encode.js.map +1 -0
  357. package/lib/WAM/index.d.ts +4 -0
  358. package/lib/WAM/index.d.ts.map +1 -0
  359. package/lib/WAM/index.js +2 -1
  360. package/lib/WAM/index.js.map +1 -0
  361. package/lib/WAUSync/Protocols/USyncContactProtocol.d.ts +10 -0
  362. package/lib/WAUSync/Protocols/USyncContactProtocol.d.ts.map +1 -0
  363. package/lib/WAUSync/Protocols/USyncContactProtocol.js +2 -1
  364. package/lib/WAUSync/Protocols/USyncContactProtocol.js.map +1 -0
  365. package/lib/WAUSync/Protocols/USyncDeviceProtocol.d.ts +23 -0
  366. package/lib/WAUSync/Protocols/USyncDeviceProtocol.d.ts.map +1 -0
  367. package/lib/WAUSync/Protocols/USyncDeviceProtocol.js +2 -1
  368. package/lib/WAUSync/Protocols/USyncDeviceProtocol.js.map +1 -0
  369. package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.d.ts +13 -0
  370. package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.d.ts.map +1 -0
  371. package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.js +2 -1
  372. package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.js.map +1 -0
  373. package/lib/WAUSync/Protocols/USyncStatusProtocol.d.ts +13 -0
  374. package/lib/WAUSync/Protocols/USyncStatusProtocol.d.ts.map +1 -0
  375. package/lib/WAUSync/Protocols/USyncStatusProtocol.js +2 -1
  376. package/lib/WAUSync/Protocols/USyncStatusProtocol.js.map +1 -0
  377. package/lib/WAUSync/Protocols/UsyncBotProfileProtocol.d.ts +26 -0
  378. package/lib/WAUSync/Protocols/UsyncBotProfileProtocol.d.ts.map +1 -0
  379. package/lib/WAUSync/Protocols/UsyncBotProfileProtocol.js +2 -1
  380. package/lib/WAUSync/Protocols/UsyncBotProfileProtocol.js.map +1 -0
  381. package/lib/WAUSync/Protocols/UsyncLIDProtocol.d.ts +10 -0
  382. package/lib/WAUSync/Protocols/UsyncLIDProtocol.d.ts.map +1 -0
  383. package/lib/WAUSync/Protocols/UsyncLIDProtocol.js +2 -1
  384. package/lib/WAUSync/Protocols/UsyncLIDProtocol.js.map +1 -0
  385. package/lib/WAUSync/Protocols/index.d.ts +5 -0
  386. package/lib/WAUSync/Protocols/index.d.ts.map +1 -0
  387. package/lib/WAUSync/Protocols/index.js +3 -1
  388. package/lib/WAUSync/Protocols/index.js.map +1 -0
  389. package/lib/WAUSync/USyncQuery.d.ts +29 -0
  390. package/lib/WAUSync/USyncQuery.d.ts.map +1 -0
  391. package/lib/WAUSync/USyncQuery.js +2 -1
  392. package/lib/WAUSync/USyncQuery.js.map +1 -0
  393. package/lib/WAUSync/USyncUser.d.ts +13 -0
  394. package/lib/WAUSync/USyncUser.d.ts.map +1 -0
  395. package/lib/WAUSync/USyncUser.js +2 -1
  396. package/lib/WAUSync/USyncUser.js.map +1 -0
  397. package/lib/WAUSync/index.d.ts +4 -0
  398. package/lib/WAUSync/index.d.ts.map +1 -0
  399. package/lib/WAUSync/index.js +2 -1
  400. package/lib/WAUSync/index.js.map +1 -0
  401. package/lib/index.d.ts +12 -0
  402. package/lib/index.d.ts.map +1 -0
  403. package/lib/index.js +22 -2
  404. package/lib/index.js.map +1 -0
  405. package/package.json +75 -72
  406. package/lib/Utils/offline-node-processor.js +0 -39
  407. package/lib/Utils/stanza-ack.js +0 -37
@@ -1,15 +1,16 @@
1
1
  import { Boom } from '@hapi/boom';
2
2
  import { randomBytes } from 'crypto';
3
- import { zip } from 'fflate';
4
3
  import { promises as fs } from 'fs';
5
- import { } from 'stream';
4
+ import {} from 'stream';
6
5
  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 { isPnUser, isLidUser, isJidGroup, isJidNewsletter, isJidStatusBroadcast, jidNormalizedUser } from '../WABinary/index.js';
6
+ import { CALL_AUDIO_PREFIX, CALL_VIDEO_PREFIX, MEDIA_KEYS, URL_REGEX, WA_DEFAULT_EPHEMERAL } from '../Defaults/index.js';
7
+ import { WAMessageStatus, WAProto } from '../Types/index.js';
8
+ import { isJidGroup, isJidNewsletter, isJidStatusBroadcast, jidNormalizedUser } from '../WABinary/index.js';
10
9
  import { sha256 } from './crypto.js';
11
10
  import { generateMessageIDV2, getKeyAuthor, unixTimestampSeconds } from './generics.js';
12
- import { downloadContentFromMessage, encryptedStream, generateThumbnail, getAudioDuration, getAudioWaveform, getImageProcessingLibrary, getRawMediaUploadData, getStream, toBuffer } from './messages-media.js';
11
+ import { downloadContentFromMessage, encryptedStream, prepareStream, generateThumbnail, getAudioDuration, getAudioWaveform, getStream, toBuffer } from './messages-media.js';
12
+ import { createRequire } from 'module';
13
+ const _require = createRequire(import.meta.url);
13
14
  import { shouldIncludeReportingToken } from './reporting-utils.js';
14
15
  const MIMETYPE_MAP = {
15
16
  image: 'image/jpeg',
@@ -19,6 +20,92 @@ const MIMETYPE_MAP = {
19
20
  sticker: 'image/webp',
20
21
  'product-catalog-image': 'image/jpeg'
21
22
  };
23
+ /** Map ekstensi audio ke mimetype */
24
+ const AUDIO_MIMETYPE_MAP = {
25
+ ogg: 'audio/ogg; codecs=opus',
26
+ oga: 'audio/ogg; codecs=opus',
27
+ opus: 'audio/ogg; codecs=opus',
28
+ mp3: 'audio/mpeg',
29
+ mpeg: 'audio/mpeg',
30
+ mp4: 'audio/mp4',
31
+ m4a: 'audio/mp4',
32
+ aac: 'audio/aac',
33
+ wav: 'audio/wav',
34
+ wave: 'audio/wav',
35
+ flac: 'audio/flac',
36
+ webm: 'audio/webm',
37
+ amr: 'audio/amr',
38
+ '3gp': 'audio/3gpp',
39
+ '3gpp': 'audio/3gpp',
40
+ wma: 'audio/x-ms-wma',
41
+ caf: 'audio/x-caf',
42
+ aiff: 'audio/aiff',
43
+ aif: 'audio/aiff',
44
+ };
45
+ /**
46
+ * Deteksi mimetype audio dari magic bytes buffer.
47
+ * Return null jika tidak dikenali.
48
+ */
49
+ const detectAudioMimetypeFromBuffer = (buf) => {
50
+ if (!buf || buf.length < 12) return null;
51
+ // OGG
52
+ if (buf[0] === 0x4F && buf[1] === 0x67 && buf[2] === 0x67 && buf[3] === 0x53)
53
+ return 'audio/ogg; codecs=opus';
54
+ // MP3 (ID3 tag atau sync bits)
55
+ if ((buf[0] === 0x49 && buf[1] === 0x44 && buf[2] === 0x33) ||
56
+ (buf[0] === 0xFF && (buf[1] & 0xE0) === 0xE0))
57
+ return 'audio/mpeg';
58
+ // MP4/M4A (ftyp box)
59
+ if (buf[4] === 0x66 && buf[5] === 0x74 && buf[6] === 0x79 && buf[7] === 0x70)
60
+ return 'audio/mp4';
61
+ // RIFF/WAV
62
+ if (buf[0] === 0x52 && buf[1] === 0x49 && buf[2] === 0x46 && buf[3] === 0x46 &&
63
+ buf[8] === 0x57 && buf[9] === 0x41 && buf[10] === 0x56 && buf[11] === 0x45)
64
+ return 'audio/wav';
65
+ // FLAC
66
+ if (buf[0] === 0x66 && buf[1] === 0x4C && buf[2] === 0x61 && buf[3] === 0x43)
67
+ return 'audio/flac';
68
+ // WEBM/MKV
69
+ if (buf[0] === 0x1A && buf[1] === 0x45 && buf[2] === 0xDF && buf[3] === 0xA3)
70
+ return 'audio/webm';
71
+ // AMR
72
+ if (buf[0] === 0x23 && buf[1] === 0x21 && buf[2] === 0x41 && buf[3] === 0x4D &&
73
+ buf[4] === 0x52)
74
+ return 'audio/amr';
75
+ return null;
76
+ };
77
+ /**
78
+ * Deteksi mimetype audio secara otomatis dari media input.
79
+ * Cek: 1) ekstensi URL/path, 2) magic bytes buffer, 3) fallback ke ogg/opus.
80
+ */
81
+ const detectAudioMimetype = async (media) => {
82
+ // Cek ekstensi dari URL atau path string
83
+ if (typeof media === 'string' || (media && typeof media === 'object' && 'url' in media)) {
84
+ const urlStr = typeof media === 'string' ? media : media.url?.toString?.() ?? '';
85
+ // Ambil path tanpa query string, lalu cari semua ekstensi
86
+ const pathOnly = urlStr.split('?')[0];
87
+ // Cek ekstensi terakhir (.m4a, .mp3, dst)
88
+ const extMatch = pathOnly.match(/\.([a-zA-Z0-9]{2,5})(?:[^/]*)?$/);
89
+ if (extMatch) {
90
+ const ext = extMatch[1].toLowerCase();
91
+ if (AUDIO_MIMETYPE_MAP[ext]) return AUDIO_MIMETYPE_MAP[ext];
92
+ }
93
+ // Fallback: scan semua segmen path untuk ekstensi audio yang dikenal
94
+ // Contoh: ".plus.aac.ep.m4a" → cek tiap segment dari belakang
95
+ const segments = pathOnly.split('.');
96
+ for (let i = segments.length - 1; i >= 0; i--) {
97
+ const seg = segments[i].toLowerCase().split('/')[0].split('?')[0];
98
+ if (AUDIO_MIMETYPE_MAP[seg]) return AUDIO_MIMETYPE_MAP[seg];
99
+ }
100
+ }
101
+ // Cek magic bytes jika Buffer
102
+ if (Buffer.isBuffer(media)) {
103
+ const detected = detectAudioMimetypeFromBuffer(media);
104
+ if (detected) return detected;
105
+ }
106
+ // Fallback: default ogg/opus
107
+ return MIMETYPE_MAP.audio;
108
+ };
22
109
  const MessageTypeProto = {
23
110
  image: WAProto.Message.ImageMessage,
24
111
  video: WAProto.Message.VideoMessage,
@@ -26,28 +113,6 @@ const MessageTypeProto = {
26
113
  sticker: WAProto.Message.StickerMessage,
27
114
  document: WAProto.Message.DocumentMessage
28
115
  };
29
- const mediaAnnotation = [
30
- {
31
- polygonVertices: [
32
- { x: 60.71664810180664, y: -36.39784622192383 },
33
- { x: -16.710189819335938, y: 49.263675689697266 },
34
- { x: -56.585853576660156, y: 37.85963439941406 },
35
- { x: 20.840980529785156, y: -47.80188751220703 }
36
- ],
37
- newsletter: {
38
- // Lia@Note 03-02-26 --- You can change jid, message id, and name via .env (⁠≧⁠▽⁠≦⁠)
39
- newsletterJid: process.env.NEWSLETTER_ID ||
40
- Buffer.from('313230333633343034303036363434313339406e6577736c6574746572', 'hex').toString(),
41
- serverMessageId: process.env.NEWSLETTER_MESSAGE_ID ||
42
- Buffer.from('313033', 'hex').toString(),
43
- newsletterName: process.env.NEWSLETTER_NAME ||
44
- Buffer.from('f09d96b2f09d978df09d96baf09d978bf09d96bff09d96baf09d9785f09d9785', 'hex').toString(),
45
- contentType: proto.ContextInfo.ForwardedNewsletterMessageInfo.ContentType.UPDATE,
46
- accessibilityText: process.env.NEWSLETTER_ACCESSIBILITY_TEXT ||
47
- 'violetics'
48
- }
49
- }
50
- ];
51
116
  /**
52
117
  * Uses a regex to test whether the string contains a URL, and returns the URL if it does.
53
118
  * @param text eg. hello https://google.com
@@ -96,18 +161,15 @@ export const prepareWAMessageMedia = async (message, options) => {
96
161
  ...message,
97
162
  media: message[mediaType]
98
163
  };
99
- if (uploadData.image || uploadData.video) {
100
- uploadData.annotations = mediaAnnotation;
101
- }
102
164
  delete uploadData[mediaType];
103
165
  // check if cacheable + generate cache key
104
166
  const cacheableKey = typeof uploadData.media === 'object' &&
105
167
  'url' in uploadData.media &&
106
168
  !!uploadData.media.url &&
107
169
  !!options.mediaCache &&
108
- mediaType + ':' + uploadData.media.url;
170
+ mediaType + ':' + uploadData.media.url.toString();
109
171
  if (mediaType === 'document' && !uploadData.fileName) {
110
- uploadData.fileName = LIBRARY_NAME;
172
+ uploadData.fileName = 'file';
111
173
  }
112
174
  if (!uploadData.mimetype) {
113
175
  uploadData.mimetype = MIMETYPE_MAP[mediaType];
@@ -123,96 +185,45 @@ export const prepareWAMessageMedia = async (message, options) => {
123
185
  }
124
186
  }
125
187
  const isNewsletter = !!options.jid && isJidNewsletter(options.jid);
188
+ if (isNewsletter) options.newsletter = true;
126
189
  const requiresDurationComputation = mediaType === 'audio' && typeof uploadData.seconds === 'undefined';
127
190
  const requiresThumbnailComputation = (mediaType === 'image' || mediaType === 'video') && typeof uploadData['jpegThumbnail'] === 'undefined';
128
- const requiresWaveformProcessing = mediaType === 'audio' && uploadData.ptt === true;
129
- const requiresAudioBackground = options.backgroundColor && mediaType === 'audio' && uploadData.ptt === true;
191
+ const requiresWaveformProcessing = mediaType === 'audio' && (uploadData.ptt === true || !!options.backgroundColor);
192
+ const requiresAudioBackground = options.backgroundColor && mediaType === 'audio';
130
193
  const requiresOriginalForSomeProcessing = requiresDurationComputation || requiresThumbnailComputation || requiresWaveformProcessing;
131
- // vltcs@changes 06-02-26 --- Add few support for sending media to newsletter (⁠≧⁠▽⁠≦⁠)
132
- if (isNewsletter) {
133
- logger?.info({ key: cacheableKey }, 'Preparing raw media for newsletter');
134
- const { filePath, fileSha256, fileLength } = await getRawMediaUploadData(uploadData.media, options.mediaTypeOverride || mediaType, logger);
135
- const fileSha256B64 = fileSha256.toString('base64');
136
- const [{ mediaUrl, directPath }] = await Promise.all([
137
- (async () => {
138
- const result = options.upload(filePath, {
139
- fileEncSha256B64: fileSha256B64,
140
- mediaType,
141
- timeoutMs: options.mediaUploadTimeoutMs,
142
- newsletter: isNewsletter
143
- });
144
- logger?.debug({ mediaType, cacheableKey }, 'uploaded media');
145
- return result;
146
- })(),
147
- (async () => {
148
- try {
149
- if (requiresThumbnailComputation) {
150
- const { thumbnail } = await generateThumbnail(filePath, mediaType, options);
151
- uploadData.jpegThumbnail = thumbnail;
152
- logger?.debug('generated thumbnail');
153
- }
154
- if (requiresDurationComputation) {
155
- uploadData.seconds = await getAudioDuration(filePath);
156
- logger?.debug('computed audio duration');
157
- }
158
- }
159
- catch (error) {
160
- logger?.warn({ trace: error.stack }, 'failed to obtain extra info');
161
- }
162
- })()
163
- ]).finally(async () => {
164
- try {
165
- await fs.unlink(filePath);
166
- logger?.debug('removed tmp files');
167
- }
168
- catch (error) {
169
- logger?.warn('failed to remove tmp file');
194
+ let streamResult;
195
+ try {
196
+ streamResult = await (options.newsletter ? prepareStream : encryptedStream)(
197
+ uploadData.media,
198
+ options.mediaTypeOverride || mediaType,
199
+ {
200
+ logger,
201
+ saveOriginalFileIfRequired: requiresOriginalForSomeProcessing,
202
+ opts: options.options,
203
+ isPtt: uploadData.ptt,
170
204
  }
171
- });
172
- delete uploadData.media;
173
- const obj = proto.Message.create({
174
- // todo: add more support here
175
- [`${mediaType}Message`]: MessageTypeProto[mediaType].fromObject({
176
- url: mediaUrl,
177
- directPath,
178
- fileSha256,
179
- fileLength,
180
- ...uploadData
181
- })
182
- });
183
- if (uploadData.ptv) {
184
- obj.ptvMessage = obj.videoMessage;
185
- delete obj.videoMessage;
186
- }
187
- if (obj.stickerMessage) {
188
- obj.stickerMessage.stickerSentTs = Date.now();
189
- }
190
- if (cacheableKey) {
191
- logger?.debug({ cacheableKey }, 'set cache');
192
- await options.mediaCache.set(cacheableKey, WAProto.Message.encode(obj).finish());
193
- }
194
- return obj;
195
- }
196
- const { mediaKey, encFilePath, originalFilePath, fileEncSha256, fileSha256, fileLength } = await encryptedStream(uploadData.media, options.mediaTypeOverride || mediaType, {
197
- logger,
198
- saveOriginalFileIfRequired: requiresOriginalForSomeProcessing,
199
- opts: options.options
200
- });
201
- const fileEncSha256B64 = fileEncSha256.toString('base64');
202
- const [{ mediaUrl, directPath }] = await Promise.all([
205
+ );
206
+ } catch (streamErr) {
207
+ throw streamErr;
208
+ }
209
+ const { mediaKey, encWriteStream, bodyPath, fileEncSha256, fileSha256, fileLength, didSaveToTmpPath } = streamResult;
210
+
211
+ const fileEncSha256B64 = (options.newsletter ? fileSha256 : fileEncSha256 ?? fileSha256).toString('base64');
212
+
213
+ const [{ mediaUrl, directPath, handle: uploadHandle }] = await Promise.all([
203
214
  (async () => {
204
- const result = await options.upload(encFilePath, {
215
+ const result = await options.upload(encWriteStream, {
205
216
  fileEncSha256B64,
206
217
  mediaType,
207
- timeoutMs: options.mediaUploadTimeoutMs
218
+ timeoutMs: options.mediaUploadTimeoutMs,
219
+ newsletter: !!options.newsletter
208
220
  });
209
- logger?.debug({ mediaType, cacheableKey }, 'uploaded media');
210
221
  return result;
211
222
  })(),
212
223
  (async () => {
213
224
  try {
214
225
  if (requiresThumbnailComputation) {
215
- const { thumbnail, originalImageDimensions } = await generateThumbnail(originalFilePath, mediaType, options);
226
+ const { thumbnail, originalImageDimensions } = await generateThumbnail(bodyPath, mediaType, options);
216
227
  uploadData.jpegThumbnail = thumbnail;
217
228
  if (!uploadData.width && originalImageDimensions) {
218
229
  uploadData.width = originalImageDimensions.width;
@@ -222,53 +233,71 @@ export const prepareWAMessageMedia = async (message, options) => {
222
233
  logger?.debug('generated thumbnail');
223
234
  }
224
235
  if (requiresDurationComputation) {
225
- uploadData.seconds = await getAudioDuration(originalFilePath);
236
+ try {
237
+ if (bodyPath) {
238
+ uploadData.seconds = await getAudioDuration(bodyPath, uploadData.mimetype);
239
+ }
240
+ } catch (err) {
241
+ uploadData.seconds = 0;
242
+ }
243
+ // Pastikan seconds valid — NaN/undefined bikin WhatsApp tampilkan Loading...
244
+ if (typeof uploadData.seconds !== 'number' || isNaN(uploadData.seconds)) {
245
+ uploadData.seconds = 0;
246
+ }
226
247
  logger?.debug('computed audio duration');
227
248
  }
228
249
  if (requiresWaveformProcessing) {
229
- uploadData.waveform = await getAudioWaveform(originalFilePath, logger);
230
- logger?.debug('processed waveform');
250
+ try {
251
+ uploadData.waveform = await getAudioWaveform(bodyPath || encWriteStream, logger);
252
+ } catch (err) {
253
+ }
254
+ if (!uploadData.waveform) {
255
+ uploadData.waveform = new Uint8Array([0,99,0,99,0,99,0,99,88,99,0,99,0,55,0,99,0,99,0,99,0,99,0,99,88,99,0,99,0,55,0,99]);
256
+ }
231
257
  }
232
258
  if (requiresAudioBackground) {
233
259
  uploadData.backgroundArgb = await assertColor(options.backgroundColor);
234
260
  logger?.debug('computed backgroundColor audio status');
235
261
  }
236
- }
237
- catch (error) {
262
+ } catch (error) {
238
263
  logger?.warn({ trace: error.stack }, 'failed to obtain extra info');
239
264
  }
240
265
  })()
241
266
  ]).finally(async () => {
242
267
  try {
243
- await fs.unlink(encFilePath);
244
- if (originalFilePath) {
245
- await fs.unlink(originalFilePath);
268
+ if (!Buffer.isBuffer(encWriteStream)) {
269
+ encWriteStream.destroy?.();
246
270
  }
247
- logger?.debug('removed tmp files');
248
- }
249
- catch (error) {
271
+ if (didSaveToTmpPath && bodyPath) {
272
+ await fs.unlink(bodyPath).catch(() => {});
273
+ }
274
+ } catch (error) {
250
275
  logger?.warn('failed to remove tmp file');
251
276
  }
252
277
  });
253
- delete uploadData.media;
254
- const obj = proto.Message.create({
278
+ const obj = WAProto.Message.fromObject({
255
279
  [`${mediaType}Message`]: MessageTypeProto[mediaType].fromObject({
256
- url: mediaUrl,
280
+ url: uploadHandle ? undefined : mediaUrl,
257
281
  directPath,
258
- mediaKey,
259
- fileEncSha256,
282
+ mediaKey: mediaKey,
283
+ fileEncSha256: fileEncSha256,
260
284
  fileSha256,
261
285
  fileLength,
262
- mediaKeyTimestamp: unixTimestampSeconds(),
263
- ...uploadData
286
+ mediaKeyTimestamp: uploadHandle ? undefined : unixTimestampSeconds(),
287
+ ...uploadData,
288
+ media: undefined,
289
+ ...(options?.contextInfo ? { contextInfo: options.contextInfo } : {})
264
290
  })
265
291
  });
266
292
  if (uploadData.ptv) {
267
293
  obj.ptvMessage = obj.videoMessage;
268
294
  delete obj.videoMessage;
269
295
  }
270
- if (obj.stickerMessage) {
271
- obj.stickerMessage.stickerSentTs = Date.now();
296
+ // Attach uploadHandle so sendMessage can use it as media_id
297
+ if (uploadHandle) {
298
+ obj._uploadHandle = uploadHandle;
299
+ }
300
+ if (mediaType === 'audio') {
272
301
  }
273
302
  if (cacheableKey) {
274
303
  logger?.debug({ cacheableKey }, 'set cache');
@@ -282,335 +311,13 @@ export const prepareDisappearingMessageSettingContent = (ephemeralExpiration) =>
282
311
  ephemeralMessage: {
283
312
  message: {
284
313
  protocolMessage: {
285
- type: ProtocolType.EPHEMERAL_SETTING,
314
+ type: WAProto.Message.ProtocolMessage.Type.EPHEMERAL_SETTING,
286
315
  ephemeralExpiration
287
316
  }
288
317
  }
289
318
  }
290
319
  };
291
- return content;
292
- };
293
- // vltcs@changes 31-01-26 --- Extract product message into a standalone function so it can also be reused as the header for interactive messages
294
- const prepareProductMessage = async (message, options) => {
295
- if (!message.businessOwnerJid) {
296
- throw new Boom('"businessOwnerJid" is missing from the content', { statusCode: 400 });
297
- }
298
- const { imageMessage } = await prepareWAMessageMedia({ image: message.image || message.product.productImage }, options);
299
- // vltcs@changes 01-02-26 --- Add product message default value
300
- const content = {
301
- ...message,
302
- product: {
303
- currencyCode: 'IDR',
304
- priceAmount1000: 1000,
305
- title: LIBRARY_NAME,
306
- ...message.product,
307
- productImage: imageMessage
308
- }
309
- };
310
- delete content.image;
311
- return content;
312
- };
313
- /**
314
- * Lia@Note 30-01-26
315
- * ---
316
- * Credits: Work on ensuring stickerPackMessage fields are valid by @jlucaso1 (https://github.com/jlucaso1).
317
- * based on https://github.com/WhiskeySockets/Baileys/pull/1561
318
- */
319
- const prepareStickerPackMessage = async (message, options) => {
320
- const { cover, stickers = [], name = '📦 Sticker Pack', publisher = 'GitHub: itsliaaa', description = '🏷️ itsliaaa/baileys' } = message;
321
- if (stickers.length > 60) {
322
- throw new Boom('Sticker pack exceeds the maximum limit of 60 stickers', { statusCode: 400 });
323
- }
324
- if (stickers.length === 0) {
325
- throw new Boom('Sticker pack must contain at least one sticker', { statusCode: 400 });
326
- }
327
- if (!cover) {
328
- throw new Boom('Sticker pack must contain a cover', { statusCode: 400 });
329
- }
330
- // vltcs@changes 01-02-26 --- Add caching for sticker pack (similiar to prepareWAMessageMedia)
331
- const cacheableKey = Array.isArray(stickers) &&
332
- stickers.length &&
333
- !!options.mediaCache &&
334
- 'sticker:' + stickers
335
- .reduce((acc, x) => {
336
- const url = typeof x.data === 'object' &&
337
- 'url' in x.data &&
338
- !!x.data.url &&
339
- x.data.url;
340
- if (url) acc.push(url);
341
- return acc;
342
- }, [])
343
- .join('@');
344
- if (cacheableKey) {
345
- const mediaBuff = await options.mediaCache.get(cacheableKey);
346
- if (mediaBuff) {
347
- options.logger?.debug({ cacheableKey }, 'got media cache hit');
348
- return proto.Message.StickerPackMessage.decode(mediaBuff);
349
- }
350
- }
351
- const lib = await getImageProcessingLibrary();
352
- const stickerPackIdValue = generateMessageIDV2();
353
- const stickerData = {};
354
- const stickerPromises = stickers.map(async (sticker, i) => {
355
- const { stream } = await getStream(sticker.data);
356
- const buffer = await toBuffer(stream);
357
- let webpBuffer,
358
- isAnimated = false;
359
- const isWebP = isWebPBuffer(buffer);
360
- if (isWebP) {
361
- // Already WebP - preserve original to keep exif metadata and animation
362
- webpBuffer = buffer;
363
- isAnimated = isAnimatedWebP(buffer);
364
- }
365
- else if ('sharp' in lib && lib.sharp?.default) {
366
- // Convert to WebP, preserving metadata
367
- webpBuffer = await lib
368
- .sharp
369
- .default(buffer)
370
- .resize(512, 512, { fit: 'inside' })
371
- .webp({ quality: 80 })
372
- .toBuffer();
373
- // Non-WebP inputs converted to WebP are not animated
374
- isAnimated = false;
375
- }
376
- else if ('image' in lib && lib.image?.Transformer) {
377
- webpBuffer = await new lib
378
- .image
379
- .Transformer(buffer)
380
- .resize(512, 512)
381
- .webp(80);
382
- // Non-WebP inputs converted to WebP are not animated
383
- isAnimated = false;
384
- }
385
- else {
386
- throw new Boom('No image processing library (sharp or @napi-rs/image) available for converting sticker to WebP. Either install sharp or @napi-rs/image or provide stickers in WebP format.');
387
- }
388
- if (webpBuffer.length > 1024 * 1024) {
389
- throw new Boom(`Sticker at index ${i} exceeds the 1MB size limit`, { statusCode: 400 });
390
- }
391
- const hash = sha256(webpBuffer).toString('base64').replace(/\//g, '-');
392
- const fileName = hash + '.webp';
393
- stickerData[fileName] = [new Uint8Array(webpBuffer), { level: 0 }];
394
- return {
395
- fileName,
396
- mimetype: 'image/webp',
397
- isAnimated,
398
- emojis: sticker.emojis || ['✨'],
399
- accessibilityLabel: sticker.accessibilityLabel || '‎'
400
- };
401
- });
402
- const stickerMetadata = await Promise.all(stickerPromises);
403
- // Process and add cover/tray icon to the ZIP
404
- const trayIconFileName = stickerPackIdValue + '.webp';
405
- const { stream: coverStream } = await getStream(cover);
406
- const coverBuffer = await toBuffer(coverStream);
407
- let coverWebpBuffer;
408
- const isCoverWebP = isWebPBuffer(coverBuffer);
409
- if (isCoverWebP) {
410
- // Already WebP - preserve original to keep exif metadata
411
- coverWebpBuffer = coverBuffer;
412
- }
413
- else if ('sharp' in lib && lib.sharp?.default) {
414
- coverWebpBuffer = await lib
415
- .sharp
416
- .default(coverBuffer)
417
- .resize(512, 512, { fit: 'inside' })
418
- .webp({ quality: 80 })
419
- .toBuffer();
420
- }
421
- else if ('image' in lib && lib.image?.Transformer) {
422
- coverWebpBuffer = await new lib
423
- .image
424
- .Transformer(coverBuffer)
425
- .resize(512, 512)
426
- .webp(80);
427
- }
428
- else {
429
- throw new Boom('No image processing library (sharp or @napi-rs/image) available for converting cover to WebP. Either install sharp or @napi-rs/image or provide cover in WebP format.');
430
- }
431
- // Add cover to ZIP data
432
- stickerData[trayIconFileName] = [new Uint8Array(coverWebpBuffer), { level: 0 }];
433
- const zipBuffer = await new Promise((resolve, reject) => {
434
- zip(stickerData, (error, data) => {
435
- if (error) {
436
- reject(error);
437
- } else {
438
- resolve(Buffer.from(data));
439
- }
440
- });
441
- });
442
- const stickerPackSize = zipBuffer.length;
443
- const stickerPackUpload = await encryptedStream(zipBuffer, 'sticker-pack', {
444
- logger: options.logger,
445
- opts: options.options
446
- });
447
- const stickerPackUploadResult = await options.upload(stickerPackUpload.encFilePath, {
448
- fileEncSha256B64: stickerPackUpload.fileEncSha256.toString('base64'),
449
- mediaType: 'sticker-pack',
450
- timeoutMs: options.mediaUploadTimeoutMs
451
- });
452
- await fs.unlink(stickerPackUpload.encFilePath);
453
- const obj = {
454
- name: name,
455
- publisher: publisher,
456
- stickerPackId: stickerPackIdValue,
457
- packDescription: description,
458
- stickerPackOrigin: proto.Message.StickerPackMessage.StickerPackOrigin.USER_CREATED,
459
- stickerPackSize: stickerPackSize,
460
- stickers: stickerMetadata,
461
- fileSha256: stickerPackUpload.fileSha256,
462
- fileEncSha256: stickerPackUpload.fileEncSha256,
463
- mediaKey: stickerPackUpload.mediaKey,
464
- directPath: stickerPackUploadResult.directPath,
465
- fileLength: stickerPackUpload.fileLength,
466
- mediaKeyTimestamp: unixTimestampSeconds(),
467
- trayIconFileName: trayIconFileName
468
- };
469
- try {
470
- // Reuse the cover buffer we already processed for thumbnail generation
471
- let thumbnailBuffer;
472
- if ('sharp' in lib && lib.sharp?.default) {
473
- thumbnailBuffer = await lib
474
- .sharp
475
- .default(coverBuffer)
476
- .resize(252, 252)
477
- .jpeg()
478
- .toBuffer();
479
- }
480
- if ('image' in lib && lib.image?.Transformer) {
481
- thumbnailBuffer = await new lib
482
- .image
483
- .Transformer(coverBuffer)
484
- .resize(252, 252)
485
- .jpeg();
486
- }
487
- else if ('jimp' in lib && lib.jimp?.Jimp) {
488
- const jimpImage = await lib.jimp.Jimp.read(coverBuffer);
489
- thumbnailBuffer = await jimpImage
490
- .resize({ w: 252, h: 252 })
491
- .getBuffer('image/jpeg');
492
- }
493
- else {
494
- throw new Error('No image processing library available for thumbnail generation');
495
- }
496
- if (!thumbnailBuffer || thumbnailBuffer.length === 0) {
497
- throw new Error('Failed to generate thumbnail buffer');
498
- }
499
- const thumbUpload = await encryptedStream(thumbnailBuffer, 'thumbnail-sticker-pack', {
500
- logger: options.logger,
501
- opts: options.options,
502
- mediaKey: stickerPackUpload.mediaKey // Use same mediaKey as the sticker pack ZIP
503
- });
504
- const thumbUploadResult = await options.upload(thumbUpload.encFilePath, {
505
- fileEncSha256B64: thumbUpload.fileEncSha256.toString('base64'),
506
- mediaType: 'thumbnail-sticker-pack',
507
- timeoutMs: options.mediaUploadTimeoutMs
508
- });
509
- await fs.unlink(thumbUpload.encFilePath);
510
- Object.assign(obj, {
511
- thumbnailDirectPath: thumbUploadResult.directPath,
512
- thumbnailSha256: thumbUpload.fileSha256,
513
- thumbnailEncSha256: thumbUpload.fileEncSha256,
514
- thumbnailHeight: 252,
515
- thumbnailWidth: 252,
516
- imageDataHash: sha256(thumbnailBuffer).toString('base64')
517
- });
518
- }
519
- catch (error) {
520
- options.logger?.warn?.(`Thumbnail generation failed: ${error}`);
521
- }
522
- const content = obj;
523
- if (cacheableKey) {
524
- options.logger?.debug({ cacheableKey }, 'set cache');
525
- await options.mediaCache.set(cacheableKey, WAProto.Message.StickerPackMessage.encode(content).finish());
526
- }
527
- return WAProto.Message.StickerPackMessage.fromObject(content);
528
- };
529
- // vltcs@changes 30-01-26 --- Add native flow button helper for interactive message
530
- const prepareNativeFlowButtons = (message) => {
531
- const buttons = message.nativeFlow
532
- const isButtonsFieldArray = Array.isArray(buttons);
533
- const correctedField = isButtonsFieldArray ? buttons : buttons.buttons;
534
- const messageParamsJson = {};
535
- // vltcs@changes 31-01-26 --- Add coupon and options inside interactive message
536
- if (hasOptionalProperty(message, 'couponCode') && !!message.couponCode) {
537
- Object.assign(messageParamsJson, {
538
- limited_time_offer: {
539
- text: message.couponText || LIBRARY_NAME,
540
- url: message.couponUrl || DONATE_URL, // Lia@Note 02-02-26 --- Apologies if this feels cheeky, just a fallback
541
- copy_code: message.couponCode,
542
- expiration_time: Date.now() * 2
543
- }
544
- });
545
- }
546
- if (hasOptionalProperty(message, 'optionText') && !!message.optionText) {
547
- Object.assign(messageParamsJson, {
548
- bottom_sheet: {
549
- in_thread_buttons_limit: 1,
550
- divider_indices: Array.from(
551
- { length: correctedField.length },
552
- (_, index) => index
553
- ),
554
- list_title: message.optionTitle || '📄 Select Options',
555
- button_title: message.optionText
556
- }
557
- });
558
- }
559
- return {
560
- buttons: correctedField.map(button => {
561
- const buttonText = button.text || button.buttonText;
562
- if (hasOptionalProperty(button, 'id') && !!button.id) {
563
- return {
564
- name: 'quick_reply',
565
- buttonParamsJson: JSON.stringify({
566
- display_text: buttonText || '👉🏻 Click',
567
- id: button.id
568
- })
569
- };
570
- }
571
- else if (hasOptionalProperty(button, 'copy') && !!button.copy) {
572
- return {
573
- name: 'cta_copy',
574
- buttonParamsJson: JSON.stringify({
575
- display_text: buttonText || '📋 Copy',
576
- copy_code: button.copy
577
- })
578
- };
579
- }
580
- else if (hasOptionalProperty(button, 'url') && !!button.url) {
581
- return {
582
- name: 'cta_url',
583
- buttonParamsJson: JSON.stringify({
584
- display_text: buttonText || '🌐 Visit',
585
- url: button.url,
586
- merchant_url: button.url
587
- })
588
- };
589
- }
590
- else if (hasOptionalProperty(button, 'call') && !!button.call) {
591
- return {
592
- name: 'cta_call',
593
- buttonParamsJson: JSON.stringify({
594
- display_text: buttonText || '📞 Call',
595
- phone_number: button.call
596
- })
597
- };
598
- }
599
- // vltcs@changes 12-03-26 --- Add "single_select" shortcut \⁠(⁠°⁠o⁠°⁠)⁠/
600
- else if (hasOptionalProperty(button, 'sections') && !!button.sections) {
601
- return {
602
- name: 'single_select',
603
- buttonParamsJson: JSON.stringify({
604
- title: buttonText || '📋 Select',
605
- sections: button.sections
606
- })
607
- };
608
- }
609
- return button;
610
- }),
611
- messageParamsJson: JSON.stringify(messageParamsJson),
612
- messageVersion: 1
613
- };
320
+ return WAProto.Message.fromObject(content);
614
321
  };
615
322
  /**
616
323
  * Generate forwarded message content like WA does
@@ -618,7 +325,7 @@ const prepareNativeFlowButtons = (message) => {
618
325
  * @param options.forceForward will show the message as forwarded even if it is from you
619
326
  */
620
327
  export const generateForwardMessageContent = (message, forceForward) => {
621
- let content = message.message || message;
328
+ let content = message.message;
622
329
  if (!content) {
623
330
  throw new Boom('no content in message', { statusCode: 400 });
624
331
  }
@@ -634,24 +341,12 @@ export const generateForwardMessageContent = (message, forceForward) => {
634
341
  key = 'extendedTextMessage';
635
342
  }
636
343
  const key_ = content?.[key];
637
- const contextInfo = {};
638
344
  if (score > 0) {
639
- contextInfo.forwardingScore = score;
640
- contextInfo.isForwarded = true;
641
- }
642
- // when forwarding a newsletter/channel message, add the newsletter context
643
- // so the server knows where to find the original media
644
- const remoteJid = message.key?.remoteJid;
645
- if (remoteJid && isJidNewsletter(remoteJid)) {
646
- contextInfo.forwardedNewsletterMessageInfo = {
647
- newsletterJid: remoteJid,
648
- serverMessageId: message.key?.server_id ? parseInt(message.key.server_id) : null,
649
- newsletterName: null
650
- };
651
- // strip messageContextInfo (contains messageSecret etc.) as WA Web does
652
- delete content.messageContextInfo;
345
+ key_.contextInfo = { forwardingScore: score, isForwarded: true };
346
+ }
347
+ else {
348
+ key_.contextInfo = {};
653
349
  }
654
- key_.contextInfo = contextInfo;
655
350
  return content;
656
351
  };
657
352
  export const hasNonNullishProperty = (message, key) => {
@@ -661,39 +356,13 @@ export const hasNonNullishProperty = (message, key) => {
661
356
  message[key] !== null &&
662
357
  message[key] !== undefined);
663
358
  };
664
- export const hasOptionalProperty = (obj, key) => {
665
- return typeof obj === 'object' &&
666
- obj !== null &&
667
- key in obj &&
668
- obj[key] !== null;
669
- };
670
- // vltcs@changes 06-02-26 --- Validate album message media to avoid bug 👀
671
- export const hasValidAlbumMedia = (message) => {
672
- return Boolean(message.imageMessage ||
673
- message.videoMessage);
674
- };
675
- export const hasValidInteractiveHeader = (message) => {
676
- return Boolean(message.imageMessage ||
677
- message.videoMessage ||
678
- message.documentMessage ||
679
- message.productMessage ||
680
- message.locationMessage);
681
- };
682
- // vltcs@changes 30-01-26 --- Validate carousel cards header to avoid bug 👀
683
- export const hasValidCarouselHeader = (message) => {
684
- return Boolean(message.imageMessage ||
685
- message.videoMessage ||
686
- message.productMessage);
687
- };
359
+ function hasOptionalProperty(obj, key) {
360
+ return typeof obj === 'object' && obj !== null && key in obj && obj[key] !== null;
361
+ }
688
362
  export const generateWAMessageContent = async (message, options) => {
689
363
  var _a, _b;
690
364
  let m = {};
691
- // vltcs@changes 30-01-26 --- Add "raw" boolean to send raw messages directly via generateWAMessage()
692
- if (hasNonNullishProperty(message, 'raw')) {
693
- delete message.raw;
694
- return proto.Message.create(message);
695
- }
696
- else if (hasNonNullishProperty(message, 'text')) {
365
+ if (hasNonNullishProperty(message, 'text')) {
697
366
  const extContent = { text: message.text };
698
367
  let urlInfo = message.linkPreview;
699
368
  if (typeof urlInfo === 'undefined') {
@@ -725,58 +394,65 @@ export const generateWAMessageContent = async (message, options) => {
725
394
  m.extendedTextMessage = extContent;
726
395
  }
727
396
  else if (hasNonNullishProperty(message, 'contacts')) {
728
- const { contacts } = message; // vltcs@changes 04-02-26 --- Destructured for readability & cleaner access (⁠✷⁠‿⁠✷⁠)
729
- const contactLen = contacts.contacts.length;
397
+ const contactLen = message.contacts.contacts.length;
730
398
  if (!contactLen) {
731
399
  throw new Boom('require atleast 1 contact', { statusCode: 400 });
732
400
  }
733
401
  if (contactLen === 1) {
734
- m.contactMessage = contacts.contacts[0];
402
+ m.contactMessage = WAProto.Message.ContactMessage.create(message.contacts.contacts[0]);
735
403
  }
736
404
  else {
737
- m.contactsArrayMessage = contacts;
405
+ m.contactsArrayMessage = WAProto.Message.ContactsArrayMessage.create(message.contacts);
738
406
  }
739
407
  }
740
408
  else if (hasNonNullishProperty(message, 'location')) {
741
- m.locationMessage = message.location;
409
+ if (message.live) {
410
+ m.liveLocationMessage = WAProto.Message.LiveLocationMessage.create(message.location);
411
+ } else {
412
+ m.locationMessage = WAProto.Message.LocationMessage.create(message.location);
413
+ }
414
+ const locType = message.live ? 'liveLocationMessage' : 'locationMessage';
415
+ if (m[locType]) {
416
+ m[locType].contextInfo = {
417
+ ...(message.contextInfo || {}),
418
+ ...(message.mentions ? { mentionedJid: message.mentions } : {})
419
+ };
420
+ }
742
421
  }
743
422
  else if (hasNonNullishProperty(message, 'react')) {
744
- const { react } = message; // vltcs@changes 04-02-26 --- Destructured for readability & cleaner access (⁠✷⁠‿⁠✷⁠)
745
- if (!react.senderTimestampMs) {
746
- react.senderTimestampMs = Date.now();
423
+ if (!message.react.senderTimestampMs) {
424
+ message.react.senderTimestampMs = Date.now();
747
425
  }
748
- m.reactionMessage = react;
426
+ m.reactionMessage = WAProto.Message.ReactionMessage.create(message.react);
749
427
  }
750
428
  else if (hasNonNullishProperty(message, 'delete')) {
751
429
  m.protocolMessage = {
752
430
  key: message.delete,
753
- type: ProtocolType.REVOKE
431
+ type: WAProto.Message.ProtocolMessage.Type.REVOKE
754
432
  };
755
433
  }
756
434
  else if (hasNonNullishProperty(message, 'forward')) {
757
435
  m = generateForwardMessageContent(message.forward, message.force);
758
436
  }
759
437
  else if (hasNonNullishProperty(message, 'disappearingMessagesInChat')) {
760
- const { disappearingMessagesInChat } = message; // vltcs@changes 04-02-26 --- Destructured for readability & cleaner access (⁠✷⁠‿⁠✷⁠)
761
- const exp = typeof disappearingMessagesInChat === 'boolean'
762
- ? disappearingMessagesInChat
438
+ const exp = typeof message.disappearingMessagesInChat === 'boolean'
439
+ ? message.disappearingMessagesInChat
763
440
  ? WA_DEFAULT_EPHEMERAL
764
441
  : 0
765
- : disappearingMessagesInChat;
442
+ : message.disappearingMessagesInChat;
766
443
  m = prepareDisappearingMessageSettingContent(exp);
767
444
  }
768
445
  else if (hasNonNullishProperty(message, 'groupInvite')) {
769
- const { groupInvite } = message; // vltcs@changes 04-02-26 --- Destructured for readability & cleaner access (⁠✷⁠‿⁠✷⁠)
770
446
  m.groupInviteMessage = {};
771
- m.groupInviteMessage.inviteCode = groupInvite.inviteCode;
772
- m.groupInviteMessage.inviteExpiration = groupInvite.inviteExpiration;
773
- m.groupInviteMessage.caption = groupInvite.text;
774
- m.groupInviteMessage.groupJid = groupInvite.jid;
775
- m.groupInviteMessage.groupName = groupInvite.subject;
447
+ m.groupInviteMessage.inviteCode = message.groupInvite.inviteCode;
448
+ m.groupInviteMessage.inviteExpiration = message.groupInvite.inviteExpiration;
449
+ m.groupInviteMessage.caption = message.groupInvite.text;
450
+ m.groupInviteMessage.groupJid = message.groupInvite.jid;
451
+ m.groupInviteMessage.groupName = message.groupInvite.subject;
776
452
  //TODO: use built-in interface and get disappearing mode info etc.
777
453
  //TODO: cache / use store!?
778
454
  if (options.getProfilePicUrl) {
779
- const pfpUrl = await options.getProfilePicUrl(groupInvite.jid, 'preview');
455
+ const pfpUrl = await options.getProfilePicUrl(message.groupInvite.jid, 'preview');
780
456
  if (pfpUrl) {
781
457
  const resp = await fetch(pfpUrl, { method: 'GET', dispatcher: options?.options?.dispatcher });
782
458
  if (resp.ok) {
@@ -786,137 +462,206 @@ export const generateWAMessageContent = async (message, options) => {
786
462
  }
787
463
  }
788
464
  }
789
- else if (hasNonNullishProperty(message, 'stickers')) {
790
- m.stickerPackMessage = await prepareStickerPackMessage(message, options);
791
- }
792
465
  else if (hasNonNullishProperty(message, 'pin')) {
793
466
  m.pinInChatMessage = {};
794
467
  m.messageContextInfo = {};
795
- m.pinInChatMessage.key = message.pin;
796
- m.pinInChatMessage.type = message.type;
797
- m.pinInChatMessage.senderTimestampMs = Date.now();
798
- m.messageContextInfo.messageAddOnDurationInSecs = message.type === 1 ? message.time || 86400 : 0;
468
+ m.pinInChatMessage.key = message.pin.key;
469
+ m.pinInChatMessage.type = message.pin?.type || 1;
470
+ m.pinInChatMessage.senderTimestampMs = message.pin?.time || Date.now();
471
+ m.messageContextInfo.messageAddOnDurationInSecs = message.pin.type === 1 ? message.pin.time || 86400 : 0;
472
+ m.messageContextInfo.messageAddOnExpiryType = proto.MessageContextInfo.MessageAddonExpiryType.STATIC;
799
473
  }
800
474
  else if (hasNonNullishProperty(message, 'keep')) {
801
475
  m.keepInChatMessage = {};
802
- m.keepInChatMessage.key = message.keep;
803
- m.keepInChatMessage.keepType = message.type;
804
- m.keepInChatMessage.timestampMs = Date.now();
805
- }
806
- else if (hasNonNullishProperty(message, 'flowReply')) {
807
- const { flowReply } = message; // vltcs@changes 04-02-26 --- Destructured for readability & cleaner access (⁠✷⁠‿⁠✷⁠)
808
- m.interactiveResponseMessage = {
809
- body: {
810
- format: flowReply.format || proto.Message.InteractiveResponseMessage.Body.Format.DEFAULT,
811
- text: flowReply.text
812
- },
813
- nativeFlowResponseMessage: {
814
- name: flowReply.name,
815
- paramsJson: flowReply.paramsJson || '{}',
816
- version: flowReply.version || 1
817
- }
476
+ m.keepInChatMessage.key = message.keep.key;
477
+ m.keepInChatMessage.keepType = message.keep?.type || 1;
478
+ m.keepInChatMessage.timestampMs = message.keep?.time || Date.now();
479
+ }
480
+ else if (hasNonNullishProperty(message, 'call')) {
481
+ m.scheduledCallCreationMessage = {};
482
+ m.scheduledCallCreationMessage.scheduledTimestampMs = message.call?.time || Date.now();
483
+ m.scheduledCallCreationMessage.callType = message.call?.type || 1;
484
+ m.scheduledCallCreationMessage.title = message.call?.name || 'Call Creation';
485
+ m.scheduledCallCreationMessage.contextInfo = {
486
+ ...(message.contextInfo || {}),
487
+ ...(message.mentions ? { mentionedJid: message.mentions } : {})
488
+ };
489
+ }
490
+ else if (hasNonNullishProperty(message, 'paymentInvite')) {
491
+ m.messageContextInfo = {};
492
+ m.paymentInviteMessage = {};
493
+ m.paymentInviteMessage.expiryTimestamp = message.paymentInvite?.expiry || 0;
494
+ m.paymentInviteMessage.serviceType = message.paymentInvite?.type || 2;
495
+ m.paymentInviteMessage.contextInfo = {
496
+ ...(message.contextInfo || {}),
497
+ ...(message.mentions ? { mentionedJid: message.mentions } : {})
818
498
  };
819
499
  }
820
500
  else if (hasNonNullishProperty(message, 'buttonReply')) {
821
- const { buttonReply } = message; // vltcs@changes 04-02-26 --- Destructured for readability & cleaner access (⁠✷⁠‿⁠✷⁠)
822
501
  switch (message.type) {
502
+ case 'list':
503
+ m.listResponseMessage = {
504
+ title: message.buttonReply.title,
505
+ description: message.buttonReply.description,
506
+ singleSelectReply: {
507
+ selectedRowId: message.buttonReply.rowId
508
+ },
509
+ listType: proto.Message.ListResponseMessage.ListType.SINGLE_SELECT
510
+ };
511
+ break;
823
512
  case 'template':
824
513
  m.templateButtonReplyMessage = {
825
- selectedDisplayText: buttonReply.displayText,
826
- selectedId: buttonReply.id,
827
- selectedIndex: buttonReply.index
514
+ selectedDisplayText: message.buttonReply.displayText,
515
+ selectedId: message.buttonReply.id,
516
+ selectedIndex: message.buttonReply.index
828
517
  };
829
518
  break;
830
519
  case 'plain':
831
520
  m.buttonsResponseMessage = {
832
- selectedButtonId: buttonReply.id,
833
- selectedDisplayText: buttonReply.displayText,
521
+ selectedButtonId: message.buttonReply.id,
522
+ selectedDisplayText: message.buttonReply.displayText,
834
523
  type: proto.Message.ButtonsResponseMessage.Type.DISPLAY_TEXT
835
524
  };
836
525
  break;
526
+ case 'interactive':
527
+ m.interactiveResponseMessage = {
528
+ body: {
529
+ text: message.buttonReply.displayText,
530
+ format: proto.Message.InteractiveResponseMessage.Body.Format.EXTENSIONS_1
531
+ },
532
+ nativeFlowResponseMessage: {
533
+ name: message.buttonReply.nativeFlows.name,
534
+ paramsJson: message.buttonReply.nativeFlows.paramsJson,
535
+ version: message.buttonReply.nativeFlows.version
536
+ }
537
+ };
538
+ break;
837
539
  }
838
540
  }
839
- else if (hasNonNullishProperty(message, 'listReply')) {
840
- const { listReply } = message; // vltcs@changes 04-02-26 --- Destructured for readability & cleaner access (⁠✷⁠‿⁠✷⁠)
841
- m.listResponseMessage = {
842
- description: listReply.description,
843
- listType: proto.Message.ListResponseMessage.ListType.SINGLE_SELECT,
844
- singleSelectReply: {
845
- selectedRowId: listReply.id
541
+ else if (hasNonNullishProperty(message, 'album')) {
542
+ const imageMessages = message.album.filter(item => 'image' in item);
543
+ const videoMessages = message.album.filter(item => 'video' in item);
544
+ m.albumMessage = WAProto.Message.AlbumMessage.fromObject({
545
+ expectedImageCount: imageMessages.length,
546
+ expectedVideoCount: videoMessages.length
547
+ });
548
+ }
549
+ else if (hasNonNullishProperty(message, 'order')) {
550
+ m.orderMessage = WAProto.Message.OrderMessage.fromObject(message.order);
551
+ m.orderMessage.contextInfo = {
552
+ ...(message.contextInfo || {}),
553
+ ...(message.mentions ? { mentionedJid: message.mentions } : {})
554
+ };
555
+ }
556
+ else if (hasNonNullishProperty(message, 'payment')) {
557
+ const requestPaymentMessage = {
558
+ amount: {
559
+ currencyCode: message.payment?.currency || 'IDR',
560
+ offset: message.payment?.offset || 0,
561
+ value: message.payment?.amount || 999999999
846
562
  },
847
- title: listReply.title
563
+ expiryTimestamp: message.payment?.expiry || 0,
564
+ amount1000: (message.payment?.amount || 999999999) * 1000,
565
+ currencyCodeIso4217: message.payment?.currency || 'IDR',
566
+ requestFrom: message.payment?.from || '0@s.whatsapp.net',
567
+ noteMessage: {
568
+ extendedTextMessage: {
569
+ text: message.payment?.note || 'Notes'
570
+ }
571
+ },
572
+ background: {
573
+ placeholderArgb: message.payment?.image?.placeholderArgb || 4278190080,
574
+ textArgb: message.payment?.image?.textArgb || 4294967295,
575
+ subtextArgb: message.payment?.image?.subtextArgb || 4294967295,
576
+ type: 1
577
+ }
578
+ };
579
+ requestPaymentMessage.noteMessage.extendedTextMessage.contextInfo = {
580
+ ...(message.contextInfo || {}),
581
+ ...(message.mentions ? { mentionedJid: message.mentions } : {})
848
582
  };
583
+ m.requestPaymentMessage = requestPaymentMessage;
584
+ }
585
+ else if (hasNonNullishProperty(message, 'pollResult')) {
586
+ if (!Array.isArray(message.pollResult.values)) {
587
+ throw new Boom('Invalid pollResult values', { statusCode: 400 });
588
+ }
589
+ const pollResultSnapshotMessage = {
590
+ name: message.pollResult.name,
591
+ pollVotes: message.pollResult.values.map(([optionName, optionVoteCount]) => ({
592
+ optionName,
593
+ optionVoteCount
594
+ }))
595
+ };
596
+ pollResultSnapshotMessage.contextInfo = {
597
+ ...(message.contextInfo || {}),
598
+ ...(message.mentions ? { mentionedJid: message.mentions } : {})
599
+ };
600
+ m.pollResultSnapshotMessage = pollResultSnapshotMessage;
849
601
  }
850
602
  else if (hasOptionalProperty(message, 'ptv') && message.ptv) {
851
603
  const { videoMessage } = await prepareWAMessageMedia({ video: message.video }, options);
852
604
  m.ptvMessage = videoMessage;
853
605
  }
854
606
  else if (hasNonNullishProperty(message, 'product')) {
855
- m.productMessage = await prepareProductMessage(message, options);
607
+ const { imageMessage } = await prepareWAMessageMedia({ image: message.product.productImage }, options);
608
+ m.productMessage = WAProto.Message.ProductMessage.create({
609
+ ...message,
610
+ product: {
611
+ ...message.product,
612
+ productImage: imageMessage
613
+ }
614
+ });
615
+ }
616
+ else if (hasNonNullishProperty(message, 'listReply')) {
617
+ m.listResponseMessage = { ...message.listReply };
856
618
  }
857
619
  else if (hasNonNullishProperty(message, 'event')) {
858
- const { event } = message; // vltcs@changes 04-02-26 --- Destructured for readability & cleaner access (⁠✷⁠‿⁠✷⁠)
859
620
  m.eventMessage = {};
860
- const startTime = Math.floor(event.startDate.getTime() / 1000);
861
- if (event.call && options.getCallLink) {
862
- const token = await options.getCallLink(event.call, { startTime });
863
- m.eventMessage.joinLink = (event.call === 'audio' ? CALL_AUDIO_PREFIX : CALL_VIDEO_PREFIX) + token;
621
+ const startTime = Math.floor(message.event.startDate.getTime() / 1000);
622
+ if (message.event.call && options.getCallLink) {
623
+ const token = await options.getCallLink(message.event.call, { startTime });
624
+ m.eventMessage.joinLink = (message.event.call === 'audio' ? CALL_AUDIO_PREFIX : CALL_VIDEO_PREFIX) + token;
864
625
  }
865
626
  m.messageContextInfo = {
866
627
  // encKey
867
- messageSecret: event.messageSecret || randomBytes(32)
628
+ messageSecret: message.event.messageSecret || randomBytes(32)
868
629
  };
869
- m.eventMessage.name = event.name;
870
- m.eventMessage.description = event.description;
630
+ m.eventMessage.name = message.event.name;
631
+ m.eventMessage.description = message.event.description;
871
632
  m.eventMessage.startTime = startTime;
872
- m.eventMessage.endTime = event.endDate ? event.endDate.getTime() / 1000 : undefined;
873
- m.eventMessage.isCanceled = event.isCancelled ?? false;
874
- m.eventMessage.extraGuestsAllowed = event.extraGuestsAllowed;
875
- m.eventMessage.isScheduleCall = event.isScheduleCall ?? false;
876
- m.eventMessage.location = event.location;
633
+ m.eventMessage.endTime = message.event.endDate ? message.event.endDate.getTime() / 1000 : undefined;
634
+ m.eventMessage.isCanceled = message.event.isCancelled ?? false;
635
+ m.eventMessage.extraGuestsAllowed = message.event.extraGuestsAllowed;
636
+ m.eventMessage.isScheduleCall = message.event.isScheduleCall ?? false;
637
+ m.eventMessage.location = message.event.location;
877
638
  }
878
639
  else if (hasNonNullishProperty(message, 'poll')) {
879
- const { poll } = message; // vltcs@changes 04-02-26 --- Destructured for readability & cleaner access (⁠✷⁠‿⁠✷⁠)
880
- (_a = poll).selectableCount || (_a.selectableCount = 0);
881
- (_b = poll).toAnnouncementGroup || (_b.toAnnouncementGroup = false);
882
- if (!Array.isArray(poll.values)) {
640
+ (_a = message.poll).selectableCount || (_a.selectableCount = 0);
641
+ (_b = message.poll).toAnnouncementGroup || (_b.toAnnouncementGroup = false);
642
+ if (!Array.isArray(message.poll.values)) {
883
643
  throw new Boom('Invalid poll values', { statusCode: 400 });
884
644
  }
885
- if (poll.selectableCount < 0 || poll.selectableCount > poll.values.length) {
886
- throw new Boom(`poll.selectableCount in poll should be >= 0 and <= ${poll.values.length}`, {
645
+ if (message.poll.selectableCount < 0 || message.poll.selectableCount > message.poll.values.length) {
646
+ throw new Boom(`poll.selectableCount in poll should be >= 0 and <= ${message.poll.values.length}`, {
887
647
  statusCode: 400
888
648
  });
889
649
  }
890
650
  m.messageContextInfo = {
891
651
  // encKey
892
- messageSecret: poll.messageSecret || randomBytes(32)
652
+ messageSecret: message.poll.messageSecret || randomBytes(32)
893
653
  };
894
654
  const pollCreationMessage = {
895
- name: poll.name,
896
- selectableOptionsCount: poll.selectableCount,
897
- options: poll.values.map(optionName => ({ optionName }))
655
+ name: message.poll.name,
656
+ selectableOptionsCount: message.poll.selectableCount,
657
+ options: message.poll.values.map(optionName => ({ optionName }))
898
658
  };
899
- if (poll.toAnnouncementGroup) {
659
+ if (message.poll.toAnnouncementGroup) {
900
660
  // poll v2 is for community announcement groups (single select and multiple)
901
661
  m.pollCreationMessageV2 = pollCreationMessage;
902
662
  }
903
663
  else {
904
- // vltcs@changes 08-02-26 --- Add quiz message support
905
- if (poll.pollType === 1) {
906
- if (!poll.correctAnswer) {
907
- throw new Boom('No "correctAnswer" provided for quiz', { statusCode: 400 });
908
- }
909
- m.pollCreationMessageV5 = {
910
- // Lia@Note 08-02-26 --- quiz for newsletter only
911
- ...pollCreationMessage,
912
- correctAnswer: {
913
- optionName: poll.correctAnswer.toString()
914
- },
915
- pollType: poll.pollType,
916
- selectableOptionsCount: 1
917
- };
918
- }
919
- else if (poll.selectableCount === 1) {
664
+ if (message.poll.selectableCount === 1) {
920
665
  //poll v3 is for single select polls
921
666
  m.pollCreationMessageV3 = pollCreationMessage;
922
667
  }
@@ -926,44 +671,211 @@ export const generateWAMessageContent = async (message, options) => {
926
671
  }
927
672
  }
928
673
  }
929
- // vltcs@changes 08-02-26 --- Add poll result snapshot message
930
- else if (hasNonNullishProperty(message, 'pollResult')) {
931
- const { pollResult } = message;
932
- const pollResultSnapshotMessage = {
933
- name: pollResult.name,
934
- pollVotes: pollResult.votes.map(vote => ({
935
- optionName: vote.name,
936
- optionVoteCount: parseInt(vote.voteCount)
937
- }))
674
+ else if (hasNonNullishProperty(message, 'stickerPack')) {
675
+ const { zip } = _require('fflate');
676
+ const { stickers, cover, name, publisher, packId, description } = message.stickerPack;
677
+
678
+ // ── Validasi jumlah sticker ───────────────────────────────────────────
679
+ if (stickers.length > 60) {
680
+ throw new Boom('Sticker pack exceeds the maximum limit of 60 stickers', { statusCode: 400 });
681
+ }
682
+ if (stickers.length === 0) {
683
+ throw new Boom('Sticker pack must contain at least one sticker', { statusCode: 400 });
684
+ }
685
+
686
+ const stickerPackId = packId || generateMessageIDV2();
687
+ const [_sharp, _jimp] = await Promise.all([import('sharp').catch(() => null), import('jimp').catch(() => null)]);
688
+ const lib = _sharp ? { sharp: _sharp } : _jimp ? { jimp: _jimp } : null;
689
+ if (!lib) throw new Boom('No image processing library available (install sharp or jimp)');
690
+
691
+ // ── Helper: deteksi WebP dari magic bytes ─────────────────────────────
692
+ const isWebPBuffer = (buf) => (
693
+ buf.length >= 12 &&
694
+ buf[0] === 0x52 && buf[1] === 0x49 && buf[2] === 0x46 && buf[3] === 0x46 &&
695
+ buf[8] === 0x57 && buf[9] === 0x45 && buf[10] === 0x42 && buf[11] === 0x50
696
+ );
697
+
698
+ // ── Helper: deteksi animasi WebP (VP8X/ANIM/ANMF chunks) ─────────────
699
+ const isAnimatedWebP = (buf) => {
700
+ if (!isWebPBuffer(buf)) return false;
701
+ let offset = 12;
702
+ while (offset < buf.length - 8) {
703
+ const fourCC = buf.toString('ascii', offset, offset + 4);
704
+ const chunkSize = buf.readUInt32LE(offset + 4);
705
+ if (fourCC === 'VP8X') {
706
+ const flagsOffset = offset + 8;
707
+ if (flagsOffset < buf.length && (buf[flagsOffset] & 0x02)) return true;
708
+ } else if (fourCC === 'ANIM' || fourCC === 'ANMF') {
709
+ return true;
710
+ }
711
+ offset += 8 + chunkSize + (chunkSize % 2);
712
+ }
713
+ return false;
938
714
  };
939
- if (pollResult.pollType === 1) {
940
- pollResultSnapshotMessage.pollType = proto.Message.PollType.QUIZ;
941
- m.pollResultSnapshotMessageV3 = pollResultSnapshotMessage;
942
- }
943
- else {
944
- pollResultSnapshotMessage.pollType = proto.Message.PollType.POLL;
945
- m.pollResultSnapshotMessage = pollResultSnapshotMessage;
715
+
716
+ // ── Step 1: proses & zip semua sticker ────────────────────────────────
717
+ const stickerData = {};
718
+ const stickerPromises = stickers.map(async (s, i) => {
719
+ const { stream } = await getStream(s.data || s.sticker);
720
+ const buffer = await toBuffer(stream);
721
+
722
+ let webpBuffer;
723
+ let isAnimated = false;
724
+ if (isWebPBuffer(buffer)) {
725
+ webpBuffer = buffer;
726
+ isAnimated = isAnimatedWebP(buffer);
727
+ } else if ('sharp' in lib && lib.sharp) {
728
+ webpBuffer = await lib.sharp.default(buffer).webp().toBuffer();
729
+ } else {
730
+ throw new Boom(
731
+ 'No image processing library (sharp) available for converting sticker to WebP. ' +
732
+ 'Either install sharp or provide stickers in WebP format.'
733
+ );
734
+ }
735
+
736
+ if (webpBuffer.length > 1024 * 1024) {
737
+ throw new Boom(`Sticker at index ${i} exceeds the 1MB size limit`, { statusCode: 400 });
738
+ }
739
+
740
+ const hash = sha256(webpBuffer).toString('base64').replace(/\//g, '-');
741
+ const fileName = `${hash}.webp`;
742
+ stickerData[fileName] = [new Uint8Array(webpBuffer), { level: 0 }];
743
+ return {
744
+ fileName,
745
+ mimetype: 'image/webp',
746
+ isAnimated,
747
+ emojis: s.emojis || [],
748
+ accessibilityLabel: s.accessibilityLabel || ''
749
+ };
750
+ });
751
+ const stickerMetadata = await Promise.all(stickerPromises);
752
+
753
+ // ── Step 2: proses cover & masukkan ke dalam ZIP ──────────────────────
754
+ const trayIconFileName = `${stickerPackId}.webp`;
755
+ const coverBuffer = await toBuffer((await getStream(cover)).stream);
756
+
757
+ let coverWebpBuffer;
758
+ if (isWebPBuffer(coverBuffer)) {
759
+ coverWebpBuffer = coverBuffer;
760
+ } else if ('sharp' in lib && lib.sharp) {
761
+ coverWebpBuffer = await lib.sharp.default(coverBuffer).webp().toBuffer();
762
+ } else {
763
+ throw new Boom(
764
+ 'No image processing library (sharp) available for converting cover to WebP. ' +
765
+ 'Either install sharp or provide cover in WebP format.'
766
+ );
767
+ }
768
+ stickerData[trayIconFileName] = [new Uint8Array(coverWebpBuffer), { level: 0 }];
769
+
770
+ // ── Step 3: buat ZIP buffer ───────────────────────────────────────────
771
+ const zipBuffer = await new Promise((resolve, reject) => {
772
+ zip(stickerData, (err, data) => {
773
+ if (err) reject(err);
774
+ else resolve(Buffer.from(data));
775
+ });
776
+ });
777
+
778
+ // ── Step 4: encrypt ZIP (generate random mediaKey) ────────────────────
779
+ const stickerPackUpload = await encryptedStream(zipBuffer, 'sticker-pack', {
780
+ logger: options.logger,
781
+ opts: options.options
782
+ });
783
+
784
+ // ── Step 5: upload ZIP ────────────────────────────────────────────────
785
+ const stickerPackUploadResult = await options.upload(stickerPackUpload.encWriteStream, {
786
+ fileEncSha256B64: stickerPackUpload.fileEncSha256.toString('base64'),
787
+ mediaType: 'sticker-pack',
788
+ timeoutMs: options.mediaUploadTimeoutMs
789
+ });
790
+
791
+ // ── Step 6: build stickerPackMessage ──────────────────────────────────
792
+ m.stickerPackMessage = {
793
+ name,
794
+ publisher,
795
+ stickerPackId,
796
+ packDescription: description,
797
+ stickerPackOrigin: WAProto.Message.StickerPackMessage.StickerPackOrigin.THIRD_PARTY,
798
+ stickerPackSize: zipBuffer.length,
799
+ stickers: stickerMetadata,
800
+ fileSha256: stickerPackUpload.fileSha256,
801
+ fileEncSha256: stickerPackUpload.fileEncSha256,
802
+ mediaKey: stickerPackUpload.mediaKey,
803
+ directPath: stickerPackUploadResult.directPath,
804
+ fileLength: stickerPackUpload.fileLength,
805
+ mediaKeyTimestamp: unixTimestampSeconds(),
806
+ trayIconFileName
807
+ };
808
+
809
+ // ── Step 7: generate & upload thumbnail (pakai mediaKey yang sama) ────
810
+ try {
811
+ let thumbnailBuffer;
812
+ if ('sharp' in lib && lib.sharp) {
813
+ thumbnailBuffer = await lib.sharp.default(coverBuffer).resize(252, 252).jpeg().toBuffer();
814
+ } else if ('jimp' in lib && lib.jimp) {
815
+ const jimpImage = await (lib.jimp.Jimp || lib.jimp.default).read(coverBuffer);
816
+ thumbnailBuffer = await jimpImage.resize({ w: 252, h: 252 }).getBuffer('image/jpeg');
817
+ } else {
818
+ throw new Error('No image processing library available for thumbnail generation');
819
+ }
820
+
821
+ if (!thumbnailBuffer || thumbnailBuffer.length === 0) {
822
+ throw new Error('Failed to generate thumbnail buffer');
823
+ }
824
+
825
+ const thumbUpload = await encryptedStream(thumbnailBuffer, 'thumbnail-sticker-pack', {
826
+ logger: options.logger,
827
+ opts: options.options,
828
+ mediaKey: stickerPackUpload.mediaKey
829
+ });
830
+
831
+ const thumbUploadResult = await options.upload(thumbUpload.encWriteStream, {
832
+ fileEncSha256B64: thumbUpload.fileEncSha256.toString('base64'),
833
+ mediaType: 'thumbnail-sticker-pack',
834
+ timeoutMs: options.mediaUploadTimeoutMs
835
+ });
836
+
837
+ Object.assign(m.stickerPackMessage, {
838
+ thumbnailDirectPath: thumbUploadResult.directPath,
839
+ thumbnailSha256: thumbUpload.fileSha256,
840
+ thumbnailEncSha256: thumbUpload.fileEncSha256,
841
+ thumbnailHeight: 252,
842
+ thumbnailWidth: 252,
843
+ imageDataHash: sha256(thumbnailBuffer).toString('base64')
844
+ });
845
+ } catch (e) {
846
+ options.logger?.warn?.(`Thumbnail generation failed: ${e}`);
946
847
  }
848
+
849
+ m.stickerPackMessage.contextInfo = {
850
+ ...(message.contextInfo || {}),
851
+ ...(message.mentions ? { mentionedJid: message.mentions } : {})
852
+ };
947
853
  }
948
- // vltcs@changes 08-02-26 --- Add poll update message
949
- else if (hasNonNullishProperty(message, 'pollUpdate')) {
950
- const { pollUpdate } = message;
951
- if (!pollUpdate.key) {
952
- throw new Boom('Message key is required', { statusCode: 400 });
953
- }
954
- if (!pollUpdate.vote) {
955
- throw new Boom('Encrypted vote payload is required', { statusCode: 400 });
854
+ else if (hasNonNullishProperty(message, 'adminInvite')) {
855
+ m.newsletterAdminInviteMessage = {};
856
+ m.newsletterAdminInviteMessage.newsletterJid = message.adminInvite.jid;
857
+ m.newsletterAdminInviteMessage.newsletterName = message.adminInvite.name;
858
+ m.newsletterAdminInviteMessage.caption = message.adminInvite.caption;
859
+ m.newsletterAdminInviteMessage.inviteExpiration = message.adminInvite.expiration;
860
+ if (message.adminInvite.jpegThumbnail) {
861
+ m.newsletterAdminInviteMessage.jpegThumbnail = message.adminInvite.jpegThumbnail;
862
+ } else if (options.getProfilePicUrl) {
863
+ try {
864
+ const pfpUrl = await options.getProfilePicUrl(message.adminInvite.jid);
865
+ if (pfpUrl) {
866
+ const { thumbnail } = await generateThumbnail(pfpUrl, 'image');
867
+ m.newsletterAdminInviteMessage.jpegThumbnail = thumbnail;
868
+ }
869
+ } catch (_) {}
956
870
  }
957
- m.pollUpdateMessage = {
958
- metadata: pollUpdate.metadata,
959
- pollCreationMessageKey: pollUpdate.key,
960
- senderTimestampMs: Date.now(),
961
- vote: pollUpdate.vote
871
+ m.newsletterAdminInviteMessage.contextInfo = {
872
+ ...(message.contextInfo || {}),
873
+ ...(message.mentions ? { mentionedJid: message.mentions } : {})
962
874
  };
963
875
  }
964
876
  else if (hasNonNullishProperty(message, 'sharePhoneNumber')) {
965
877
  m.protocolMessage = {
966
- type: ProtocolType.SHARE_PHONE_NUMBER
878
+ type: proto.Message.ProtocolMessage.Type.SHARE_PHONE_NUMBER
967
879
  };
968
880
  }
969
881
  else if (hasNonNullishProperty(message, 'requestPhoneNumber')) {
@@ -971,7 +883,7 @@ export const generateWAMessageContent = async (message, options) => {
971
883
  }
972
884
  else if (hasNonNullishProperty(message, 'limitSharing')) {
973
885
  m.protocolMessage = {
974
- type: ProtocolType.LIMIT_SHARING,
886
+ type: proto.Message.ProtocolMessage.Type.LIMIT_SHARING,
975
887
  limitSharing: {
976
888
  sharingLimited: message.limitSharing === true,
977
889
  trigger: 1,
@@ -980,444 +892,288 @@ export const generateWAMessageContent = async (message, options) => {
980
892
  }
981
893
  };
982
894
  }
983
- // vltcs@changes 01-02-26 --- Add payment invite message
984
- else if (hasNonNullishProperty(message, 'paymentInviteServiceType')) {
985
- m.paymentInviteMessage = {
986
- expiryTimestamp: Date.now(),
987
- serviceType: message.paymentInviteServiceType
988
- };
895
+ else if ('interactiveMessage' in message && !!message.interactiveMessage) {
896
+ // ── Passthrough interactiveMessage raw object ──────────────────────
897
+ // Must be BEFORE the else block to avoid hitting prepareWAMessageMedia
898
+ // which throws 'Invalid media type' for interactiveMessage keys.
899
+ m = { interactiveMessage: message.interactiveMessage };
989
900
  }
990
- // vltcs@changes 01-02-26 --- Add order message
991
- else if (hasNonNullishProperty(message, 'orderText')) {
992
- if (!Buffer.isBuffer(message.thumbnail)) {
993
- throw new Boom('Must provide thumbnail buffer in order message', { statusCode: 400 });
994
- }
995
- m.orderMessage = {
996
- itemCount: 1,
997
- messageVersion: 1,
998
- orderTitle: LIBRARY_NAME,
999
- status: proto.Message.OrderMessage.OrderStatus.INQUIRY,
1000
- surface: proto.Message.OrderMessage.OrderSurface.CATALOG,
1001
- token: generateMessageIDV2(),
1002
- totalAmount1000: 1000,
1003
- totalCurrencyCode: 'IDR',
1004
- ...message,
1005
- message: message.orderText
1006
- };
1007
- delete m.orderMessage.orderText;
901
+ else {
902
+ m = await prepareWAMessageMedia(message, options);
1008
903
  }
1009
- // vltcs@changes 31-01-26 --- Add support for album messages
1010
- else if (hasNonNullishProperty(message, 'album')) {
1011
- const { album } = message;
1012
- if (!Array.isArray(album)) {
1013
- throw new Boom('Invalid album type. Expected an array.', { statusCode: 400 });
1014
- }
1015
- let videoCount = 0;
1016
- for (let i = 0; i < album.length; i++) {
1017
- if (album[i].video) videoCount++;
1018
- };
1019
- let imageCount = 0;
1020
- for (let i = 0; i < album.length; i++) {
1021
- if (album[i].image) imageCount++;
904
+ if ('sections' in message && !!message.sections) {
905
+ const listMessage = {
906
+ title: message.title,
907
+ buttonText: message.buttonText,
908
+ footerText: message.footer,
909
+ description: message.text,
910
+ sections: message.sections,
911
+ listType: proto.Message.ListMessage.ListType.SINGLE_SELECT
1022
912
  };
1023
- if ((videoCount + imageCount) < 2) {
1024
- throw new Boom('Minimum provide 2 media to upload album message', { statusCode: 400 });
1025
- }
1026
- m.albumMessage = {
1027
- expectedImageCount: imageCount,
1028
- expectedVideoCount: videoCount
913
+ listMessage.contextInfo = {
914
+ ...(message.contextInfo || {}),
915
+ ...(message.mentions ? { mentionedJid: message.mentions } : {})
1029
916
  };
917
+ m = { listMessage };
1030
918
  }
1031
- else {
1032
- m = await prepareWAMessageMedia(message, options);
919
+ else if ('productList' in message && !!message.productList) {
920
+ const thumbnail = message.thumbnail ? await generateThumbnail(message.thumbnail, 'image') : null;
921
+ const listMessage = {
922
+ title: message.title,
923
+ buttonText: message.buttonText,
924
+ footerText: message.footer,
925
+ description: message.text,
926
+ productListInfo: {
927
+ productSections: message.productList,
928
+ headerImage: {
929
+ productId: message.productList[0].products[0].productId,
930
+ jpegThumbnail: thumbnail?.thumbnail || null
931
+ },
932
+ businessOwnerJid: message.businessOwnerJid
933
+ },
934
+ listType: proto.Message.ListMessage.ListType.PRODUCT_LIST
935
+ };
936
+ listMessage.contextInfo = {
937
+ ...(message.contextInfo || {}),
938
+ ...(message.mentions ? { mentionedJid: message.mentions } : {})
939
+ };
940
+ m = { listMessage };
1033
941
  }
1034
- // vltcs@changes 30-01-26 --- Add interactive messages (buttonsMessage, listMessage, interactiveMessage, templateMessage, and carouselMessage)
1035
- if (hasNonNullishProperty(message, 'buttons')) {
942
+ else if ('buttons' in message && !!message.buttons) {
1036
943
  const buttonsMessage = {
1037
- buttons: message.buttons.map(button => {
1038
- // vltcs@changes 12-03-26 --- Add "single_select" shortcut!
1039
- const buttonText = button.text || button.buttonText
1040
- if (hasOptionalProperty(button, 'sections')) {
1041
- return {
1042
- nativeFlowInfo: {
1043
- name: 'single_select',
1044
- paramsJson: JSON.stringify({
1045
- title: buttonText,
1046
- sections: button.sections
1047
- })
1048
- },
1049
- type: ButtonType.NATIVE_FLOW
1050
- };
1051
- }
1052
- else if (hasOptionalProperty(button, 'name')) {
1053
- return {
1054
- nativeFlowInfo: {
1055
- name: button.name,
1056
- paramsJson: button.paramsJson
1057
- },
1058
- type: ButtonType.NATIVE_FLOW
1059
- };
1060
- }
1061
- return {
1062
- buttonId: button.id || button.buttonId,
1063
- buttonText: typeof buttonText === 'string' ? { displayText: buttonText } : buttonText,
1064
- type: button.type || ButtonType.RESPONSE
1065
- };
1066
- })
944
+ buttons: message.buttons.map(b => ({ ...b, type: proto.Message.ButtonsMessage.Button.Type.RESPONSE }))
1067
945
  };
1068
- if (hasOptionalProperty(message, 'text')) {
946
+ if ('text' in message) {
1069
947
  buttonsMessage.contentText = message.text;
1070
- buttonsMessage.headerType = ButtonHeaderType.EMPTY;
948
+ buttonsMessage.headerType = proto.Message.ButtonsMessage.HeaderType.EMPTY;
1071
949
  }
1072
950
  else {
1073
- if (hasOptionalProperty(message, 'caption')) {
951
+ if ('caption' in message) {
1074
952
  buttonsMessage.contentText = message.caption;
1075
953
  }
1076
954
  const type = Object.keys(m)[0].replace('Message', '').toUpperCase();
1077
- buttonsMessage.headerType = ButtonHeaderType[type];
955
+ buttonsMessage.headerType = proto.Message.ButtonsMessage.HeaderType[type];
1078
956
  Object.assign(buttonsMessage, m);
1079
957
  }
1080
- if (hasOptionalProperty(message, 'footer')) {
958
+ if ('footer' in message && !!message.footer) {
1081
959
  buttonsMessage.footerText = message.footer;
1082
960
  }
1083
- m = { buttonsMessage };
1084
- }
1085
- else if (hasNonNullishProperty(message, 'sections')) {
1086
- const listMessage = {
1087
- sections: message.sections,
1088
- buttonText: message.buttonText,
1089
- title: message.title,
1090
- footerText: message.footer,
1091
- description: message.text,
1092
- listType: ListType.SINGLE_SELECT
961
+ if ('title' in message && !!message.title) {
962
+ buttonsMessage.text = message.title;
963
+ buttonsMessage.headerType = proto.Message.ButtonsMessage.HeaderType.TEXT;
964
+ }
965
+ buttonsMessage.contextInfo = {
966
+ ...(message.contextInfo || {}),
967
+ ...(message.mentions ? { mentionedJid: message.mentions } : {})
1093
968
  };
1094
- m = { listMessage };
969
+ m = { buttonsMessage };
1095
970
  }
1096
- // Lia@Note 03-02-26 --- This message type is shown on WhatsApp Web/Desktop and iOS (I guess 。⁠◕⁠‿⁠◕⁠。). On Android, it only appears in newsletter (so far ಥ⁠‿⁠ಥ)
1097
- else if (hasNonNullishProperty(message, 'templateButtons')) {
971
+ else if ('templateButtons' in message && !!message.templateButtons) {
1098
972
  const hydratedTemplate = {
1099
- hydratedButtons: message.templateButtons.map((button, i) => {
1100
- if (hasOptionalProperty(button, 'id')) {
1101
- return {
1102
- index: i,
1103
- quickReplyButton: {
1104
- displayText: button.text || button.buttonText || '👉🏻 Click',
1105
- id: button.id
1106
- }
1107
- };
1108
- }
1109
- else if (hasOptionalProperty(button, 'url')) {
1110
- return {
1111
- index: i,
1112
- urlButton: {
1113
- displayText: button.text || button.buttonText || '🌐 Visit',
1114
- url: button.url
1115
- }
1116
- };
1117
- }
1118
- else if (hasOptionalProperty(button, 'call')) {
1119
- return {
1120
- index: i,
1121
- callButton: {
1122
- displayText: button.text || button.buttonText || '📞 Call',
1123
- phoneNumber: button.call
1124
- }
1125
- };
1126
- }
1127
- button.index = button.index || i;
1128
- return button;
1129
- })
973
+ hydratedButtons: message.templateButtons
1130
974
  };
1131
- if (hasOptionalProperty(message, 'text')) {
975
+ if ('text' in message) {
1132
976
  hydratedTemplate.hydratedContentText = message.text;
1133
977
  }
1134
978
  else {
1135
- if (hasOptionalProperty(message, 'caption')) {
1136
- hydratedTemplate.hydratedTitleText = message.title;
979
+ if ('caption' in message) {
1137
980
  hydratedTemplate.hydratedContentText = message.caption;
1138
- };
981
+ }
1139
982
  Object.assign(hydratedTemplate, m);
1140
983
  }
1141
- if (hasOptionalProperty(message, 'footer')) {
984
+ if ('footer' in message && !!message.footer) {
1142
985
  hydratedTemplate.hydratedFooterText = message.footer;
1143
986
  }
1144
- hydratedTemplate.templateId = message.id || 'template-' + Date.now(); // Lia@Note 04-02-26 --- Minimal templateId to satisfy WhatsApp (⁠ ⁠ꈍ⁠ᴗ⁠ꈍ⁠)
1145
- m = {
1146
- templateMessage: {
1147
- hydratedFourRowTemplate: hydratedTemplate,
1148
- hydratedTemplate: hydratedTemplate
1149
- }
1150
- }
987
+ hydratedTemplate.contextInfo = {
988
+ ...(message.contextInfo || {}),
989
+ ...(message.mentions ? { mentionedJid: message.mentions } : {})
990
+ };
991
+ m = { templateMessage: { fourRowTemplate: hydratedTemplate, hydratedTemplate } };
1151
992
  }
1152
- else if (hasNonNullishProperty(message, 'nativeFlow')) {
993
+ else if ('interactiveButtons' in message && !!message.interactiveButtons) {
1153
994
  const interactiveMessage = {
1154
- nativeFlowMessage: prepareNativeFlowButtons(message)
995
+ nativeFlowMessage: proto.Message.InteractiveMessage.NativeFlowMessage.fromObject({
996
+ buttons: message.interactiveButtons,
997
+ })
1155
998
  };
1156
- if (hasOptionalProperty(message, 'bizJid')) {
1157
- interactiveMessage.collectionMessage = {
1158
- bizJid: message.bizJid,
1159
- id: message.id,
1160
- messageVersion: 1
1161
- };
999
+ if ('text' in message) {
1000
+ interactiveMessage.body = { text: message.text };
1162
1001
  }
1163
- else if (hasOptionalProperty(message, 'shopSurface')) {
1164
- interactiveMessage.shopStorefrontMessage = {
1165
- surface: message.shopSurface,
1166
- id: message.id,
1167
- messageVersion: 1
1002
+ else if ('caption' in message) {
1003
+ interactiveMessage.body = { text: message.caption };
1004
+ interactiveMessage.header = {
1005
+ title: message.title,
1006
+ subtitle: message.subtitle,
1007
+ hasMediaAttachment: message?.media ?? false,
1168
1008
  };
1009
+ Object.assign(interactiveMessage.header, m);
1169
1010
  }
1170
- if (hasOptionalProperty(message, 'text')) {
1171
- interactiveMessage.body = { text: message.text };
1011
+ if ('footer' in message && !!message.footer) {
1012
+ interactiveMessage.footer = { text: message.footer };
1172
1013
  }
1173
- else {
1174
- if (hasOptionalProperty(message, 'caption')) {
1175
- const isValidHeader = hasValidInteractiveHeader(m)
1176
- if (!isValidHeader) {
1177
- throw new Boom('Invalid media type for interactive message header', { statusCode: 400 });
1178
- }
1179
- interactiveMessage.header = {
1180
- title: message.title || '',
1181
- subtitle: message.subtitle || '',
1182
- hasMediaAttachment: isValidHeader
1183
- };
1184
- interactiveMessage.body = { text: message.caption };
1185
- }
1186
- if (hasOptionalProperty(message, 'thumbnail') && !!message.thumbnail) {
1187
- interactiveMessage.jpegThumbnail = message.thumbnail;
1188
- }
1014
+ if ('title' in message && !!message.title) {
1015
+ interactiveMessage.header = {
1016
+ title: message.title,
1017
+ subtitle: message.subtitle,
1018
+ hasMediaAttachment: message?.media ?? false,
1019
+ };
1189
1020
  Object.assign(interactiveMessage.header, m);
1190
1021
  }
1191
- if (hasOptionalProperty(message, 'audioFooter')) {
1192
- const parseFooter = await prepareWAMessageMedia({
1193
- audio: message.audioFooter
1194
- }, options);
1195
- interactiveMessage.footer = {
1196
- audioMessage: parseFooter.audioMessage,
1197
- hasMediaAttachment: true
1198
- };
1022
+ if ('contextInfo' in message && !!message.contextInfo) {
1023
+ interactiveMessage.contextInfo = message.contextInfo;
1199
1024
  }
1200
- else if (hasOptionalProperty(message, 'footer')) {
1201
- interactiveMessage.footer = { text: message.footer };
1025
+ if ('mentions' in message && !!message.mentions) {
1026
+ interactiveMessage.contextInfo = { mentionedJid: message.mentions };
1202
1027
  }
1203
1028
  m = { interactiveMessage };
1204
1029
  }
1205
- else if (hasNonNullishProperty(message, 'cards')) {
1030
+ else if ('shop' in message && !!message.shop) {
1206
1031
  const interactiveMessage = {
1207
- carouselMessage: {
1208
- cards: await Promise.all(message.cards.map(async card => {
1209
- let carouselHeader = {};
1210
- if (hasNonNullishProperty(card, 'product')) {
1211
- carouselHeader.productMessage = await prepareProductMessage(card, options);
1212
- }
1213
- else {
1214
- carouselHeader = await prepareWAMessageMedia(card, options).catch(() => ({}));
1215
- }
1216
- const isValidHeader = hasValidCarouselHeader(carouselHeader)
1217
- if (!isValidHeader) {
1218
- throw new Boom('Invalid media type for carousel card', { statusCode: 400 });
1219
- }
1220
- const carouselCard = {
1221
- nativeFlowMessage: prepareNativeFlowButtons(card.nativeFlow ? card : [])
1222
- };
1223
- if (hasOptionalProperty(card, 'text')) {
1224
- carouselCard.body = { text: card.text };
1225
- }
1226
- else {
1227
- if (hasOptionalProperty(card, 'caption')) {
1228
- carouselCard.header = {
1229
- title: card.title || '',
1230
- subtitle: card.subtitle || '',
1231
- hasMediaAttachment: isValidHeader
1232
- };
1233
- carouselCard.body = { text: card.caption };
1234
- }
1235
- if (hasOptionalProperty(card, 'thumbnail') && !!card.thumbnail) {
1236
- carouselCard.jpegThumbnail = card.thumbnail;
1237
- }
1238
- Object.assign(carouselCard.header, carouselHeader);
1239
- }
1240
- if (hasOptionalProperty(card, 'audioFooter')) {
1241
- const parseFooter = await prepareWAMessageMedia({
1242
- audio: card.audioFooter
1243
- }, options);
1244
- carouselCard.footer = {
1245
- audioMessage: parseFooter.audioMessage,
1246
- hasMediaAttachment: true
1247
- };
1248
- }
1249
- else if (hasOptionalProperty(card, 'footer')) {
1250
- carouselCard.footer = { text: card.footer };
1251
- }
1252
- return carouselCard
1253
- })),
1254
- carouselCardType: CarouselCardType.UNKNOWN,
1255
- messageVersion: 1
1256
- }
1032
+ shopStorefrontMessage: proto.Message.InteractiveMessage.ShopMessage.fromObject({
1033
+ surface: message.shop,
1034
+ id: message.id
1035
+ })
1257
1036
  };
1258
- if (hasOptionalProperty(message, 'text')) {
1037
+ if ('text' in message) {
1259
1038
  interactiveMessage.body = { text: message.text };
1260
1039
  }
1261
- if (hasOptionalProperty(message, 'footer')) {
1262
- interactiveMessage.footer = { text: message.footer };
1040
+ else if ('caption' in message) {
1041
+ interactiveMessage.body = { text: message.caption };
1042
+ interactiveMessage.header = {
1043
+ title: message.title,
1044
+ subtitle: message.subtitle,
1045
+ hasMediaAttachment: message?.media ?? false,
1046
+ };
1047
+ Object.assign(interactiveMessage.header, m);
1263
1048
  }
1264
- m = { interactiveMessage };
1265
- }
1266
- // vltcs@changes 01-02-26 --- Add request payment message
1267
- else if (hasNonNullishProperty(message, 'requestPaymentFrom')) {
1268
- const requestPaymentMessage = {
1269
- amount: {
1270
- currencyCode: 'IDR',
1271
- offset: 1000,
1272
- value: 1000
1273
- },
1274
- amount1000: 1000,
1275
- currencyCodeIso4217: 'IDR',
1276
- expiryTimestamp: Date.now(),
1277
- noteMessage: m,
1278
- requestFrom: message.requestPaymentFrom,
1279
- ...message
1280
- };
1281
- delete requestPaymentMessage.requestPaymentFrom;
1282
- if (hasNonNullishProperty(m, 'extendedTextMessage') || hasNonNullishProperty(m, 'stickerMessage')) {
1283
- Object.assign(requestPaymentMessage.noteMessage, m);
1049
+ if ('footer' in message && !!message.footer) {
1050
+ interactiveMessage.footer = { text: message.footer };
1284
1051
  }
1285
- else {
1286
- throw new Boom('Invalid message type for request payment note message', { statusCode: 400 });
1052
+ if ('title' in message && !!message.title) {
1053
+ interactiveMessage.header = {
1054
+ title: message.title,
1055
+ subtitle: message.subtitle,
1056
+ hasMediaAttachment: message?.media ?? false,
1057
+ };
1058
+ Object.assign(interactiveMessage.header, m);
1287
1059
  }
1288
- m = { requestPaymentMessage };
1289
- }
1290
- // vltcs@changes 01-02-26 --- Add invoice message
1291
- else if (hasNonNullishProperty(message, 'invoiceNote')) {
1292
- const attachment = m.imageMessage || m.documentMessage;
1293
- const type = Object.keys(m)[0].replace('Message', '').toUpperCase();
1294
- const invoiceMessage = {
1295
- attachmentType: proto.Message.InvoiceMessage.AttachmentType[type === 'DOCUMENT' ? 'PDF' : 'IMAGE'],
1296
- note: message.invoiceNote
1297
- };
1298
- if (attachment) {
1299
- const { directPath, fileEncSha256, fileSha256, jpegThumbnail = undefined, mediaKey, mediaKeyTimestamp, mimetype } = attachment;
1300
- Object.assign(invoiceMessage, {
1301
- attachmentDirectPath: directPath,
1302
- attachmentFileEncSha256: fileEncSha256,
1303
- attachmentFileSha256: fileSha256,
1304
- attachmentJpegThumbnail: jpegThumbnail,
1305
- attachmentMediaKey: mediaKey,
1306
- attachmentMediaKeyTimestamp: mediaKeyTimestamp,
1307
- attachmentMimetype: mimetype,
1308
- token: generateMessageIDV2()
1309
- });
1060
+ if ('contextInfo' in message && !!message.contextInfo) {
1061
+ interactiveMessage.contextInfo = message.contextInfo;
1310
1062
  }
1311
- else {
1312
- throw new Boom('Invalid media type for invoice message', { statusCode: 400 });
1063
+ if ('mentions' in message && !!message.mentions) {
1064
+ interactiveMessage.contextInfo = { mentionedJid: message.mentions };
1313
1065
  }
1314
- m = { invoiceMessage };
1066
+ m = { interactiveMessage };
1315
1067
  }
1316
- // vltcs@changes 31-01-26 --- Add direct externalAdReply access (no need to create contextInfo first)
1317
- if (hasOptionalProperty(message, 'externalAdReply') && !!message.externalAdReply) {
1318
- const messageType = Object.keys(m)[0];
1319
- const key = m[messageType];
1320
- const content = message.externalAdReply;
1321
- if ('thumbnail' in content && !Buffer.isBuffer(content.thumbnail)) {
1322
- throw new Boom('Thumbnail must in buffer type', { statusCode: 400 });
1323
- }
1324
- if (!content.url || typeof content.url !== 'string') {
1325
- content.url = DONATE_URL; // Lia@Note 02-02-26 --- Apologies if this feels cheeky, just a fallback
1326
- }
1327
- const externalAdReply = {
1328
- ...content,
1329
- body: content.body,
1330
- mediaType: content.mediaType || 1,
1331
- mediaUrl: content.url + '?update=' + Date.now(),
1332
- renderLargerThumbnail: content.largeThumbnail,
1333
- thumbnail: content.thumbnail,
1334
- thumbnailUrl: content.url,
1335
- title: content.title || LIBRARY_NAME
1068
+ else if ('collection' in message && !!message.collection) {
1069
+ const interactiveMessage = {
1070
+ collectionMessage: {
1071
+ bizJid: message.collection.bizJid,
1072
+ id: message.collection.id,
1073
+ messageVersion: message?.collection?.version
1074
+ }
1336
1075
  };
1337
- delete externalAdReply.subTitle;
1338
- delete externalAdReply.largeThumbnail;
1339
- delete externalAdReply.url;
1340
- if ('contextInfo' in key && !!key.contextInfo) {
1341
- key.contextInfo.externalAdReply = { ...key.contextInfo.externalAdReply, ...externalAdReply };
1342
- }
1343
- else if (key) {
1344
- key.contextInfo = { externalAdReply };
1345
- }
1346
- }
1347
- if (
1348
- (hasOptionalProperty(message, 'mentions') && message.mentions?.length) ||
1349
- (hasOptionalProperty(message, 'mentionAll') && message.mentionAll)
1350
- ) {
1351
- const messageType = Object.keys(m)[0];
1352
- const key = m[messageType];
1353
- if ('contextInfo' in key && !!key.contextInfo) {
1354
- key.contextInfo.mentionedJid = message.mentions || [];
1355
- }
1356
- else if (key) {
1357
- key.contextInfo = {
1358
- mentionedJid: message.mentions || []
1076
+ if ('text' in message) {
1077
+ interactiveMessage.body = { text: message.text };
1078
+ interactiveMessage.header = {
1079
+ title: message.title,
1080
+ subtitle: message.subtitle,
1081
+ hasMediaAttachment: false
1359
1082
  };
1360
1083
  }
1361
- if (message.mentionAll) {
1362
- key.contextInfo.mentionedJid = [];
1363
- key.contextInfo.nonJidMentions = 1;
1364
- }
1365
- }
1366
- if (hasOptionalProperty(message, 'contextInfo') && !!message.contextInfo) {
1367
- const messageType = Object.keys(m)[0];
1368
- const key = m[messageType];
1369
- if ('contextInfo' in key && !!key.contextInfo) {
1370
- key.contextInfo = { ...key.contextInfo, ...message.contextInfo };
1084
+ else {
1085
+ if ('caption' in message) {
1086
+ interactiveMessage.body = { text: message.caption };
1087
+ interactiveMessage.header = {
1088
+ title: message.title,
1089
+ subtitle: message.subtitle,
1090
+ hasMediaAttachment: message.hasMediaAttachment ? message.hasMediaAttachment : false,
1091
+ ...Object.assign(interactiveMessage, m)
1092
+ };
1093
+ }
1371
1094
  }
1372
- else if (key) {
1373
- key.contextInfo = message.contextInfo;
1095
+ if ('footer' in message && !!message.footer) {
1096
+ interactiveMessage.footer = { text: message.footer };
1374
1097
  }
1098
+ interactiveMessage.contextInfo = {
1099
+ ...(message.contextInfo || {}),
1100
+ ...(message.mentions ? { mentionedJid: message.mentions } : {})
1101
+ };
1102
+ m = { interactiveMessage };
1375
1103
  }
1376
- // vltcs@changes 31-01-26 --- Add "groupStatus" boolean to set contextInfo.isGroupStatus and wrap message into groupStatusMessageV2
1377
- if (hasOptionalProperty(message, 'groupStatus') && !!message.groupStatus) {
1378
- const messageType = Object.keys(m)[0];
1379
- const key = m[messageType];
1380
- if ('contextInfo' in key && !!key.contextInfo) {
1381
- key.contextInfo.isGroupStatus = message.groupStatus;
1382
- }
1383
- else if (key) {
1384
- key.contextInfo = {
1385
- isGroupStatus: message.groupStatus
1104
+ else if ('cards' in message && !!message.cards) {
1105
+ const slides = await Promise.all(message.cards.map(async (slide) => {
1106
+ const { image, video, product, title, body, footer, buttons } = slide;
1107
+ let header;
1108
+ if (product) {
1109
+ const { imageMessage } = await prepareWAMessageMedia({ image: product.productImage, ...options }, options);
1110
+ header = {
1111
+ productMessage: {
1112
+ product: {
1113
+ ...product,
1114
+ productImage: imageMessage,
1115
+ },
1116
+ ...slide
1117
+ }
1118
+ };
1119
+ }
1120
+ else if (image) {
1121
+ header = await prepareWAMessageMedia({ image: image, ...options }, options);
1122
+ }
1123
+ else if (video) {
1124
+ header = await prepareWAMessageMedia({ video: video, ...options }, options);
1386
1125
  }
1126
+ return {
1127
+ header: {
1128
+ title,
1129
+ hasMediaAttachment: true,
1130
+ ...header
1131
+ },
1132
+ body: { text: body },
1133
+ footer: { text: footer },
1134
+ nativeFlowMessage: { buttons }
1135
+ };
1136
+ }));
1137
+ const interactiveMessage = {
1138
+ carouselMessage: { cards: slides }
1139
+ };
1140
+ if ('text' in message) {
1141
+ interactiveMessage.body = { text: message.text };
1142
+ interactiveMessage.header = {
1143
+ title: message.title,
1144
+ subtitle: message.subtitle,
1145
+ hasMediaAttachment: false
1146
+ };
1387
1147
  }
1388
- m = { groupStatusMessageV2: { message: m } };
1389
- delete message.groupStatus;
1390
- }
1391
- // vltcs@changes 02-02-26 --- Add "interactiveAsTemplate" boolean to wrap interactiveMessage into templateMessage
1392
- else if (hasOptionalProperty(message, 'interactiveAsTemplate') && !!message.interactiveAsTemplate) {
1393
- if (!m.interactiveMessage) {
1394
- throw new Boom('Invalid message type for template', { statusCode: 400 }); // Lia@Note 02-02-26 --- To avoid bug 👀
1148
+ if ('footer' in message && !!message.footer) {
1149
+ interactiveMessage.footer = { text: message.footer };
1395
1150
  }
1396
- m = {
1397
- templateMessage: {
1398
- interactiveMessageTemplate: m.interactiveMessage,
1399
- templateId: message.id || 'template-' + Date.now() // Lia@Note 04-02-26 --- Minimal templateId to satisfy WhatsApp (⁠ ⁠ꈍ⁠ᴗ⁠ꈍ⁠)
1400
- }
1151
+ interactiveMessage.contextInfo = {
1152
+ ...(message.contextInfo || {}),
1153
+ ...(message.mentions ? { mentionedJid: message.mentions } : {})
1401
1154
  };
1402
- delete message.interactiveAsTemplate;
1155
+ m = { interactiveMessage };
1403
1156
  }
1404
- // vltcs@changes 30-01-26 --- Add "ephemeral" boolean to wrap message into ephemeralMessage like "viewOnce"
1405
1157
  if (hasOptionalProperty(message, 'ephemeral') && !!message.ephemeral) {
1406
1158
  m = { ephemeralMessage: { message: m } };
1407
- delete message.ephemeral;
1408
- }
1409
- else if (hasOptionalProperty(message, 'viewOnce') && !!message.viewOnce) {
1410
- m = { viewOnceMessage: { message: m } };
1411
1159
  }
1412
- // vltcs@changes 03-02-26 --- Add "viewOnceV2" boolean to wrap message into viewOnceMessageV2 like "viewOnce"
1413
- else if (hasOptionalProperty(message, 'viewOnceV2') && !!message.viewOnceV2) {
1160
+ if (hasOptionalProperty(message, 'viewOnce') && !!message.viewOnce) {
1414
1161
  m = { viewOnceMessageV2: { message: m } };
1415
- delete message.viewOnceV2;
1416
1162
  }
1417
- // vltcs@changes 03-02-26 --- Add "viewOnceV2Extension" boolean to wrap message into viewOnceMessageV2Extension like "viewOnce"
1418
- else if (hasOptionalProperty(message, 'viewOnceV2Extension') && !!message.viewOnceV2Extension) {
1163
+ if (hasOptionalProperty(message, 'viewOnceExt') && !!message.viewOnceExt) {
1419
1164
  m = { viewOnceMessageV2Extension: { message: m } };
1420
- delete message.viewOnceV2Extension;
1165
+ }
1166
+ if (hasOptionalProperty(message, 'mentions') && message.mentions?.length) {
1167
+ const messageType = Object.keys(m)[0];
1168
+ const key = m[messageType];
1169
+ if ('contextInfo' in key && !!key.contextInfo) {
1170
+ key.contextInfo.mentionedJid = message.mentions;
1171
+ }
1172
+ else if (key) {
1173
+ key.contextInfo = {
1174
+ mentionedJid: message.mentions
1175
+ };
1176
+ }
1421
1177
  }
1422
1178
  if (hasOptionalProperty(message, 'edit')) {
1423
1179
  m = {
@@ -1425,31 +1181,39 @@ export const generateWAMessageContent = async (message, options) => {
1425
1181
  key: message.edit,
1426
1182
  editedMessage: m,
1427
1183
  timestampMs: Date.now(),
1428
- type: ProtocolType.MESSAGE_EDIT
1184
+ type: WAProto.Message.ProtocolMessage.Type.MESSAGE_EDIT
1429
1185
  }
1186
+ };
1187
+ }
1188
+ if (hasOptionalProperty(message, 'contextInfo') && !!message.contextInfo) {
1189
+ const messageType = Object.keys(m)[0];
1190
+ const key = m[messageType];
1191
+ if ('contextInfo' in key && !!key.contextInfo) {
1192
+ key.contextInfo = { ...key.contextInfo, ...message.contextInfo };
1193
+ }
1194
+ else if (key) {
1195
+ key.contextInfo = message.contextInfo;
1430
1196
  }
1431
1197
  }
1432
- if (shouldIncludeReportingToken(m)) {
1198
+ if (shouldIncludeReportingToken(m) && !options.newsletter) {
1433
1199
  m.messageContextInfo = m.messageContextInfo || {};
1434
1200
  if (!m.messageContextInfo.messageSecret) {
1435
1201
  m.messageContextInfo.messageSecret = randomBytes(32);
1436
1202
  }
1437
1203
  }
1438
- return proto.Message.create(m);
1204
+ return WAProto.Message.create(m);
1439
1205
  };
1440
1206
  export const generateWAMessageFromContent = (jid, message, options) => {
1441
1207
  // set timestamp to now
1442
1208
  // if not specified
1443
1209
  if (!options.timestamp) {
1444
- options.timestamp = Date.now();
1210
+ options.timestamp = new Date();
1445
1211
  }
1446
- const messageContextInfo = message.messageContextInfo
1447
1212
  const innerMessage = normalizeMessageContent(message);
1448
1213
  const key = getContentType(innerMessage);
1449
1214
  const timestamp = unixTimestampSeconds(options.timestamp);
1450
- const isNewsletter = isJidNewsletter(jid);
1451
1215
  const { quoted, userJid } = options;
1452
- if (quoted && !isNewsletter) {
1216
+ if (quoted && !isJidNewsletter(jid)) {
1453
1217
  const participant = quoted.key.fromMe
1454
1218
  ? userJid // TODO: Add support for LIDs
1455
1219
  : quoted.participant || quoted.key.participant || quoted.key.remoteJid;
@@ -1476,14 +1240,14 @@ export const generateWAMessageFromContent = (jid, message, options) => {
1476
1240
  }
1477
1241
  }
1478
1242
  if (
1479
- // if we want to send a disappearing message
1480
- !!options?.ephemeralExpiration &&
1243
+ // if we want to send a disappearing message
1244
+ !!options?.ephemeralExpiration &&
1481
1245
  // and it's not a protocol message -- delete, toggle disappear message
1482
1246
  key !== 'protocolMessage' &&
1483
1247
  // already not converted to disappearing message
1484
1248
  key !== 'ephemeralMessage' &&
1485
1249
  // newsletters don't support ephemeral messages
1486
- !isNewsletter) {
1250
+ !isJidNewsletter(jid)) {
1487
1251
  /* @ts-ignore */
1488
1252
  innerMessage[key].contextInfo = {
1489
1253
  ...(innerMessage[key].contextInfo || {}),
@@ -1491,15 +1255,7 @@ export const generateWAMessageFromContent = (jid, message, options) => {
1491
1255
  //ephemeralSettingTimestamp: options.ephemeralOptions.eph_setting_ts?.toString()
1492
1256
  };
1493
1257
  }
1494
- // vltcs@changes 30-01-26 --- Add deviceListMetadata inside messageContextInfo for private chat
1495
- else if (messageContextInfo?.messageSecret && (isPnUser(jid) || isLidUser(jid))) {
1496
- messageContextInfo.deviceListMetadata = {
1497
- recipientKeyHash: randomBytes(10),
1498
- recipientTimestamp: unixTimestampSeconds()
1499
- };
1500
- messageContextInfo.deviceListMetadataVersion = 2
1501
- }
1502
- message = proto.Message.create(message);
1258
+ message = WAProto.Message.create(message);
1503
1259
  const messageJSON = {
1504
1260
  key: {
1505
1261
  remoteJid: jid,
@@ -1514,14 +1270,12 @@ export const generateWAMessageFromContent = (jid, message, options) => {
1514
1270
  };
1515
1271
  return WAProto.WebMessageInfo.fromObject(messageJSON);
1516
1272
  };
1517
- export const generateWAMessage = async (jid, content, options = {}) => {
1273
+ export const generateWAMessage = async (jid, content, options) => {
1518
1274
  // ensure msg ID is with every log
1519
1275
  options.logger = options?.logger?.child({ msgId: options.messageId });
1520
- // Pass jid in the options to generateWAMessageContent
1521
- if (jid && typeof options === 'object') {
1522
- options.jid = jid;
1523
- }
1524
- return generateWAMessageFromContent(jid, await generateWAMessageContent(content, options), options);
1276
+ // Pass jid + newsletter flag to generateWAMessageContent (like wiley)
1277
+ const _isNewsletter = typeof jid === 'string' && jid.endsWith('@newsletter');
1278
+ return generateWAMessageFromContent(jid, await generateWAMessageContent(content, { newsletter: _isNewsletter, ...options, jid }), options);
1525
1279
  };
1526
1280
  /** Get the key to access the true type of content */
1527
1281
  export const getContentType = (content) => {
@@ -1531,6 +1285,21 @@ export const getContentType = (content) => {
1531
1285
  return key;
1532
1286
  }
1533
1287
  };
1288
+ /**
1289
+ * Maps a message content type key to its MediaType string.
1290
+ * Handles ptvMessage → 'ptv', audioMessage ptt → 'ptt', etc.
1291
+ */
1292
+ export const getMediaTypeFromContentType = (contentType, content) => {
1293
+ if (!contentType)
1294
+ return undefined;
1295
+ if (contentType === 'ptvMessage')
1296
+ return 'ptv';
1297
+ if (contentType === 'audioMessage' && content?.[contentType]?.ptt)
1298
+ return 'ptt';
1299
+ if (contentType === 'videoMessage' && content?.[contentType]?.gifPlayback)
1300
+ return 'gif';
1301
+ return contentType.replace('Message', '');
1302
+ };
1534
1303
  /**
1535
1304
  * Normalizes ephemeral, view once messages to regular message content
1536
1305
  * Eg. image messages in ephemeral messages, in view once messages etc.
@@ -1550,33 +1319,16 @@ export const normalizeMessageContent = (content) => {
1550
1319
  content = inner.message;
1551
1320
  }
1552
1321
  return content;
1553
- // vltcs@changes 03-02-26 --- Add all futureProofMessage into getFutureProofMessage()
1554
1322
  function getFutureProofMessage(message) {
1555
- return (
1556
- message?.associatedChildMessage ||
1557
- message?.botForwardedMessage ||
1558
- message?.botInvokeMessage ||
1559
- message?.botTaskMessage ||
1323
+ return (message?.ephemeralMessage ||
1324
+ message?.viewOnceMessage ||
1560
1325
  message?.documentWithCaptionMessage ||
1326
+ message?.viewOnceMessageV2 ||
1327
+ message?.viewOnceMessageV2Extension ||
1561
1328
  message?.editedMessage ||
1562
- message?.ephemeralMessage ||
1563
- message?.eventCoverImage ||
1564
- message?.groupMentionedMessage ||
1565
- message?.groupStatusMentionMessage ||
1329
+ message?.associatedChildMessage ||
1566
1330
  message?.groupStatusMessage ||
1567
- message?.groupStatusMessageV2 ||
1568
- message?.limitSharingMessage ||
1569
- message?.lottieStickerMessage ||
1570
- message?.pollCreationMessageV4 ||
1571
- message?.pollCreationOptionImageMessage ||
1572
- message?.questionMessage ||
1573
- message?.questionReplyMessage ||
1574
- message?.statusAddYours ||
1575
- message?.statusMentionMessage ||
1576
- message?.viewOnceMessage ||
1577
- message?.viewOnceMessageV2 ||
1578
- message?.viewOnceMessageV2Extension
1579
- );
1331
+ message?.groupStatusMessageV2);
1580
1332
  }
1581
1333
  };
1582
1334
  /**
@@ -1769,7 +1521,7 @@ export const downloadMediaMessage = async (message, type, options, ctx) => {
1769
1521
  throw new Boom('No message present', { statusCode: 400, data: message });
1770
1522
  }
1771
1523
  const contentType = getContentType(mContent);
1772
- let mediaType = contentType?.replace('Message', '');
1524
+ let mediaType = getMediaTypeFromContentType(contentType, mContent);
1773
1525
  const media = mContent[contentType];
1774
1526
  if (!media || typeof media !== 'object' || (!('url' in media) && !('thumbnailDirectPath' in media))) {
1775
1527
  throw new Boom(`"${contentType}" message is not a media message`);
@@ -1809,77 +1561,138 @@ export const assertMediaContent = (content) => {
1809
1561
  }
1810
1562
  return mediaContent;
1811
1563
  };
1812
- /**
1813
- * Checks if a WebP buffer is animated by looking for VP8X chunk with animation flag
1814
- * or ANIM/ANMF chunks
1815
- */
1816
- const isAnimatedWebP = (buffer) => {
1817
- // WebP must start with RIFF....WEBP
1818
- if (
1819
- buffer.length < 12 ||
1820
- buffer[0] !== 0x52 ||
1821
- buffer[1] !== 0x49 ||
1822
- buffer[2] !== 0x46 ||
1823
- buffer[3] !== 0x46 ||
1824
- buffer[8] !== 0x57 ||
1825
- buffer[9] !== 0x45 ||
1826
- buffer[10] !== 0x42 ||
1827
- buffer[11] !== 0x50
1564
+
1565
+ export const patchMessageForMdIfRequired = (message) => {
1566
+ if (message?.buttonsMessage ||
1567
+ message?.templateMessage ||
1568
+ message?.listMessage ||
1569
+ message?.interactiveMessage?.nativeFlowMessage
1828
1570
  ) {
1829
- return false;
1830
- };
1831
- // Parse chunks starting after RIFF header (12 bytes)
1832
- let offset = 12;
1833
- while (offset < buffer.length - 8) {
1834
- const chunkFourCC = buffer.toString('ascii', offset, offset + 4);
1835
- const chunkSize = buffer.readUInt32LE(offset + 4);
1836
- if (chunkFourCC === 'VP8X') {
1837
- // VP8X extended header, check animation flag (bit 1 at offset+8)
1838
- const flagsOffset = offset + 8;
1839
- if (flagsOffset < buffer.length) {
1840
- const flags = buffer[flagsOffset];
1841
- if (flags & 0x02) {
1842
- return true;
1843
- };
1844
- };
1845
- } else if (chunkFourCC === 'ANIM' || chunkFourCC === 'ANMF') {
1846
- // ANIM or ANMF chunks indicate animation
1847
- return true;
1571
+ message = {
1572
+ viewOnceMessageV2Extension: {
1573
+ message: {
1574
+ messageContextInfo: {
1575
+ deviceListMetadataVersion: 2,
1576
+ deviceListMetadata: {}
1577
+ },
1578
+ ...message
1579
+ }
1580
+ }
1848
1581
  };
1849
- // Move to next chunk (chunk size + 8 bytes header, padded to even)
1850
- offset += 8 + chunkSize + (chunkSize % 2);
1851
- };
1852
- return false;
1582
+ }
1583
+ return message;
1853
1584
  };
1854
- /**
1855
- * Checks if a buffer is a WebP file
1856
- */
1857
- const isWebPBuffer = (buffer) => {
1858
- return (
1859
- buffer.length >= 12 &&
1860
- buffer[0] === 0x52 &&
1861
- buffer[1] === 0x49 &&
1862
- buffer[2] === 0x46 &&
1863
- buffer[3] === 0x46 &&
1864
- buffer[8] === 0x57 &&
1865
- buffer[9] === 0x45 &&
1866
- buffer[10] === 0x42 &&
1867
- buffer[11] === 0x50
1868
- );
1585
+ export const prepareAlbumMessageContent = async (jid, albums, options) => {
1586
+ let mediaHandle;
1587
+ let mediaMsg;
1588
+ const message = [];
1589
+ const albumMsg = generateWAMessageFromContent(jid, {
1590
+ albumMessage: {
1591
+ expectedImageCount: albums.filter(item => 'image' in item).length,
1592
+ expectedVideoCount: albums.filter(item => 'video' in item).length
1593
+ }
1594
+ }, options);
1595
+ await options.sock.relayMessage(jid, albumMsg.message, { messageId: albumMsg.key.id });
1596
+ for (const i in albums) {
1597
+ const media = albums[i];
1598
+ if ('image' in media) {
1599
+ mediaMsg = await generateWAMessage(jid, { image: media.image, ...media, ...options }, {
1600
+ userJid: options.userJid,
1601
+ upload: async (encFilePath, opts) => {
1602
+ const up = await options.sock.waUploadToServer(encFilePath, { ...opts, newsletter: isJidNewsletter(jid) });
1603
+ mediaHandle = up.handle;
1604
+ return up;
1605
+ },
1606
+ ...options
1607
+ });
1608
+ } else if ('video' in media) {
1609
+ mediaMsg = await generateWAMessage(jid, { video: media.video, ...media, ...options }, {
1610
+ userJid: options.userJid,
1611
+ upload: async (encFilePath, opts) => {
1612
+ const up = await options.sock.waUploadToServer(encFilePath, { ...opts, newsletter: isJidNewsletter(jid) });
1613
+ mediaHandle = up.handle;
1614
+ return up;
1615
+ },
1616
+ ...options
1617
+ });
1618
+ }
1619
+ if (mediaMsg) {
1620
+ mediaMsg.message.messageContextInfo = {
1621
+ messageSecret: randomBytes(32),
1622
+ messageAssociation: {
1623
+ associationType: 1,
1624
+ parentMessageKey: albumMsg.key
1625
+ }
1626
+ };
1627
+ }
1628
+ message.push(mediaMsg);
1629
+ }
1630
+ return message;
1869
1631
  };
1870
1632
  /**
1871
- * vltcs@changes 30-01-26
1872
- * ---
1873
- * Determines whether a message should include a Biz Binary Node.
1874
- * A Biz Binary Node is added only for interactive messages
1875
- * such as buttons or other supported interactive types.
1633
+ * Ekstrak quoted message dari contextInfo pesan.
1634
+ * Return object yang sudah dinormalisasi, termasuk debug info LID/PN.
1635
+ *
1636
+ * @param {object} msg - WAMessage object dari event messages.upsert
1637
+ * @param {object} [options]
1638
+ * @param {boolean} [options.debug] - jika true, log info ke console
1639
+ * @returns {object|null} quoted message info atau null jika tidak ada
1876
1640
  */
1877
- export const shouldIncludeBizBinaryNode = (message) => {
1878
- const messageType = getContentType(message);
1879
- return (
1880
- messageType === 'buttonsMessage' ||
1881
- messageType === 'interactiveMessage' ||
1882
- messageType === 'listMessage' ||
1883
- messageType === 'templateMessage'
1884
- );
1885
- };
1641
+ export const getQuotedMsg = (msg, options = {}) => {
1642
+ const { debug = false } = options;
1643
+ const msgContent = normalizeMessageContent(msg?.message);
1644
+ if (!msgContent) return null;
1645
+ const msgType = getContentType(msgContent);
1646
+ if (!msgType) return null;
1647
+ const innerMsg = msgContent[msgType];
1648
+ if (!innerMsg) return null;
1649
+ const contextInfo = innerMsg.contextInfo;
1650
+ if (!contextInfo?.stanzaId || !contextInfo?.quotedMessage) return null;
1651
+
1652
+ // Normalisasi participant (sender quoted message)
1653
+ const participant = contextInfo.participant || msg.key?.participant || msg.key?.remoteJid || '';
1654
+
1655
+ // Normalisasi mentionedJid di quoted message
1656
+ const quotedMsgContent = normalizeMessageContent(contextInfo.quotedMessage);
1657
+ const quotedMsgType = getContentType(quotedMsgContent);
1658
+ const quotedInnerMsg = quotedMsgType ? quotedMsgContent?.[quotedMsgType] : null;
1659
+ const quotedMentionedJid = quotedInnerMsg?.contextInfo?.mentionedJid || [];
1660
+ const quotedText = quotedInnerMsg?.text || quotedInnerMsg?.caption || quotedInnerMsg?.conversation || '';
1661
+
1662
+ const result = {
1663
+ key: {
1664
+ id: contextInfo.stanzaId,
1665
+ remoteJid: contextInfo.remoteJid || msg.key?.remoteJid || '',
1666
+ participant: participant,
1667
+ fromMe: false
1668
+ },
1669
+ message: contextInfo.quotedMessage,
1670
+ participant,
1671
+ sender: participant,
1672
+ text: quotedText,
1673
+ type: quotedMsgType || '',
1674
+ mentionedJid: quotedMentionedJid,
1675
+ // Debug info
1676
+ _rawContextInfo: debug ? contextInfo : undefined
1677
+ };
1678
+
1679
+ if (debug) {
1680
+ const hasLidInText = typeof quotedText === 'string' && /@\d{13,20}/.test(quotedText);
1681
+ const hasLidInMentioned = quotedMentionedJid.some(j => j?.endsWith('@lid'));
1682
+ const hasLidParticipant = participant?.endsWith('@lid');
1683
+ console.log('[getQuotedMsg DEBUG]', JSON.stringify({
1684
+ stanzaId: contextInfo.stanzaId,
1685
+ participant,
1686
+ participantIsLid: hasLidParticipant,
1687
+ quotedText,
1688
+ textHasLid: hasLidInText,
1689
+ mentionedJid: quotedMentionedJid,
1690
+ mentionedHasLid: hasLidInMentioned,
1691
+ quotedMsgType
1692
+ }, null, 2));
1693
+ }
1694
+
1695
+ return result;
1696
+ };
1697
+
1698
+ //# sourceMappingURL=messages.js.map