violetics 7.0.7-alpha → 7.0.9-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 (411) hide show
  1. package/LICENSE +1 -2
  2. package/README.md +493 -980
  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 +2670 -9199
  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 +14 -22
  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 +2 -1
  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 +154 -69
  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 +471 -253
  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 +620 -226
  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 +109 -62
  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 +12 -3
  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 +2 -1
  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 -6
  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 +7 -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 +2 -1
  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 +185 -185
  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 +932 -1120
  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 -1
  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 +55 -77
  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 +14 -113
  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 +5 -2
  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 -53
  406. package/.github/workflows/publish.yml +0 -75
  407. package/.pnp.cjs +0 -8572
  408. package/.pnp.loader.mjs +0 -2126
  409. package/.yarn/install-state.gz +0 -0
  410. package/lib/Utils/offline-node-processor.js +0 -39
  411. 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
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
- '@itsliaaa/baileys'
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];
@@ -122,99 +184,46 @@ export const prepareWAMessageMedia = async (message, options) => {
122
184
  return obj;
123
185
  }
124
186
  }
125
- const isNewsletter = isJidNewsletter(options.jid);
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
- const requiresThumbnailComputation = (mediaType === 'image' || mediaType === 'video') && typeof uploadData.jpegThumbnail === 'undefined';
128
- const requiresWaveformProcessing = mediaType === 'audio' && uploadData.ptt === true && typeof uploadData.waveform === 'undefined';
129
- const requiresAudioBackground = options.backgroundColor && mediaType === 'audio' && uploadData.ptt === true;
190
+ const requiresThumbnailComputation = (mediaType === 'image' || mediaType === 'video') && typeof uploadData['jpegThumbnail'] === 'undefined';
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
- // Lia@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, thumbnailDirectPath, thumbnailSha256 }] = 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
- thumbnailDirectPath,
181
- thumbnailSha256,
182
- ...uploadData
183
- })
184
- });
185
- if (uploadData.ptv) {
186
- obj.ptvMessage = obj.videoMessage;
187
- delete obj.videoMessage;
188
- }
189
- if (obj.stickerMessage) {
190
- obj.stickerMessage.stickerSentTs = Date.now();
191
- }
192
- if (cacheableKey) {
193
- logger?.debug({ cacheableKey }, 'set cache');
194
- await options.mediaCache.set(cacheableKey, WAProto.Message.encode(obj).finish());
195
- }
196
- return obj;
197
- }
198
- const { mediaKey, encFilePath, originalFilePath, fileEncSha256, fileSha256, fileLength } = await encryptedStream(uploadData.media, options.mediaTypeOverride || mediaType, {
199
- logger,
200
- saveOriginalFileIfRequired: requiresOriginalForSomeProcessing,
201
- opts: options.options
202
- });
203
- const fileEncSha256B64 = fileEncSha256.toString('base64');
204
- 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([
205
214
  (async () => {
206
- const result = await options.upload(encFilePath, {
215
+ const result = await options.upload(encWriteStream, {
207
216
  fileEncSha256B64,
208
217
  mediaType,
209
- timeoutMs: options.mediaUploadTimeoutMs
218
+ timeoutMs: options.mediaUploadTimeoutMs,
219
+ newsletter: !!options.newsletter
210
220
  });
211
- logger?.debug({ mediaType, cacheableKey }, 'uploaded media');
212
221
  return result;
213
222
  })(),
214
223
  (async () => {
215
224
  try {
216
225
  if (requiresThumbnailComputation) {
217
- const { thumbnail, originalImageDimensions } = await generateThumbnail(originalFilePath, mediaType, options);
226
+ const { thumbnail, originalImageDimensions } = await generateThumbnail(bodyPath, mediaType, options);
218
227
  uploadData.jpegThumbnail = thumbnail;
219
228
  if (!uploadData.width && originalImageDimensions) {
220
229
  uploadData.width = originalImageDimensions.width;
@@ -224,53 +233,71 @@ export const prepareWAMessageMedia = async (message, options) => {
224
233
  logger?.debug('generated thumbnail');
225
234
  }
226
235
  if (requiresDurationComputation) {
227
- 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
+ }
228
247
  logger?.debug('computed audio duration');
229
248
  }
230
249
  if (requiresWaveformProcessing) {
231
- uploadData.waveform = await getAudioWaveform(originalFilePath, logger);
232
- 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
+ }
233
257
  }
234
258
  if (requiresAudioBackground) {
235
259
  uploadData.backgroundArgb = await assertColor(options.backgroundColor);
236
260
  logger?.debug('computed backgroundColor audio status');
237
261
  }
238
- }
239
- catch (error) {
262
+ } catch (error) {
240
263
  logger?.warn({ trace: error.stack }, 'failed to obtain extra info');
241
264
  }
242
265
  })()
243
266
  ]).finally(async () => {
244
267
  try {
245
- await fs.unlink(encFilePath);
246
- if (originalFilePath) {
247
- await fs.unlink(originalFilePath);
268
+ if (!Buffer.isBuffer(encWriteStream)) {
269
+ encWriteStream.destroy?.();
248
270
  }
249
- logger?.debug('removed tmp files');
250
- }
251
- catch (error) {
271
+ if (didSaveToTmpPath && bodyPath) {
272
+ await fs.unlink(bodyPath).catch(() => {});
273
+ }
274
+ } catch (error) {
252
275
  logger?.warn('failed to remove tmp file');
253
276
  }
254
277
  });
255
- delete uploadData.media;
256
- const obj = proto.Message.create({
278
+ const obj = WAProto.Message.fromObject({
257
279
  [`${mediaType}Message`]: MessageTypeProto[mediaType].fromObject({
258
- url: mediaUrl,
280
+ url: uploadHandle ? undefined : mediaUrl,
259
281
  directPath,
260
- mediaKey,
261
- fileEncSha256,
282
+ mediaKey: mediaKey,
283
+ fileEncSha256: fileEncSha256,
262
284
  fileSha256,
263
285
  fileLength,
264
- mediaKeyTimestamp: unixTimestampSeconds(),
265
- ...uploadData
286
+ mediaKeyTimestamp: uploadHandle ? undefined : unixTimestampSeconds(),
287
+ ...uploadData,
288
+ media: undefined,
289
+ ...(options?.contextInfo ? { contextInfo: options.contextInfo } : {})
266
290
  })
267
291
  });
268
292
  if (uploadData.ptv) {
269
293
  obj.ptvMessage = obj.videoMessage;
270
294
  delete obj.videoMessage;
271
295
  }
272
- if (obj.stickerMessage) {
273
- 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') {
274
301
  }
275
302
  if (cacheableKey) {
276
303
  logger?.debug({ cacheableKey }, 'set cache');
@@ -284,335 +311,13 @@ export const prepareDisappearingMessageSettingContent = (ephemeralExpiration) =>
284
311
  ephemeralMessage: {
285
312
  message: {
286
313
  protocolMessage: {
287
- type: ProtocolType.EPHEMERAL_SETTING,
314
+ type: WAProto.Message.ProtocolMessage.Type.EPHEMERAL_SETTING,
288
315
  ephemeralExpiration
289
316
  }
290
317
  }
291
318
  }
292
319
  };
293
- return content;
294
- };
295
- // Lia@Changes 31-01-26 --- Extract product message into a standalone function so it can also be reused as the header for interactive messages
296
- const prepareProductMessage = async (message, options) => {
297
- if (!message.businessOwnerJid) {
298
- throw new Boom('"businessOwnerJid" is missing from the content', { statusCode: 400 });
299
- }
300
- const { imageMessage } = await prepareWAMessageMedia({ image: message.image || message.product.productImage }, options);
301
- // Lia@Changes 01-02-26 --- Add product message default value
302
- const content = {
303
- ...message,
304
- product: {
305
- currencyCode: 'IDR',
306
- priceAmount1000: 1000,
307
- title: LIBRARY_NAME,
308
- ...message.product,
309
- productImage: imageMessage
310
- }
311
- };
312
- delete content.image;
313
- return content;
314
- };
315
- /**
316
- * Lia@Note 30-01-26
317
- * ---
318
- * Credits: Work on ensuring stickerPackMessage fields are valid by @jlucaso1 (https://github.com/jlucaso1).
319
- * based on https://github.com/WhiskeySockets/Baileys/pull/1561
320
- */
321
- const prepareStickerPackMessage = async (message, options) => {
322
- const { cover, stickers = [], name = '📦 Sticker Pack', publisher = 'GitHub: itsliaaa', description = '🏷️ itsliaaa/baileys' } = message;
323
- if (stickers.length > 60) {
324
- throw new Boom('Sticker pack exceeds the maximum limit of 60 stickers', { statusCode: 400 });
325
- }
326
- if (stickers.length === 0) {
327
- throw new Boom('Sticker pack must contain at least one sticker', { statusCode: 400 });
328
- }
329
- if (!cover) {
330
- throw new Boom('Sticker pack must contain a cover', { statusCode: 400 });
331
- }
332
- // Lia@Changes 01-02-26 --- Add caching for sticker pack (similiar to prepareWAMessageMedia)
333
- const cacheableKey = Array.isArray(stickers) &&
334
- stickers.length &&
335
- !!options.mediaCache &&
336
- 'sticker:' + stickers
337
- .reduce((acc, x) => {
338
- const url = typeof x.data === 'object' &&
339
- 'url' in x.data &&
340
- !!x.data.url &&
341
- x.data.url;
342
- if (url) acc.push(url);
343
- return acc;
344
- }, [])
345
- .join('@');
346
- if (cacheableKey) {
347
- const mediaBuff = await options.mediaCache.get(cacheableKey);
348
- if (mediaBuff) {
349
- options.logger?.debug({ cacheableKey }, 'got media cache hit');
350
- return proto.Message.StickerPackMessage.decode(mediaBuff);
351
- }
352
- }
353
- const lib = await getImageProcessingLibrary();
354
- const stickerPackIdValue = generateMessageIDV2();
355
- const stickerData = {};
356
- const stickerPromises = stickers.map(async (sticker, i) => {
357
- const { stream } = await getStream(sticker.data);
358
- const buffer = await toBuffer(stream);
359
- let webpBuffer,
360
- isAnimated = false;
361
- const isWebP = isWebPBuffer(buffer);
362
- if (isWebP) {
363
- // Already WebP - preserve original to keep exif metadata and animation
364
- webpBuffer = buffer;
365
- isAnimated = isAnimatedWebP(buffer);
366
- }
367
- else if ('sharp' in lib && lib.sharp?.default) {
368
- // Convert to WebP, preserving metadata
369
- webpBuffer = await lib
370
- .sharp
371
- .default(buffer)
372
- .resize(512, 512, { fit: 'inside' })
373
- .webp({ quality: 80 })
374
- .toBuffer();
375
- // Non-WebP inputs converted to WebP are not animated
376
- isAnimated = false;
377
- }
378
- else if ('image' in lib && lib.image?.Transformer) {
379
- webpBuffer = await new lib
380
- .image
381
- .Transformer(buffer)
382
- .resize(512, 512)
383
- .webp(80);
384
- // Non-WebP inputs converted to WebP are not animated
385
- isAnimated = false;
386
- }
387
- else {
388
- 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.');
389
- }
390
- if (webpBuffer.length > 1024 * 1024) {
391
- throw new Boom(`Sticker at index ${i} exceeds the 1MB size limit`, { statusCode: 400 });
392
- }
393
- const hash = sha256(webpBuffer).toString('base64').replace(/\//g, '-');
394
- const fileName = hash + '.webp';
395
- stickerData[fileName] = [new Uint8Array(webpBuffer), { level: 0 }];
396
- return {
397
- fileName,
398
- mimetype: 'image/webp',
399
- isAnimated,
400
- emojis: sticker.emojis || ['✨'],
401
- accessibilityLabel: sticker.accessibilityLabel || '‎'
402
- };
403
- });
404
- const stickerMetadata = await Promise.all(stickerPromises);
405
- // Process and add cover/tray icon to the ZIP
406
- const trayIconFileName = stickerPackIdValue + '.webp';
407
- const { stream: coverStream } = await getStream(cover);
408
- const coverBuffer = await toBuffer(coverStream);
409
- let coverWebpBuffer;
410
- const isCoverWebP = isWebPBuffer(coverBuffer);
411
- if (isCoverWebP) {
412
- // Already WebP - preserve original to keep exif metadata
413
- coverWebpBuffer = coverBuffer;
414
- }
415
- else if ('sharp' in lib && lib.sharp?.default) {
416
- coverWebpBuffer = await lib
417
- .sharp
418
- .default(coverBuffer)
419
- .resize(512, 512, { fit: 'inside' })
420
- .webp({ quality: 80 })
421
- .toBuffer();
422
- }
423
- else if ('image' in lib && lib.image?.Transformer) {
424
- coverWebpBuffer = await new lib
425
- .image
426
- .Transformer(coverBuffer)
427
- .resize(512, 512)
428
- .webp(80);
429
- }
430
- else {
431
- 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.');
432
- }
433
- // Add cover to ZIP data
434
- stickerData[trayIconFileName] = [new Uint8Array(coverWebpBuffer), { level: 0 }];
435
- const zipBuffer = await new Promise((resolve, reject) => {
436
- zip(stickerData, (error, data) => {
437
- if (error) {
438
- reject(error);
439
- } else {
440
- resolve(Buffer.from(data));
441
- }
442
- });
443
- });
444
- const stickerPackSize = zipBuffer.length;
445
- const stickerPackUpload = await encryptedStream(zipBuffer, 'sticker-pack', {
446
- logger: options.logger,
447
- opts: options.options
448
- });
449
- const stickerPackUploadResult = await options.upload(stickerPackUpload.encFilePath, {
450
- fileEncSha256B64: stickerPackUpload.fileEncSha256.toString('base64'),
451
- mediaType: 'sticker-pack',
452
- timeoutMs: options.mediaUploadTimeoutMs
453
- });
454
- await fs.unlink(stickerPackUpload.encFilePath);
455
- const obj = {
456
- name: name,
457
- publisher: publisher,
458
- stickerPackId: stickerPackIdValue,
459
- packDescription: description,
460
- stickerPackOrigin: proto.Message.StickerPackMessage.StickerPackOrigin.USER_CREATED,
461
- stickerPackSize: stickerPackSize,
462
- stickers: stickerMetadata,
463
- fileSha256: stickerPackUpload.fileSha256,
464
- fileEncSha256: stickerPackUpload.fileEncSha256,
465
- mediaKey: stickerPackUpload.mediaKey,
466
- directPath: stickerPackUploadResult.directPath,
467
- fileLength: stickerPackUpload.fileLength,
468
- mediaKeyTimestamp: unixTimestampSeconds(),
469
- trayIconFileName: trayIconFileName
470
- };
471
- try {
472
- // Reuse the cover buffer we already processed for thumbnail generation
473
- let thumbnailBuffer;
474
- if ('sharp' in lib && lib.sharp?.default) {
475
- thumbnailBuffer = await lib
476
- .sharp
477
- .default(coverBuffer)
478
- .resize(252, 252)
479
- .jpeg()
480
- .toBuffer();
481
- }
482
- if ('image' in lib && lib.image?.Transformer) {
483
- thumbnailBuffer = await new lib
484
- .image
485
- .Transformer(coverBuffer)
486
- .resize(252, 252)
487
- .jpeg();
488
- }
489
- else if ('jimp' in lib && lib.jimp?.Jimp) {
490
- const jimpImage = await lib.jimp.Jimp.read(coverBuffer);
491
- thumbnailBuffer = await jimpImage
492
- .resize({ w: 252, h: 252 })
493
- .getBuffer('image/jpeg');
494
- }
495
- else {
496
- throw new Error('No image processing library available for thumbnail generation');
497
- }
498
- if (!thumbnailBuffer || thumbnailBuffer.length === 0) {
499
- throw new Error('Failed to generate thumbnail buffer');
500
- }
501
- const thumbUpload = await encryptedStream(thumbnailBuffer, 'thumbnail-sticker-pack', {
502
- logger: options.logger,
503
- opts: options.options,
504
- mediaKey: stickerPackUpload.mediaKey // Use same mediaKey as the sticker pack ZIP
505
- });
506
- const thumbUploadResult = await options.upload(thumbUpload.encFilePath, {
507
- fileEncSha256B64: thumbUpload.fileEncSha256.toString('base64'),
508
- mediaType: 'thumbnail-sticker-pack',
509
- timeoutMs: options.mediaUploadTimeoutMs
510
- });
511
- await fs.unlink(thumbUpload.encFilePath);
512
- Object.assign(obj, {
513
- thumbnailDirectPath: thumbUploadResult.directPath,
514
- thumbnailSha256: thumbUpload.fileSha256,
515
- thumbnailEncSha256: thumbUpload.fileEncSha256,
516
- thumbnailHeight: 252,
517
- thumbnailWidth: 252,
518
- imageDataHash: sha256(thumbnailBuffer).toString('base64')
519
- });
520
- }
521
- catch (error) {
522
- options.logger?.warn?.(`Thumbnail generation failed: ${error}`);
523
- }
524
- const content = obj;
525
- if (cacheableKey) {
526
- options.logger?.debug({ cacheableKey }, 'set cache');
527
- await options.mediaCache.set(cacheableKey, WAProto.Message.StickerPackMessage.encode(content).finish());
528
- }
529
- return WAProto.Message.StickerPackMessage.fromObject(content);
530
- };
531
- // Lia@Changes 30-01-26 --- Add native flow button helper for interactive message
532
- const prepareNativeFlowButtons = (message) => {
533
- const buttons = message.nativeFlow
534
- const isButtonsFieldArray = Array.isArray(buttons);
535
- const correctedField = isButtonsFieldArray ? buttons : buttons.buttons;
536
- const messageParamsJson = {};
537
- // Lia@Changes 31-01-26 --- Add coupon and options inside interactive message
538
- if (hasOptionalProperty(message, 'couponCode') && !!message.couponCode) {
539
- Object.assign(messageParamsJson, {
540
- limited_time_offer: {
541
- text: message.couponText || LIBRARY_NAME,
542
- url: message.couponUrl || DONATE_URL, // Lia@Note 02-02-26 --- Apologies if this feels cheeky, just a fallback
543
- copy_code: message.couponCode,
544
- expiration_time: Date.now() * 2
545
- }
546
- });
547
- }
548
- if (hasOptionalProperty(message, 'optionText') && !!message.optionText) {
549
- Object.assign(messageParamsJson, {
550
- bottom_sheet: {
551
- in_thread_buttons_limit: 1,
552
- divider_indices: Array.from(
553
- { length: correctedField.length },
554
- (_, index) => index
555
- ),
556
- list_title: message.optionTitle || '📄 Select Options',
557
- button_title: message.optionText
558
- }
559
- });
560
- }
561
- return {
562
- buttons: correctedField.map(button => {
563
- const buttonText = button.text || button.buttonText;
564
- if (hasOptionalProperty(button, 'id') && !!button.id) {
565
- return {
566
- name: 'quick_reply',
567
- buttonParamsJson: JSON.stringify({
568
- display_text: buttonText || '👉🏻 Click',
569
- id: button.id
570
- })
571
- };
572
- }
573
- else if (hasOptionalProperty(button, 'copy') && !!button.copy) {
574
- return {
575
- name: 'cta_copy',
576
- buttonParamsJson: JSON.stringify({
577
- display_text: buttonText || '📋 Copy',
578
- copy_code: button.copy
579
- })
580
- };
581
- }
582
- else if (hasOptionalProperty(button, 'url') && !!button.url) {
583
- return {
584
- name: 'cta_url',
585
- buttonParamsJson: JSON.stringify({
586
- display_text: buttonText || '🌐 Visit',
587
- url: button.url,
588
- merchant_url: button.url
589
- })
590
- };
591
- }
592
- else if (hasOptionalProperty(button, 'call') && !!button.call) {
593
- return {
594
- name: 'cta_call',
595
- buttonParamsJson: JSON.stringify({
596
- display_text: buttonText || '📞 Call',
597
- phone_number: button.call
598
- })
599
- };
600
- }
601
- // Lia@Changes 12-03-26 --- Add "single_select" shortcut \⁠(⁠°⁠o⁠°⁠)⁠/
602
- else if (hasOptionalProperty(button, 'sections') && !!button.sections) {
603
- return {
604
- name: 'single_select',
605
- buttonParamsJson: JSON.stringify({
606
- title: buttonText || '📋 Select',
607
- sections: button.sections
608
- })
609
- };
610
- }
611
- return button;
612
- }),
613
- messageParamsJson: JSON.stringify(messageParamsJson),
614
- messageVersion: 1
615
- };
320
+ return WAProto.Message.fromObject(content);
616
321
  };
617
322
  /**
618
323
  * Generate forwarded message content like WA does
@@ -620,7 +325,7 @@ const prepareNativeFlowButtons = (message) => {
620
325
  * @param options.forceForward will show the message as forwarded even if it is from you
621
326
  */
622
327
  export const generateForwardMessageContent = (message, forceForward) => {
623
- let content = message.message || message;
328
+ let content = message.message;
624
329
  if (!content) {
625
330
  throw new Boom('no content in message', { statusCode: 400 });
626
331
  }
@@ -636,65 +341,28 @@ export const generateForwardMessageContent = (message, forceForward) => {
636
341
  key = 'extendedTextMessage';
637
342
  }
638
343
  const key_ = content?.[key];
639
- const contextInfo = {};
640
344
  if (score > 0) {
641
- contextInfo.forwardingScore = score;
642
- contextInfo.isForwarded = true;
643
- }
644
- // when forwarding a newsletter/channel message, add the newsletter context
645
- // so the server knows where to find the original media
646
- const remoteJid = message.key?.remoteJid;
647
- if (remoteJid && isJidNewsletter(remoteJid)) {
648
- contextInfo.forwardedNewsletterMessageInfo = {
649
- newsletterJid: remoteJid,
650
- serverMessageId: message.key?.server_id ? parseInt(message.key.server_id) : null,
651
- newsletterName: null
652
- };
653
- // strip messageContextInfo (contains messageSecret etc.) as WA Web does
654
- delete content.messageContextInfo;
345
+ key_.contextInfo = { forwardingScore: score, isForwarded: true };
346
+ }
347
+ else {
348
+ key_.contextInfo = {};
655
349
  }
656
- key_.contextInfo = contextInfo;
657
350
  return content;
658
351
  };
659
352
  export const hasNonNullishProperty = (message, key) => {
660
- return message != null &&
661
- typeof message === 'object' &&
353
+ return (typeof message === 'object' &&
354
+ message !== null &&
662
355
  key in message &&
663
- message[key] != null;
664
- };
665
- export const hasOptionalProperty = (obj, key) => {
666
- return obj != null &&
667
- typeof obj === 'object' &&
668
- key in obj &&
669
- obj[key] != null;
670
- };
671
- // Lia@Changes 06-02-26 --- Validate album message media to avoid bug 👀
672
- export const hasValidAlbumMedia = (message) => {
673
- return message.imageMessage ||
674
- message.videoMessage;
675
- };
676
- export const hasValidInteractiveHeader = (message) => {
677
- return message.imageMessage ||
678
- message.videoMessage ||
679
- message.documentMessage ||
680
- message.productMessage ||
681
- message.locationMessage;
682
- };
683
- // Lia@Changes 30-01-26 --- Validate carousel cards header to avoid bug 👀
684
- export const hasValidCarouselHeader = (message) => {
685
- return message.imageMessage ||
686
- message.videoMessage ||
687
- message.productMessage;
356
+ message[key] !== null &&
357
+ message[key] !== undefined);
688
358
  };
359
+ function hasOptionalProperty(obj, key) {
360
+ return typeof obj === 'object' && obj !== null && key in obj && obj[key] !== null;
361
+ }
689
362
  export const generateWAMessageContent = async (message, options) => {
690
363
  var _a, _b;
691
364
  let m = {};
692
- // Lia@Changes 30-01-26 --- Add "raw" boolean to send raw messages directly via generateWAMessage()
693
- if (hasNonNullishProperty(message, 'raw')) {
694
- delete message.raw;
695
- return proto.Message.create(message);
696
- }
697
- else if (hasNonNullishProperty(message, 'text')) {
365
+ if (hasNonNullishProperty(message, 'text')) {
698
366
  const extContent = { text: message.text };
699
367
  let urlInfo = message.linkPreview;
700
368
  if (typeof urlInfo === 'undefined') {
@@ -726,58 +394,65 @@ export const generateWAMessageContent = async (message, options) => {
726
394
  m.extendedTextMessage = extContent;
727
395
  }
728
396
  else if (hasNonNullishProperty(message, 'contacts')) {
729
- const { contacts } = message; // Lia@Changes 04-02-26 --- Destructured for readability & cleaner access (⁠✷⁠‿⁠✷⁠)
730
- const contactLen = contacts.contacts.length;
397
+ const contactLen = message.contacts.contacts.length;
731
398
  if (!contactLen) {
732
399
  throw new Boom('require atleast 1 contact', { statusCode: 400 });
733
400
  }
734
401
  if (contactLen === 1) {
735
- m.contactMessage = contacts.contacts[0];
402
+ m.contactMessage = WAProto.Message.ContactMessage.create(message.contacts.contacts[0]);
736
403
  }
737
404
  else {
738
- m.contactsArrayMessage = contacts;
405
+ m.contactsArrayMessage = WAProto.Message.ContactsArrayMessage.create(message.contacts);
739
406
  }
740
407
  }
741
408
  else if (hasNonNullishProperty(message, 'location')) {
742
- 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
+ }
743
421
  }
744
422
  else if (hasNonNullishProperty(message, 'react')) {
745
- const { react } = message; // Lia@Changes 04-02-26 --- Destructured for readability & cleaner access (⁠✷⁠‿⁠✷⁠)
746
- if (!react.senderTimestampMs) {
747
- react.senderTimestampMs = Date.now();
423
+ if (!message.react.senderTimestampMs) {
424
+ message.react.senderTimestampMs = Date.now();
748
425
  }
749
- m.reactionMessage = react;
426
+ m.reactionMessage = WAProto.Message.ReactionMessage.create(message.react);
750
427
  }
751
428
  else if (hasNonNullishProperty(message, 'delete')) {
752
429
  m.protocolMessage = {
753
430
  key: message.delete,
754
- type: ProtocolType.REVOKE
431
+ type: WAProto.Message.ProtocolMessage.Type.REVOKE
755
432
  };
756
433
  }
757
434
  else if (hasNonNullishProperty(message, 'forward')) {
758
435
  m = generateForwardMessageContent(message.forward, message.force);
759
436
  }
760
437
  else if (hasNonNullishProperty(message, 'disappearingMessagesInChat')) {
761
- const { disappearingMessagesInChat } = message; // Lia@Changes 04-02-26 --- Destructured for readability & cleaner access (⁠✷⁠‿⁠✷⁠)
762
- const exp = typeof disappearingMessagesInChat === 'boolean'
763
- ? disappearingMessagesInChat
438
+ const exp = typeof message.disappearingMessagesInChat === 'boolean'
439
+ ? message.disappearingMessagesInChat
764
440
  ? WA_DEFAULT_EPHEMERAL
765
441
  : 0
766
- : disappearingMessagesInChat;
442
+ : message.disappearingMessagesInChat;
767
443
  m = prepareDisappearingMessageSettingContent(exp);
768
444
  }
769
445
  else if (hasNonNullishProperty(message, 'groupInvite')) {
770
- const { groupInvite } = message; // Lia@Changes 04-02-26 --- Destructured for readability & cleaner access (⁠✷⁠‿⁠✷⁠)
771
446
  m.groupInviteMessage = {};
772
- m.groupInviteMessage.inviteCode = groupInvite.inviteCode;
773
- m.groupInviteMessage.inviteExpiration = groupInvite.inviteExpiration;
774
- m.groupInviteMessage.caption = groupInvite.text;
775
- m.groupInviteMessage.groupJid = groupInvite.jid;
776
- 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;
777
452
  //TODO: use built-in interface and get disappearing mode info etc.
778
453
  //TODO: cache / use store!?
779
454
  if (options.getProfilePicUrl) {
780
- const pfpUrl = await options.getProfilePicUrl(groupInvite.jid, 'preview');
455
+ const pfpUrl = await options.getProfilePicUrl(message.groupInvite.jid, 'preview');
781
456
  if (pfpUrl) {
782
457
  const resp = await fetch(pfpUrl, { method: 'GET', dispatcher: options?.options?.dispatcher });
783
458
  if (resp.ok) {
@@ -787,137 +462,206 @@ export const generateWAMessageContent = async (message, options) => {
787
462
  }
788
463
  }
789
464
  }
790
- else if (hasNonNullishProperty(message, 'stickers')) {
791
- m.stickerPackMessage = await prepareStickerPackMessage(message, options);
792
- }
793
465
  else if (hasNonNullishProperty(message, 'pin')) {
794
466
  m.pinInChatMessage = {};
795
467
  m.messageContextInfo = {};
796
- m.pinInChatMessage.key = message.pin;
797
- m.pinInChatMessage.type = message.type;
798
- m.pinInChatMessage.senderTimestampMs = Date.now();
799
- 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;
800
473
  }
801
474
  else if (hasNonNullishProperty(message, 'keep')) {
802
475
  m.keepInChatMessage = {};
803
- m.keepInChatMessage.key = message.keep;
804
- m.keepInChatMessage.keepType = message.type;
805
- m.keepInChatMessage.timestampMs = Date.now();
806
- }
807
- else if (hasNonNullishProperty(message, 'flowReply')) {
808
- const { flowReply } = message; // Lia@Changes 04-02-26 --- Destructured for readability & cleaner access (⁠✷⁠‿⁠✷⁠)
809
- m.interactiveResponseMessage = {
810
- body: {
811
- format: flowReply.format || proto.Message.InteractiveResponseMessage.Body.Format.DEFAULT,
812
- text: flowReply.text
813
- },
814
- nativeFlowResponseMessage: {
815
- name: flowReply.name,
816
- paramsJson: flowReply.paramsJson || '{}',
817
- version: flowReply.version || 1
818
- }
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 } : {})
819
498
  };
820
499
  }
821
500
  else if (hasNonNullishProperty(message, 'buttonReply')) {
822
- const { buttonReply } = message; // Lia@Changes 04-02-26 --- Destructured for readability & cleaner access (⁠✷⁠‿⁠✷⁠)
823
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;
824
512
  case 'template':
825
513
  m.templateButtonReplyMessage = {
826
- selectedDisplayText: buttonReply.displayText,
827
- selectedId: buttonReply.id,
828
- selectedIndex: buttonReply.index
514
+ selectedDisplayText: message.buttonReply.displayText,
515
+ selectedId: message.buttonReply.id,
516
+ selectedIndex: message.buttonReply.index
829
517
  };
830
518
  break;
831
519
  case 'plain':
832
520
  m.buttonsResponseMessage = {
833
- selectedButtonId: buttonReply.id,
834
- selectedDisplayText: buttonReply.displayText,
521
+ selectedButtonId: message.buttonReply.id,
522
+ selectedDisplayText: message.buttonReply.displayText,
835
523
  type: proto.Message.ButtonsResponseMessage.Type.DISPLAY_TEXT
836
524
  };
837
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;
838
539
  }
839
540
  }
840
- else if (hasNonNullishProperty(message, 'listReply')) {
841
- const { listReply } = message; // Lia@Changes 04-02-26 --- Destructured for readability & cleaner access (⁠✷⁠‿⁠✷⁠)
842
- m.listResponseMessage = {
843
- description: listReply.description,
844
- listType: proto.Message.ListResponseMessage.ListType.SINGLE_SELECT,
845
- singleSelectReply: {
846
- 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
847
562
  },
848
- 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
+ }
849
578
  };
579
+ requestPaymentMessage.noteMessage.extendedTextMessage.contextInfo = {
580
+ ...(message.contextInfo || {}),
581
+ ...(message.mentions ? { mentionedJid: message.mentions } : {})
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;
850
601
  }
851
602
  else if (hasOptionalProperty(message, 'ptv') && message.ptv) {
852
603
  const { videoMessage } = await prepareWAMessageMedia({ video: message.video }, options);
853
604
  m.ptvMessage = videoMessage;
854
605
  }
855
606
  else if (hasNonNullishProperty(message, 'product')) {
856
- 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 };
857
618
  }
858
619
  else if (hasNonNullishProperty(message, 'event')) {
859
- const { event } = message; // Lia@Changes 04-02-26 --- Destructured for readability & cleaner access (⁠✷⁠‿⁠✷⁠)
860
620
  m.eventMessage = {};
861
- const startTime = Math.floor(event.startDate.getTime() / 1000);
862
- if (event.call && options.getCallLink) {
863
- const token = await options.getCallLink(event.call, { startTime });
864
- 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;
865
625
  }
866
626
  m.messageContextInfo = {
867
627
  // encKey
868
- messageSecret: event.messageSecret || randomBytes(32)
628
+ messageSecret: message.event.messageSecret || randomBytes(32)
869
629
  };
870
- m.eventMessage.name = event.name;
871
- m.eventMessage.description = event.description;
630
+ m.eventMessage.name = message.event.name;
631
+ m.eventMessage.description = message.event.description;
872
632
  m.eventMessage.startTime = startTime;
873
- m.eventMessage.endTime = event.endDate ? event.endDate.getTime() / 1000 : undefined;
874
- m.eventMessage.isCanceled = event.isCancelled ?? false;
875
- m.eventMessage.extraGuestsAllowed = event.extraGuestsAllowed;
876
- m.eventMessage.isScheduleCall = event.isScheduleCall ?? false;
877
- 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;
878
638
  }
879
639
  else if (hasNonNullishProperty(message, 'poll')) {
880
- const { poll } = message; // Lia@Changes 04-02-26 --- Destructured for readability & cleaner access (⁠✷⁠‿⁠✷⁠)
881
- (_a = poll).selectableCount || (_a.selectableCount = 0);
882
- (_b = poll).toAnnouncementGroup || (_b.toAnnouncementGroup = false);
883
- 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)) {
884
643
  throw new Boom('Invalid poll values', { statusCode: 400 });
885
644
  }
886
- if (poll.selectableCount < 0 || poll.selectableCount > poll.values.length) {
887
- 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}`, {
888
647
  statusCode: 400
889
648
  });
890
649
  }
891
650
  m.messageContextInfo = {
892
651
  // encKey
893
- messageSecret: poll.messageSecret || randomBytes(32)
652
+ messageSecret: message.poll.messageSecret || randomBytes(32)
894
653
  };
895
654
  const pollCreationMessage = {
896
- name: poll.name,
897
- selectableOptionsCount: poll.selectableCount,
898
- options: poll.values.map(optionName => ({ optionName }))
655
+ name: message.poll.name,
656
+ selectableOptionsCount: message.poll.selectableCount,
657
+ options: message.poll.values.map(optionName => ({ optionName }))
899
658
  };
900
- if (poll.toAnnouncementGroup) {
659
+ if (message.poll.toAnnouncementGroup) {
901
660
  // poll v2 is for community announcement groups (single select and multiple)
902
661
  m.pollCreationMessageV2 = pollCreationMessage;
903
662
  }
904
663
  else {
905
- // Lia@Changes 08-02-26 --- Add quiz message support
906
- if (poll.pollType === 1) {
907
- if (!poll.correctAnswer) {
908
- throw new Boom('No "correctAnswer" provided for quiz', { statusCode: 400 });
909
- }
910
- m.pollCreationMessageV5 = {
911
- // Lia@Note 08-02-26 --- quiz for newsletter only
912
- ...pollCreationMessage,
913
- correctAnswer: {
914
- optionName: poll.correctAnswer.toString()
915
- },
916
- pollType: poll.pollType,
917
- selectableOptionsCount: 1
918
- };
919
- }
920
- else if (poll.selectableCount === 1) {
664
+ if (message.poll.selectableCount === 1) {
921
665
  //poll v3 is for single select polls
922
666
  m.pollCreationMessageV3 = pollCreationMessage;
923
667
  }
@@ -927,44 +671,211 @@ export const generateWAMessageContent = async (message, options) => {
927
671
  }
928
672
  }
929
673
  }
930
- // Lia@Changes 08-02-26 --- Add poll result snapshot message
931
- else if (hasNonNullishProperty(message, 'pollResult')) {
932
- const { pollResult } = message;
933
- const pollResultSnapshotMessage = {
934
- name: pollResult.name,
935
- pollVotes: pollResult.votes.map(vote => ({
936
- optionName: vote.name,
937
- optionVoteCount: parseInt(vote.voteCount)
938
- }))
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;
939
714
  };
940
- if (pollResult.pollType === 1) {
941
- pollResultSnapshotMessage.pollType = proto.Message.PollType.QUIZ;
942
- m.pollResultSnapshotMessageV3 = pollResultSnapshotMessage;
943
- }
944
- else {
945
- pollResultSnapshotMessage.pollType = proto.Message.PollType.POLL;
946
- 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}`);
947
847
  }
848
+
849
+ m.stickerPackMessage.contextInfo = {
850
+ ...(message.contextInfo || {}),
851
+ ...(message.mentions ? { mentionedJid: message.mentions } : {})
852
+ };
948
853
  }
949
- // Lia@Changes 08-02-26 --- Add poll update message
950
- else if (hasNonNullishProperty(message, 'pollUpdate')) {
951
- const { pollUpdate } = message;
952
- if (!pollUpdate.key) {
953
- throw new Boom('Message key is required', { statusCode: 400 });
954
- }
955
- if (!pollUpdate.vote) {
956
- 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 (_) {}
957
870
  }
958
- m.pollUpdateMessage = {
959
- metadata: pollUpdate.metadata,
960
- pollCreationMessageKey: pollUpdate.key,
961
- senderTimestampMs: Date.now(),
962
- vote: pollUpdate.vote
871
+ m.newsletterAdminInviteMessage.contextInfo = {
872
+ ...(message.contextInfo || {}),
873
+ ...(message.mentions ? { mentionedJid: message.mentions } : {})
963
874
  };
964
875
  }
965
876
  else if (hasNonNullishProperty(message, 'sharePhoneNumber')) {
966
877
  m.protocolMessage = {
967
- type: ProtocolType.SHARE_PHONE_NUMBER
878
+ type: proto.Message.ProtocolMessage.Type.SHARE_PHONE_NUMBER
968
879
  };
969
880
  }
970
881
  else if (hasNonNullishProperty(message, 'requestPhoneNumber')) {
@@ -972,7 +883,7 @@ export const generateWAMessageContent = async (message, options) => {
972
883
  }
973
884
  else if (hasNonNullishProperty(message, 'limitSharing')) {
974
885
  m.protocolMessage = {
975
- type: ProtocolType.LIMIT_SHARING,
886
+ type: proto.Message.ProtocolMessage.Type.LIMIT_SHARING,
976
887
  limitSharing: {
977
888
  sharingLimited: message.limitSharing === true,
978
889
  trigger: 1,
@@ -981,442 +892,288 @@ export const generateWAMessageContent = async (message, options) => {
981
892
  }
982
893
  };
983
894
  }
984
- // Lia@Changes 01-02-26 --- Add payment invite message
985
- else if (hasNonNullishProperty(message, 'paymentInviteServiceType')) {
986
- m.paymentInviteMessage = {
987
- expiryTimestamp: Date.now(),
988
- serviceType: message.paymentInviteServiceType
989
- };
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 };
990
900
  }
991
- // Lia@Changes 01-02-26 --- Add order message
992
- else if (hasNonNullishProperty(message, 'orderText')) {
993
- if (!Buffer.isBuffer(message.thumbnail)) {
994
- throw new Boom('Must provide thumbnail buffer in order message', { statusCode: 400 });
995
- }
996
- m.orderMessage = {
997
- itemCount: 1,
998
- messageVersion: 1,
999
- orderTitle: LIBRARY_NAME,
1000
- status: proto.Message.OrderMessage.OrderStatus.INQUIRY,
1001
- surface: proto.Message.OrderMessage.OrderSurface.CATALOG,
1002
- token: generateMessageIDV2(),
1003
- totalAmount1000: 1000,
1004
- totalCurrencyCode: 'IDR',
1005
- ...message,
1006
- message: message.orderText
1007
- };
1008
- delete m.orderMessage.orderText;
901
+ else {
902
+ m = await prepareWAMessageMedia(message, options);
1009
903
  }
1010
- // Lia@Changes 31-01-26 --- Add support for album messages
1011
- else if (hasNonNullishProperty(message, 'album')) {
1012
- const { album } = message;
1013
- if (!Array.isArray(album)) {
1014
- throw new Boom('Invalid album type. Expected an array.', { statusCode: 400 });
1015
- }
1016
- let videoCount = 0;
1017
- for (let i = 0; i < album.length; i++) {
1018
- if (album[i].video) videoCount++;
1019
- };
1020
- let imageCount = 0;
1021
- for (let i = 0; i < album.length; i++) {
1022
- 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
1023
912
  };
1024
- if ((videoCount + imageCount) < 2) {
1025
- throw new Boom('Minimum provide 2 media to upload album message', { statusCode: 400 });
1026
- }
1027
- m.albumMessage = {
1028
- expectedImageCount: imageCount,
1029
- expectedVideoCount: videoCount
913
+ listMessage.contextInfo = {
914
+ ...(message.contextInfo || {}),
915
+ ...(message.mentions ? { mentionedJid: message.mentions } : {})
1030
916
  };
917
+ m = { listMessage };
1031
918
  }
1032
- else {
1033
- 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 };
1034
941
  }
1035
- // Lia@Changes 30-01-26 --- Add interactive messages (buttonsMessage, listMessage, interactiveMessage, templateMessage, and carouselMessage)
1036
- if (hasNonNullishProperty(message, 'buttons')) {
942
+ else if ('buttons' in message && !!message.buttons) {
1037
943
  const buttonsMessage = {
1038
- buttons: message.buttons.map(button => {
1039
- // Lia@Changes 12-03-26 --- Add "single_select" shortcut!
1040
- const buttonText = button.text || button.buttonText
1041
- if (hasOptionalProperty(button, 'sections')) {
1042
- return {
1043
- nativeFlowInfo: {
1044
- name: 'single_select',
1045
- paramsJson: JSON.stringify({
1046
- title: buttonText,
1047
- sections: button.sections
1048
- })
1049
- },
1050
- type: ButtonType.NATIVE_FLOW
1051
- };
1052
- }
1053
- else if (hasOptionalProperty(button, 'name')) {
1054
- return {
1055
- nativeFlowInfo: {
1056
- name: button.name,
1057
- paramsJson: button.paramsJson
1058
- },
1059
- type: ButtonType.NATIVE_FLOW
1060
- };
1061
- }
1062
- return {
1063
- buttonId: button.id || button.buttonId,
1064
- buttonText: typeof buttonText === 'string' ? { displayText: buttonText } : buttonText,
1065
- type: button.type || ButtonType.RESPONSE
1066
- };
1067
- })
944
+ buttons: message.buttons.map(b => ({ ...b, type: proto.Message.ButtonsMessage.Button.Type.RESPONSE }))
1068
945
  };
1069
- if (hasOptionalProperty(message, 'text')) {
946
+ if ('text' in message) {
1070
947
  buttonsMessage.contentText = message.text;
1071
- buttonsMessage.headerType = ButtonHeaderType.EMPTY;
948
+ buttonsMessage.headerType = proto.Message.ButtonsMessage.HeaderType.EMPTY;
1072
949
  }
1073
950
  else {
1074
- if (hasOptionalProperty(message, 'caption')) {
951
+ if ('caption' in message) {
1075
952
  buttonsMessage.contentText = message.caption;
1076
953
  }
1077
954
  const type = Object.keys(m)[0].replace('Message', '').toUpperCase();
1078
- buttonsMessage.headerType = ButtonHeaderType[type];
955
+ buttonsMessage.headerType = proto.Message.ButtonsMessage.HeaderType[type];
1079
956
  Object.assign(buttonsMessage, m);
1080
957
  }
1081
- if (hasOptionalProperty(message, 'footer')) {
958
+ if ('footer' in message && !!message.footer) {
1082
959
  buttonsMessage.footerText = message.footer;
1083
960
  }
1084
- m = { buttonsMessage };
1085
- }
1086
- else if (hasNonNullishProperty(message, 'sections')) {
1087
- const listMessage = {
1088
- sections: message.sections,
1089
- buttonText: message.buttonText,
1090
- title: message.title,
1091
- footerText: message.footer,
1092
- description: message.text,
1093
- 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 } : {})
1094
968
  };
1095
- m = { listMessage };
969
+ m = { buttonsMessage };
1096
970
  }
1097
- // 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 ಥ⁠‿⁠ಥ)
1098
- else if (hasNonNullishProperty(message, 'templateButtons')) {
971
+ else if ('templateButtons' in message && !!message.templateButtons) {
1099
972
  const hydratedTemplate = {
1100
- hydratedButtons: message.templateButtons.map((button, i) => {
1101
- if (hasOptionalProperty(button, 'id')) {
1102
- return {
1103
- index: i,
1104
- quickReplyButton: {
1105
- displayText: button.text || button.buttonText || '👉🏻 Click',
1106
- id: button.id
1107
- }
1108
- };
1109
- }
1110
- else if (hasOptionalProperty(button, 'url')) {
1111
- return {
1112
- index: i,
1113
- urlButton: {
1114
- displayText: button.text || button.buttonText || '🌐 Visit',
1115
- url: button.url
1116
- }
1117
- };
1118
- }
1119
- else if (hasOptionalProperty(button, 'call')) {
1120
- return {
1121
- index: i,
1122
- callButton: {
1123
- displayText: button.text || button.buttonText || '📞 Call',
1124
- phoneNumber: button.call
1125
- }
1126
- };
1127
- }
1128
- button.index = button.index || i;
1129
- return button;
1130
- })
973
+ hydratedButtons: message.templateButtons
1131
974
  };
1132
- if (hasOptionalProperty(message, 'text')) {
975
+ if ('text' in message) {
1133
976
  hydratedTemplate.hydratedContentText = message.text;
1134
977
  }
1135
978
  else {
1136
- if (hasOptionalProperty(message, 'caption')) {
1137
- hydratedTemplate.hydratedTitleText = message.title;
979
+ if ('caption' in message) {
1138
980
  hydratedTemplate.hydratedContentText = message.caption;
1139
- };
981
+ }
1140
982
  Object.assign(hydratedTemplate, m);
1141
983
  }
1142
- if (hasOptionalProperty(message, 'footer')) {
984
+ if ('footer' in message && !!message.footer) {
1143
985
  hydratedTemplate.hydratedFooterText = message.footer;
1144
986
  }
1145
- hydratedTemplate.templateId = message.id || 'template-' + Date.now(); // Lia@Note 04-02-26 --- Minimal templateId to satisfy WhatsApp (⁠ ⁠ꈍ⁠ᴗ⁠ꈍ⁠)
1146
- m = {
1147
- templateMessage: {
1148
- hydratedFourRowTemplate: hydratedTemplate,
1149
- hydratedTemplate: hydratedTemplate
1150
- }
1151
- }
987
+ hydratedTemplate.contextInfo = {
988
+ ...(message.contextInfo || {}),
989
+ ...(message.mentions ? { mentionedJid: message.mentions } : {})
990
+ };
991
+ m = { templateMessage: { fourRowTemplate: hydratedTemplate, hydratedTemplate } };
1152
992
  }
1153
- else if (hasNonNullishProperty(message, 'nativeFlow')) {
993
+ else if ('interactiveButtons' in message && !!message.interactiveButtons) {
1154
994
  const interactiveMessage = {
1155
- nativeFlowMessage: prepareNativeFlowButtons(message)
995
+ nativeFlowMessage: proto.Message.InteractiveMessage.NativeFlowMessage.fromObject({
996
+ buttons: message.interactiveButtons,
997
+ })
1156
998
  };
1157
- if (hasOptionalProperty(message, 'bizJid')) {
1158
- interactiveMessage.collectionMessage = {
1159
- bizJid: message.bizJid,
1160
- id: message.id,
1161
- messageVersion: 1
1162
- };
999
+ if ('text' in message) {
1000
+ interactiveMessage.body = { text: message.text };
1163
1001
  }
1164
- else if (hasOptionalProperty(message, 'shopSurface')) {
1165
- interactiveMessage.shopStorefrontMessage = {
1166
- surface: message.shopSurface,
1167
- id: message.id,
1168
- 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,
1169
1008
  };
1009
+ Object.assign(interactiveMessage.header, m);
1170
1010
  }
1171
- if (hasOptionalProperty(message, 'text')) {
1172
- interactiveMessage.body = { text: message.text };
1011
+ if ('footer' in message && !!message.footer) {
1012
+ interactiveMessage.footer = { text: message.footer };
1173
1013
  }
1174
- else {
1175
- if (hasOptionalProperty(message, 'caption')) {
1176
- const isValidHeader = hasValidInteractiveHeader(m)
1177
- if (!isValidHeader) {
1178
- throw new Boom('Invalid media type for interactive message header', { statusCode: 400 });
1179
- }
1180
- interactiveMessage.header = {
1181
- title: message.title || '',
1182
- subtitle: message.subtitle || '',
1183
- hasMediaAttachment: isValidHeader
1184
- };
1185
- interactiveMessage.body = { text: message.caption };
1186
- }
1187
- if (hasOptionalProperty(message, 'thumbnail') && !!message.thumbnail) {
1188
- interactiveMessage.jpegThumbnail = message.thumbnail;
1189
- }
1014
+ if ('title' in message && !!message.title) {
1015
+ interactiveMessage.header = {
1016
+ title: message.title,
1017
+ subtitle: message.subtitle,
1018
+ hasMediaAttachment: message?.media ?? false,
1019
+ };
1190
1020
  Object.assign(interactiveMessage.header, m);
1191
1021
  }
1192
- if (hasOptionalProperty(message, 'audioFooter')) {
1193
- const parseFooter = await prepareWAMessageMedia({
1194
- audio: message.audioFooter
1195
- }, options);
1196
- interactiveMessage.footer = {
1197
- audioMessage: parseFooter.audioMessage,
1198
- hasMediaAttachment: true
1199
- };
1022
+ if ('contextInfo' in message && !!message.contextInfo) {
1023
+ interactiveMessage.contextInfo = message.contextInfo;
1200
1024
  }
1201
- else if (hasOptionalProperty(message, 'footer')) {
1202
- interactiveMessage.footer = { text: message.footer };
1025
+ if ('mentions' in message && !!message.mentions) {
1026
+ interactiveMessage.contextInfo = { mentionedJid: message.mentions };
1203
1027
  }
1204
1028
  m = { interactiveMessage };
1205
1029
  }
1206
- else if (hasNonNullishProperty(message, 'cards')) {
1030
+ else if ('shop' in message && !!message.shop) {
1207
1031
  const interactiveMessage = {
1208
- carouselMessage: {
1209
- cards: await Promise.all(message.cards.map(async card => {
1210
- let carouselHeader = {};
1211
- if (hasNonNullishProperty(card, 'product')) {
1212
- carouselHeader.productMessage = await prepareProductMessage(card, options);
1213
- }
1214
- else {
1215
- carouselHeader = await prepareWAMessageMedia(card, options).catch(() => ({ }));
1216
- }
1217
- const isValidHeader = hasValidCarouselHeader(carouselHeader)
1218
- if (!isValidHeader) {
1219
- throw new Boom('Invalid media type for carousel card', { statusCode: 400 });
1220
- }
1221
- const carouselCard = {
1222
- nativeFlowMessage: prepareNativeFlowButtons(card.nativeFlow ? card : [])
1223
- };
1224
- if (hasOptionalProperty(card, 'text')) {
1225
- carouselCard.body = { text: card.text };
1226
- }
1227
- else {
1228
- if (hasOptionalProperty(card, 'caption')) {
1229
- carouselCard.header = {
1230
- title: card.title || '',
1231
- subtitle: card.subtitle || '',
1232
- hasMediaAttachment: isValidHeader
1233
- };
1234
- carouselCard.body = { text: card.caption };
1235
- }
1236
- if (hasOptionalProperty(card, 'thumbnail') && !!card.thumbnail) {
1237
- carouselCard.jpegThumbnail = card.thumbnail;
1238
- }
1239
- Object.assign(carouselCard.header, carouselHeader);
1240
- }
1241
- if (hasOptionalProperty(card, 'audioFooter')) {
1242
- const parseFooter = await prepareWAMessageMedia({
1243
- audio: card.audioFooter
1244
- }, options);
1245
- carouselCard.footer = {
1246
- audioMessage: parseFooter.audioMessage,
1247
- hasMediaAttachment: true
1248
- };
1249
- }
1250
- else if (hasOptionalProperty(card, 'footer')) {
1251
- carouselCard.footer = { text: card.footer };
1252
- }
1253
- return carouselCard
1254
- })),
1255
- carouselCardType: CarouselCardType.UNKNOWN,
1256
- messageVersion: 1
1257
- }
1032
+ shopStorefrontMessage: proto.Message.InteractiveMessage.ShopMessage.fromObject({
1033
+ surface: message.shop,
1034
+ id: message.id
1035
+ })
1258
1036
  };
1259
- if (hasOptionalProperty(message, 'text')) {
1037
+ if ('text' in message) {
1260
1038
  interactiveMessage.body = { text: message.text };
1261
1039
  }
1262
- if (hasOptionalProperty(message, 'footer')) {
1263
- 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);
1264
1048
  }
1265
- m = { interactiveMessage };
1266
- }
1267
- // Lia@Changes 01-02-26 --- Add request payment message
1268
- else if (hasNonNullishProperty(message, 'requestPaymentFrom')) {
1269
- const requestPaymentMessage = {
1270
- amount: {
1271
- currencyCode: 'IDR',
1272
- offset: 1000,
1273
- value: 1000
1274
- },
1275
- amount1000: 1000,
1276
- currencyCodeIso4217: 'IDR',
1277
- expiryTimestamp: Date.now(),
1278
- noteMessage: m,
1279
- requestFrom: message.requestPaymentFrom,
1280
- ...message
1281
- };
1282
- delete requestPaymentMessage.requestPaymentFrom;
1283
- if (hasNonNullishProperty(m, 'extendedTextMessage') || hasNonNullishProperty(m, 'stickerMessage')) {
1284
- Object.assign(requestPaymentMessage.noteMessage, m);
1049
+ if ('footer' in message && !!message.footer) {
1050
+ interactiveMessage.footer = { text: message.footer };
1285
1051
  }
1286
- else {
1287
- 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);
1288
1059
  }
1289
- m = { requestPaymentMessage };
1290
- }
1291
- // Lia@Changes 01-02-26 --- Add invoice message
1292
- else if (hasNonNullishProperty(message, 'invoiceNote')) {
1293
- const attachment = m.imageMessage || m.documentMessage;
1294
- const type = Object.keys(m)[0].replace('Message', '').toUpperCase();
1295
- const invoiceMessage = {
1296
- attachmentType: proto.Message.InvoiceMessage.AttachmentType[type === 'DOCUMENT' ? 'PDF' : 'IMAGE'],
1297
- note: message.invoiceNote
1298
- };
1299
- if (attachment) {
1300
- const { directPath, fileEncSha256, fileSha256, jpegThumbnail = undefined, mediaKey, mediaKeyTimestamp, mimetype } = attachment;
1301
- Object.assign(invoiceMessage, {
1302
- attachmentDirectPath: directPath,
1303
- attachmentFileEncSha256: fileEncSha256,
1304
- attachmentFileSha256: fileSha256,
1305
- attachmentJpegThumbnail: jpegThumbnail,
1306
- attachmentMediaKey: mediaKey,
1307
- attachmentMediaKeyTimestamp: mediaKeyTimestamp,
1308
- attachmentMimetype: mimetype,
1309
- token: generateMessageIDV2()
1310
- });
1060
+ if ('contextInfo' in message && !!message.contextInfo) {
1061
+ interactiveMessage.contextInfo = message.contextInfo;
1311
1062
  }
1312
- else {
1313
- throw new Boom('Invalid media type for invoice message', { statusCode: 400 });
1063
+ if ('mentions' in message && !!message.mentions) {
1064
+ interactiveMessage.contextInfo = { mentionedJid: message.mentions };
1314
1065
  }
1315
- m = { invoiceMessage };
1066
+ m = { interactiveMessage };
1316
1067
  }
1317
- // Lia@Changes 31-01-26 --- Add direct externalAdReply access (no need to create contextInfo first)
1318
- if (hasOptionalProperty(message, 'externalAdReply') && !!message.externalAdReply) {
1319
- const messageType = Object.keys(m)[0];
1320
- const key = m[messageType];
1321
- const content = message.externalAdReply;
1322
- if ('thumbnail' in content && !Buffer.isBuffer(content.thumbnail)) {
1323
- throw new Boom('Thumbnail must in buffer type', { statusCode: 400 });
1324
- }
1325
- if (!content.url || typeof content.url !== 'string') {
1326
- content.url = DONATE_URL; // Lia@Note 02-02-26 --- Apologies if this feels cheeky, just a fallback
1327
- }
1328
- const externalAdReply = {
1329
- ...content,
1330
- body: content.body,
1331
- mediaType: content.mediaType || 1,
1332
- mediaUrl: content.url + '?update=' + Date.now(),
1333
- renderLargerThumbnail: content.largeThumbnail,
1334
- thumbnail: content.thumbnail,
1335
- thumbnailUrl: content.url,
1336
- 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
+ }
1337
1075
  };
1338
- delete externalAdReply.subTitle;
1339
- delete externalAdReply.largeThumbnail;
1340
- delete externalAdReply.url;
1341
- if ('contextInfo' in key && !!key.contextInfo) {
1342
- key.contextInfo.externalAdReply = { ...key.contextInfo.externalAdReply, ...externalAdReply };
1343
- }
1344
- else if (key) {
1345
- key.contextInfo = { externalAdReply };
1346
- }
1347
- }
1348
- if ((hasOptionalProperty(message, 'mentions') && message.mentions?.length) ||
1349
- (hasOptionalProperty(message, 'mentionAll') && message.mentionAll)) {
1350
- const messageType = Object.keys(m)[0];
1351
- const key = m[messageType];
1352
- if ('contextInfo' in key && !!key.contextInfo) {
1353
- key.contextInfo.mentionedJid = message.mentions || [];
1354
- }
1355
- else if (key) {
1356
- key.contextInfo = {
1357
- 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
1358
1082
  };
1359
1083
  }
1360
- if (message.mentionAll) {
1361
- key.contextInfo.mentionedJid = [];
1362
- key.contextInfo.nonJidMentions = 1;
1363
- }
1364
- }
1365
- if (hasOptionalProperty(message, 'contextInfo') && !!message.contextInfo) {
1366
- const messageType = Object.keys(m)[0];
1367
- const key = m[messageType];
1368
- if ('contextInfo' in key && !!key.contextInfo) {
1369
- 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
+ }
1370
1094
  }
1371
- else if (key) {
1372
- key.contextInfo = message.contextInfo;
1095
+ if ('footer' in message && !!message.footer) {
1096
+ interactiveMessage.footer = { text: message.footer };
1373
1097
  }
1098
+ interactiveMessage.contextInfo = {
1099
+ ...(message.contextInfo || {}),
1100
+ ...(message.mentions ? { mentionedJid: message.mentions } : {})
1101
+ };
1102
+ m = { interactiveMessage };
1374
1103
  }
1375
- // Lia@Changes 31-01-26 --- Add "groupStatus" boolean to set contextInfo.isGroupStatus and wrap message into groupStatusMessageV2
1376
- if (hasOptionalProperty(message, 'groupStatus') && !!message.groupStatus) {
1377
- const messageType = Object.keys(m)[0];
1378
- const key = m[messageType];
1379
- if ('contextInfo' in key && !!key.contextInfo) {
1380
- key.contextInfo.isGroupStatus = message.groupStatus;
1381
- }
1382
- else if (key) {
1383
- key.contextInfo = {
1384
- 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);
1385
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
+ };
1386
1147
  }
1387
- m = { groupStatusMessageV2: { message: m } };
1388
- delete message.groupStatus;
1389
- }
1390
- // Lia@Changes 02-02-26 --- Add "interactiveAsTemplate" boolean to wrap interactiveMessage into templateMessage
1391
- else if (hasOptionalProperty(message, 'interactiveAsTemplate') && !!message.interactiveAsTemplate) {
1392
- if (!m.interactiveMessage) {
1393
- 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 };
1394
1150
  }
1395
- m = {
1396
- templateMessage: {
1397
- interactiveMessageTemplate: m.interactiveMessage,
1398
- templateId: message.id || 'template-' + Date.now() // Lia@Note 04-02-26 --- Minimal templateId to satisfy WhatsApp (⁠ ⁠ꈍ⁠ᴗ⁠ꈍ⁠)
1399
- }
1151
+ interactiveMessage.contextInfo = {
1152
+ ...(message.contextInfo || {}),
1153
+ ...(message.mentions ? { mentionedJid: message.mentions } : {})
1400
1154
  };
1401
- delete message.interactiveAsTemplate;
1155
+ m = { interactiveMessage };
1402
1156
  }
1403
- // Lia@Changes 30-01-26 --- Add "ephemeral" boolean to wrap message into ephemeralMessage like "viewOnce"
1404
1157
  if (hasOptionalProperty(message, 'ephemeral') && !!message.ephemeral) {
1405
1158
  m = { ephemeralMessage: { message: m } };
1406
- delete message.ephemeral;
1407
- }
1408
- else if (hasOptionalProperty(message, 'viewOnce') && !!message.viewOnce) {
1409
- m = { viewOnceMessage: { message: m } };
1410
1159
  }
1411
- // Lia@Changes 03-02-26 --- Add "viewOnceV2" boolean to wrap message into viewOnceMessageV2 like "viewOnce"
1412
- else if (hasOptionalProperty(message, 'viewOnceV2') && !!message.viewOnceV2) {
1160
+ if (hasOptionalProperty(message, 'viewOnce') && !!message.viewOnce) {
1413
1161
  m = { viewOnceMessageV2: { message: m } };
1414
- delete message.viewOnceV2;
1415
1162
  }
1416
- // Lia@Changes 03-02-26 --- Add "viewOnceV2Extension" boolean to wrap message into viewOnceMessageV2Extension like "viewOnce"
1417
- else if (hasOptionalProperty(message, 'viewOnceV2Extension') && !!message.viewOnceV2Extension) {
1163
+ if (hasOptionalProperty(message, 'viewOnceExt') && !!message.viewOnceExt) {
1418
1164
  m = { viewOnceMessageV2Extension: { message: m } };
1419
- 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
+ }
1420
1177
  }
1421
1178
  if (hasOptionalProperty(message, 'edit')) {
1422
1179
  m = {
@@ -1424,31 +1181,39 @@ export const generateWAMessageContent = async (message, options) => {
1424
1181
  key: message.edit,
1425
1182
  editedMessage: m,
1426
1183
  timestampMs: Date.now(),
1427
- type: ProtocolType.MESSAGE_EDIT
1184
+ type: WAProto.Message.ProtocolMessage.Type.MESSAGE_EDIT
1428
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;
1429
1196
  }
1430
1197
  }
1431
- if (shouldIncludeReportingToken(m)) {
1198
+ if (shouldIncludeReportingToken(m) && !options.newsletter) {
1432
1199
  m.messageContextInfo = m.messageContextInfo || {};
1433
1200
  if (!m.messageContextInfo.messageSecret) {
1434
1201
  m.messageContextInfo.messageSecret = randomBytes(32);
1435
1202
  }
1436
1203
  }
1437
- return proto.Message.create(m);
1204
+ return WAProto.Message.create(m);
1438
1205
  };
1439
1206
  export const generateWAMessageFromContent = (jid, message, options) => {
1440
1207
  // set timestamp to now
1441
1208
  // if not specified
1442
1209
  if (!options.timestamp) {
1443
- options.timestamp = Date.now();
1210
+ options.timestamp = new Date();
1444
1211
  }
1445
- const messageContextInfo = message.messageContextInfo
1446
1212
  const innerMessage = normalizeMessageContent(message);
1447
1213
  const key = getContentType(innerMessage);
1448
1214
  const timestamp = unixTimestampSeconds(options.timestamp);
1449
- const isNewsletter = isJidNewsletter(jid);
1450
1215
  const { quoted, userJid } = options;
1451
- if (quoted && !isNewsletter) {
1216
+ if (quoted && !isJidNewsletter(jid)) {
1452
1217
  const participant = quoted.key.fromMe
1453
1218
  ? userJid // TODO: Add support for LIDs
1454
1219
  : quoted.participant || quoted.key.participant || quoted.key.remoteJid;
@@ -1482,7 +1247,7 @@ export const generateWAMessageFromContent = (jid, message, options) => {
1482
1247
  // already not converted to disappearing message
1483
1248
  key !== 'ephemeralMessage' &&
1484
1249
  // newsletters don't support ephemeral messages
1485
- !isNewsletter) {
1250
+ !isJidNewsletter(jid)) {
1486
1251
  /* @ts-ignore */
1487
1252
  innerMessage[key].contextInfo = {
1488
1253
  ...(innerMessage[key].contextInfo || {}),
@@ -1490,15 +1255,7 @@ export const generateWAMessageFromContent = (jid, message, options) => {
1490
1255
  //ephemeralSettingTimestamp: options.ephemeralOptions.eph_setting_ts?.toString()
1491
1256
  };
1492
1257
  }
1493
- // Lia@Changes 30-01-26 --- Add deviceListMetadata inside messageContextInfo for private chat
1494
- if (messageContextInfo?.messageSecret && (isPnUser(jid) || isLidUser(jid))) {
1495
- messageContextInfo.deviceListMetadata = {
1496
- recipientKeyHash: randomBytes(10),
1497
- recipientTimestamp: unixTimestampSeconds()
1498
- };
1499
- messageContextInfo.deviceListMetadataVersion = 2
1500
- }
1501
- message = proto.Message.create(message);
1258
+ message = WAProto.Message.create(message);
1502
1259
  const messageJSON = {
1503
1260
  key: {
1504
1261
  remoteJid: jid,
@@ -1513,14 +1270,12 @@ export const generateWAMessageFromContent = (jid, message, options) => {
1513
1270
  };
1514
1271
  return WAProto.WebMessageInfo.fromObject(messageJSON);
1515
1272
  };
1516
- export const generateWAMessage = async (jid, content, options = {}) => {
1273
+ export const generateWAMessage = async (jid, content, options) => {
1517
1274
  // ensure msg ID is with every log
1518
1275
  options.logger = options?.logger?.child({ msgId: options.messageId });
1519
- // Pass jid in the options to generateWAMessageContent
1520
- if (jid && typeof options === 'object') {
1521
- options.jid = jid;
1522
- }
1523
- 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);
1524
1279
  };
1525
1280
  /** Get the key to access the true type of content */
1526
1281
  export const getContentType = (content) => {
@@ -1530,6 +1285,21 @@ export const getContentType = (content) => {
1530
1285
  return key;
1531
1286
  }
1532
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
+ };
1533
1303
  /**
1534
1304
  * Normalizes ephemeral, view once messages to regular message content
1535
1305
  * Eg. image messages in ephemeral messages, in view once messages etc.
@@ -1549,33 +1319,16 @@ export const normalizeMessageContent = (content) => {
1549
1319
  content = inner.message;
1550
1320
  }
1551
1321
  return content;
1552
- // Lia@Changes 03-02-26 --- Add all futureProofMessage into getFutureProofMessage()
1553
1322
  function getFutureProofMessage(message) {
1554
- return (
1555
- message?.associatedChildMessage ||
1556
- message?.botForwardedMessage ||
1557
- message?.botInvokeMessage ||
1558
- message?.botTaskMessage ||
1323
+ return (message?.ephemeralMessage ||
1324
+ message?.viewOnceMessage ||
1559
1325
  message?.documentWithCaptionMessage ||
1326
+ message?.viewOnceMessageV2 ||
1327
+ message?.viewOnceMessageV2Extension ||
1560
1328
  message?.editedMessage ||
1561
- message?.ephemeralMessage ||
1562
- message?.eventCoverImage ||
1563
- message?.groupMentionedMessage ||
1564
- message?.groupStatusMentionMessage ||
1329
+ message?.associatedChildMessage ||
1565
1330
  message?.groupStatusMessage ||
1566
- message?.groupStatusMessageV2 ||
1567
- message?.limitSharingMessage ||
1568
- message?.lottieStickerMessage ||
1569
- message?.pollCreationMessageV4 ||
1570
- message?.pollCreationOptionImageMessage ||
1571
- message?.questionMessage ||
1572
- message?.questionReplyMessage ||
1573
- message?.statusAddYours ||
1574
- message?.statusMentionMessage ||
1575
- message?.viewOnceMessage ||
1576
- message?.viewOnceMessageV2 ||
1577
- message?.viewOnceMessageV2Extension
1578
- );
1331
+ message?.groupStatusMessageV2);
1579
1332
  }
1580
1333
  };
1581
1334
  /**
@@ -1768,7 +1521,7 @@ export const downloadMediaMessage = async (message, type, options, ctx) => {
1768
1521
  throw new Boom('No message present', { statusCode: 400, data: message });
1769
1522
  }
1770
1523
  const contentType = getContentType(mContent);
1771
- let mediaType = contentType?.replace('Message', '');
1524
+ let mediaType = getMediaTypeFromContentType(contentType, mContent);
1772
1525
  const media = mContent[contentType];
1773
1526
  if (!media || typeof media !== 'object' || (!('url' in media) && !('thumbnailDirectPath' in media))) {
1774
1527
  throw new Boom(`"${contentType}" message is not a media message`);
@@ -1808,79 +1561,138 @@ export const assertMediaContent = (content) => {
1808
1561
  }
1809
1562
  return mediaContent;
1810
1563
  };
1811
- /**
1812
- * Checks if a WebP buffer is animated by looking for VP8X chunk with animation flag
1813
- * or ANIM/ANMF chunks
1814
- */
1815
- const isAnimatedWebP = (buffer) => {
1816
- // WebP must start with RIFF....WEBP
1817
- if (
1818
- buffer.length < 12 ||
1819
- buffer[0] !== 0x52 ||
1820
- buffer[1] !== 0x49 ||
1821
- buffer[2] !== 0x46 ||
1822
- buffer[3] !== 0x46 ||
1823
- buffer[8] !== 0x57 ||
1824
- buffer[9] !== 0x45 ||
1825
- buffer[10] !== 0x42 ||
1826
- buffer[11] !== 0x50
1564
+
1565
+ export const patchMessageForMdIfRequired = (message) => {
1566
+ if (message?.buttonsMessage ||
1567
+ message?.templateMessage ||
1568
+ message?.listMessage ||
1569
+ message?.interactiveMessage?.nativeFlowMessage
1827
1570
  ) {
1828
- return false;
1829
- };
1830
- // Parse chunks starting after RIFF header (12 bytes)
1831
- let offset = 12;
1832
- while (offset < buffer.length - 8) {
1833
- const chunkFourCC = buffer.toString('ascii', offset, offset + 4);
1834
- const chunkSize = buffer.readUInt32LE(offset + 4);
1835
- if (chunkFourCC === 'VP8X') {
1836
- // VP8X extended header, check animation flag (bit 1 at offset+8)
1837
- const flagsOffset = offset + 8;
1838
- if (flagsOffset < buffer.length) {
1839
- const flags = buffer[flagsOffset];
1840
- if (flags & 0x02) {
1841
- return true;
1842
- };
1843
- };
1844
- } else if (chunkFourCC === 'ANIM' || chunkFourCC === 'ANMF') {
1845
- // ANIM or ANMF chunks indicate animation
1846
- return true;
1571
+ message = {
1572
+ viewOnceMessageV2Extension: {
1573
+ message: {
1574
+ messageContextInfo: {
1575
+ deviceListMetadataVersion: 2,
1576
+ deviceListMetadata: {}
1577
+ },
1578
+ ...message
1579
+ }
1580
+ }
1847
1581
  };
1848
- // Move to next chunk (chunk size + 8 bytes header, padded to even)
1849
- offset += 8 + chunkSize + (chunkSize % 2);
1850
- };
1851
- return false;
1582
+ }
1583
+ return message;
1852
1584
  };
1853
- /**
1854
- * Checks if a buffer is a WebP file
1855
- */
1856
- const isWebPBuffer = (buffer) => {
1857
- return (
1858
- buffer.length >= 12 &&
1859
- buffer[0] === 0x52 &&
1860
- buffer[1] === 0x49 &&
1861
- buffer[2] === 0x46 &&
1862
- buffer[3] === 0x46 &&
1863
- buffer[8] === 0x57 &&
1864
- buffer[9] === 0x45 &&
1865
- buffer[10] === 0x42 &&
1866
- buffer[11] === 0x50
1867
- );
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;
1868
1631
  };
1869
1632
  /**
1870
- * Lia@Changes 30-01-26
1871
- * ---
1872
- * Determines whether a message should include a Biz Binary Node.
1873
- * A Biz Binary Node is added only for interactive messages
1874
- * 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
1875
1640
  */
1876
- export const shouldIncludeBizBinaryNode = (message) => {
1877
- const hasValidInteractive =
1878
- message.interactiveMessage &&
1879
- !message.interactiveMessage.carouselMessage &&
1880
- !message.interactiveMessage.collectionMessage &&
1881
- !message.interactiveMessage.shopStorefrontMessage;
1882
- return (message.buttonsMessage ||
1883
- message.interactiveMessage ||
1884
- message.listMessage ||
1885
- hasValidInteractive);
1886
- };
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