@fer2809fl/baileys 1.4.9 → 7.0.0
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.
- package/LICENSE +21 -674
- package/README.md +556 -523
- package/WAProto/GenerateStatics.sh +3 -4
- package/WAProto/WAProto.proto +1215 -511
- package/WAProto/fix-imports.js +85 -0
- package/WAProto/index.d.ts +4347 -45387
- package/WAProto/index.js +65475 -137445
- package/engine-requirements.js +10 -0
- package/lib/Defaults/index.d.ts +34 -18
- package/lib/Defaults/index.d.ts.map +1 -0
- package/lib/Defaults/index.js +95 -67
- package/lib/Defaults/index.js.map +1 -0
- package/lib/Signal/Group/ciphertext-message.d.ts +1 -0
- package/lib/Signal/Group/ciphertext-message.d.ts.map +1 -0
- package/lib/Signal/Group/ciphertext-message.js +2 -5
- package/lib/Signal/Group/ciphertext-message.js.map +1 -0
- package/lib/Signal/Group/group-session-builder.d.ts +4 -3
- package/lib/Signal/Group/group-session-builder.d.ts.map +1 -0
- package/lib/Signal/Group/group-session-builder.js +7 -41
- package/lib/Signal/Group/group-session-builder.js.map +1 -0
- package/lib/Signal/Group/group_cipher.d.ts +4 -4
- package/lib/Signal/Group/group_cipher.d.ts.map +1 -0
- package/lib/Signal/Group/group_cipher.js +37 -60
- package/lib/Signal/Group/group_cipher.js.map +1 -0
- package/lib/Signal/Group/index.d.ts +12 -11
- package/lib/Signal/Group/index.d.ts.map +1 -0
- package/lib/Signal/Group/index.js +12 -57
- package/lib/Signal/Group/index.js.map +1 -0
- package/lib/Signal/Group/keyhelper.d.ts +2 -1
- package/lib/Signal/Group/keyhelper.d.ts.map +1 -0
- package/lib/Signal/Group/keyhelper.js +7 -44
- package/lib/Signal/Group/keyhelper.js.map +1 -0
- package/lib/Signal/Group/sender-chain-key.d.ts +3 -2
- package/lib/Signal/Group/sender-chain-key.d.ts.map +1 -0
- package/lib/Signal/Group/sender-chain-key.js +7 -15
- package/lib/Signal/Group/sender-chain-key.js.map +1 -0
- package/lib/Signal/Group/sender-key-distribution-message.d.ts +2 -1
- package/lib/Signal/Group/sender-key-distribution-message.d.ts.map +1 -0
- package/lib/Signal/Group/sender-key-distribution-message.js +8 -11
- package/lib/Signal/Group/sender-key-distribution-message.js.map +1 -0
- package/lib/Signal/Group/sender-key-message.d.ts +2 -1
- package/lib/Signal/Group/sender-key-message.d.ts.map +1 -0
- package/lib/Signal/Group/sender-key-message.js +9 -12
- package/lib/Signal/Group/sender-key-message.js.map +1 -0
- package/lib/Signal/Group/sender-key-name.d.ts +1 -0
- package/lib/Signal/Group/sender-key-name.d.ts.map +1 -0
- package/lib/Signal/Group/sender-key-name.js +2 -5
- package/lib/Signal/Group/sender-key-name.js.map +1 -0
- package/lib/Signal/Group/sender-key-record.d.ts +3 -2
- package/lib/Signal/Group/sender-key-record.d.ts.map +1 -0
- package/lib/Signal/Group/sender-key-record.js +12 -37
- package/lib/Signal/Group/sender-key-record.js.map +1 -0
- package/lib/Signal/Group/sender-key-state.d.ts +7 -6
- package/lib/Signal/Group/sender-key-state.d.ts.map +1 -0
- package/lib/Signal/Group/sender-key-state.js +27 -42
- package/lib/Signal/Group/sender-key-state.js.map +1 -0
- package/lib/Signal/Group/sender-message-key.d.ts +1 -0
- package/lib/Signal/Group/sender-message-key.d.ts.map +1 -0
- package/lib/Signal/Group/sender-message-key.js +4 -7
- package/lib/Signal/Group/sender-message-key.js.map +1 -0
- package/lib/Signal/libsignal.d.ts +5 -3
- package/lib/Signal/libsignal.d.ts.map +1 -0
- package/lib/Signal/libsignal.js +347 -90
- package/lib/Signal/libsignal.js.map +1 -0
- package/lib/Signal/lid-mapping.d.ts +23 -0
- package/lib/Signal/lid-mapping.d.ts.map +1 -0
- package/lib/Signal/lid-mapping.js +277 -0
- package/lib/Signal/lid-mapping.js.map +1 -0
- package/lib/Socket/Client/index.d.ts +3 -2
- package/lib/Socket/Client/index.d.ts.map +1 -0
- package/lib/Socket/Client/index.js +3 -18
- package/lib/Socket/Client/index.js.map +1 -0
- package/lib/Socket/Client/types.d.ts +4 -4
- package/lib/Socket/Client/types.d.ts.map +1 -0
- package/lib/Socket/Client/types.js +4 -6
- package/lib/Socket/Client/types.js.map +1 -0
- package/lib/Socket/Client/websocket.d.ts +3 -3
- package/lib/Socket/Client/websocket.d.ts.map +1 -0
- package/lib/Socket/Client/websocket.js +21 -78
- package/lib/Socket/Client/websocket.js.map +1 -0
- package/lib/Socket/business.d.ts +205 -93
- package/lib/Socket/business.d.ts.map +1 -0
- package/lib/Socket/business.js +162 -43
- package/lib/Socket/business.js.map +1 -0
- package/lib/Socket/chats.d.ts +77 -28
- package/lib/Socket/chats.d.ts.map +1 -0
- package/lib/Socket/chats.js +639 -248
- package/lib/Socket/chats.js.map +1 -0
- package/lib/Socket/communities.d.ts +340 -0
- package/lib/Socket/communities.d.ts.map +1 -0
- package/lib/Socket/communities.js +434 -0
- package/lib/Socket/communities.js.map +1 -0
- package/lib/Socket/groups.d.ts +93 -46
- package/lib/Socket/groups.d.ts.map +1 -0
- package/lib/Socket/groups.js +260 -102
- package/lib/Socket/groups.js.map +1 -0
- package/lib/Socket/index.d.ts +253 -98
- package/lib/Socket/index.d.ts.map +1 -0
- package/lib/Socket/index.js +11 -9
- package/lib/Socket/index.js.map +1 -0
- package/lib/Socket/messages-recv.d.ts +192 -83
- package/lib/Socket/messages-recv.d.ts.map +1 -0
- package/lib/Socket/messages-recv.js +1314 -559
- package/lib/Socket/messages-recv.js.map +1 -0
- package/lib/Socket/messages-send.d.ts +190 -75
- package/lib/Socket/messages-send.d.ts.map +1 -0
- package/lib/Socket/messages-send.js +937 -581
- package/lib/Socket/messages-send.js.map +1 -0
- package/lib/Socket/mex.d.ts +4 -0
- package/lib/Socket/mex.d.ts.map +1 -0
- package/lib/Socket/mex.js +62 -0
- package/lib/Socket/mex.js.map +1 -0
- package/lib/Socket/newsletter.d.ts +129 -73
- package/lib/Socket/newsletter.d.ts.map +1 -0
- package/lib/Socket/newsletter.js +373 -222
- package/lib/Socket/newsletter.js.map +1 -0
- package/lib/Socket/smgss.d.ts +119 -0
- package/lib/Socket/smgss.d.ts.map +1 -0
- package/lib/Socket/smgss.js +453 -0
- package/lib/Socket/smgss.js.map +1 -0
- package/lib/Socket/socket.d.ts +59 -270
- package/lib/Socket/socket.d.ts.map +1 -0
- package/lib/Socket/socket.js +738 -326
- package/lib/Socket/socket.js.map +1 -0
- package/lib/Store/index.d.ts +4 -2
- package/lib/Store/index.d.ts.map +1 -0
- package/lib/Store/index.js +4 -8
- package/lib/Store/index.js.map +1 -0
- package/lib/Store/make-in-memory-store.d.ts +74 -60
- package/lib/Store/make-in-memory-store.d.ts.map +1 -0
- package/lib/Store/make-in-memory-store.js +130 -119
- package/lib/Store/make-in-memory-store.js.map +1 -0
- package/lib/Store/make-ordered-dictionary.d.ts +4 -3
- package/lib/Store/make-ordered-dictionary.d.ts.map +1 -0
- package/lib/Store/make-ordered-dictionary.js +5 -6
- package/lib/Store/make-ordered-dictionary.js.map +1 -0
- package/lib/Store/object-repository.d.ts +6 -3
- package/lib/Store/object-repository.d.ts.map +1 -0
- package/lib/Store/object-repository.js +2 -5
- package/lib/Store/object-repository.js.map +1 -0
- package/lib/Types/Auth.d.ts +19 -5
- package/lib/Types/Auth.d.ts.map +1 -0
- package/lib/Types/Auth.js +2 -2
- package/lib/Types/Auth.js.map +1 -0
- package/lib/Types/Bussines.d.ts +25 -0
- package/lib/Types/Bussines.d.ts.map +1 -0
- package/lib/Types/Bussines.js +2 -0
- package/lib/Types/Bussines.js.map +1 -0
- package/lib/Types/Call.d.ts +3 -1
- package/lib/Types/Call.d.ts.map +1 -0
- package/lib/Types/Call.js +2 -2
- package/lib/Types/Call.js.map +1 -0
- package/lib/Types/Chat.d.ts +24 -9
- package/lib/Types/Chat.d.ts.map +1 -0
- package/lib/Types/Chat.js +8 -4
- package/lib/Types/Chat.js.map +1 -0
- package/lib/Types/Contact.d.ts +7 -4
- package/lib/Types/Contact.d.ts.map +1 -0
- package/lib/Types/Contact.js +2 -2
- package/lib/Types/Contact.js.map +1 -0
- package/lib/Types/Events.d.ts +90 -33
- package/lib/Types/Events.d.ts.map +1 -0
- package/lib/Types/Events.js +2 -2
- package/lib/Types/Events.js.map +1 -0
- package/lib/Types/GroupMetadata.d.ts +16 -9
- package/lib/Types/GroupMetadata.d.ts.map +1 -0
- package/lib/Types/GroupMetadata.js +2 -2
- package/lib/Types/GroupMetadata.js.map +1 -0
- package/lib/Types/Label.d.ts +12 -0
- package/lib/Types/Label.d.ts.map +1 -0
- package/lib/Types/Label.js +3 -5
- package/lib/Types/Label.js.map +1 -0
- package/lib/Types/LabelAssociation.d.ts +1 -0
- package/lib/Types/LabelAssociation.d.ts.map +1 -0
- package/lib/Types/LabelAssociation.js +3 -5
- package/lib/Types/LabelAssociation.js.map +1 -0
- package/lib/Types/Message.d.ts +176 -148
- package/lib/Types/Message.d.ts.map +1 -0
- package/lib/Types/Message.js +15 -7
- package/lib/Types/Message.js.map +1 -0
- package/lib/Types/Mex.d.ts +154 -0
- package/lib/Types/Mex.d.ts.map +1 -0
- package/lib/Types/Mex.js +39 -0
- package/lib/Types/Mex.js.map +1 -0
- package/lib/Types/Product.d.ts +2 -1
- package/lib/Types/Product.d.ts.map +1 -0
- package/lib/Types/Product.js +2 -2
- package/lib/Types/Product.js.map +1 -0
- package/lib/Types/Signal.d.ts +32 -2
- package/lib/Types/Signal.d.ts.map +1 -0
- package/lib/Types/Signal.js +2 -2
- package/lib/Types/Signal.js.map +1 -0
- package/lib/Types/Socket.d.ts +46 -20
- package/lib/Types/Socket.d.ts.map +1 -0
- package/lib/Types/Socket.js +3 -2
- package/lib/Types/Socket.js.map +1 -0
- package/lib/Types/State.d.ts +79 -2
- package/lib/Types/State.d.ts.map +1 -0
- package/lib/Types/State.js +56 -2
- package/lib/Types/State.js.map +1 -0
- package/lib/Types/USync.d.ts +3 -2
- package/lib/Types/USync.d.ts.map +1 -0
- package/lib/Types/USync.js +2 -2
- package/lib/Types/USync.js.map +1 -0
- package/lib/Types/index.d.ts +16 -14
- package/lib/Types/index.d.ts.map +1 -0
- package/lib/Types/index.js +15 -31
- package/lib/Types/index.js.map +1 -0
- package/lib/Utils/auth-utils.d.ts +10 -4
- package/lib/Utils/auth-utils.d.ts.map +1 -0
- package/lib/Utils/auth-utils.js +242 -151
- package/lib/Utils/auth-utils.js.map +1 -0
- package/lib/Utils/browser-utils.d.ts +4 -0
- package/lib/Utils/browser-utils.d.ts.map +1 -0
- package/lib/Utils/browser-utils.js +29 -0
- package/lib/Utils/browser-utils.js.map +1 -0
- package/lib/Utils/business.d.ts +3 -2
- package/lib/Utils/business.d.ts.map +1 -0
- package/lib/Utils/business.js +66 -69
- package/lib/Utils/business.js.map +1 -0
- package/lib/Utils/button-handler.d.ts +28 -0
- package/lib/Utils/button-handler.d.ts.map +1 -0
- package/lib/Utils/button-handler.js +79 -90
- package/lib/Utils/button-handler.js.map +1 -0
- package/lib/Utils/chat-utils.d.ts +42 -12
- package/lib/Utils/chat-utils.d.ts.map +1 -0
- package/lib/Utils/chat-utils.js +380 -238
- package/lib/Utils/chat-utils.js.map +1 -0
- package/lib/Utils/companion-reg-client-utils.d.ts +17 -0
- package/lib/Utils/companion-reg-client-utils.d.ts.map +1 -0
- package/lib/Utils/companion-reg-client-utils.js +35 -0
- package/lib/Utils/companion-reg-client-utils.js.map +1 -0
- package/lib/Utils/crypto.d.ts +11 -14
- package/lib/Utils/crypto.d.ts.map +1 -0
- package/lib/Utils/crypto.js +41 -116
- package/lib/Utils/crypto.js.map +1 -0
- package/lib/Utils/decode-wa-message.d.ts +36 -5
- package/lib/Utils/decode-wa-message.d.ts.map +1 -0
- package/lib/Utils/decode-wa-message.js +173 -63
- package/lib/Utils/decode-wa-message.js.map +1 -0
- package/lib/Utils/event-buffer.d.ts +8 -7
- package/lib/Utils/event-buffer.d.ts.map +1 -0
- package/lib/Utils/event-buffer.js +174 -70
- package/lib/Utils/event-buffer.js.map +1 -0
- package/lib/Utils/generics.d.ts +20 -17
- package/lib/Utils/generics.d.ts.map +1 -0
- package/lib/Utils/generics.js +159 -207
- package/lib/Utils/generics.js.map +1 -0
- package/lib/Utils/history.d.ts +16 -11
- package/lib/Utils/history.d.ts.map +1 -0
- package/lib/Utils/history.js +93 -53
- package/lib/Utils/history.js.map +1 -0
- package/lib/Utils/identity-change-handler.d.ts +44 -0
- package/lib/Utils/identity-change-handler.d.ts.map +1 -0
- package/lib/Utils/identity-change-handler.js +50 -0
- package/lib/Utils/identity-change-handler.js.map +1 -0
- package/lib/Utils/index.d.ts +30 -18
- package/lib/Utils/index.d.ts.map +1 -0
- package/lib/Utils/index.js +30 -34
- package/lib/Utils/index.js.map +1 -0
- package/lib/Utils/interactive.d.ts +60 -0
- package/lib/Utils/interactive.d.ts.map +1 -0
- package/lib/Utils/interactive.js +87 -117
- package/lib/Utils/interactive.js.map +1 -0
- package/lib/Utils/link-preview.d.ts +4 -4
- package/lib/Utils/link-preview.d.ts.map +1 -0
- package/lib/Utils/link-preview.js +17 -58
- package/lib/Utils/link-preview.js.map +1 -0
- package/lib/Utils/logger.d.ts +8 -6
- package/lib/Utils/logger.d.ts.map +1 -0
- package/lib/Utils/logger.js +3 -10
- package/lib/Utils/logger.js.map +1 -0
- package/lib/Utils/lt-hash.d.ts +8 -12
- package/lib/Utils/lt-hash.d.ts.map +1 -0
- package/lib/Utils/lt-hash.js +3 -46
- package/lib/Utils/lt-hash.js.map +1 -0
- package/lib/Utils/make-mutex.d.ts +2 -0
- package/lib/Utils/make-mutex.d.ts.map +1 -0
- package/lib/Utils/make-mutex.js +24 -34
- package/lib/Utils/make-mutex.js.map +1 -0
- package/lib/Utils/message-composer.d.ts +93 -0
- package/lib/Utils/message-composer.d.ts.map +1 -0
- package/lib/Utils/message-composer.js +311 -0
- package/lib/Utils/message-composer.js.map +1 -0
- package/lib/Utils/message-retry-manager.d.ts +115 -0
- package/lib/Utils/message-retry-manager.d.ts.map +1 -0
- package/lib/Utils/message-retry-manager.js +265 -0
- package/lib/Utils/message-retry-manager.js.map +1 -0
- package/lib/Utils/messages-media.d.ts +82 -40
- package/lib/Utils/messages-media.d.ts.map +1 -0
- package/lib/Utils/messages-media.js +674 -677
- package/lib/Utils/messages-media.js.map +1 -0
- package/lib/Utils/messages-newsletter.d.ts +74 -0
- package/lib/Utils/messages-newsletter.d.ts.map +1 -0
- package/lib/Utils/messages-newsletter.js +128 -0
- package/lib/Utils/messages-newsletter.js.map +1 -0
- package/lib/Utils/messages.d.ts +38 -14
- package/lib/Utils/messages.d.ts.map +1 -0
- package/lib/Utils/messages.js +835 -602
- package/lib/Utils/messages.js.map +1 -0
- package/lib/Utils/noise-handler.d.ts +9 -8
- package/lib/Utils/noise-handler.d.ts.map +1 -0
- package/lib/Utils/noise-handler.js +149 -98
- package/lib/Utils/noise-handler.js.map +1 -0
- package/lib/Utils/offline-node-processor.d.ts +17 -0
- package/lib/Utils/offline-node-processor.d.ts.map +1 -0
- package/lib/Utils/offline-node-processor.js +40 -0
- package/lib/Utils/offline-node-processor.js.map +1 -0
- package/lib/Utils/pre-key-manager.d.ts +28 -0
- package/lib/Utils/pre-key-manager.d.ts.map +1 -0
- package/lib/Utils/pre-key-manager.js +106 -0
- package/lib/Utils/pre-key-manager.js.map +1 -0
- package/lib/Utils/process-message.d.ts +30 -12
- package/lib/Utils/process-message.d.ts.map +1 -0
- package/lib/Utils/process-message.js +438 -230
- package/lib/Utils/process-message.js.map +1 -0
- package/lib/Utils/reporting-utils.d.ts +11 -0
- package/lib/Utils/reporting-utils.d.ts.map +1 -0
- package/lib/Utils/reporting-utils.js +258 -0
- package/lib/Utils/reporting-utils.js.map +1 -0
- package/lib/Utils/shortcake-crypto.d.ts +36 -0
- package/lib/Utils/shortcake-crypto.d.ts.map +1 -0
- package/lib/Utils/shortcake-crypto.js +71 -0
- package/lib/Utils/shortcake-crypto.js.map +1 -0
- package/lib/Utils/shortcake.d.ts +36 -0
- package/lib/Utils/shortcake.d.ts.map +1 -0
- package/lib/Utils/shortcake.js +162 -0
- package/lib/Utils/shortcake.js.map +1 -0
- package/lib/Utils/signal.d.ts +20 -6
- package/lib/Utils/signal.d.ts.map +1 -0
- package/lib/Utils/signal.js +112 -64
- package/lib/Utils/signal.js.map +1 -0
- package/lib/Utils/stanza-ack.d.ts +11 -0
- package/lib/Utils/stanza-ack.d.ts.map +1 -0
- package/lib/Utils/stanza-ack.js +38 -0
- package/lib/Utils/stanza-ack.js.map +1 -0
- package/lib/Utils/sync-action-utils.d.ts +19 -0
- package/lib/Utils/sync-action-utils.d.ts.map +1 -0
- package/lib/Utils/sync-action-utils.js +49 -0
- package/lib/Utils/sync-action-utils.js.map +1 -0
- package/lib/Utils/tc-token-utils.d.ts +37 -0
- package/lib/Utils/tc-token-utils.d.ts.map +1 -0
- package/lib/Utils/tc-token-utils.js +164 -0
- package/lib/Utils/tc-token-utils.js.map +1 -0
- package/lib/Utils/use-multi-file-auth-state.d.ts +2 -1
- package/lib/Utils/use-multi-file-auth-state.d.ts.map +1 -0
- package/lib/Utils/use-multi-file-auth-state.js +40 -64
- package/lib/Utils/use-multi-file-auth-state.js.map +1 -0
- package/lib/Utils/use-single-file-auth-state.d.ts +6 -0
- package/lib/Utils/use-single-file-auth-state.d.ts.map +1 -0
- package/lib/Utils/use-single-file-auth-state.js +111 -0
- package/lib/Utils/use-single-file-auth-state.js.map +1 -0
- package/lib/Utils/use-sqlite-auth-state.d.ts +14 -0
- package/lib/Utils/use-sqlite-auth-state.d.ts.map +1 -0
- package/lib/Utils/use-sqlite-auth-state.js +107 -0
- package/lib/Utils/use-sqlite-auth-state.js.map +1 -0
- package/lib/Utils/validate-connection.d.ts +5 -3
- package/lib/Utils/validate-connection.d.ts.map +1 -0
- package/lib/Utils/validate-connection.js +82 -101
- package/lib/Utils/validate-connection.js.map +1 -0
- package/lib/WABinary/constants.d.ts +1 -0
- package/lib/WABinary/constants.d.ts.map +1 -0
- package/lib/WABinary/constants.js +10 -12
- package/lib/WABinary/constants.js.map +1 -0
- package/lib/WABinary/decode.d.ts +2 -1
- package/lib/WABinary/decode.d.ts.map +1 -0
- package/lib/WABinary/decode.js +49 -52
- package/lib/WABinary/decode.js.map +1 -0
- package/lib/WABinary/encode.d.ts +2 -1
- package/lib/WABinary/encode.d.ts.map +1 -0
- package/lib/WABinary/encode.js +19 -49
- package/lib/WABinary/encode.js.map +1 -0
- package/lib/WABinary/generic-utils.d.ts +5 -4
- package/lib/WABinary/generic-utils.d.ts.map +1 -0
- package/lib/WABinary/generic-utils.js +46 -43
- package/lib/WABinary/generic-utils.js.map +1 -0
- package/lib/WABinary/index.d.ts +6 -5
- package/lib/WABinary/index.d.ts.map +1 -0
- package/lib/WABinary/index.js +6 -21
- package/lib/WABinary/index.js.map +1 -0
- package/lib/WABinary/jid-utils.d.ts +32 -10
- package/lib/WABinary/jid-utils.d.ts.map +1 -0
- package/lib/WABinary/jid-utils.js +103 -59
- package/lib/WABinary/jid-utils.js.map +1 -0
- package/lib/WABinary/types.d.ts +2 -1
- package/lib/WABinary/types.d.ts.map +1 -0
- package/lib/WABinary/types.js +2 -2
- package/lib/WABinary/types.js.map +1 -0
- package/lib/WAM/BinaryInfo.d.ts +2 -1
- package/lib/WAM/BinaryInfo.d.ts.map +1 -0
- package/lib/WAM/BinaryInfo.js +2 -5
- package/lib/WAM/BinaryInfo.js.map +1 -0
- package/lib/WAM/constants.d.ts +5 -3
- package/lib/WAM/constants.d.ts.map +1 -0
- package/lib/WAM/constants.js +19071 -11568
- package/lib/WAM/constants.js.map +1 -0
- package/lib/WAM/encode.d.ts +2 -1
- package/lib/WAM/encode.d.ts.map +1 -0
- package/lib/WAM/encode.js +17 -22
- package/lib/WAM/encode.js.map +1 -0
- package/lib/WAM/index.d.ts +4 -3
- package/lib/WAM/index.d.ts.map +1 -0
- package/lib/WAM/index.js +4 -19
- package/lib/WAM/index.js.map +1 -0
- package/lib/WAUSync/Protocols/USyncContactProtocol.d.ts +4 -3
- package/lib/WAUSync/Protocols/USyncContactProtocol.d.ts.map +1 -0
- package/lib/WAUSync/Protocols/USyncContactProtocol.js +33 -13
- package/lib/WAUSync/Protocols/USyncContactProtocol.js.map +1 -0
- package/lib/WAUSync/Protocols/USyncDeviceProtocol.d.ts +3 -2
- package/lib/WAUSync/Protocols/USyncDeviceProtocol.d.ts.map +1 -0
- package/lib/WAUSync/Protocols/USyncDeviceProtocol.js +11 -14
- package/lib/WAUSync/Protocols/USyncDeviceProtocol.js.map +1 -0
- package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.d.ts +3 -2
- package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.d.ts.map +1 -0
- package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.js +8 -11
- package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.js.map +1 -0
- package/lib/WAUSync/Protocols/USyncStatusProtocol.d.ts +3 -2
- package/lib/WAUSync/Protocols/USyncStatusProtocol.d.ts.map +1 -0
- package/lib/WAUSync/Protocols/USyncStatusProtocol.js +9 -13
- package/lib/WAUSync/Protocols/USyncStatusProtocol.js.map +1 -0
- package/lib/WAUSync/Protocols/USyncUsernameProtocol.d.ts +10 -0
- package/lib/WAUSync/Protocols/USyncUsernameProtocol.d.ts.map +1 -0
- package/lib/WAUSync/Protocols/USyncUsernameProtocol.js +25 -0
- package/lib/WAUSync/Protocols/USyncUsernameProtocol.js.map +1 -0
- package/lib/WAUSync/Protocols/UsyncBotProfileProtocol.d.ts +4 -3
- package/lib/WAUSync/Protocols/UsyncBotProfileProtocol.d.ts.map +1 -0
- package/lib/WAUSync/Protocols/UsyncBotProfileProtocol.js +20 -22
- package/lib/WAUSync/Protocols/UsyncBotProfileProtocol.js.map +1 -0
- package/lib/WAUSync/Protocols/UsyncLIDProtocol.d.ts +5 -3
- package/lib/WAUSync/Protocols/UsyncLIDProtocol.d.ts.map +1 -0
- package/lib/WAUSync/Protocols/UsyncLIDProtocol.js +13 -8
- package/lib/WAUSync/Protocols/UsyncLIDProtocol.js.map +1 -0
- package/lib/WAUSync/Protocols/index.d.ts +6 -4
- package/lib/WAUSync/Protocols/index.d.ts.map +1 -0
- package/lib/WAUSync/Protocols/index.js +6 -20
- package/lib/WAUSync/Protocols/index.js.map +1 -0
- package/lib/WAUSync/USyncQuery.d.ts +6 -4
- package/lib/WAUSync/USyncQuery.d.ts.map +1 -0
- package/lib/WAUSync/USyncQuery.js +44 -35
- package/lib/WAUSync/USyncQuery.js.map +1 -0
- package/lib/WAUSync/USyncUser.d.ts +10 -5
- package/lib/WAUSync/USyncUser.d.ts.map +1 -0
- package/lib/WAUSync/USyncUser.js +10 -5
- package/lib/WAUSync/USyncUser.js.map +1 -0
- package/lib/WAUSync/index.d.ts +4 -3
- package/lib/WAUSync/index.d.ts.map +1 -0
- package/lib/WAUSync/index.js +4 -19
- package/lib/WAUSync/index.js.map +1 -0
- package/lib/index.d.ts +12 -15
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +15 -48
- package/lib/index.js.map +1 -0
- package/package.json +44 -64
- package/WAProto/index.ts.ts +0 -53473
- package/WAProto/n +0 -1
- package/lib/Baileys.jpeg +0 -0
- package/lib/Defaults/baileys-version.json +0 -3
- package/lib/Defaults/n +0 -1
- package/lib/Signal/Group/queue-job.d.ts +0 -1
- package/lib/Signal/Group/queue-job.js +0 -57
- package/lib/Signal/n +0 -1
- package/lib/Socket/Client/n +0 -1
- package/lib/Socket/n +0 -1
- package/lib/Socket/setup.js +0 -480
- package/lib/Socket/setup.ts +0 -622
- package/lib/Socket/usync.d.ts +0 -36
- package/lib/Socket/usync.js +0 -70
- package/lib/Store/n +0 -1
- package/lib/Types/Newsletter.d.ts +0 -79
- package/lib/Types/Newsletter.js +0 -18
- package/lib/Types/n +0 -1
- package/lib/Utils/baileys-event-stream.d.ts +0 -16
- package/lib/Utils/baileys-event-stream.js +0 -63
- package/lib/Utils/error-handler.js +0 -37
- package/lib/Utils/n +0 -1
- package/lib/WABinary/jid-utils.js.bak +0 -83
- package/lib/WABinary/n +0 -1
- package/lib/WAM/n +0 -1
- package/lib/WAUSync/Protocols/n +0 -1
- package/lib/WAUSync/n +0 -1
- package/lib/n +0 -1
|
@@ -1,494 +1,313 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
-
};
|
|
38
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
-
exports.getStatusCodeForMediaRetry = exports.decryptMediaRetryData = exports.decodeMediaRetryNode = exports.encryptMediaRetryRequest = exports.getWAUploadToServer = exports.downloadEncryptedContent = exports.downloadContentFromMessage = exports.getUrlFromDirectPath = exports.encryptedStream = exports.prepareStream = exports.getHttpStream = exports.getStream = exports.toBuffer = exports.toReadable = exports.mediaMessageSHA256B64 = exports.generateProfilePicture = exports.encodeBase64EncodedStringForUpload = exports.extractImageThumb = exports.extractVideoThumb = exports.hkdfInfoKey = void 0;
|
|
40
|
-
exports.getMediaKeys = getMediaKeys;
|
|
41
|
-
exports.uploadFile = uploadFile;
|
|
42
|
-
exports.vid2jpg = vid2jpg;
|
|
43
|
-
exports.getAudioDuration = getAudioDuration;
|
|
44
|
-
exports.getAudioWaveform = getAudioWaveform;
|
|
45
|
-
exports.generateThumbnail = generateThumbnail;
|
|
46
|
-
exports.extensionForMediaMessage = extensionForMediaMessage;
|
|
47
|
-
const boom_1 = require("@hapi/boom");
|
|
48
|
-
const axios_1 = __importDefault(require("axios"));
|
|
49
|
-
const form_data_1 = __importDefault(require("form-data"));
|
|
50
|
-
const cheerio = __importStar(require("cheerio"));
|
|
51
|
-
const Crypto = __importStar(require("crypto"));
|
|
52
|
-
const events_1 = require("events");
|
|
53
|
-
const fs_1 = require("fs");
|
|
54
|
-
const os_1 = require("os");
|
|
55
|
-
const path_1 = require("path");
|
|
56
|
-
const jimp_1 = __importDefault(require("jimp"));
|
|
57
|
-
const stream_1 = require("stream");
|
|
58
|
-
const child_process_1 = require("child_process");
|
|
59
|
-
const WAProto_1 = require("../../WAProto");
|
|
60
|
-
const Defaults_1 = require("../Defaults");
|
|
61
|
-
const WABinary_1 = require("../WABinary");
|
|
62
|
-
const crypto_1 = require("./crypto");
|
|
63
|
-
const generics_1 = require("./generics");
|
|
64
|
-
const getTmpFilesDirectory = () => (0, os_1.tmpdir)();
|
|
65
|
-
const getImageProcessingLibrary = async () => {
|
|
66
|
-
const [_jimp, sharp] = await Promise.all([
|
|
67
|
-
(async () => {
|
|
68
|
-
const jimp = await (Promise.resolve().then(() => __importStar(require('jimp'))).catch(() => { }));
|
|
69
|
-
return jimp;
|
|
70
|
-
})(),
|
|
71
|
-
(async () => {
|
|
72
|
-
const sharp = await (Promise.resolve().then(() => __importStar(require('sharp'))).catch(() => { }));
|
|
73
|
-
return sharp;
|
|
74
|
-
})()
|
|
1
|
+
import { Boom } from '@hapi/boom';
|
|
2
|
+
import { spawn } from 'child_process';
|
|
3
|
+
import * as Crypto from 'crypto';
|
|
4
|
+
import { once } from 'events';
|
|
5
|
+
import { createReadStream, createWriteStream, promises as fs, WriteStream } from 'fs';
|
|
6
|
+
import { tmpdir } from 'os';
|
|
7
|
+
import { join } from 'path';
|
|
8
|
+
import { Readable, Transform } from 'stream';
|
|
9
|
+
import { URL } from 'url';
|
|
10
|
+
import { proto } from '../../WAProto/index.js';
|
|
11
|
+
import { DEFAULT_ORIGIN, MEDIA_HKDF_KEY_MAPPING, MEDIA_PATH_MAP } from '../Defaults/index.js';
|
|
12
|
+
import { getBinaryNodeChild, getBinaryNodeChildBuffer, jidNormalizedUser } from '../WABinary/index.js';
|
|
13
|
+
import { aesDecryptGCM, aesEncryptGCM, hkdf } from './crypto.js';
|
|
14
|
+
import { generateMessageIDV2 } from './generics.js';
|
|
15
|
+
const getTmpFilesDirectory = () => tmpdir();
|
|
16
|
+
let imageProcessingLibrary;
|
|
17
|
+
export const getImageProcessingLibrary = async () => {
|
|
18
|
+
if (imageProcessingLibrary) {
|
|
19
|
+
return imageProcessingLibrary;
|
|
20
|
+
}
|
|
21
|
+
//@ts-ignore
|
|
22
|
+
const [jimp, sharp, napi] = await Promise.all([
|
|
23
|
+
import('jimp').catch(() => { }),
|
|
24
|
+
import('sharp').catch(() => { }),
|
|
25
|
+
//@ts-ignore
|
|
26
|
+
import('@napi-rs/image').catch(() => { })
|
|
75
27
|
]);
|
|
76
28
|
if (sharp) {
|
|
77
|
-
|
|
29
|
+
imageProcessingLibrary = { sharp };
|
|
30
|
+
}
|
|
31
|
+
else if (napi) {
|
|
32
|
+
imageProcessingLibrary = { napi };
|
|
78
33
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
34
|
+
else if (jimp) {
|
|
35
|
+
imageProcessingLibrary = { jimp };
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
throw new Boom('No image processing library available');
|
|
82
39
|
}
|
|
83
|
-
|
|
40
|
+
return imageProcessingLibrary;
|
|
84
41
|
};
|
|
85
|
-
const hkdfInfoKey = (type) => {
|
|
86
|
-
const hkdfInfo =
|
|
42
|
+
export const hkdfInfoKey = (type) => {
|
|
43
|
+
const hkdfInfo = MEDIA_HKDF_KEY_MAPPING[type];
|
|
87
44
|
return `WhatsApp ${hkdfInfo} Keys`;
|
|
88
45
|
};
|
|
89
|
-
|
|
46
|
+
export const getRawMediaUploadData = async (media, mediaType, logger) => {
|
|
47
|
+
const { stream } = await getStream(media);
|
|
48
|
+
logger?.debug('got stream for raw upload');
|
|
49
|
+
const hasher = Crypto.createHash('sha256');
|
|
50
|
+
const filePath = join(tmpdir(), mediaType + generateMessageIDV2());
|
|
51
|
+
const fileWriteStream = createWriteStream(filePath);
|
|
52
|
+
let fileLength = 0;
|
|
53
|
+
try {
|
|
54
|
+
for await (const data of stream) {
|
|
55
|
+
fileLength += data.length;
|
|
56
|
+
hasher.update(data);
|
|
57
|
+
if (!fileWriteStream.write(data)) {
|
|
58
|
+
await once(fileWriteStream, 'drain');
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
fileWriteStream.end();
|
|
62
|
+
await once(fileWriteStream, 'finish');
|
|
63
|
+
stream.destroy();
|
|
64
|
+
const fileSha256 = hasher.digest();
|
|
65
|
+
logger?.debug('hashed data for raw upload');
|
|
66
|
+
return {
|
|
67
|
+
filePath: filePath,
|
|
68
|
+
fileSha256,
|
|
69
|
+
fileLength
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
fileWriteStream.destroy();
|
|
74
|
+
stream.destroy();
|
|
75
|
+
try {
|
|
76
|
+
await fs.unlink(filePath);
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
//
|
|
80
|
+
}
|
|
81
|
+
throw error;
|
|
82
|
+
}
|
|
83
|
+
};
|
|
90
84
|
/** generates all the keys required to encrypt/decrypt & sign a media message */
|
|
91
|
-
async function getMediaKeys(buffer, mediaType) {
|
|
85
|
+
export async function getMediaKeys(buffer, mediaType) {
|
|
92
86
|
if (!buffer) {
|
|
93
|
-
throw new
|
|
87
|
+
throw new Boom('Cannot derive from empty media key');
|
|
94
88
|
}
|
|
95
89
|
if (typeof buffer === 'string') {
|
|
96
90
|
buffer = Buffer.from(buffer.replace('data:;base64,', ''), 'base64');
|
|
97
91
|
}
|
|
98
92
|
// expand using HKDF to 112 bytes, also pass in the relevant app info
|
|
99
|
-
const expandedMediaKey =
|
|
93
|
+
const expandedMediaKey = hkdf(buffer, 112, { info: hkdfInfoKey(mediaType) });
|
|
100
94
|
return {
|
|
101
95
|
iv: expandedMediaKey.slice(0, 16),
|
|
102
96
|
cipherKey: expandedMediaKey.slice(16, 48),
|
|
103
|
-
macKey: expandedMediaKey.slice(48, 80)
|
|
97
|
+
macKey: expandedMediaKey.slice(48, 80)
|
|
104
98
|
};
|
|
105
99
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
});
|
|
122
|
-
form.append("reqtype", "fileupload");
|
|
123
|
-
return form;
|
|
124
|
-
},
|
|
125
|
-
parseResponse: res => res.data
|
|
126
|
-
},
|
|
127
|
-
{
|
|
128
|
-
name: "pdi.moe",
|
|
129
|
-
url: "https://scdn.pdi.moe/upload",
|
|
130
|
-
buildForm: () => {
|
|
131
|
-
const form = new form_data_1.default();
|
|
132
|
-
form.append("file", buffer, {
|
|
133
|
-
filename: `file.${ext}`,
|
|
134
|
-
contentType: mime
|
|
135
|
-
});
|
|
136
|
-
return form;
|
|
137
|
-
},
|
|
138
|
-
parseResponse: res => res.data.result.url
|
|
139
|
-
},
|
|
140
|
-
{
|
|
141
|
-
name: "qu.ax",
|
|
142
|
-
url: "https://qu.ax/upload.php",
|
|
143
|
-
buildForm: () => {
|
|
144
|
-
const form = new form_data_1.default();
|
|
145
|
-
form.append("files[]", buffer, {
|
|
146
|
-
filename: `file.${ext}`,
|
|
147
|
-
contentType: mime || "application/octet-stream"
|
|
148
|
-
});
|
|
149
|
-
return form;
|
|
150
|
-
},
|
|
151
|
-
parseResponse: res => {
|
|
152
|
-
var _a, _b, _c;
|
|
153
|
-
if (!((_c = (_b = (_a = res.data) === null || _a === void 0 ? void 0 : _a.files) === null || _b === void 0 ? void 0 : _b[0]) === null || _c === void 0 ? void 0 : _c.url))
|
|
154
|
-
throw new Error("Failed to get URL from qu.ax");
|
|
155
|
-
return res.data.files[0].url;
|
|
156
|
-
}
|
|
157
|
-
},
|
|
158
|
-
{
|
|
159
|
-
name: "uguu.se",
|
|
160
|
-
url: "https://uguu.se/upload.php",
|
|
161
|
-
buildForm: () => {
|
|
162
|
-
const form = new form_data_1.default();
|
|
163
|
-
form.append("files[]", buffer, {
|
|
164
|
-
filename: `file.${ext}`,
|
|
165
|
-
contentType: mime || "application/octet-stream"
|
|
166
|
-
});
|
|
167
|
-
return form;
|
|
168
|
-
},
|
|
169
|
-
parseResponse: res => {
|
|
170
|
-
var _a, _b, _c;
|
|
171
|
-
if (!((_c = (_b = (_a = res.data) === null || _a === void 0 ? void 0 : _a.files) === null || _b === void 0 ? void 0 : _b[0]) === null || _c === void 0 ? void 0 : _c.url))
|
|
172
|
-
throw new Error("Failed to get URL from uguu.se");
|
|
173
|
-
return res.data.files[0].url;
|
|
174
|
-
}
|
|
175
|
-
},
|
|
176
|
-
{
|
|
177
|
-
name: "tmpfiles",
|
|
178
|
-
url: "https://tmpfiles.org/api/v1/upload",
|
|
179
|
-
buildForm: () => {
|
|
180
|
-
const form = new form_data_1.default();
|
|
181
|
-
form.append("file", buffer, {
|
|
182
|
-
filename: `file.${ext}`,
|
|
183
|
-
contentType: mime
|
|
184
|
-
});
|
|
185
|
-
return form;
|
|
186
|
-
},
|
|
187
|
-
parseResponse: res => {
|
|
188
|
-
const match = res.data.data.url.match(/https:\/\/tmpfiles\.org\/(.*)/);
|
|
189
|
-
if (!match)
|
|
190
|
-
throw new Error("Failed to parse tmpfiles URL.");
|
|
191
|
-
return `https://tmpfiles.org/dl/${match[1]}`;
|
|
192
|
-
}
|
|
193
|
-
}
|
|
100
|
+
/** Extracts video thumb using FFMPEG */
|
|
101
|
+
const extractVideoThumb = async (path, destPath, time, size) => new Promise((resolve, reject) => {
|
|
102
|
+
const args = [
|
|
103
|
+
'-ss',
|
|
104
|
+
time,
|
|
105
|
+
'-i',
|
|
106
|
+
path,
|
|
107
|
+
'-y',
|
|
108
|
+
'-vf',
|
|
109
|
+
`scale=${size.width}:-1`,
|
|
110
|
+
'-vframes',
|
|
111
|
+
'1',
|
|
112
|
+
'-f',
|
|
113
|
+
'image2',
|
|
114
|
+
destPath
|
|
194
115
|
];
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
catch (error) {
|
|
205
|
-
logger === null || logger === void 0 ? void 0 : logger.debug(`[${service.name}] eror:`, (error === null || error === void 0 ? void 0 : error.message) || error);
|
|
116
|
+
const child = spawn('ffmpeg', args);
|
|
117
|
+
let stderr = '';
|
|
118
|
+
child.stderr?.on('data', chunk => {
|
|
119
|
+
stderr += chunk;
|
|
120
|
+
});
|
|
121
|
+
child.on('error', reject);
|
|
122
|
+
child.on('close', code => {
|
|
123
|
+
if (code === 0) {
|
|
124
|
+
resolve();
|
|
206
125
|
}
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
}
|
|
210
|
-
async function vid2jpg(videoUrl) {
|
|
211
|
-
try {
|
|
212
|
-
const { data } = await axios_1.default.get(`https://ezgif.com/video-to-jpg?url=${encodeURIComponent(videoUrl)}`);
|
|
213
|
-
const $ = cheerio.load(data);
|
|
214
|
-
const fileToken = $('input[name="file"]').attr("value");
|
|
215
|
-
if (!fileToken) {
|
|
216
|
-
throw new Error("Failed to retrieve file token. The video URL may be invalid or inaccessible.");
|
|
217
|
-
}
|
|
218
|
-
const formData = new URLSearchParams();
|
|
219
|
-
formData.append("file", fileToken);
|
|
220
|
-
formData.append("end", "1");
|
|
221
|
-
formData.append("video-to-jpg", "Convert to JPG!");
|
|
222
|
-
const convert = await axios_1.default.post(`https://ezgif.com/video-to-jpg/${fileToken}`, formData);
|
|
223
|
-
const $2 = cheerio.load(convert.data);
|
|
224
|
-
let imageUrl = $2("#output img").first().attr("src");
|
|
225
|
-
if (!imageUrl) {
|
|
226
|
-
throw new Error("Could not locate the converted image output.");
|
|
227
|
-
}
|
|
228
|
-
if (imageUrl.startsWith("//")) {
|
|
229
|
-
imageUrl = "https:" + imageUrl;
|
|
230
|
-
}
|
|
231
|
-
else if (imageUrl.startsWith("/")) {
|
|
232
|
-
const cdnMatch = imageUrl.match(/\/(s\d+\..+?)\/.*/);
|
|
233
|
-
if (cdnMatch) {
|
|
234
|
-
imageUrl = "https://" + imageUrl.slice(2);
|
|
235
|
-
}
|
|
236
|
-
else {
|
|
237
|
-
imageUrl = "https://ezgif.com" + imageUrl;
|
|
238
|
-
}
|
|
126
|
+
else {
|
|
127
|
+
reject(new Boom(`ffmpeg exited with code ${code}`, { data: stderr }));
|
|
239
128
|
}
|
|
240
|
-
return imageUrl;
|
|
241
|
-
}
|
|
242
|
-
catch (error) {
|
|
243
|
-
throw new Error("Failed to convert video to JPG: " + error.message);
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
/**
|
|
247
|
-
* Extracts video thumbnail using FFmpeg
|
|
248
|
-
*/
|
|
249
|
-
const extractVideoThumb = async (videoPath, time = '00:00:00', size = { width: 256 }) => {
|
|
250
|
-
return new Promise((resolve, reject) => {
|
|
251
|
-
const args = [
|
|
252
|
-
'-ss', time,
|
|
253
|
-
'-i', videoPath,
|
|
254
|
-
'-y',
|
|
255
|
-
'-vf', `scale=${size.width}:-1`,
|
|
256
|
-
'-vframes', '1',
|
|
257
|
-
'-f', 'image2',
|
|
258
|
-
'-vcodec', 'mjpeg',
|
|
259
|
-
'pipe:1'
|
|
260
|
-
];
|
|
261
|
-
const ffmpeg = (0, child_process_1.spawn)('ffmpeg', args);
|
|
262
|
-
const chunks = [];
|
|
263
|
-
let errorOutput = '';
|
|
264
|
-
ffmpeg.stdout.on('data', chunk => chunks.push(chunk));
|
|
265
|
-
ffmpeg.stderr.on('data', data => {
|
|
266
|
-
errorOutput += data.toString();
|
|
267
|
-
});
|
|
268
|
-
ffmpeg.on('error', reject);
|
|
269
|
-
ffmpeg.on('close', code => {
|
|
270
|
-
if (code === 0) return resolve(Buffer.concat(chunks));
|
|
271
|
-
reject(new Error(`ffmpeg exited with code ${code}\n${errorOutput}`));
|
|
272
|
-
});
|
|
273
129
|
});
|
|
274
|
-
};
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
if (bufferOrFilePath instanceof
|
|
279
|
-
bufferOrFilePath = await
|
|
130
|
+
});
|
|
131
|
+
export const extractImageThumb = async (bufferOrFilePath, width = 32) => {
|
|
132
|
+
// TODO: Move entirely to sharp, removing jimp as it supports readable streams
|
|
133
|
+
// This will have positive speed and performance impacts as well as minimizing RAM usage.
|
|
134
|
+
if (bufferOrFilePath instanceof Readable) {
|
|
135
|
+
bufferOrFilePath = await toBuffer(bufferOrFilePath);
|
|
280
136
|
}
|
|
281
137
|
const lib = await getImageProcessingLibrary();
|
|
282
|
-
if ('sharp' in lib && typeof
|
|
138
|
+
if ('sharp' in lib && typeof lib.sharp?.default === 'function') {
|
|
283
139
|
const img = lib.sharp.default(bufferOrFilePath);
|
|
284
140
|
const dimensions = await img.metadata();
|
|
285
|
-
const buffer = await img
|
|
286
|
-
.resize(width)
|
|
287
|
-
.jpeg({ quality: 50 })
|
|
288
|
-
.toBuffer();
|
|
141
|
+
const buffer = await img.resize(width).jpeg({ quality: 50 }).toBuffer();
|
|
289
142
|
return {
|
|
290
143
|
buffer,
|
|
291
144
|
original: {
|
|
292
145
|
width: dimensions.width,
|
|
293
|
-
height: dimensions.height
|
|
294
|
-
}
|
|
146
|
+
height: dimensions.height
|
|
147
|
+
}
|
|
295
148
|
};
|
|
296
149
|
}
|
|
297
|
-
else if ('jimp' in lib && typeof
|
|
298
|
-
const
|
|
299
|
-
const jimp = await read(bufferOrFilePath);
|
|
150
|
+
else if ('jimp' in lib && typeof lib.jimp?.Jimp === 'object') {
|
|
151
|
+
const jimp = await lib.jimp.Jimp.read(bufferOrFilePath);
|
|
300
152
|
const dimensions = {
|
|
301
|
-
width: jimp.
|
|
302
|
-
height: jimp.
|
|
153
|
+
width: jimp.width,
|
|
154
|
+
height: jimp.height
|
|
303
155
|
};
|
|
304
156
|
const buffer = await jimp
|
|
305
|
-
.
|
|
306
|
-
.
|
|
307
|
-
.getBufferAsync(MIME_JPEG);
|
|
157
|
+
.resize({ w: width, mode: lib.jimp.ResizeStrategy.BILINEAR })
|
|
158
|
+
.getBuffer('image/jpeg', { quality: 50 });
|
|
308
159
|
return {
|
|
309
160
|
buffer,
|
|
310
161
|
original: dimensions
|
|
311
162
|
};
|
|
312
163
|
}
|
|
164
|
+
else if ('napi' in lib && typeof lib.napi?.Transformer === 'function') {
|
|
165
|
+
const input = typeof bufferOrFilePath === 'string' ? await fs.readFile(bufferOrFilePath) : bufferOrFilePath;
|
|
166
|
+
const img = new lib.napi.Transformer(input);
|
|
167
|
+
const meta = await img.metadata();
|
|
168
|
+
const buffer = await img.resize(width).jpeg(50);
|
|
169
|
+
return {
|
|
170
|
+
buffer,
|
|
171
|
+
original: {
|
|
172
|
+
width: meta.width,
|
|
173
|
+
height: meta.height
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
}
|
|
313
177
|
else {
|
|
314
|
-
throw new
|
|
178
|
+
throw new Boom('No image processing library available');
|
|
315
179
|
}
|
|
316
180
|
};
|
|
317
|
-
|
|
318
|
-
const
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
exports.encodeBase64EncodedStringForUpload = encodeBase64EncodedStringForUpload;
|
|
323
|
-
const generateProfilePicture = async (mediaUpload) => {
|
|
324
|
-
let bufferOrFilePath;
|
|
325
|
-
let img;
|
|
181
|
+
export const encodeBase64EncodedStringForUpload = (b64) => encodeURIComponent(b64.replace(/\+/g, '-').replace(/\//g, '_').replace(/\=+$/, ''));
|
|
182
|
+
export const generateProfilePicture = async (mediaUpload, dimensions, opts) => {
|
|
183
|
+
let buffer;
|
|
184
|
+
const { full = true } = opts || {};
|
|
185
|
+
const { width: w = full ? 720 : 640, height: h = full ? 720 : 640 } = dimensions || {};
|
|
326
186
|
if (Buffer.isBuffer(mediaUpload)) {
|
|
327
|
-
|
|
187
|
+
buffer = mediaUpload;
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
// Use getStream to handle all WAMediaUpload types (Buffer, Stream, URL)
|
|
191
|
+
const { stream } = await getStream(mediaUpload);
|
|
192
|
+
// Convert the resulting stream to a buffer
|
|
193
|
+
buffer = await toBuffer(stream);
|
|
194
|
+
}
|
|
195
|
+
const lib = await getImageProcessingLibrary();
|
|
196
|
+
let img;
|
|
197
|
+
if ('sharp' in lib && typeof lib.sharp?.default === 'function') {
|
|
198
|
+
img = lib.sharp
|
|
199
|
+
.default(buffer)
|
|
200
|
+
.resize(w, h, full ? { fit: 'inside', withoutEnlargement: false } : undefined)
|
|
201
|
+
.jpeg({
|
|
202
|
+
quality: full ? 100 : 50
|
|
203
|
+
})
|
|
204
|
+
.toBuffer();
|
|
328
205
|
}
|
|
329
|
-
else if ('
|
|
330
|
-
|
|
206
|
+
else if ('jimp' in lib && typeof lib.jimp?.Jimp === 'function') {
|
|
207
|
+
const jimp = await lib.jimp.Jimp.read(buffer);
|
|
208
|
+
let resized;
|
|
209
|
+
if (full) {
|
|
210
|
+
const scale = Math.min(w / jimp.width, h / jimp.height, 1);
|
|
211
|
+
const targetW = Math.max(1, Math.round(jimp.width * scale));
|
|
212
|
+
const targetH = Math.max(1, Math.round(jimp.height * scale));
|
|
213
|
+
resized = jimp.resize({ w: targetW, h: targetH, mode: lib.jimp.ResizeStrategy.BILINEAR });
|
|
214
|
+
}
|
|
215
|
+
else {
|
|
216
|
+
const min = Math.min(jimp.width, jimp.height);
|
|
217
|
+
const cropped = jimp.crop({ x: 0, y: 0, w: min, h: min });
|
|
218
|
+
resized = cropped.resize({ w, h, mode: lib.jimp.ResizeStrategy.BILINEAR });
|
|
219
|
+
}
|
|
220
|
+
img = resized.getBuffer('image/jpeg', { quality: full ? 100 : 50 });
|
|
221
|
+
}
|
|
222
|
+
else if ('napi' in lib && typeof lib.napi?.Transformer === 'function') {
|
|
223
|
+
const transformer = new lib.napi.Transformer(buffer);
|
|
224
|
+
const meta = await transformer.metadata();
|
|
225
|
+
if (full) {
|
|
226
|
+
img = transformer.resize(w, h).jpeg(100);
|
|
227
|
+
}
|
|
228
|
+
else {
|
|
229
|
+
const min = Math.min(meta.width, meta.height);
|
|
230
|
+
img = transformer.crop(0, 0, min, min).resize(w, h).jpeg(50);
|
|
231
|
+
}
|
|
331
232
|
}
|
|
332
233
|
else {
|
|
333
|
-
|
|
234
|
+
throw new Boom('No image processing library available');
|
|
334
235
|
}
|
|
335
|
-
const jimp = await jimp_1.default.read(bufferOrFilePath);
|
|
336
|
-
const cropped = jimp.getWidth() > jimp.getHeight() ? jimp.resize(550, -1) : jimp.resize(-1, 650);
|
|
337
|
-
img = cropped
|
|
338
|
-
.quality(100)
|
|
339
|
-
.getBufferAsync(jimp_1.default.MIME_JPEG);
|
|
340
236
|
return {
|
|
341
|
-
img: await img
|
|
237
|
+
img: await img
|
|
342
238
|
};
|
|
343
239
|
};
|
|
344
|
-
exports.generateProfilePicture = generateProfilePicture;
|
|
345
240
|
/** gets the SHA256 of the given media message */
|
|
346
|
-
const mediaMessageSHA256B64 = (message) => {
|
|
241
|
+
export const mediaMessageSHA256B64 = (message) => {
|
|
347
242
|
const media = Object.values(message)[0];
|
|
348
|
-
return
|
|
243
|
+
return media?.fileSha256 && Buffer.from(media.fileSha256).toString('base64');
|
|
349
244
|
};
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
inputStream.end(buffer);
|
|
359
|
-
|
|
360
|
-
ff(inputStream)
|
|
361
|
-
.ffprobe((err, data) => {
|
|
362
|
-
if (err) reject(err);
|
|
363
|
-
else resolve(data.format.duration);
|
|
364
|
-
});
|
|
365
|
-
});
|
|
366
|
-
} catch (error) {
|
|
367
|
-
const musicMetadata = await import('music-metadata');
|
|
368
|
-
let metadata;
|
|
369
|
-
if (Buffer.isBuffer(buffer)) {
|
|
370
|
-
metadata = await musicMetadata.parseBuffer(buffer, undefined, {
|
|
371
|
-
duration: true
|
|
372
|
-
});
|
|
373
|
-
} else if (typeof buffer === 'string') {
|
|
374
|
-
const rStream = (0, fs_1.createReadStream)(buffer);
|
|
375
|
-
try {
|
|
376
|
-
metadata = await musicMetadata.parseStream(rStream, undefined, {
|
|
377
|
-
duration: true
|
|
378
|
-
});
|
|
379
|
-
} finally {
|
|
380
|
-
rStream.destroy();
|
|
381
|
-
}
|
|
382
|
-
} else {
|
|
383
|
-
metadata = await musicMetadata.parseStream(buffer, undefined, {
|
|
384
|
-
duration: true
|
|
385
|
-
});
|
|
386
|
-
}
|
|
387
|
-
return metadata.format.duration;
|
|
245
|
+
export async function getAudioDuration(buffer) {
|
|
246
|
+
const musicMetadata = await import('music-metadata');
|
|
247
|
+
let metadata;
|
|
248
|
+
const options = {
|
|
249
|
+
duration: true
|
|
250
|
+
};
|
|
251
|
+
if (Buffer.isBuffer(buffer)) {
|
|
252
|
+
metadata = await musicMetadata.parseBuffer(buffer, undefined, options);
|
|
388
253
|
}
|
|
254
|
+
else if (typeof buffer === 'string') {
|
|
255
|
+
metadata = await musicMetadata.parseFile(buffer, options);
|
|
256
|
+
}
|
|
257
|
+
else {
|
|
258
|
+
metadata = await musicMetadata.parseStream(buffer, undefined, options);
|
|
259
|
+
}
|
|
260
|
+
return metadata.format.duration;
|
|
389
261
|
}
|
|
390
|
-
|
|
391
|
-
|
|
262
|
+
/**
|
|
263
|
+
referenced from and modifying https://github.com/wppconnect-team/wa-js/blob/main/src/chat/functions/prepareAudioWaveform.ts
|
|
264
|
+
*/
|
|
265
|
+
export async function getAudioWaveform(buffer, logger) {
|
|
392
266
|
try {
|
|
393
|
-
|
|
394
|
-
const
|
|
395
|
-
|
|
267
|
+
// @ts-ignore
|
|
268
|
+
const { default: decoder } = await import('audio-decode');
|
|
396
269
|
let audioData;
|
|
397
270
|
if (Buffer.isBuffer(buffer)) {
|
|
398
271
|
audioData = buffer;
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
.
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
const blockSize = Math.floor(amplitudes.length / bars);
|
|
427
|
-
const avg = [];
|
|
428
|
-
for (let i = 0; i < bars; i++) {
|
|
429
|
-
const block = amplitudes.slice(i * blockSize, (i + 1) * blockSize);
|
|
430
|
-
avg.push(block.reduce((a, b) => a + b, 0) / block.length);
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
const max = Math.max(...avg);
|
|
434
|
-
const normalized = avg.map(v => Math.floor((v / max) * 100));
|
|
435
|
-
resolve(new Uint8Array(normalized));
|
|
436
|
-
})
|
|
437
|
-
.pipe()
|
|
438
|
-
.on('data', chunk => chunks.push(chunk));
|
|
439
|
-
});
|
|
440
|
-
} catch (e) {
|
|
441
|
-
logger?.debug(e);
|
|
272
|
+
}
|
|
273
|
+
else if (typeof buffer === 'string') {
|
|
274
|
+
const rStream = createReadStream(buffer);
|
|
275
|
+
audioData = await toBuffer(rStream);
|
|
276
|
+
}
|
|
277
|
+
else {
|
|
278
|
+
audioData = await toBuffer(buffer);
|
|
279
|
+
}
|
|
280
|
+
const audioBuffer = await decoder(audioData);
|
|
281
|
+
const rawData = audioBuffer.getChannelData(0); // We only need to work with one channel of data
|
|
282
|
+
const samples = 64; // Number of samples we want to have in our final data set
|
|
283
|
+
const blockSize = Math.floor(rawData.length / samples); // the number of samples in each subdivision
|
|
284
|
+
const filteredData = [];
|
|
285
|
+
for (let i = 0; i < samples; i++) {
|
|
286
|
+
const blockStart = blockSize * i; // the location of the first sample in the block
|
|
287
|
+
let sum = 0;
|
|
288
|
+
for (let j = 0; j < blockSize; j++) {
|
|
289
|
+
sum = sum + Math.abs(rawData[blockStart + j]); // find the sum of all the samples in the block
|
|
290
|
+
}
|
|
291
|
+
filteredData.push(sum / blockSize); // divide the sum by the block size to get the average
|
|
292
|
+
}
|
|
293
|
+
// This guarantees that the largest data point will be set to 1, and the rest of the data will scale proportionally.
|
|
294
|
+
const multiplier = Math.pow(Math.max(...filteredData), -1);
|
|
295
|
+
const normalizedData = filteredData.map(n => n * multiplier);
|
|
296
|
+
// Generate waveform like WhatsApp
|
|
297
|
+
const waveform = new Uint8Array(normalizedData.map(n => Math.floor(100 * n)));
|
|
298
|
+
return waveform;
|
|
442
299
|
}
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
async function convertToOpusBuffer(buffer, logger) {
|
|
446
|
-
try {
|
|
447
|
-
const { PassThrough } = require('stream');
|
|
448
|
-
const ff = require('fluent-ffmpeg');
|
|
449
|
-
|
|
450
|
-
return await new Promise((resolve, reject) => {
|
|
451
|
-
const inStream = new PassThrough();
|
|
452
|
-
const outStream = new PassThrough();
|
|
453
|
-
const chunks = [];
|
|
454
|
-
inStream.end(buffer);
|
|
455
|
-
|
|
456
|
-
ff(inStream)
|
|
457
|
-
.noVideo()
|
|
458
|
-
.audioCodec('libopus')
|
|
459
|
-
.format('ogg')
|
|
460
|
-
.audioBitrate('48k')
|
|
461
|
-
.audioChannels(1)
|
|
462
|
-
.audioFrequency(48000)
|
|
463
|
-
.outputOptions([
|
|
464
|
-
'-vn',
|
|
465
|
-
'-b:a 64k',
|
|
466
|
-
'-ac 2',
|
|
467
|
-
'-ar 48000',
|
|
468
|
-
'-map_metadata', '-1',
|
|
469
|
-
'-application', 'voip'
|
|
470
|
-
])
|
|
471
|
-
.on('error', reject)
|
|
472
|
-
.on('end', () => resolve(Buffer.concat(chunks)))
|
|
473
|
-
.pipe(outStream, {
|
|
474
|
-
end: true
|
|
475
|
-
});
|
|
476
|
-
outStream.on('data', c => chunks.push(c));
|
|
477
|
-
});
|
|
478
|
-
} catch (e) {
|
|
479
|
-
logger?.debug(e);
|
|
480
|
-
throw e;
|
|
300
|
+
catch (e) {
|
|
301
|
+
logger?.debug('Failed to generate waveform: ' + e);
|
|
481
302
|
}
|
|
482
303
|
}
|
|
483
|
-
|
|
484
|
-
const
|
|
485
|
-
const readable = new stream_1.Readable({ read: () => { } });
|
|
304
|
+
export const toReadable = (buffer) => {
|
|
305
|
+
const readable = new Readable({ read: () => { } });
|
|
486
306
|
readable.push(buffer);
|
|
487
307
|
readable.push(null);
|
|
488
308
|
return readable;
|
|
489
309
|
};
|
|
490
|
-
|
|
491
|
-
const toBuffer = async (stream) => {
|
|
310
|
+
export const toBuffer = async (stream) => {
|
|
492
311
|
const chunks = [];
|
|
493
312
|
for await (const chunk of stream) {
|
|
494
313
|
chunks.push(chunk);
|
|
@@ -496,61 +315,47 @@ const toBuffer = async (stream) => {
|
|
|
496
315
|
stream.destroy();
|
|
497
316
|
return Buffer.concat(chunks);
|
|
498
317
|
};
|
|
499
|
-
|
|
500
|
-
const getStream = async (item, opts) => {
|
|
318
|
+
export const getStream = async (item, opts) => {
|
|
501
319
|
if (Buffer.isBuffer(item)) {
|
|
502
|
-
return { stream:
|
|
320
|
+
return { stream: toReadable(item), type: 'buffer' };
|
|
503
321
|
}
|
|
504
322
|
if ('stream' in item) {
|
|
505
323
|
return { stream: item.stream, type: 'readable' };
|
|
506
324
|
}
|
|
507
|
-
|
|
508
|
-
|
|
325
|
+
const urlStr = item.url.toString();
|
|
326
|
+
if (urlStr.startsWith('data:')) {
|
|
327
|
+
const buffer = Buffer.from(urlStr.split(',')[1], 'base64');
|
|
328
|
+
return { stream: toReadable(buffer), type: 'buffer' };
|
|
329
|
+
}
|
|
330
|
+
if (urlStr.startsWith('http://') || urlStr.startsWith('https://')) {
|
|
331
|
+
return { stream: await getHttpStream(item.url, opts), type: 'remote' };
|
|
509
332
|
}
|
|
510
|
-
return { stream:
|
|
333
|
+
return { stream: createReadStream(item.url), type: 'file' };
|
|
511
334
|
};
|
|
512
|
-
exports.getStream = getStream;
|
|
513
335
|
/** generates a thumbnail for a given media, if required */
|
|
514
|
-
async function generateThumbnail(file, mediaType, options) {
|
|
515
|
-
var _a;
|
|
336
|
+
export async function generateThumbnail(file, mediaType, options) {
|
|
516
337
|
let thumbnail;
|
|
517
338
|
let originalImageDimensions;
|
|
518
339
|
if (mediaType === 'image') {
|
|
519
|
-
const { buffer, original } = await
|
|
340
|
+
const { buffer, original } = await extractImageThumb(file);
|
|
520
341
|
thumbnail = buffer.toString('base64');
|
|
521
342
|
if (original.width && original.height) {
|
|
522
343
|
originalImageDimensions = {
|
|
523
344
|
width: original.width,
|
|
524
|
-
height: original.height
|
|
345
|
+
height: original.height
|
|
525
346
|
};
|
|
526
347
|
}
|
|
527
348
|
}
|
|
528
349
|
else if (mediaType === 'video') {
|
|
350
|
+
const imgFilename = join(getTmpFilesDirectory(), generateMessageIDV2() + '.jpg');
|
|
529
351
|
try {
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
await fs_1.promises.writeFile(videoPath, buffer);
|
|
535
|
-
}
|
|
536
|
-
const thumbnailBuffer = await (0, exports.extractVideoThumb)(videoPath);
|
|
537
|
-
const imgFilename = (0, path_1.join)(getTmpFilesDirectory(), (0, generics_1.generateMessageIDV2)() + '.jpg');
|
|
538
|
-
await fs_1.promises.writeFile(imgFilename, thumbnailBuffer);
|
|
539
|
-
const { buffer: processedThumbnailBuffer, original } = await (0, exports.extractImageThumb)(imgFilename);
|
|
540
|
-
thumbnail = processedThumbnailBuffer.toString('base64');
|
|
541
|
-
if (original.width && original.height) {
|
|
542
|
-
originalImageDimensions = {
|
|
543
|
-
width: original.width,
|
|
544
|
-
height: original.height,
|
|
545
|
-
};
|
|
546
|
-
}
|
|
547
|
-
await fs_1.promises.unlink(imgFilename);
|
|
548
|
-
if (videoPath !== file) {
|
|
549
|
-
await fs_1.promises.unlink(videoPath);
|
|
550
|
-
}
|
|
352
|
+
await extractVideoThumb(file, imgFilename, '00:00:00', { width: 32, height: 32 });
|
|
353
|
+
const buff = await fs.readFile(imgFilename);
|
|
354
|
+
thumbnail = buff.toString('base64');
|
|
355
|
+
await fs.unlink(imgFilename);
|
|
551
356
|
}
|
|
552
357
|
catch (err) {
|
|
553
|
-
|
|
358
|
+
options.logger?.debug('could not generate video thumb: ' + err);
|
|
554
359
|
}
|
|
555
360
|
}
|
|
556
361
|
return {
|
|
@@ -558,182 +363,145 @@ async function generateThumbnail(file, mediaType, options) {
|
|
|
558
363
|
originalImageDimensions
|
|
559
364
|
};
|
|
560
365
|
}
|
|
561
|
-
const getHttpStream = async (url, options = {}) => {
|
|
562
|
-
const
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
let bodyPath;
|
|
570
|
-
let didSaveToTmpPath = false;
|
|
571
|
-
try {
|
|
572
|
-
const buffer = await (0, exports.toBuffer)(stream);
|
|
573
|
-
if (type === 'file') {
|
|
574
|
-
bodyPath = media.url;
|
|
575
|
-
}
|
|
576
|
-
else if (saveOriginalFileIfRequired) {
|
|
577
|
-
bodyPath = (0, path_1.join)(getTmpFilesDirectory(), mediaType + (0, generics_1.generateMessageIDV2)());
|
|
578
|
-
(0, fs_1.writeFileSync)(bodyPath, buffer);
|
|
579
|
-
didSaveToTmpPath = true;
|
|
580
|
-
}
|
|
581
|
-
const fileLength = buffer.length;
|
|
582
|
-
const fileSha256 = Crypto.createHash('sha256').update(buffer).digest();
|
|
583
|
-
stream === null || stream === void 0 ? void 0 : stream.destroy();
|
|
584
|
-
logger === null || logger === void 0 ? void 0 : logger.debug('prepare stream data successfully');
|
|
585
|
-
return {
|
|
586
|
-
mediaKey: undefined,
|
|
587
|
-
encWriteStream: buffer,
|
|
588
|
-
fileLength,
|
|
589
|
-
fileSha256,
|
|
590
|
-
fileEncSha256: undefined,
|
|
591
|
-
bodyPath,
|
|
592
|
-
didSaveToTmpPath
|
|
593
|
-
};
|
|
594
|
-
}
|
|
595
|
-
catch (error) {
|
|
596
|
-
stream.destroy();
|
|
597
|
-
if (didSaveToTmpPath) {
|
|
598
|
-
try {
|
|
599
|
-
await fs_1.promises.unlink(bodyPath);
|
|
600
|
-
}
|
|
601
|
-
catch (err) {
|
|
602
|
-
logger === null || logger === void 0 ? void 0 : logger.error({ err }, 'failed to save to tmp path');
|
|
603
|
-
}
|
|
604
|
-
}
|
|
605
|
-
throw error;
|
|
366
|
+
export const getHttpStream = async (url, options = {}) => {
|
|
367
|
+
const response = await fetch(url.toString(), {
|
|
368
|
+
dispatcher: options.dispatcher,
|
|
369
|
+
method: 'GET',
|
|
370
|
+
headers: options.headers
|
|
371
|
+
});
|
|
372
|
+
if (!response.ok) {
|
|
373
|
+
throw new Boom(`Failed to fetch stream from ${url}`, { statusCode: response.status, data: { url } });
|
|
606
374
|
}
|
|
375
|
+
// @ts-ignore Node18+ Readable.fromWeb exists
|
|
376
|
+
return response.body instanceof Readable ? response.body : Readable.fromWeb(response.body);
|
|
607
377
|
};
|
|
608
|
-
|
|
609
|
-
const
|
|
610
|
-
const { stream, type } = await (0, exports.getStream)(media, opts);
|
|
378
|
+
export const encryptedStream = async (media, mediaType, { logger, saveOriginalFileIfRequired, opts } = {}) => {
|
|
379
|
+
const { stream, type } = await getStream(media, opts);
|
|
611
380
|
logger?.debug('fetched media stream');
|
|
612
|
-
|
|
613
|
-
let finalStream = stream;
|
|
614
|
-
|
|
615
|
-
// Tambahan untuk konversi audio jadi Opus (PTT)
|
|
616
|
-
if (mediaType === 'audio' && (isPtt === true || forceOpus === true)) {
|
|
617
|
-
try {
|
|
618
|
-
const buffer = await (0, exports.toBuffer)(stream);
|
|
619
|
-
const opusBuffer = await exports.convertToOpusBuffer(buffer, logger);
|
|
620
|
-
finalStream = (0, exports.toReadable)(opusBuffer);
|
|
621
|
-
logger?.debug('converted audio to Opus');
|
|
622
|
-
} catch (error) {
|
|
623
|
-
logger?.error('failed to convert audio to Opus, fallback to original stream');
|
|
624
|
-
const { stream: newStream } = await (0, exports.getStream)(media, opts);
|
|
625
|
-
finalStream = newStream;
|
|
626
|
-
}
|
|
627
|
-
}
|
|
628
|
-
|
|
629
381
|
const mediaKey = Crypto.randomBytes(32);
|
|
630
382
|
const { cipherKey, iv, macKey } = await getMediaKeys(mediaKey, mediaType);
|
|
631
|
-
const
|
|
632
|
-
|
|
633
|
-
let
|
|
634
|
-
let
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
}
|
|
639
|
-
bodyPath = (0, path_1.join)(getTmpFilesDirectory(), mediaType + (0, generics_1.generateMessageIDV2)());
|
|
640
|
-
writeStream = (0, fs_1.createWriteStream)(bodyPath);
|
|
641
|
-
didSaveToTmpPath = true;
|
|
642
|
-
}
|
|
643
|
-
|
|
383
|
+
const encFilePath = join(getTmpFilesDirectory(), mediaType + generateMessageIDV2() + '-enc');
|
|
384
|
+
const encFileWriteStream = createWriteStream(encFilePath);
|
|
385
|
+
let originalFileStream;
|
|
386
|
+
let originalFilePath;
|
|
387
|
+
if (saveOriginalFileIfRequired) {
|
|
388
|
+
originalFilePath = join(getTmpFilesDirectory(), mediaType + generateMessageIDV2() + '-original');
|
|
389
|
+
originalFileStream = createWriteStream(originalFilePath);
|
|
390
|
+
}
|
|
644
391
|
let fileLength = 0;
|
|
645
392
|
const aes = Crypto.createCipheriv('aes-256-cbc', cipherKey, iv);
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
393
|
+
const hmac = Crypto.createHmac('sha256', macKey).update(iv);
|
|
394
|
+
const sha256Plain = Crypto.createHash('sha256');
|
|
395
|
+
const sha256Enc = Crypto.createHash('sha256');
|
|
396
|
+
const onChunk = async (buff) => {
|
|
397
|
+
sha256Enc.update(buff);
|
|
398
|
+
hmac.update(buff);
|
|
399
|
+
// Handle backpressure: if write returns false, wait for drain
|
|
400
|
+
if (!encFileWriteStream.write(buff)) {
|
|
401
|
+
await once(encFileWriteStream, 'drain');
|
|
402
|
+
}
|
|
403
|
+
};
|
|
650
404
|
try {
|
|
651
|
-
for await (const data of
|
|
405
|
+
for await (const data of stream) {
|
|
652
406
|
fileLength += data.length;
|
|
653
|
-
if (type === 'remote' &&
|
|
654
|
-
|
|
407
|
+
if (type === 'remote' &&
|
|
408
|
+
opts?.maxContentLength &&
|
|
409
|
+
fileLength + data.length > opts.maxContentLength) {
|
|
410
|
+
throw new Boom(`content length exceeded when encrypting "${type}"`, {
|
|
655
411
|
data: { media, type }
|
|
656
412
|
});
|
|
657
413
|
}
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
await (0, events_1.once)(writeStream, 'drain');
|
|
414
|
+
if (originalFileStream) {
|
|
415
|
+
if (!originalFileStream.write(data)) {
|
|
416
|
+
await once(originalFileStream, 'drain');
|
|
662
417
|
}
|
|
663
418
|
}
|
|
664
|
-
|
|
419
|
+
sha256Plain.update(data);
|
|
420
|
+
await onChunk(aes.update(data));
|
|
665
421
|
}
|
|
666
|
-
|
|
667
|
-
onChunk(aes.final());
|
|
422
|
+
await onChunk(aes.final());
|
|
668
423
|
const mac = hmac.digest().slice(0, 10);
|
|
669
|
-
sha256Enc
|
|
424
|
+
sha256Enc.update(mac);
|
|
670
425
|
const fileSha256 = sha256Plain.digest();
|
|
671
426
|
const fileEncSha256 = sha256Enc.digest();
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
427
|
+
encFileWriteStream.write(mac);
|
|
428
|
+
const encFinishPromise = once(encFileWriteStream, 'finish');
|
|
429
|
+
const originalFinishPromise = originalFileStream ? once(originalFileStream, 'finish') : Promise.resolve();
|
|
430
|
+
encFileWriteStream.end();
|
|
431
|
+
originalFileStream?.end?.();
|
|
432
|
+
stream.destroy();
|
|
433
|
+
// Wait for write streams to fully flush to disk
|
|
434
|
+
// This helps reduce memory pressure by allowing OS to release buffers
|
|
435
|
+
await encFinishPromise;
|
|
436
|
+
await originalFinishPromise;
|
|
678
437
|
logger?.debug('encrypted data successfully');
|
|
679
|
-
|
|
680
438
|
return {
|
|
681
439
|
mediaKey,
|
|
682
|
-
|
|
683
|
-
|
|
440
|
+
originalFilePath,
|
|
441
|
+
encFilePath,
|
|
684
442
|
mac,
|
|
685
443
|
fileEncSha256,
|
|
686
444
|
fileSha256,
|
|
687
|
-
fileLength
|
|
688
|
-
didSaveToTmpPath
|
|
445
|
+
fileLength
|
|
689
446
|
};
|
|
690
|
-
}
|
|
691
|
-
|
|
692
|
-
|
|
447
|
+
}
|
|
448
|
+
catch (error) {
|
|
449
|
+
// destroy all streams with error
|
|
450
|
+
encFileWriteStream.destroy();
|
|
451
|
+
originalFileStream?.destroy?.();
|
|
693
452
|
aes.destroy();
|
|
694
453
|
hmac.destroy();
|
|
695
454
|
sha256Plain.destroy();
|
|
696
455
|
sha256Enc.destroy();
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
await
|
|
702
|
-
} catch (err) {
|
|
703
|
-
logger?.error({ err }, 'failed to save to tmp path');
|
|
456
|
+
stream.destroy();
|
|
457
|
+
try {
|
|
458
|
+
await fs.unlink(encFilePath);
|
|
459
|
+
if (originalFilePath) {
|
|
460
|
+
await fs.unlink(originalFilePath);
|
|
704
461
|
}
|
|
705
462
|
}
|
|
463
|
+
catch (err) {
|
|
464
|
+
logger?.error({ err }, 'failed deleting tmp files');
|
|
465
|
+
}
|
|
706
466
|
throw error;
|
|
707
467
|
}
|
|
708
|
-
|
|
709
|
-
function onChunk(buff) {
|
|
710
|
-
sha256Enc = sha256Enc.update(buff);
|
|
711
|
-
hmac = hmac.update(buff);
|
|
712
|
-
encWriteStream.push(buff);
|
|
713
|
-
}
|
|
714
468
|
};
|
|
715
|
-
|
|
716
|
-
const DEF_HOST = 'mmg.whatsapp.net';
|
|
469
|
+
export const DEF_MEDIA_HOST = 'mmg.whatsapp.net';
|
|
717
470
|
const AES_CHUNK_SIZE = 16;
|
|
718
471
|
const toSmallestChunkSize = (num) => {
|
|
719
472
|
return Math.floor(num / AES_CHUNK_SIZE) * AES_CHUNK_SIZE;
|
|
720
473
|
};
|
|
721
|
-
const getUrlFromDirectPath = (directPath) => `https://${
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
474
|
+
export const getUrlFromDirectPath = (directPath, host = DEF_MEDIA_HOST) => `https://${host}${directPath}`;
|
|
475
|
+
const extractHost = (url) => {
|
|
476
|
+
if (!url)
|
|
477
|
+
return undefined;
|
|
478
|
+
try {
|
|
479
|
+
return new URL(url).host;
|
|
480
|
+
}
|
|
481
|
+
catch {
|
|
482
|
+
return undefined;
|
|
483
|
+
}
|
|
484
|
+
};
|
|
485
|
+
export const downloadContentFromMessage = async ({ mediaKey, directPath, url }, type, opts = {}) => {
|
|
486
|
+
// Fallback host: explicit opt > host parsed from `url` > DEF_MEDIA_HOST.
|
|
487
|
+
// Lets us honor a non-default host carried by the proto without forcing callers to thread it through.
|
|
488
|
+
const fallbackHost = opts.host ?? extractHost(url);
|
|
489
|
+
const downloadUrl = directPath ? getUrlFromDirectPath(directPath, fallbackHost) : url;
|
|
726
490
|
if (!downloadUrl) {
|
|
727
|
-
throw new
|
|
491
|
+
throw new Boom('No valid media URL or directPath present in message', { statusCode: 400 });
|
|
728
492
|
}
|
|
729
493
|
const keys = await getMediaKeys(mediaKey, type);
|
|
730
|
-
return
|
|
494
|
+
return downloadEncryptedContent(downloadUrl, keys, opts);
|
|
731
495
|
};
|
|
732
|
-
|
|
733
|
-
|
|
496
|
+
/**
|
|
497
|
+
* Decrypts and downloads an AES256-CBC encrypted file given the keys.
|
|
498
|
+
* Assumes the SHA256 of the plaintext is appended to the end of the ciphertext
|
|
499
|
+
* */
|
|
500
|
+
export const downloadEncryptedContent = async (downloadUrl, { cipherKey, iv }, { startByte, endByte, options } = {}) => {
|
|
734
501
|
let bytesFetched = 0;
|
|
735
502
|
let startChunk = 0;
|
|
736
503
|
let firstBlockIsIV = false;
|
|
504
|
+
// if a start byte is specified -- then we need to fetch the previous chunk as that will form the IV
|
|
737
505
|
if (startByte) {
|
|
738
506
|
const chunk = toSmallestChunkSize(startByte || 0);
|
|
739
507
|
if (chunk) {
|
|
@@ -743,9 +511,14 @@ const downloadEncryptedContent = async (downloadUrl, { cipherKey, iv }, { startB
|
|
|
743
511
|
}
|
|
744
512
|
}
|
|
745
513
|
const endChunk = endByte ? toSmallestChunkSize(endByte || 0) + AES_CHUNK_SIZE : undefined;
|
|
514
|
+
const headersInit = options?.headers ? options.headers : undefined;
|
|
746
515
|
const headers = {
|
|
747
|
-
...(
|
|
748
|
-
|
|
516
|
+
...(headersInit
|
|
517
|
+
? Array.isArray(headersInit)
|
|
518
|
+
? Object.fromEntries(headersInit)
|
|
519
|
+
: headersInit
|
|
520
|
+
: {}),
|
|
521
|
+
Origin: DEFAULT_ORIGIN
|
|
749
522
|
};
|
|
750
523
|
if (startChunk || endChunk) {
|
|
751
524
|
headers.Range = `bytes=${startChunk}-`;
|
|
@@ -753,11 +526,10 @@ const downloadEncryptedContent = async (downloadUrl, { cipherKey, iv }, { startB
|
|
|
753
526
|
headers.Range += endChunk;
|
|
754
527
|
}
|
|
755
528
|
}
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
maxContentLength: Infinity,
|
|
529
|
+
// download the message
|
|
530
|
+
const fetched = await getHttpStream(downloadUrl, {
|
|
531
|
+
...(options || {}),
|
|
532
|
+
headers
|
|
761
533
|
});
|
|
762
534
|
let remainingBytes = Buffer.from([]);
|
|
763
535
|
let aes;
|
|
@@ -772,9 +544,9 @@ const downloadEncryptedContent = async (downloadUrl, { cipherKey, iv }, { startB
|
|
|
772
544
|
push(bytes);
|
|
773
545
|
}
|
|
774
546
|
};
|
|
775
|
-
const output = new
|
|
547
|
+
const output = new Transform({
|
|
776
548
|
transform(chunk, _, callback) {
|
|
777
|
-
let data = Buffer.concat([remainingBytes, chunk]);
|
|
549
|
+
let data = remainingBytes.length ? Buffer.concat([remainingBytes, chunk]) : chunk;
|
|
778
550
|
const decryptLength = toSmallestChunkSize(data.length);
|
|
779
551
|
remainingBytes = data.slice(decryptLength);
|
|
780
552
|
data = data.slice(0, decryptLength);
|
|
@@ -785,6 +557,8 @@ const downloadEncryptedContent = async (downloadUrl, { cipherKey, iv }, { startB
|
|
|
785
557
|
data = data.slice(AES_CHUNK_SIZE);
|
|
786
558
|
}
|
|
787
559
|
aes = Crypto.createDecipheriv('aes-256-cbc', cipherKey, ivValue);
|
|
560
|
+
// if an end byte that is not EOF is specified
|
|
561
|
+
// stop auto padding (PKCS7) -- otherwise throws an error for decryption
|
|
788
562
|
if (endByte) {
|
|
789
563
|
aes.setAutoPadding(false);
|
|
790
564
|
}
|
|
@@ -805,18 +579,15 @@ const downloadEncryptedContent = async (downloadUrl, { cipherKey, iv }, { startB
|
|
|
805
579
|
catch (error) {
|
|
806
580
|
callback(error);
|
|
807
581
|
}
|
|
808
|
-
}
|
|
582
|
+
}
|
|
809
583
|
});
|
|
810
584
|
return fetched.pipe(output, { end: true });
|
|
811
585
|
};
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
const getExtension = (mimetype) => mimetype.split(';')[0].split('/')[1];
|
|
586
|
+
export function extensionForMediaMessage(message) {
|
|
587
|
+
const getExtension = (mimetype) => mimetype.split(';')[0]?.split('/')[1];
|
|
815
588
|
const type = Object.keys(message)[0];
|
|
816
589
|
let extension;
|
|
817
|
-
if (type === 'locationMessage' ||
|
|
818
|
-
type === 'liveLocationMessage' ||
|
|
819
|
-
type === 'productMessage') {
|
|
590
|
+
if (type === 'locationMessage' || type === 'liveLocationMessage' || type === 'productMessage') {
|
|
820
591
|
extension = '.jpeg';
|
|
821
592
|
}
|
|
822
593
|
else {
|
|
@@ -825,52 +596,158 @@ function extensionForMediaMessage(message) {
|
|
|
825
596
|
}
|
|
826
597
|
return extension;
|
|
827
598
|
}
|
|
828
|
-
const
|
|
829
|
-
return
|
|
830
|
-
|
|
599
|
+
const isNodeRuntime = () => {
|
|
600
|
+
return (typeof process !== 'undefined' &&
|
|
601
|
+
process.versions?.node !== null &&
|
|
602
|
+
typeof process.versions.bun === 'undefined' &&
|
|
603
|
+
typeof globalThis.Deno === 'undefined');
|
|
604
|
+
};
|
|
605
|
+
export const uploadWithNodeHttp = async ({ url, filePath, headers, timeoutMs, agent }, redirectCount = 0) => {
|
|
606
|
+
if (redirectCount > 5) {
|
|
607
|
+
throw new Error('Too many redirects');
|
|
608
|
+
}
|
|
609
|
+
const parsedUrl = new URL(url);
|
|
610
|
+
const httpModule = parsedUrl.protocol === 'https:' ? await import('https') : await import('http');
|
|
611
|
+
// Get file size for Content-Length header (required for Node.js streaming)
|
|
612
|
+
const fileStats = await fs.stat(filePath);
|
|
613
|
+
const fileSize = fileStats.size;
|
|
614
|
+
return new Promise((resolve, reject) => {
|
|
615
|
+
const req = httpModule.request({
|
|
616
|
+
hostname: parsedUrl.hostname,
|
|
617
|
+
port: parsedUrl.port || (parsedUrl.protocol === 'https:' ? 443 : 80),
|
|
618
|
+
path: parsedUrl.pathname + parsedUrl.search,
|
|
619
|
+
method: 'POST',
|
|
620
|
+
headers: {
|
|
621
|
+
...headers,
|
|
622
|
+
'Content-Length': fileSize
|
|
623
|
+
},
|
|
624
|
+
agent,
|
|
625
|
+
timeout: timeoutMs
|
|
626
|
+
}, res => {
|
|
627
|
+
// Handle redirects (3xx)
|
|
628
|
+
if (res.statusCode && res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
629
|
+
res.resume(); // Consume response to free resources
|
|
630
|
+
const newUrl = new URL(res.headers.location, url).toString();
|
|
631
|
+
resolve(uploadWithNodeHttp({
|
|
632
|
+
url: newUrl,
|
|
633
|
+
filePath,
|
|
634
|
+
headers,
|
|
635
|
+
timeoutMs,
|
|
636
|
+
agent
|
|
637
|
+
}, redirectCount + 1));
|
|
638
|
+
return;
|
|
639
|
+
}
|
|
640
|
+
let body = '';
|
|
641
|
+
res.on('data', chunk => (body += chunk));
|
|
642
|
+
res.on('end', () => {
|
|
643
|
+
try {
|
|
644
|
+
resolve(JSON.parse(body));
|
|
645
|
+
}
|
|
646
|
+
catch {
|
|
647
|
+
resolve(undefined);
|
|
648
|
+
}
|
|
649
|
+
});
|
|
650
|
+
});
|
|
651
|
+
req.on('error', reject);
|
|
652
|
+
req.on('timeout', () => {
|
|
653
|
+
req.destroy();
|
|
654
|
+
reject(new Error('Upload timeout'));
|
|
655
|
+
});
|
|
656
|
+
const stream = createReadStream(filePath);
|
|
657
|
+
stream.pipe(req);
|
|
658
|
+
stream.on('error', err => {
|
|
659
|
+
req.destroy();
|
|
660
|
+
reject(err);
|
|
661
|
+
});
|
|
662
|
+
});
|
|
663
|
+
};
|
|
664
|
+
const uploadWithFetch = async ({ url, filePath, headers, timeoutMs, agent }) => {
|
|
665
|
+
// Convert Node.js Readable to Web ReadableStream
|
|
666
|
+
const nodeStream = createReadStream(filePath);
|
|
667
|
+
const webStream = Readable.toWeb(nodeStream);
|
|
668
|
+
// Native fetch only accepts Undici-style dispatchers, not generic https Agents.
|
|
669
|
+
const dispatcher = typeof agent?.dispatch === 'function' ? agent : undefined;
|
|
670
|
+
const response = await fetch(url, {
|
|
671
|
+
...(dispatcher ? { dispatcher } : {}),
|
|
672
|
+
method: 'POST',
|
|
673
|
+
body: webStream,
|
|
674
|
+
headers,
|
|
675
|
+
duplex: 'half',
|
|
676
|
+
signal: timeoutMs ? AbortSignal.timeout(timeoutMs) : undefined
|
|
677
|
+
});
|
|
678
|
+
try {
|
|
679
|
+
return (await response.json());
|
|
680
|
+
}
|
|
681
|
+
catch {
|
|
682
|
+
return undefined;
|
|
683
|
+
}
|
|
684
|
+
};
|
|
685
|
+
/**
|
|
686
|
+
* Uploads media to WhatsApp servers.
|
|
687
|
+
*
|
|
688
|
+
* ## Why we have two upload implementations:
|
|
689
|
+
*
|
|
690
|
+
* Node.js's native `fetch` (powered by undici) has a known bug where it buffers
|
|
691
|
+
* the entire request body in memory before sending, even when using streams.
|
|
692
|
+
* This causes memory issues with large files (e.g., 1GB file = 1GB+ memory usage).
|
|
693
|
+
* See: https://github.com/nodejs/undici/issues/4058
|
|
694
|
+
*
|
|
695
|
+
* Other runtimes (Bun, Deno, browsers) correctly stream the request body without
|
|
696
|
+
* buffering, so we can use the web-standard Fetch API there.
|
|
697
|
+
*
|
|
698
|
+
* ## Future considerations:
|
|
699
|
+
* Once the undici bug is fixed, we can simplify this to use only the Fetch API
|
|
700
|
+
* across all runtimes. Monitor the GitHub issue for updates.
|
|
701
|
+
*/
|
|
702
|
+
const uploadMedia = async (params, logger) => {
|
|
703
|
+
if (isNodeRuntime()) {
|
|
704
|
+
logger?.debug('Using Node.js https module for upload (avoids undici buffering bug)');
|
|
705
|
+
return uploadWithNodeHttp(params);
|
|
706
|
+
}
|
|
707
|
+
else {
|
|
708
|
+
logger?.debug('Using web-standard Fetch API for upload');
|
|
709
|
+
return uploadWithFetch(params);
|
|
710
|
+
}
|
|
711
|
+
};
|
|
712
|
+
export const getWAUploadToServer = ({ customUploadHosts, fetchAgent, logger, options }, refreshMediaConn) => {
|
|
713
|
+
return async (filePath, { mediaType, fileEncSha256B64, timeoutMs }) => {
|
|
714
|
+
// send a query JSON to obtain the url & auth token to upload our media
|
|
831
715
|
let uploadInfo = await refreshMediaConn(false);
|
|
832
716
|
let urls;
|
|
833
717
|
const hosts = [...customUploadHosts, ...uploadInfo.hosts];
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
718
|
+
fileEncSha256B64 = encodeBase64EncodedStringForUpload(fileEncSha256B64);
|
|
719
|
+
// Prepare common headers
|
|
720
|
+
const customHeaders = (() => {
|
|
721
|
+
const hdrs = options?.headers;
|
|
722
|
+
if (!hdrs)
|
|
723
|
+
return {};
|
|
724
|
+
return Array.isArray(hdrs) ? Object.fromEntries(hdrs) : hdrs;
|
|
725
|
+
})();
|
|
726
|
+
const headers = {
|
|
727
|
+
...customHeaders,
|
|
728
|
+
'Content-Type': 'application/octet-stream',
|
|
729
|
+
Origin: DEFAULT_ORIGIN
|
|
730
|
+
};
|
|
731
|
+
for (const { hostname } of hosts) {
|
|
847
732
|
logger.debug(`uploading to "${hostname}"`);
|
|
848
733
|
const auth = encodeURIComponent(uploadInfo.auth);
|
|
849
|
-
const url = `https://${hostname}${
|
|
734
|
+
const url = `https://${hostname}${MEDIA_PATH_MAP[mediaType]}/${fileEncSha256B64}?auth=${auth}&token=${fileEncSha256B64}`;
|
|
850
735
|
let result;
|
|
851
736
|
try {
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
'Origin': Defaults_1.DEFAULT_ORIGIN
|
|
861
|
-
},
|
|
862
|
-
httpsAgent: fetchAgent,
|
|
863
|
-
timeout: timeoutMs,
|
|
864
|
-
responseType: 'json',
|
|
865
|
-
maxBodyLength: Infinity,
|
|
866
|
-
maxContentLength: Infinity,
|
|
867
|
-
});
|
|
868
|
-
result = body.data;
|
|
869
|
-
if ((result === null || result === void 0 ? void 0 : result.url) || (result === null || result === void 0 ? void 0 : result.directPath)) {
|
|
737
|
+
result = await uploadMedia({
|
|
738
|
+
url,
|
|
739
|
+
filePath,
|
|
740
|
+
headers,
|
|
741
|
+
timeoutMs,
|
|
742
|
+
agent: fetchAgent
|
|
743
|
+
}, logger);
|
|
744
|
+
if (result?.url || result?.direct_path) {
|
|
870
745
|
urls = {
|
|
871
746
|
mediaUrl: result.url,
|
|
872
747
|
directPath: result.direct_path,
|
|
873
|
-
|
|
748
|
+
meta_hmac: result.meta_hmac,
|
|
749
|
+
fbid: result.fbid,
|
|
750
|
+
ts: result.ts
|
|
874
751
|
};
|
|
875
752
|
break;
|
|
876
753
|
}
|
|
@@ -880,37 +757,39 @@ const getWAUploadToServer = ({ customUploadHosts, fetchAgent, logger, options },
|
|
|
880
757
|
}
|
|
881
758
|
}
|
|
882
759
|
catch (error) {
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
}
|
|
886
|
-
const isLast = hostname === ((_b = hosts[uploadInfo.hosts.length - 1]) === null || _b === void 0 ? void 0 : _b.hostname);
|
|
887
|
-
logger.warn({ trace: error.stack, uploadResult: result }, `Error in uploading to ${hostname} ${isLast ? '' : ', retrying...'}`);
|
|
760
|
+
const isLast = hostname === hosts[uploadInfo.hosts.length - 1]?.hostname;
|
|
761
|
+
logger.warn({ trace: error?.stack, uploadResult: result }, `Error in uploading to ${hostname} ${isLast ? '' : ', retrying...'}`);
|
|
888
762
|
}
|
|
889
763
|
}
|
|
890
764
|
if (!urls) {
|
|
891
|
-
throw new
|
|
765
|
+
throw new Boom('Media upload failed on all hosts', { statusCode: 500 });
|
|
892
766
|
}
|
|
893
767
|
return urls;
|
|
894
768
|
};
|
|
895
769
|
};
|
|
896
|
-
exports.getWAUploadToServer = getWAUploadToServer;
|
|
897
770
|
const getMediaRetryKey = (mediaKey) => {
|
|
898
|
-
return
|
|
771
|
+
return hkdf(mediaKey, 32, { info: 'WhatsApp Media Retry Notification' });
|
|
899
772
|
};
|
|
900
|
-
|
|
773
|
+
/**
|
|
774
|
+
* Generate a binary node that will request the phone to re-upload the media & return the newly uploaded URL
|
|
775
|
+
*/
|
|
776
|
+
export const encryptMediaRetryRequest = (key, mediaKey, meId) => {
|
|
901
777
|
const recp = { stanzaId: key.id };
|
|
902
|
-
const recpBuffer =
|
|
778
|
+
const recpBuffer = proto.ServerErrorReceipt.encode(recp).finish();
|
|
903
779
|
const iv = Crypto.randomBytes(12);
|
|
904
|
-
const retryKey =
|
|
905
|
-
const ciphertext =
|
|
780
|
+
const retryKey = getMediaRetryKey(mediaKey);
|
|
781
|
+
const ciphertext = aesEncryptGCM(recpBuffer, retryKey, iv, Buffer.from(key.id));
|
|
906
782
|
const req = {
|
|
907
783
|
tag: 'receipt',
|
|
908
784
|
attrs: {
|
|
909
785
|
id: key.id,
|
|
910
|
-
to:
|
|
786
|
+
to: jidNormalizedUser(meId),
|
|
911
787
|
type: 'server-error'
|
|
912
788
|
},
|
|
913
789
|
content: [
|
|
790
|
+
// this encrypt node is actually pretty useless
|
|
791
|
+
// the media is returned even without this node
|
|
792
|
+
// keeping it here to maintain parity with WA Web
|
|
914
793
|
{
|
|
915
794
|
tag: 'encrypt',
|
|
916
795
|
attrs: {},
|
|
@@ -923,7 +802,8 @@ const encryptMediaRetryRequest = async (key, mediaKey, meId) => {
|
|
|
923
802
|
tag: 'rmr',
|
|
924
803
|
attrs: {
|
|
925
804
|
jid: key.remoteJid,
|
|
926
|
-
|
|
805
|
+
from_me: (!!key.fromMe).toString(),
|
|
806
|
+
// @ts-ignore
|
|
927
807
|
participant: key.participant || undefined
|
|
928
808
|
}
|
|
929
809
|
}
|
|
@@ -931,9 +811,8 @@ const encryptMediaRetryRequest = async (key, mediaKey, meId) => {
|
|
|
931
811
|
};
|
|
932
812
|
return req;
|
|
933
813
|
};
|
|
934
|
-
|
|
935
|
-
const
|
|
936
|
-
const rmrNode = (0, WABinary_1.getBinaryNodeChild)(node, 'rmr');
|
|
814
|
+
export const decodeMediaRetryNode = (node) => {
|
|
815
|
+
const rmrNode = getBinaryNodeChild(node, 'rmr');
|
|
937
816
|
const event = {
|
|
938
817
|
key: {
|
|
939
818
|
id: node.attrs.id,
|
|
@@ -942,39 +821,157 @@ const decodeMediaRetryNode = (node) => {
|
|
|
942
821
|
participant: rmrNode.attrs.participant
|
|
943
822
|
}
|
|
944
823
|
};
|
|
945
|
-
const errorNode =
|
|
824
|
+
const errorNode = getBinaryNodeChild(node, 'error');
|
|
946
825
|
if (errorNode) {
|
|
947
826
|
const errorCode = +errorNode.attrs.code;
|
|
948
|
-
event.error = new
|
|
827
|
+
event.error = new Boom(`Failed to re-upload media (${errorCode})`, {
|
|
828
|
+
data: errorNode.attrs,
|
|
829
|
+
statusCode: getStatusCodeForMediaRetry(errorCode)
|
|
830
|
+
});
|
|
949
831
|
}
|
|
950
832
|
else {
|
|
951
|
-
const encryptedInfoNode =
|
|
952
|
-
const ciphertext =
|
|
953
|
-
const iv =
|
|
833
|
+
const encryptedInfoNode = getBinaryNodeChild(node, 'encrypt');
|
|
834
|
+
const ciphertext = getBinaryNodeChildBuffer(encryptedInfoNode, 'enc_p');
|
|
835
|
+
const iv = getBinaryNodeChildBuffer(encryptedInfoNode, 'enc_iv');
|
|
954
836
|
if (ciphertext && iv) {
|
|
955
837
|
event.media = { ciphertext, iv };
|
|
956
838
|
}
|
|
957
839
|
else {
|
|
958
|
-
event.error = new
|
|
840
|
+
event.error = new Boom('Failed to re-upload media (missing ciphertext)', { statusCode: 404 });
|
|
959
841
|
}
|
|
960
842
|
}
|
|
961
843
|
return event;
|
|
962
844
|
};
|
|
963
|
-
|
|
964
|
-
const
|
|
965
|
-
const
|
|
966
|
-
|
|
967
|
-
return WAProto_1.proto.MediaRetryNotification.decode(plaintext);
|
|
845
|
+
export const decryptMediaRetryData = ({ ciphertext, iv }, mediaKey, msgId) => {
|
|
846
|
+
const retryKey = getMediaRetryKey(mediaKey);
|
|
847
|
+
const plaintext = aesDecryptGCM(ciphertext, retryKey, iv, Buffer.from(msgId));
|
|
848
|
+
return proto.MediaRetryNotification.decode(plaintext);
|
|
968
849
|
};
|
|
969
|
-
|
|
970
|
-
const getStatusCodeForMediaRetry = (code) => MEDIA_RETRY_STATUS_MAP[code];
|
|
971
|
-
exports.getStatusCodeForMediaRetry = getStatusCodeForMediaRetry;
|
|
850
|
+
export const getStatusCodeForMediaRetry = (code) => MEDIA_RETRY_STATUS_MAP[code];
|
|
972
851
|
const MEDIA_RETRY_STATUS_MAP = {
|
|
973
|
-
[
|
|
974
|
-
[
|
|
975
|
-
[
|
|
976
|
-
[
|
|
852
|
+
[proto.MediaRetryNotification.ResultType.SUCCESS]: 200,
|
|
853
|
+
[proto.MediaRetryNotification.ResultType.DECRYPTION_ERROR]: 412,
|
|
854
|
+
[proto.MediaRetryNotification.ResultType.NOT_FOUND]: 404,
|
|
855
|
+
[proto.MediaRetryNotification.ResultType.GENERAL_ERROR]: 418
|
|
856
|
+
};
|
|
857
|
+
const runFfmpegBuffer = (args, input) => {
|
|
858
|
+
return new Promise((resolve, reject) => {
|
|
859
|
+
const ff = spawn('ffmpeg', args, { stdio: ['pipe', 'pipe', 'ignore'] });
|
|
860
|
+
const chunks = [];
|
|
861
|
+
let len = 0;
|
|
862
|
+
ff.stdout.on('data', (c) => {
|
|
863
|
+
chunks.push(c);
|
|
864
|
+
len += c.length;
|
|
865
|
+
});
|
|
866
|
+
ff.on('close', code => (code === 0 ? resolve(Buffer.concat(chunks, len)) : reject(new Boom(`ffmpeg exited with code ${code}`))));
|
|
867
|
+
ff.on('error', reject);
|
|
868
|
+
ff.stdin.end(input);
|
|
869
|
+
});
|
|
870
|
+
};
|
|
871
|
+
export const resizeImage = async (buf, width, height, opts = {}) => {
|
|
872
|
+
const { quality = 80 } = opts;
|
|
873
|
+
const lib = await getImageProcessingLibrary();
|
|
874
|
+
if ('sharp' in lib && typeof lib.sharp?.default === 'function') {
|
|
875
|
+
return lib.sharp.default(buf).resize(width, height, { fit: 'inside' }).jpeg({ quality }).toBuffer();
|
|
876
|
+
}
|
|
877
|
+
//@ts-ignore
|
|
878
|
+
const jimpMod = await import('jimp').catch(() => undefined);
|
|
879
|
+
if (jimpMod) {
|
|
880
|
+
const Jimp = jimpMod.default || jimpMod;
|
|
881
|
+
const img = await Jimp.read(buf);
|
|
882
|
+
img.resize(width, height);
|
|
883
|
+
img.quality(quality);
|
|
884
|
+
return img.getBufferAsync(Jimp.MIME_JPEG);
|
|
885
|
+
}
|
|
886
|
+
throw new Boom('resizeImage requires "sharp" or "jimp" to be installed', { statusCode: 500 });
|
|
887
|
+
};
|
|
888
|
+
export const convertMedia = async (buf, opts) => {
|
|
889
|
+
const fmt = opts.to.toLowerCase().replace('.', '');
|
|
890
|
+
const imageFormats = { jpeg: 'jpeg', jpg: 'jpeg', png: 'png', webp: 'webp' };
|
|
891
|
+
if (imageFormats[fmt]) {
|
|
892
|
+
const lib = await getImageProcessingLibrary();
|
|
893
|
+
if ('sharp' in lib && typeof lib.sharp?.default === 'function') {
|
|
894
|
+
return lib.sharp.default(buf).toFormat(imageFormats[fmt]).toBuffer();
|
|
895
|
+
}
|
|
896
|
+
throw new Boom('convertMedia to an image format requires "sharp" to be installed', { statusCode: 500 });
|
|
897
|
+
}
|
|
898
|
+
const args = ['-i', 'pipe:0', '-y'];
|
|
899
|
+
if (fmt === 'mp4') {
|
|
900
|
+
args.push('-movflags', 'frag_keyframe+empty_moov', '-f', 'mp4');
|
|
901
|
+
}
|
|
902
|
+
else {
|
|
903
|
+
args.push('-f', fmt);
|
|
904
|
+
}
|
|
905
|
+
args.push('pipe:1');
|
|
906
|
+
return runFfmpegBuffer(args, buf);
|
|
907
|
+
};
|
|
908
|
+
export const imageToWebpSticker = async (buf, opts = {}) => {
|
|
909
|
+
const { quality = 80 } = opts;
|
|
910
|
+
const lib = await getImageProcessingLibrary();
|
|
911
|
+
if ('sharp' in lib && typeof lib.sharp?.default === 'function') {
|
|
912
|
+
return lib.sharp
|
|
913
|
+
.default(buf)
|
|
914
|
+
.resize(512, 512, { fit: 'contain', background: { r: 0, g: 0, b: 0, alpha: 0 } })
|
|
915
|
+
.webp({ quality })
|
|
916
|
+
.toBuffer();
|
|
917
|
+
}
|
|
918
|
+
throw new Boom('imageToWebpSticker requires "sharp" to be installed', { statusCode: 500 });
|
|
919
|
+
};
|
|
920
|
+
export const compressMedia = async (buf, opts = {}) => {
|
|
921
|
+
const { quality = 50 } = opts;
|
|
922
|
+
const lib = await getImageProcessingLibrary();
|
|
923
|
+
if ('sharp' in lib && typeof lib.sharp?.default === 'function') {
|
|
924
|
+
try {
|
|
925
|
+
const { format } = await lib.sharp.default(buf).metadata();
|
|
926
|
+
if (format) {
|
|
927
|
+
return lib.sharp.default(buf).toFormat(format, { quality }).toBuffer();
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
catch { }
|
|
931
|
+
}
|
|
932
|
+
const crf = String(Math.round(51 - (quality / 100) * 51));
|
|
933
|
+
return runFfmpegBuffer(['-i', 'pipe:0', '-y', '-crf', crf, '-preset', 'ultrafast', '-movflags', 'frag_keyframe+empty_moov', '-f', 'mp4', 'pipe:1'], buf);
|
|
934
|
+
};
|
|
935
|
+
export const getMediaMetadata = async (buf) => {
|
|
936
|
+
const result = { size: buf.length };
|
|
937
|
+
const lib = await getImageProcessingLibrary();
|
|
938
|
+
if ('sharp' in lib && typeof lib.sharp?.default === 'function') {
|
|
939
|
+
try {
|
|
940
|
+
const meta = await lib.sharp.default(buf).metadata();
|
|
941
|
+
if (meta.format) {
|
|
942
|
+
result.mimetype = `image/${meta.format}`;
|
|
943
|
+
result.width = meta.width;
|
|
944
|
+
result.height = meta.height;
|
|
945
|
+
result.channels = meta.channels;
|
|
946
|
+
result.hasAlpha = meta.hasAlpha;
|
|
947
|
+
return result;
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
catch { }
|
|
951
|
+
}
|
|
952
|
+
return new Promise(resolve => {
|
|
953
|
+
const ff = spawn('ffprobe', ['-v', 'quiet', '-print_format', 'json', '-show_format', '-show_streams', 'pipe:0'], {
|
|
954
|
+
stdio: ['pipe', 'pipe', 'ignore']
|
|
955
|
+
});
|
|
956
|
+
const chunks = [];
|
|
957
|
+
ff.stdout.on('data', (c) => chunks.push(c));
|
|
958
|
+
ff.on('close', () => {
|
|
959
|
+
try {
|
|
960
|
+
const d = JSON.parse(Buffer.concat(chunks).toString());
|
|
961
|
+
const vid = d.streams?.find((s) => s.codec_type === 'video');
|
|
962
|
+
const aud = d.streams?.find((s) => s.codec_type === 'audio');
|
|
963
|
+
if (vid) {
|
|
964
|
+
result.width = vid.width;
|
|
965
|
+
result.height = vid.height;
|
|
966
|
+
}
|
|
967
|
+
result.duration = parseFloat(d.format?.duration) || undefined;
|
|
968
|
+
result.mimetype = vid ? 'video/mp4' : aud ? 'audio/mpeg' : undefined;
|
|
969
|
+
}
|
|
970
|
+
catch { }
|
|
971
|
+
resolve(result);
|
|
972
|
+
});
|
|
973
|
+
ff.on('error', () => resolve(result));
|
|
974
|
+
ff.stdin.end(buf);
|
|
975
|
+
});
|
|
977
976
|
};
|
|
978
|
-
|
|
979
|
-
throw new Error('Function not implemented.');
|
|
980
|
-
}
|
|
977
|
+
//# sourceMappingURL=messages-media.js.map
|