@vanzxysenpai/baileys 0.0.0-stage → 0.0.2
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/CHANGELOG.md +30 -0
- package/LICENSE +25 -0
- package/NOTICE.md +158 -0
- package/README.md +5294 -2
- package/VanzxyBanner.js +134 -0
- package/WAProto/index.d.ts +13317 -0
- package/WAProto/index.js +147553 -0
- package/lib/Builders/A2UI.d.ts +131 -0
- package/lib/Builders/A2UI.js +343 -0
- package/lib/Builders/AIRich.d.ts +1021 -0
- package/lib/Builders/AIRich.js +2889 -0
- package/lib/Builders/Album.d.ts +51 -0
- package/lib/Builders/Album.js +99 -0
- package/lib/Builders/Button.d.ts +137449 -0
- package/lib/Builders/Button.js +1054 -0
- package/lib/Builders/ButtonV2.d.ts +43 -0
- package/lib/Builders/ButtonV2.js +175 -0
- package/lib/Builders/ButtonV3.d.ts +35 -0
- package/lib/Builders/ButtonV3.js +189 -0
- package/lib/Builders/Carousel.d.ts +68 -0
- package/lib/Builders/Carousel.js +161 -0
- package/lib/Builders/Event.d.ts +61 -0
- package/lib/Builders/Event.js +118 -0
- package/lib/Builders/Expiring.d.ts +17 -0
- package/lib/Builders/Expiring.js +27 -0
- package/lib/Builders/Poll.d.ts +63 -0
- package/lib/Builders/Poll.js +139 -0
- package/lib/Builders/VanzxyBaileys.d.ts +42 -0
- package/lib/Builders/VanzxyBaileys.js +110 -0
- package/lib/Builders/errors.d.ts +25 -0
- package/lib/Builders/errors.js +29 -0
- package/lib/Builders/index.d.ts +14 -0
- package/lib/Builders/index.js +23 -0
- package/lib/Builders/shared.d.ts +395 -0
- package/lib/Builders/shared.js +738 -0
- package/lib/Defaults/bot-jid.d.ts +1 -0
- package/lib/Defaults/bot-jid.js +1 -0
- package/lib/Defaults/index.d.ts +161 -0
- package/lib/Defaults/index.js +165 -0
- package/lib/Defaults/version.d.ts +1 -0
- package/lib/Defaults/version.js +1 -0
- package/lib/Framework/Bot.d.ts +865 -0
- package/lib/Framework/Bot.js +230 -0
- package/lib/Framework/Context.d.ts +17 -0
- package/lib/Framework/Context.js +90 -0
- package/lib/Framework/MediaManager.d.ts +15 -0
- package/lib/Framework/MediaManager.js +154 -0
- package/lib/Framework/SessionManager.d.ts +10 -0
- package/lib/Framework/SessionManager.js +34 -0
- package/lib/Framework/StatsManager.d.ts +20 -0
- package/lib/Framework/StatsManager.js +117 -0
- package/lib/Framework/Store/SQLiteStore.d.ts +13 -0
- package/lib/Framework/Store/SQLiteStore.js +68 -0
- package/lib/Framework/index.d.ts +6 -0
- package/lib/Framework/index.js +11 -0
- package/lib/Signal/Group/ciphertext-message.d.ts +9 -0
- package/lib/Signal/Group/ciphertext-message.js +11 -0
- package/lib/Signal/Group/group-session-builder.d.ts +7 -0
- package/lib/Signal/Group/group-session-builder.js +29 -0
- package/lib/Signal/Group/group_cipher.d.ts +10 -0
- package/lib/Signal/Group/group_cipher.js +81 -0
- package/lib/Signal/Group/index.d.ts +11 -0
- package/lib/Signal/Group/index.js +11 -0
- package/lib/Signal/Group/keyhelper.d.ts +6 -0
- package/lib/Signal/Group/keyhelper.js +17 -0
- package/lib/Signal/Group/sender-chain-key.d.ts +13 -0
- package/lib/Signal/Group/sender-chain-key.js +25 -0
- package/lib/Signal/Group/sender-key-distribution-message.d.ts +16 -0
- package/lib/Signal/Group/sender-key-distribution-message.js +62 -0
- package/lib/Signal/Group/sender-key-message.d.ts +18 -0
- package/lib/Signal/Group/sender-key-message.js +65 -0
- package/lib/Signal/Group/sender-key-name.d.ts +11 -0
- package/lib/Signal/Group/sender-key-name.js +47 -0
- package/lib/Signal/Group/sender-key-record.d.ts +12 -0
- package/lib/Signal/Group/sender-key-record.js +40 -0
- package/lib/Signal/Group/sender-key-state.d.ts +16 -0
- package/lib/Signal/Group/sender-key-state.js +83 -0
- package/lib/Signal/Group/sender-message-key.d.ts +11 -0
- package/lib/Signal/Group/sender-message-key.js +25 -0
- package/lib/Signal/libsignal.d.ts +58 -0
- package/lib/Signal/libsignal.js +436 -0
- package/lib/Signal/lid-mapping.d.ts +20 -0
- package/lib/Signal/lid-mapping.js +276 -0
- package/lib/Socket/Client/index.d.ts +2 -0
- package/lib/Socket/Client/index.js +2 -0
- package/lib/Socket/Client/types.d.ts +5 -0
- package/lib/Socket/Client/types.js +10 -0
- package/lib/Socket/Client/websocket.d.ts +12 -0
- package/lib/Socket/Client/websocket.js +53 -0
- package/lib/Socket/business.d.ts +674 -0
- package/lib/Socket/business.js +379 -0
- package/lib/Socket/chats.d.ts +137 -0
- package/lib/Socket/chats.js +1299 -0
- package/lib/Socket/communities.d.ts +805 -0
- package/lib/Socket/communities.js +454 -0
- package/lib/Socket/dugong.d.ts +118 -0
- package/lib/Socket/dugong.js +810 -0
- package/lib/Socket/groups.d.ts +287 -0
- package/lib/Socket/groups.js +386 -0
- package/lib/Socket/index.d.ts +826 -0
- package/lib/Socket/index.js +35 -0
- package/lib/Socket/messages-recv.d.ts +613 -0
- package/lib/Socket/messages-recv.js +1851 -0
- package/lib/Socket/messages-send.d.ts +608 -0
- package/lib/Socket/messages-send.js +1574 -0
- package/lib/Socket/mex-fallback.d.ts +51 -0
- package/lib/Socket/mex-fallback.js +97 -0
- package/lib/Socket/mex.d.ts +1 -0
- package/lib/Socket/mex.js +41 -0
- package/lib/Socket/newsletter-ext.d.ts +47 -0
- package/lib/Socket/newsletter-ext.js +262 -0
- package/lib/Socket/newsletter.d.ts +285 -0
- package/lib/Socket/newsletter.js +223 -0
- package/lib/Socket/socket.d.ts +60 -0
- package/lib/Socket/socket.js +1001 -0
- package/lib/Socket/username.d.ts +842 -0
- package/lib/Socket/username.js +156 -0
- package/lib/Store/index.d.ts +3 -0
- package/lib/Store/index.js +3 -0
- package/lib/Store/make-in-memory-store.d.ts +62 -0
- package/lib/Store/make-in-memory-store.js +425 -0
- package/lib/Store/make-ordered-dictionary.d.ts +12 -0
- package/lib/Store/make-ordered-dictionary.js +78 -0
- package/lib/Store/object-repository.d.ts +10 -0
- package/lib/Store/object-repository.js +23 -0
- package/lib/Types/Auth.d.ts +1 -0
- package/lib/Types/Auth.js +1 -0
- package/lib/Types/Bussines.d.ts +1 -0
- package/lib/Types/Bussines.js +1 -0
- package/lib/Types/Call.d.ts +1 -0
- package/lib/Types/Call.js +1 -0
- package/lib/Types/Chat.d.ts +1 -0
- package/lib/Types/Chat.js +7 -0
- package/lib/Types/Contact.d.ts +1 -0
- package/lib/Types/Contact.js +1 -0
- package/lib/Types/Events.d.ts +1 -0
- package/lib/Types/Events.js +1 -0
- package/lib/Types/GroupMetadata.d.ts +1 -0
- package/lib/Types/GroupMetadata.js +1 -0
- package/lib/Types/Label.d.ts +2 -0
- package/lib/Types/Label.js +24 -0
- package/lib/Types/LabelAssociation.d.ts +2 -0
- package/lib/Types/LabelAssociation.js +6 -0
- package/lib/Types/Message.d.ts +11 -0
- package/lib/Types/Message.js +17 -0
- package/lib/Types/Mex.d.ts +2 -0
- package/lib/Types/Mex.js +38 -0
- package/lib/Types/Product.d.ts +1 -0
- package/lib/Types/Product.js +1 -0
- package/lib/Types/RichType.d.ts +2 -0
- package/lib/Types/RichType.js +22 -0
- package/lib/Types/Signal.d.ts +1 -0
- package/lib/Types/Signal.js +1 -0
- package/lib/Types/Socket.d.ts +1 -0
- package/lib/Types/Socket.js +1 -0
- package/lib/Types/State.d.ts +5 -0
- package/lib/Types/State.js +55 -0
- package/lib/Types/USync.d.ts +1 -0
- package/lib/Types/USync.js +1 -0
- package/lib/Types/index.d.ts +15 -0
- package/lib/Types/index.js +27 -0
- package/lib/Utils/PersistentStore.d.ts +83 -0
- package/lib/Utils/PersistentStore.js +592 -0
- package/lib/Utils/anchor-guard.d.ts +31 -0
- package/lib/Utils/anchor-guard.js +329 -0
- package/lib/Utils/android-stanza-id.d.ts +4 -0
- package/lib/Utils/android-stanza-id.js +21 -0
- package/lib/Utils/anti-delete.d.ts +62 -0
- package/lib/Utils/anti-delete.js +212 -0
- package/lib/Utils/auth-secure.d.ts +180 -0
- package/lib/Utils/auth-secure.js +791 -0
- package/lib/Utils/auth-utils.d.ts +59 -0
- package/lib/Utils/auth-utils.js +326 -0
- package/lib/Utils/auto-reconnect.d.ts +8 -0
- package/lib/Utils/auto-reconnect.js +204 -0
- package/lib/Utils/auto-reply.d.ts +35 -0
- package/lib/Utils/auto-reply.js +152 -0
- package/lib/Utils/bad-mac-handler.d.ts +59 -0
- package/lib/Utils/bad-mac-handler.js +119 -0
- package/lib/Utils/baileys-event-stream.d.ts +5 -0
- package/lib/Utils/baileys-event-stream.js +62 -0
- package/lib/Utils/browser-utils.d.ts +9 -0
- package/lib/Utils/browser-utils.js +29 -0
- package/lib/Utils/business.d.ts +49 -0
- package/lib/Utils/business.js +236 -0
- package/lib/Utils/button-helper-utils.d.ts +59 -0
- package/lib/Utils/button-helper-utils.js +266 -0
- package/lib/Utils/button-sender.d.ts +164 -0
- package/lib/Utils/button-sender.js +845 -0
- package/lib/Utils/chat-control.d.ts +98 -0
- package/lib/Utils/chat-control.js +231 -0
- package/lib/Utils/chat-history-helpers.d.ts +4 -0
- package/lib/Utils/chat-history-helpers.js +70 -0
- package/lib/Utils/chat-utils.d.ts +408 -0
- package/lib/Utils/chat-utils.js +879 -0
- package/lib/Utils/companion-reg-client-utils.d.ts +4 -0
- package/lib/Utils/companion-reg-client-utils.js +39 -0
- package/lib/Utils/connection-watchdog.d.ts +10 -0
- package/lib/Utils/connection-watchdog.js +74 -0
- package/lib/Utils/crypto.d.ts +41 -0
- package/lib/Utils/crypto.js +145 -0
- package/lib/Utils/decode-wa-message.d.ts +83 -0
- package/lib/Utils/decode-wa-message.js +315 -0
- package/lib/Utils/disconnect-reason.d.ts +13 -0
- package/lib/Utils/disconnect-reason.js +36 -0
- package/lib/Utils/event-buffer.d.ts +12 -0
- package/lib/Utils/event-buffer.js +640 -0
- package/lib/Utils/fetch-timeout.d.ts +4 -0
- package/lib/Utils/fetch-timeout.js +14 -0
- package/lib/Utils/ffmpeg-path.d.ts +5 -0
- package/lib/Utils/ffmpeg-path.js +130 -0
- package/lib/Utils/generics.d.ts +79 -0
- package/lib/Utils/generics.js +426 -0
- package/lib/Utils/group-metadata-cache.d.ts +3 -0
- package/lib/Utils/group-metadata-cache.js +25 -0
- package/lib/Utils/group-status-detection.d.ts +5 -0
- package/lib/Utils/group-status-detection.js +114 -0
- package/lib/Utils/history.d.ts +91 -0
- package/lib/Utils/history.js +144 -0
- package/lib/Utils/html-app.d.ts +14 -0
- package/lib/Utils/html-app.js +97 -0
- package/lib/Utils/html-multiplayer.d.ts +8 -0
- package/lib/Utils/html-multiplayer.js +90 -0
- package/lib/Utils/humanizer.d.ts +8 -0
- package/lib/Utils/humanizer.js +74 -0
- package/lib/Utils/identity-change-handler.d.ts +13 -0
- package/lib/Utils/identity-change-handler.js +49 -0
- package/lib/Utils/index.d.ts +88 -0
- package/lib/Utils/index.js +93 -0
- package/lib/Utils/jid-tools.d.ts +3 -0
- package/lib/Utils/jid-tools.js +78 -0
- package/lib/Utils/link-preview-metadata.d.ts +47 -0
- package/lib/Utils/link-preview-metadata.js +90 -0
- package/lib/Utils/link-preview.d.ts +12 -0
- package/lib/Utils/link-preview.js +84 -0
- package/lib/Utils/lock-manager.d.ts +5 -0
- package/lib/Utils/lock-manager.js +50 -0
- package/lib/Utils/logger.d.ts +2 -0
- package/lib/Utils/logger.js +2 -0
- package/lib/Utils/lt-hash.d.ts +12 -0
- package/lib/Utils/lt-hash.js +26 -0
- package/lib/Utils/make-mutex.d.ts +6 -0
- package/lib/Utils/make-mutex.js +32 -0
- package/lib/Utils/media-messages.d.ts +35 -0
- package/lib/Utils/media-messages.js +70 -0
- package/lib/Utils/media-processor.d.ts +6 -0
- package/lib/Utils/media-processor.js +174 -0
- package/lib/Utils/media-set.d.ts +6 -0
- package/lib/Utils/media-set.js +169 -0
- package/lib/Utils/message-kind.d.ts +26 -0
- package/lib/Utils/message-kind.js +138 -0
- package/lib/Utils/message-retry-manager.d.ts +79 -0
- package/lib/Utils/message-retry-manager.js +264 -0
- package/lib/Utils/message-search.d.ts +40 -0
- package/lib/Utils/message-search.js +181 -0
- package/lib/Utils/messages-media.d.ts +136 -0
- package/lib/Utils/messages-media.js +867 -0
- package/lib/Utils/messages.d.ts +66 -0
- package/lib/Utils/messages.js +2271 -0
- package/lib/Utils/meta-ai-msmsg.d.ts +3 -0
- package/lib/Utils/meta-ai-msmsg.js +132 -0
- package/lib/Utils/meta-ai.d.ts +4 -0
- package/lib/Utils/meta-ai.js +75 -0
- package/lib/Utils/migrate-auth-state.d.ts +23 -0
- package/lib/Utils/migrate-auth-state.js +126 -0
- package/lib/Utils/mini-app.d.ts +20 -0
- package/lib/Utils/mini-app.js +215 -0
- package/lib/Utils/mock-socket.d.ts +67 -0
- package/lib/Utils/mock-socket.js +254 -0
- package/lib/Utils/modern-messages.d.ts +64 -0
- package/lib/Utils/modern-messages.js +497 -0
- package/lib/Utils/native-flow.d.ts +19 -0
- package/lib/Utils/native-flow.js +112 -0
- package/lib/Utils/newsletter-status.d.ts +157 -0
- package/lib/Utils/newsletter-status.js +652 -0
- package/lib/Utils/noise-handler.d.ts +20 -0
- package/lib/Utils/noise-handler.js +200 -0
- package/lib/Utils/offline-node-processor.d.ts +9 -0
- package/lib/Utils/offline-node-processor.js +39 -0
- package/lib/Utils/pairing-utils.d.ts +5 -0
- package/lib/Utils/pairing-utils.js +29 -0
- package/lib/Utils/passkey.d.ts +4 -0
- package/lib/Utils/passkey.js +11 -0
- package/lib/Utils/past-participants.d.ts +2 -0
- package/lib/Utils/past-participants.js +40 -0
- package/lib/Utils/payment-detection.d.ts +40 -0
- package/lib/Utils/payment-detection.js +213 -0
- package/lib/Utils/payment-guard.d.ts +28 -0
- package/lib/Utils/payment-guard.js +143 -0
- package/lib/Utils/payment-messages.d.ts +42 -0
- package/lib/Utils/payment-messages.js +161 -0
- package/lib/Utils/payment-utils.d.ts +75 -0
- package/lib/Utils/payment-utils.js +145 -0
- package/lib/Utils/phone-utils.d.ts +3 -0
- package/lib/Utils/phone-utils.js +29 -0
- package/lib/Utils/pre-key-manager.d.ts +25 -0
- package/lib/Utils/pre-key-manager.js +105 -0
- package/lib/Utils/process-message.d.ts +103 -0
- package/lib/Utils/process-message.js +868 -0
- package/lib/Utils/qr-terminal.d.ts +1 -0
- package/lib/Utils/qr-terminal.js +16 -0
- package/lib/Utils/reconnect-backoff.d.ts +25 -0
- package/lib/Utils/reconnect-backoff.js +47 -0
- package/lib/Utils/reporting-utils.d.ts +12 -0
- package/lib/Utils/reporting-utils.js +257 -0
- package/lib/Utils/rich-message-utils.d.ts +54 -0
- package/lib/Utils/rich-message-utils.js +435 -0
- package/lib/Utils/rich-messages.d.ts +298 -0
- package/lib/Utils/rich-messages.js +1145 -0
- package/lib/Utils/scheduled-message.d.ts +81 -0
- package/lib/Utils/scheduled-message.js +101 -0
- package/lib/Utils/scheduling.d.ts +59 -0
- package/lib/Utils/scheduling.js +139 -0
- package/lib/Utils/signal.d.ts +82 -0
- package/lib/Utils/signal.js +200 -0
- package/lib/Utils/stanza-ack.d.ts +16 -0
- package/lib/Utils/stanza-ack.js +37 -0
- package/lib/Utils/status-stickers.d.ts +79 -0
- package/lib/Utils/status-stickers.js +261 -0
- package/lib/Utils/status.d.ts +107 -0
- package/lib/Utils/status.js +131 -0
- package/lib/Utils/sticker-pack-maker.d.ts +20 -0
- package/lib/Utils/sticker-pack-maker.js +112 -0
- package/lib/Utils/stickerpack.d.ts +18 -0
- package/lib/Utils/stickerpack.js +316 -0
- package/lib/Utils/sync-action-utils.d.ts +2 -0
- package/lib/Utils/sync-action-utils.js +48 -0
- package/lib/Utils/tag.d.ts +8 -0
- package/lib/Utils/tag.js +45 -0
- package/lib/Utils/tc-token-utils.d.ts +29 -0
- package/lib/Utils/tc-token-utils.js +162 -0
- package/lib/Utils/templates.d.ts +86 -0
- package/lib/Utils/templates.js +148 -0
- package/lib/Utils/text-format.d.ts +13 -0
- package/lib/Utils/text-format.js +81 -0
- package/lib/Utils/text-tools.d.ts +3 -0
- package/lib/Utils/text-tools.js +38 -0
- package/lib/Utils/ttl-cache.d.ts +29 -0
- package/lib/Utils/ttl-cache.js +123 -0
- package/lib/Utils/use-cache-manager-auth-state.d.ts +22 -0
- package/lib/Utils/use-cache-manager-auth-state.js +86 -0
- package/lib/Utils/use-multi-file-auth-state.d.ts +20 -0
- package/lib/Utils/use-multi-file-auth-state.js +201 -0
- package/lib/Utils/use-single-file-auth-state.d.ts +10 -0
- package/lib/Utils/use-single-file-auth-state.js +111 -0
- package/lib/Utils/use-sqlite-auth-state.d.ts +13 -0
- package/lib/Utils/use-sqlite-auth-state.js +171 -0
- package/lib/Utils/username.d.ts +22 -0
- package/lib/Utils/username.js +59 -0
- package/lib/Utils/validate-connection.d.ts +43 -0
- package/lib/Utils/validate-connection.js +197 -0
- package/lib/Utils/vcard.d.ts +40 -0
- package/lib/Utils/vcard.js +92 -0
- package/lib/Utils/voice-note.d.ts +13 -0
- package/lib/Utils/voice-note.js +47 -0
- package/lib/VoIP/audio-feeder.d.ts +15 -0
- package/lib/VoIP/audio-feeder.js +132 -0
- package/lib/VoIP/index.d.ts +22 -0
- package/lib/VoIP/index.js +277 -0
- package/lib/VoIP/relay-transport.d.ts +16 -0
- package/lib/VoIP/relay-transport.js +559 -0
- package/lib/VoIP/signaling.d.ts +16 -0
- package/lib/VoIP/signaling.js +624 -0
- package/lib/VoIP/types.d.ts +11 -0
- package/lib/VoIP/types.js +17 -0
- package/lib/VoIP/wasm-engine.d.ts +28 -0
- package/lib/VoIP/wasm-engine.js +1214 -0
- package/lib/VoIP/worker-bootstrap.d.ts +1 -0
- package/lib/VoIP/worker-bootstrap.js +1042 -0
- package/lib/WABinary/constants.d.ts +61 -0
- package/lib/WABinary/constants.js +1466 -0
- package/lib/WABinary/decode.d.ts +9 -0
- package/lib/WABinary/decode.js +261 -0
- package/lib/WABinary/encode.d.ts +2 -0
- package/lib/WABinary/encode.js +219 -0
- package/lib/WABinary/generic-utils.d.ts +45 -0
- package/lib/WABinary/generic-utils.js +285 -0
- package/lib/WABinary/index.d.ts +5 -0
- package/lib/WABinary/index.js +5 -0
- package/lib/WABinary/jid-utils.d.ts +32 -0
- package/lib/WABinary/jid-utils.js +102 -0
- package/lib/WABinary/types.d.ts +1 -0
- package/lib/WABinary/types.js +1 -0
- package/lib/WAM/BinaryInfo.d.ts +7 -0
- package/lib/WAM/BinaryInfo.js +9 -0
- package/lib/WAM/constants.d.ts +34926 -0
- package/lib/WAM/constants.js +22852 -0
- package/lib/WAM/encode.d.ts +1 -0
- package/lib/WAM/encode.js +149 -0
- package/lib/WAM/index.d.ts +3 -0
- package/lib/WAM/index.js +3 -0
- package/lib/WAUSync/Protocols/USyncBusinessProtocol.d.ts +43 -0
- package/lib/WAUSync/Protocols/USyncBusinessProtocol.js +32 -0
- package/lib/WAUSync/Protocols/USyncContactProtocol.d.ts +36 -0
- package/lib/WAUSync/Protocols/USyncContactProtocol.js +51 -0
- package/lib/WAUSync/Protocols/USyncDeviceProtocol.d.ts +29 -0
- package/lib/WAUSync/Protocols/USyncDeviceProtocol.js +60 -0
- package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.d.ts +12 -0
- package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.js +26 -0
- package/lib/WAUSync/Protocols/USyncFeatureProtocol.d.ts +13 -0
- package/lib/WAUSync/Protocols/USyncFeatureProtocol.js +28 -0
- package/lib/WAUSync/Protocols/USyncPictureProtocol.d.ts +22 -0
- package/lib/WAUSync/Protocols/USyncPictureProtocol.js +28 -0
- package/lib/WAUSync/Protocols/USyncSidelistProtocol.d.ts +20 -0
- package/lib/WAUSync/Protocols/USyncSidelistProtocol.js +23 -0
- package/lib/WAUSync/Protocols/USyncStatusProtocol.d.ts +12 -0
- package/lib/WAUSync/Protocols/USyncStatusProtocol.js +37 -0
- package/lib/WAUSync/Protocols/USyncTextStatusProtocol.d.ts +14 -0
- package/lib/WAUSync/Protocols/USyncTextStatusProtocol.js +24 -0
- package/lib/WAUSync/Protocols/USyncUsernameProtocol.d.ts +9 -0
- package/lib/WAUSync/Protocols/USyncUsernameProtocol.js +24 -0
- package/lib/WAUSync/Protocols/UsyncBotProfileProtocol.d.ts +38 -0
- package/lib/WAUSync/Protocols/UsyncBotProfileProtocol.js +49 -0
- package/lib/WAUSync/Protocols/UsyncLIDProtocol.d.ts +14 -0
- package/lib/WAUSync/Protocols/UsyncLIDProtocol.js +28 -0
- package/lib/WAUSync/Protocols/index.d.ts +12 -0
- package/lib/WAUSync/Protocols/index.js +12 -0
- package/lib/WAUSync/USyncQuery.d.ts +25 -0
- package/lib/WAUSync/USyncQuery.js +140 -0
- package/lib/WAUSync/USyncUser.d.ts +24 -0
- package/lib/WAUSync/USyncUser.js +47 -0
- package/lib/WAUSync/index.d.ts +3 -0
- package/lib/WAUSync/index.js +3 -0
- package/lib/assets/wasm/loader.js +5 -0
- package/lib/assets/wasm/whatsapp.wasm +0 -0
- package/lib/assets/wasm/worker-modules.js +273 -0
- package/lib/exports-extra.d.ts +45 -0
- package/lib/exports-extra.js +47 -0
- package/lib/index.d.ts +16 -0
- package/lib/index.js +24 -0
- package/package.json +161 -4
|
@@ -0,0 +1,2889 @@
|
|
|
1
|
+
import { BaseBuilder, Toolkit, extractIE, waitAllPromises, botMetadataSignature, botMetadataCertificate, crypto, generateWAMessageFromContent, prepareWAMessageMedia, generateMessageIDV2 } from './shared.js';
|
|
2
|
+
import { DEFAULT_AI_BOT_JID } from '../Defaults/bot-jid.js';
|
|
3
|
+
|
|
4
|
+
// --- error classes (self-contained: only AIRich.js uses these three) --------------------------
|
|
5
|
+
|
|
6
|
+
class AIRichError extends Error {
|
|
7
|
+
constructor(message, code = 'AIRICH_ERROR', meta = {}) {
|
|
8
|
+
super(message);
|
|
9
|
+
this.name = 'AIRichError';
|
|
10
|
+
this.code = code;
|
|
11
|
+
Object.assign(this, meta);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
class ItemNotFoundError extends AIRichError {
|
|
15
|
+
constructor(message, meta = {}) { super(message, 'ITEM_NOT_FOUND', meta); this.name = 'ItemNotFoundError'; }
|
|
16
|
+
}
|
|
17
|
+
class DuplicateIdError extends AIRichError {
|
|
18
|
+
constructor(message, meta = {}) { super(message, 'DUPLICATE_ID', meta); this.name = 'DuplicateIdError'; }
|
|
19
|
+
}
|
|
20
|
+
class ContentValidationError extends AIRichError {
|
|
21
|
+
constructor(message, meta = {}) { super(message, 'CONTENT_VALIDATION', meta); this.name = 'ContentValidationError'; }
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// --- { id, insertAt, replace } bookkeeping for every add*()/set*() call ------------------------
|
|
25
|
+
// Wraps a builder instance in a Proxy that tracks two parallel arrays (a flat "sub" list and a
|
|
26
|
+
// grouped "sections" list) in a `_blocks` Map (id -> { subItems, secItems }), so add*/set* calls
|
|
27
|
+
// can be addressed, replaced, or inserted-after by id. Inlined here — AIRich.js is its only user.
|
|
28
|
+
// setters whose arguments can legitimately carry an `id`/`replace` key (a quoted message, a raw payload) —
|
|
29
|
+
// these must NOT be read as { id, insertAt, replace } block options by the proxy below.
|
|
30
|
+
const NON_BLOCK_SETTERS = new Set(['setQuoted', 'setUnifiedResponseData', 'setMentions']);
|
|
31
|
+
|
|
32
|
+
function withBlockTracking(target, { subField = '_submessages', secField = '_sections' } = {}) {
|
|
33
|
+
target._blocks = new Map();
|
|
34
|
+
target._lastBlock = null;
|
|
35
|
+
|
|
36
|
+
return new Proxy(target, {
|
|
37
|
+
get(target, prop, receiver) {
|
|
38
|
+
const orig = Reflect.get(target, prop, receiver);
|
|
39
|
+
if (typeof orig !== 'function') return orig;
|
|
40
|
+
|
|
41
|
+
if (!/^(add|set)/.test(String(prop)) || NON_BLOCK_SETTERS.has(String(prop))) {
|
|
42
|
+
return (...args) => {
|
|
43
|
+
const result = orig.apply(target, args);
|
|
44
|
+
return result === target ? receiver : result;
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return (...args) => {
|
|
49
|
+
const pending = target._pendingOpts ?? null;
|
|
50
|
+
target._pendingOpts = null;
|
|
51
|
+
const opts = args.find((a) => a && typeof a === 'object' && !Array.isArray(a) && !Buffer.isBuffer(a) && ('id' in a || 'insertAt' in a || 'replace' in a)) ?? pending;
|
|
52
|
+
const id = opts?.id;
|
|
53
|
+
const insertAt = opts?.insertAt;
|
|
54
|
+
const replace = opts?.replace;
|
|
55
|
+
|
|
56
|
+
if (id && target._blocks.has(id) && replace !== id) {
|
|
57
|
+
throw new DuplicateIdError(`add*/set*: id "${id}" is already registered — each id must be unique (pass { replace: "${id}" } to update that block instead, or use a different id)`);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const subBefore = target[subField].length;
|
|
61
|
+
const secBefore = target[secField].length;
|
|
62
|
+
|
|
63
|
+
const result = orig.apply(target, args);
|
|
64
|
+
|
|
65
|
+
const subItems = target[subField].splice(subBefore);
|
|
66
|
+
const secItems = target[secField].splice(secBefore);
|
|
67
|
+
|
|
68
|
+
if (insertAt) {
|
|
69
|
+
const anchor = target._blocks.get(insertAt);
|
|
70
|
+
if (!anchor) throw new ItemNotFoundError(`insertAt: no block registered with id "${insertAt}" (register it by passing { id: "${insertAt}" } on an earlier add*() call)`);
|
|
71
|
+
|
|
72
|
+
// insertAtOffset (via optsAt): 0 = right after the anchor block, -1 = right before it, -2 = one block earlier, ...
|
|
73
|
+
const offset = Number.isInteger(opts?.insertAtOffset) ? opts.insertAtOffset : 0;
|
|
74
|
+
const place = (field, items, anchorItems) => {
|
|
75
|
+
const arr = target[field];
|
|
76
|
+
let idx;
|
|
77
|
+
if (offset < 0) {
|
|
78
|
+
const first = anchorItems[0];
|
|
79
|
+
idx = first ? arr.indexOf(first) + offset + 1 : arr.length;
|
|
80
|
+
} else {
|
|
81
|
+
const last = anchorItems[anchorItems.length - 1];
|
|
82
|
+
idx = last ? arr.indexOf(last) + 1 : arr.length;
|
|
83
|
+
}
|
|
84
|
+
arr.splice(Math.max(0, Math.min(idx, arr.length)), 0, ...items);
|
|
85
|
+
};
|
|
86
|
+
place(subField, subItems, anchor.subItems);
|
|
87
|
+
place(secField, secItems, anchor.secItems);
|
|
88
|
+
} else if (replace) {
|
|
89
|
+
const old = target._blocks.get(replace);
|
|
90
|
+
if (!old) throw new ItemNotFoundError(`replace: no block registered with id "${replace}" (register it first with { id: "${replace}" })`);
|
|
91
|
+
|
|
92
|
+
let subIdx = old.subItems.length > 0 ? target[subField].indexOf(old.subItems[0]) : target[subField].length;
|
|
93
|
+
if (subIdx === -1) subIdx = target[subField].length;
|
|
94
|
+
for (const item of old.subItems) {
|
|
95
|
+
const i = target[subField].indexOf(item);
|
|
96
|
+
if (i !== -1) target[subField].splice(i, 1);
|
|
97
|
+
}
|
|
98
|
+
target[subField].splice(subIdx, 0, ...subItems);
|
|
99
|
+
|
|
100
|
+
let secIdx = old.secItems.length > 0 ? target[secField].indexOf(old.secItems[0]) : target[secField].length;
|
|
101
|
+
if (secIdx === -1) secIdx = target[secField].length;
|
|
102
|
+
for (const item of old.secItems) {
|
|
103
|
+
const i = target[secField].indexOf(item);
|
|
104
|
+
if (i !== -1) target[secField].splice(i, 1);
|
|
105
|
+
}
|
|
106
|
+
target[secField].splice(secIdx, 0, ...secItems);
|
|
107
|
+
|
|
108
|
+
target._blocks.delete(replace);
|
|
109
|
+
if (id) target._blocks.set(id, { subItems, secItems });
|
|
110
|
+
else target._blocks.set(replace, { subItems, secItems });
|
|
111
|
+
} else {
|
|
112
|
+
target[subField].push(...subItems);
|
|
113
|
+
target[secField].push(...secItems);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (id) target._blocks.set(id, { subItems, secItems });
|
|
117
|
+
|
|
118
|
+
target._lastBlock = !id && !replace && (subItems.length || secItems.length) ? { subItems, secItems } : null;
|
|
119
|
+
|
|
120
|
+
return result === target ? receiver : result;
|
|
121
|
+
};
|
|
122
|
+
},
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// --- syntax-highlight tokenizer for addCode() ---------------------------------------------------
|
|
127
|
+
// Tokenizes `code` into `{ type, value }` spans covering JS/TS/Python/Java/Go/C/C++/PHP/Rust/HTML/
|
|
128
|
+
// Bash/Markdown; unsupported languages fall back to a single plain-text token.
|
|
129
|
+
export function tokenizeCodeForRich(code, lang = 'javascript') {
|
|
130
|
+
const keywordsMap = {
|
|
131
|
+
javascript: new Set(['break', 'case', 'catch', 'continue', 'debugger', 'delete', 'do', 'else', 'finally', 'for', 'function', 'if', 'in', 'instanceof', 'new', 'return', 'switch', 'this', 'throw', 'try', 'typeof', 'var', 'void', 'while', 'with', 'true', 'false', 'null', 'undefined', 'class', 'const', 'let', 'super', 'extends', 'export', 'import', 'yield', 'static', 'constructor', 'async', 'await', 'get', 'set']),
|
|
132
|
+
typescript: new Set(['abstract', 'any', 'as', 'asserts', 'bigint', 'boolean', 'declare', 'enum', 'implements', 'infer', 'interface', 'is', 'keyof', 'module', 'namespace', 'never', 'readonly', 'require', 'number', 'object', 'override', 'private', 'protected', 'public', 'satisfies', 'string', 'symbol', 'type', 'unknown', 'using', 'from', 'break', 'case', 'catch', 'continue', 'do', 'else', 'finally', 'for', 'function', 'if', 'new', 'return', 'switch', 'this', 'throw', 'try', 'var', 'void', 'while', 'class', 'const', 'let', 'extends', 'import', 'export', 'async', 'await']),
|
|
133
|
+
python: new Set(['False', 'None', 'True', 'and', 'as', 'assert', 'async', 'await', 'break', 'class', 'continue', 'def', 'del', 'elif', 'else', 'except', 'finally', 'for', 'from', 'global', 'if', 'import', 'in', 'is', 'lambda', 'nonlocal', 'not', 'or', 'pass', 'raise', 'return', 'try', 'while', 'with', 'yield']),
|
|
134
|
+
java: new Set(['abstract', 'assert', 'boolean', 'break', 'byte', 'case', 'catch', 'char', 'class', 'const', 'continue', 'default', 'do', 'double', 'else', 'enum', 'extends', 'final', 'finally', 'float', 'for', 'goto', 'if', 'implements', 'import', 'instanceof', 'int', 'interface', 'long', 'native', 'new', 'package', 'private', 'protected', 'public', 'return', 'short', 'static', 'strictfp', 'super', 'switch', 'synchronized', 'this', 'throw', 'throws', 'transient', 'try', 'void', 'volatile', 'while']),
|
|
135
|
+
golang: new Set(['break', 'case', 'chan', 'const', 'continue', 'default', 'defer', 'else', 'fallthrough', 'for', 'func', 'go', 'goto', 'if', 'import', 'interface', 'map', 'package', 'range', 'return', 'select', 'struct', 'switch', 'type', 'var']),
|
|
136
|
+
c: new Set(['auto', 'break', 'case', 'char', 'const', 'continue', 'default', 'do', 'double', 'else', 'enum', 'extern', 'float', 'for', 'goto', 'if', 'int', 'long', 'register', 'return', 'short', 'signed', 'sizeof', 'static', 'struct', 'switch', 'typedef', 'union', 'unsigned', 'void', 'volatile', 'while']),
|
|
137
|
+
cpp: new Set(['alignas', 'alignof', 'and', 'auto', 'bool', 'break', 'case', 'catch', 'class', 'const', 'constexpr', 'continue', 'delete', 'do', 'double', 'else', 'enum', 'explicit', 'export', 'extern', 'false', 'float', 'for', 'friend', 'if', 'inline', 'int', 'long', 'mutable', 'namespace', 'new', 'noexcept', 'nullptr', 'operator', 'private', 'protected', 'public', 'return', 'short', 'signed', 'sizeof', 'static', 'struct', 'switch', 'template', 'this', 'throw', 'true', 'try', 'typedef', 'typename', 'union', 'unsigned', 'using', 'virtual', 'void', 'while']),
|
|
138
|
+
php: new Set(['abstract', 'and', 'array', 'as', 'break', 'callable', 'case', 'catch', 'class', 'clone', 'const', 'continue', 'declare', 'default', 'do', 'echo', 'else', 'elseif', 'empty', 'enddeclare', 'endfor', 'endforeach', 'endif', 'endswitch', 'endwhile', 'extends', 'final', 'finally', 'fn', 'for', 'foreach', 'function', 'global', 'goto', 'if', 'implements', 'include', 'include_once', 'instanceof', 'interface', 'match', 'namespace', 'new', 'null', 'or', 'private', 'protected', 'public', 'require', 'require_once', 'return', 'static', 'switch', 'throw', 'trait', 'try', 'use', 'var', 'while', 'yield']),
|
|
139
|
+
rust: new Set(['as', 'break', 'const', 'continue', 'crate', 'else', 'enum', 'extern', 'false', 'fn', 'for', 'if', 'impl', 'in', 'let', 'loop', 'match', 'mod', 'move', 'mut', 'pub', 'ref', 'return', 'self', 'Self', 'static', 'struct', 'super', 'trait', 'true', 'type', 'unsafe', 'use', 'where', 'while']),
|
|
140
|
+
html: new Set(['html', 'head', 'body', 'div', 'span', 'p', 'a', 'img', 'video', 'audio', 'script', 'style', 'link', 'meta', 'form', 'input', 'button', 'table', 'tr', 'td', 'th', 'ul', 'ol', 'li', 'section', 'article', 'header', 'footer', 'nav', 'main']),
|
|
141
|
+
bash: new Set(['if', 'then', 'else', 'elif', 'fi', 'for', 'while', 'do', 'done', 'case', 'esac', 'function', 'in', 'select', 'until', 'break', 'continue', 'return', 'export', 'readonly', 'local', 'declare']),
|
|
142
|
+
markdown: new Set(['#', '##', '###', '####', '#####', '######']),
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
if (!lang || lang === 'txt' || lang === 'text' || lang === 'plaintext') {
|
|
146
|
+
return {
|
|
147
|
+
codeBlock: [{ codeContent: code, highlightType: 0 }],
|
|
148
|
+
unified_codeBlock: [{ content: code, type: 'DEFAULT' }],
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const TYPE_MAP = { 0: 'DEFAULT', 1: 'KEYWORD', 2: 'METHOD', 3: 'STR', 4: 'NUMBER', 5: 'COMMENT' };
|
|
153
|
+
|
|
154
|
+
const keywords = keywordsMap[lang.toLowerCase()] || new Set();
|
|
155
|
+
const tokens = [];
|
|
156
|
+
|
|
157
|
+
let i = 0;
|
|
158
|
+
|
|
159
|
+
const push = (content, type) => {
|
|
160
|
+
if (!content) return;
|
|
161
|
+
const last = tokens[tokens.length - 1];
|
|
162
|
+
if (last && last.highlightType === type) {
|
|
163
|
+
last.codeContent += content;
|
|
164
|
+
} else {
|
|
165
|
+
tokens.push({ codeContent: content, highlightType: type });
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
const isIdentifier = (char) => {
|
|
170
|
+
switch (lang.toLowerCase()) {
|
|
171
|
+
case 'css':
|
|
172
|
+
return /[a-zA-Z0-9_$-]/.test(char);
|
|
173
|
+
case 'html':
|
|
174
|
+
return /[a-zA-Z0-9_$:-]/.test(char);
|
|
175
|
+
default:
|
|
176
|
+
return /[a-zA-Z0-9_$]/.test(char);
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
while (i < code.length) {
|
|
181
|
+
const c = code[i];
|
|
182
|
+
|
|
183
|
+
if (/\s/.test(c)) {
|
|
184
|
+
let s = i;
|
|
185
|
+
while (i < code.length && /\s/.test(code[i])) i++;
|
|
186
|
+
push(code.slice(s, i), 0);
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if ((c === '/' && code[i + 1] === '/') || (c === '#' && ['python', 'bash'].includes(lang))) {
|
|
191
|
+
let s = i;
|
|
192
|
+
while (i < code.length && code[i] !== '\n') i++;
|
|
193
|
+
push(code.slice(s, i), 5);
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (c === '"' || c === "'" || c === '`') {
|
|
198
|
+
let s = i;
|
|
199
|
+
const q = c;
|
|
200
|
+
i++;
|
|
201
|
+
while (i < code.length) {
|
|
202
|
+
if (code[i] === '\\' && i + 1 < code.length) {
|
|
203
|
+
i += 2;
|
|
204
|
+
} else if (code[i] === q) {
|
|
205
|
+
i++;
|
|
206
|
+
break;
|
|
207
|
+
} else {
|
|
208
|
+
i++;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
push(code.slice(s, i), 3);
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (/[0-9]/.test(c)) {
|
|
216
|
+
let s = i;
|
|
217
|
+
while (i < code.length && /[0-9._]/.test(code[i])) i++;
|
|
218
|
+
push(code.slice(s, i), 4);
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (/[a-zA-Z_$]/.test(c)) {
|
|
223
|
+
let s = i;
|
|
224
|
+
while (i < code.length && isIdentifier(code[i])) i++;
|
|
225
|
+
|
|
226
|
+
const word = code.slice(s, i);
|
|
227
|
+
let type = 0;
|
|
228
|
+
|
|
229
|
+
if (keywords.has(word)) {
|
|
230
|
+
type = 1;
|
|
231
|
+
} else if (lang === 'css') {
|
|
232
|
+
let j = i;
|
|
233
|
+
while (j < code.length && /\s/.test(code[j])) j++;
|
|
234
|
+
if (code[j] === ':') type = 1;
|
|
235
|
+
} else if (lang === 'html') {
|
|
236
|
+
let p = s - 1;
|
|
237
|
+
while (p >= 0 && /\s/.test(code[p])) p--;
|
|
238
|
+
if (code[p] === '<' || (code[p] === '/' && code[p - 1] === '<')) type = 1;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (type === 0) {
|
|
242
|
+
let j = i;
|
|
243
|
+
while (j < code.length && /\s/.test(code[j])) j++;
|
|
244
|
+
if (code[j] === '(') type = 2;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
push(word, type);
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
push(c, 0);
|
|
252
|
+
i++;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
return {
|
|
256
|
+
codeBlock: tokens,
|
|
257
|
+
unified_codeBlock: tokens.map((t) => ({ content: t.codeContent, type: TYPE_MAP[t.highlightType] })),
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* "Rich AI-response" style message builder: text with hyperlink/citation/latex
|
|
263
|
+
* inline entities, code blocks, tables, sources, image/video attachments,
|
|
264
|
+
* inline product/post cards, tip banners and quick-reply suggestions —
|
|
265
|
+
* everything ChatGPT/Gemini-in-WhatsApp-style bots typically render.
|
|
266
|
+
*
|
|
267
|
+
* Rewritten from MessageBuilder v4.7's `AIRich` reference implementation:
|
|
268
|
+
* same public API and message-building logic as v4.7, wired into this fork's
|
|
269
|
+
* `BaseBuilder` (title/footer/contextInfo/extraPayload) and the `withBlockTracking()` proxy
|
|
270
|
+
* defined at the top of this file (the `{ id, insertAt, replace }` mechanism on every add*()/set*() call) in
|
|
271
|
+
* place of v4.7's own `_nodes`/`_idIndex` bookkeeping.
|
|
272
|
+
*/
|
|
273
|
+
class AIRich extends BaseBuilder {
|
|
274
|
+
#client;
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* @param {import('../../WAProto/index.js').WASocket} client Active Baileys socket.
|
|
278
|
+
* @param {object} [options]
|
|
279
|
+
* @param {boolean} [options.dynamic=true] When true, `build()` mints a fresh `response_id`/
|
|
280
|
+
* `botResponseId` on every call instead of reusing the pinned ones.
|
|
281
|
+
* @param {boolean} [options.unsupportedTypeAlert=true] Gates `createAlert()` — see that method.
|
|
282
|
+
*/
|
|
283
|
+
constructor(client, { dynamic = true, unsupportedTypeAlert = true } = {}) {
|
|
284
|
+
if (!client) {
|
|
285
|
+
throw new Error('Socket is required');
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
super();
|
|
289
|
+
this.#client = client;
|
|
290
|
+
this._contextInfo = {};
|
|
291
|
+
this._sections = [];
|
|
292
|
+
this._submessages = [];
|
|
293
|
+
this._unsupportedTypeAlert = !!unsupportedTypeAlert;
|
|
294
|
+
this._dynamic = !!dynamic;
|
|
295
|
+
this._responseId = crypto.randomUUID();
|
|
296
|
+
this._botResponseId = crypto.randomUUID();
|
|
297
|
+
this._lastMessageKey = null;
|
|
298
|
+
this._inlineImages = [];
|
|
299
|
+
this._title = '';
|
|
300
|
+
this._botMetadataExtra = {};
|
|
301
|
+
this._forwardBotJid = DEFAULT_AI_BOT_JID;
|
|
302
|
+
this._embeddedScreens = [];
|
|
303
|
+
this._verification = false;
|
|
304
|
+
this._notification = false;
|
|
305
|
+
this._notificationText = undefined;
|
|
306
|
+
this._mentions = [];
|
|
307
|
+
this._quoted = undefined;
|
|
308
|
+
this._quotedParticipant = undefined;
|
|
309
|
+
this._noContextInfo = false;
|
|
310
|
+
this._unifiedResponseData = undefined;
|
|
311
|
+
this._messageSecret = false;
|
|
312
|
+
this._bypassDownload = true;
|
|
313
|
+
this._forwarded = true;
|
|
314
|
+
this._includesUnifiedResponse = true;
|
|
315
|
+
this._includesSubmessages = true;
|
|
316
|
+
this._rawEnvelope = false;
|
|
317
|
+
this._deferred = [];
|
|
318
|
+
|
|
319
|
+
// { id, insertAt, replace } support for every add*()/set*() call — see withBlockTracking() at the top of this file.
|
|
320
|
+
return withBlockTracking(this, { subField: '_submessages', secField: '_sections' });
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Reconstruct this builder's state from an already-built/sent AIRich message (e.g. one fetched
|
|
325
|
+
* back from your own message store), so it can be edited and re-sent via `sendEdit()`.
|
|
326
|
+
* @param {object} msg A raw WA message object — either `{ key, message }` (as stored/fetched) or
|
|
327
|
+
* just the `message` object itself. Must contain a `richResponseMessage` (nested under
|
|
328
|
+
* `botForwardedMessage.message`, `botForwardedMessage`, or at the top level).
|
|
329
|
+
*/
|
|
330
|
+
loadFrom(msg) {
|
|
331
|
+
if (!msg) throw new Error('AI Rich message needed');
|
|
332
|
+
|
|
333
|
+
const message = msg.message ?? msg;
|
|
334
|
+
|
|
335
|
+
let richResponseMessage = message?.botForwardedMessage?.message?.richResponseMessage;
|
|
336
|
+
if (!richResponseMessage) richResponseMessage = message?.botForwardedMessage?.richResponseMessage;
|
|
337
|
+
if (!richResponseMessage) richResponseMessage = message?.richResponseMessage;
|
|
338
|
+
if (!richResponseMessage) throw new Error('richResponseMessage not found');
|
|
339
|
+
|
|
340
|
+
const messageContextInfo = message?.messageContextInfo ?? {};
|
|
341
|
+
const botMetadata = messageContextInfo?.botMetadata ?? {};
|
|
342
|
+
|
|
343
|
+
this._title = botMetadata?.messageDisclaimerText ?? '';
|
|
344
|
+
this._contextInfo = structuredClone(richResponseMessage?.contextInfo ?? {});
|
|
345
|
+
this._submessages = Array.isArray(richResponseMessage?.submessages) ? structuredClone(richResponseMessage.submessages) : [];
|
|
346
|
+
this._sections = [];
|
|
347
|
+
|
|
348
|
+
const unifiedData = richResponseMessage?.unifiedResponse?.data;
|
|
349
|
+
if (unifiedData) {
|
|
350
|
+
try {
|
|
351
|
+
const decoded = Buffer.from(unifiedData, 'base64').toString('utf8');
|
|
352
|
+
const unifiedResponse = JSON.parse(decoded);
|
|
353
|
+
if (Array.isArray(unifiedResponse?.sections)) this._sections = structuredClone(unifiedResponse.sections);
|
|
354
|
+
} catch {
|
|
355
|
+
// malformed/foreign unifiedResponse.data — fall back to empty sections rather than
|
|
356
|
+
// throw; submessages (already loaded above) still render on their own.
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// ids aren't recoverable from the serialized payload (they only ever existed in the sending
|
|
361
|
+
// builder's in-memory `_blocks` Map) — start with an empty tracking map, same as a freshly
|
|
362
|
+
// constructed instance.
|
|
363
|
+
this._blocks = new Map();
|
|
364
|
+
this._lastBlock = null;
|
|
365
|
+
|
|
366
|
+
this._extraPayload = {};
|
|
367
|
+
for (const [key, value] of Object.entries(message ?? {})) {
|
|
368
|
+
if (key !== 'messageContextInfo' && key !== 'botForwardedMessage' && key !== 'richResponseMessage') {
|
|
369
|
+
this._extraPayload[key] = structuredClone(value);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// lets sendEdit() be called immediately with no jid/id args if `msg` came with a `.key`.
|
|
374
|
+
if (msg?.key?.remoteJid && msg?.key?.id) {
|
|
375
|
+
this._lastMessageKey = { remoteJid: msg.key.remoteJid, fromMe: !!msg.key.fromMe, id: msg.key.id };
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
return this;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/** Pin `unifiedResponse.response_id` to a specific value instead of a fresh random one each build() — needed to re-send an edited version of an already-sent message in place. */
|
|
382
|
+
setResponseId(id) {
|
|
383
|
+
if (typeof id !== 'string') throw new TypeError('ID must be a string');
|
|
384
|
+
this._responseId = id;
|
|
385
|
+
return this;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/** Force a fresh random `response_id` on the next build(), discarding any pinned value. */
|
|
389
|
+
refreshResponseId() {
|
|
390
|
+
this._responseId = crypto.randomUUID();
|
|
391
|
+
return this;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/** Pin `botMetadata.botResponseId` to a specific value (see setResponseId()). */
|
|
395
|
+
setBotResponseId(id) {
|
|
396
|
+
if (typeof id !== 'string') throw new TypeError('ID must be a string');
|
|
397
|
+
this._botResponseId = id;
|
|
398
|
+
return this;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/** Force a fresh random `botResponseId` on the next build(), discarding any pinned value. */
|
|
402
|
+
refreshBotResponseId() {
|
|
403
|
+
this._botResponseId = crypto.randomUUID();
|
|
404
|
+
return this;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/** Set `botMetadata.messageDisclaimerText` — shown as the small title line above the response. */
|
|
408
|
+
setTitle(title) {
|
|
409
|
+
if (typeof title !== 'string') {
|
|
410
|
+
throw new TypeError('setTitle(title) requires a string');
|
|
411
|
+
}
|
|
412
|
+
this._title = title;
|
|
413
|
+
return this;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* Merge extra fields into `messageContextInfo.botMetadata` at build time (e.g.
|
|
418
|
+
* `{ unifiedResponseMutation: { mediaDetailsMetadataList: [...] } }`, needed for a file/media
|
|
419
|
+
* artifact whose preview comes from an uploaded media entry rather than a plain URL).
|
|
420
|
+
* @param {object} extra
|
|
421
|
+
*/
|
|
422
|
+
setBotMetadata(extra) {
|
|
423
|
+
if (!extra || typeof extra !== 'object' || Array.isArray(extra)) {
|
|
424
|
+
throw new TypeError('setBotMetadata(extra) requires a plain object');
|
|
425
|
+
}
|
|
426
|
+
this._botMetadataExtra = { ...this._botMetadataExtra, ...extra };
|
|
427
|
+
return this;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/**
|
|
431
|
+
* Override the `forwardedAiBotMessageInfo.botJid` stamped on forwarded builds (default: the
|
|
432
|
+
* Meta AI bot jid). Only used when `forwarded` is true (the default in send()/build()).
|
|
433
|
+
* @param {string} jid A `...@bot` jid.
|
|
434
|
+
*/
|
|
435
|
+
setForwardBotJid(jid) {
|
|
436
|
+
if (typeof jid !== 'string' || !jid.endsWith('@bot')) {
|
|
437
|
+
throw new TypeError("setForwardBotJid(jid) requires a string ending in '@bot'");
|
|
438
|
+
}
|
|
439
|
+
this._forwardBotJid = jid;
|
|
440
|
+
return this;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/** Alias of `setForwardBotJid()` (README casing). */
|
|
444
|
+
setForwardBotJID(jid) {
|
|
445
|
+
return this.setForwardBotJid(jid);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
/**
|
|
449
|
+
* Toggle the `botMetadata.verificationMetadata` block that `build()` attaches. Default `false`. NOTE: the proof is
|
|
450
|
+
* random bytes, not a real Meta signature, so it is opt-in (the Go reference `MessageBuilderv2` also ships OFF).
|
|
451
|
+
* Call `setVerificationMetadata(true)` only if you explicitly want the placeholder proof attached.
|
|
452
|
+
* @param {boolean} [enabled=true]
|
|
453
|
+
*/
|
|
454
|
+
setVerificationMetadata(enabled = true) {
|
|
455
|
+
this._verification = !!enabled;
|
|
456
|
+
return this;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* Default for `build({ notification })` / `send({ notification })`: attach `sessionTransparencyMetadata`.
|
|
461
|
+
* @param {boolean} [enabled=true]
|
|
462
|
+
* @param {string} [text] Disclaimer text shown to the recipient. Defaults to the `setTitle()` text; omitted if neither is set.
|
|
463
|
+
*/
|
|
464
|
+
setNotification(enabled = true, text) {
|
|
465
|
+
this._notification = !!enabled;
|
|
466
|
+
if (text !== undefined) {
|
|
467
|
+
if (typeof text !== 'string') throw new TypeError('setNotification(enabled, text): text must be a string');
|
|
468
|
+
this._notificationText = text;
|
|
469
|
+
}
|
|
470
|
+
return this;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
/**
|
|
474
|
+
* Tag JIDs on the card (`contextInfo.mentionedJid`). Keep the `@<lid>` tokens in your text as-is —
|
|
475
|
+
* WhatsApp renders them as the contact's name. Use LID jids so they match the token.
|
|
476
|
+
* @param {...(string|string[])} jids
|
|
477
|
+
*/
|
|
478
|
+
setMentions(...jids) {
|
|
479
|
+
const list = jids.flat();
|
|
480
|
+
if (!list.every((j) => typeof j === 'string' && j)) throw new TypeError('setMentions(...jids) requires non-empty strings');
|
|
481
|
+
this._mentions = list;
|
|
482
|
+
return this;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
/**
|
|
486
|
+
* Default quoted message for `build()`/`send()` (same shapes `build({ quoted })` accepts).
|
|
487
|
+
* @param {object} quoted A WAMessage (`{ key, message }`) or `{ id, participant }`.
|
|
488
|
+
* @param {string} [participant]
|
|
489
|
+
*/
|
|
490
|
+
setQuoted(quoted, participant) {
|
|
491
|
+
if (quoted != null && (typeof quoted !== 'object' || Array.isArray(quoted))) throw new TypeError('setQuoted(quoted) requires a message object');
|
|
492
|
+
this._quoted = quoted ?? undefined;
|
|
493
|
+
this._quotedParticipant = participant;
|
|
494
|
+
return this;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
/** Drop `contextInfo` from the rich response entirely (bare rich, as in captured native edits). */
|
|
498
|
+
setNoContextInfo(enabled = true) {
|
|
499
|
+
this._noContextInfo = !!enabled;
|
|
500
|
+
return this;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
/** Mint a fresh `response_id`/`botResponseId` on every `build()` (same as the constructor's `dynamic` option). */
|
|
504
|
+
setDynamicIDs(enabled = true) {
|
|
505
|
+
this._dynamic = !!enabled;
|
|
506
|
+
return this;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
/**
|
|
510
|
+
* Force `unifiedResponse.data` from a raw payload instead of the sections model.
|
|
511
|
+
* @param {object|string|Buffer|null} data Plain object (JSON-stringified), JSON string, or Buffer. `null` clears the override.
|
|
512
|
+
*/
|
|
513
|
+
setUnifiedResponseData(data) {
|
|
514
|
+
if (data == null) {
|
|
515
|
+
this._unifiedResponseData = undefined;
|
|
516
|
+
return this;
|
|
517
|
+
}
|
|
518
|
+
if (!(typeof data === 'string' || Buffer.isBuffer(data) || typeof data === 'object')) throw new TypeError('setUnifiedResponseData(data) requires an object, string or Buffer');
|
|
519
|
+
this._unifiedResponseData = data;
|
|
520
|
+
return this;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
/** Stamp a random 32-byte `messageContextInfo.messageSecret` on every `build()`. */
|
|
524
|
+
setMessageSecret(enabled = true) {
|
|
525
|
+
this._messageSecret = !!enabled;
|
|
526
|
+
return this;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
/** Runtime toggle for the constructor's `unsupportedTypeAlert` option (gates `createAlert()`). */
|
|
530
|
+
setUnsupportedTypeAlert(enabled = true) {
|
|
531
|
+
this._unsupportedTypeAlert = !!enabled;
|
|
532
|
+
return this;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
/** Default for `send({ bypassDownload })` — auto `sendEdit()` after `send()`. Default `true`. */
|
|
536
|
+
setByPassDownload(enabled = true) {
|
|
537
|
+
this._bypassDownload = !!enabled;
|
|
538
|
+
return this;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
/**
|
|
542
|
+
* Shortcut for `botMetadata.capabilityMetadata.capabilities`. Accepts `BotCapabilityType` enum
|
|
543
|
+
* names or numbers, e.g. `setCapabilities(['RICH_RESPONSE_UR_BLOKS_ENABLED'])`.
|
|
544
|
+
* @param {Array<string|number>} capabilities
|
|
545
|
+
*/
|
|
546
|
+
setCapabilities(capabilities) {
|
|
547
|
+
if (!Array.isArray(capabilities)) throw new TypeError('setCapabilities(capabilities) requires an array');
|
|
548
|
+
return this.setBotMetadata({ capabilityMetadata: { capabilities } });
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
/**
|
|
552
|
+
* Shortcut for `botMetadata.botRenderingConfigMetadata`.
|
|
553
|
+
* @param {string} bloksVersioningId
|
|
554
|
+
* @param {number} [pixelDensity=1]
|
|
555
|
+
*/
|
|
556
|
+
setBotRenderingConfig(bloksVersioningId, pixelDensity = 1) {
|
|
557
|
+
if (typeof bloksVersioningId !== 'string' || !bloksVersioningId) {
|
|
558
|
+
throw new TypeError('setBotRenderingConfig(bloksVersioningId) requires a non-empty string');
|
|
559
|
+
}
|
|
560
|
+
if (typeof pixelDensity !== 'number') throw new TypeError('pixelDensity must be a number');
|
|
561
|
+
return this.setBotMetadata({ botRenderingConfigMetadata: { bloksVersioningId, pixelDensity } });
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
/** Shortcut for `botMetadata.botThreadInfo.clientInfo.type` (`AIThreadType`: UNKNOWN / DEFAULT / INCOGNITO / SIDE_CHAT). */
|
|
565
|
+
setThreadType(type) {
|
|
566
|
+
if (!(typeof type === 'string' || typeof type === 'number')) throw new TypeError('setThreadType(type) requires an enum name or number');
|
|
567
|
+
return this.setBotMetadata({ botThreadInfo: { clientInfo: { type } } });
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
/** Shortcut for `botMetadata.imagineMetadata.imagineType`. */
|
|
571
|
+
setImagineType(type) {
|
|
572
|
+
if (!(typeof type === 'string' || typeof type === 'number')) throw new TypeError('setImagineType(type) requires an enum name or number');
|
|
573
|
+
return this.setBotMetadata({ imagineMetadata: { imagineType: type } });
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
/** Shortcut for `botMetadata.subscriptionUpsellMetadata.requestType` (`AISubscriptionRequestType`: UNSPECIFIED / THINK_HARD / IMAGE_GEN / VIDEO_GEN). */
|
|
577
|
+
setSubscriptionUpsell(requestType) {
|
|
578
|
+
if (!(typeof requestType === 'string' || typeof requestType === 'number')) throw new TypeError('setSubscriptionUpsell(requestType) requires an enum name or number');
|
|
579
|
+
return this.setBotMetadata({ subscriptionUpsellMetadata: { requestType } });
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
/**
|
|
583
|
+
* Add a full-screen/bottom-sheet "embedded screen" — a tabbed view opened by tapping into the
|
|
584
|
+
* response, as opposed to a section rendered inline. Confirmed shape (`embedded_screens` array
|
|
585
|
+
* inside `unifiedResponse.data`, alongside `sections`) from yudzxml 7.6.6 / elaina 1.3.10's core
|
|
586
|
+
* `index.js`. Used by `addHTMLCard()` below; pass a raw screen object directly for anything
|
|
587
|
+
* beyond that.
|
|
588
|
+
* @param {{id?: string, title?: string, content?: object[], tabs?: object[]}} screen
|
|
589
|
+
*/
|
|
590
|
+
addEmbeddedScreen(screen) {
|
|
591
|
+
if (!screen || typeof screen !== 'object' || Array.isArray(screen)) {
|
|
592
|
+
throw new TypeError('addEmbeddedScreen(screen) requires a plain object');
|
|
593
|
+
}
|
|
594
|
+
this._embeddedScreens.push(screen);
|
|
595
|
+
return this;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
/**
|
|
599
|
+
* Build a generic `[ UNSUPPORTED_TYPE - <type> ]` submessage fallback, or `undefined` if
|
|
600
|
+
* `unsupportedTypeAlert` was set to `false` in the constructor.
|
|
601
|
+
* @param {string} type The primitive's `__typename` (or any label), shown in the banner text.
|
|
602
|
+
*/
|
|
603
|
+
createAlert(type) {
|
|
604
|
+
if (this._unsupportedTypeAlert) {
|
|
605
|
+
return {
|
|
606
|
+
messageType: 2,
|
|
607
|
+
messageText: `[ UNSUPPORTED_TYPE - ${type}]`,
|
|
608
|
+
};
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
return undefined;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
// --- block id lookup/removal, backed by withBlockTracking()'s `_blocks` Map -----------------
|
|
615
|
+
|
|
616
|
+
/** Check whether a block id was registered by an earlier `add*()`/`set*()` call passing `{id}`. */
|
|
617
|
+
hasId(id) {
|
|
618
|
+
return typeof id === 'string' && this._blocks.has(id);
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
/** List every block id registered so far, in no particular order. */
|
|
622
|
+
getIds() {
|
|
623
|
+
return [...this._blocks.keys()];
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
/** Inspect a registered block without modifying it. Returns `null` if `id` isn't registered. */
|
|
627
|
+
peek(id) {
|
|
628
|
+
const block = this._blocks.get(id);
|
|
629
|
+
if (!block) return null;
|
|
630
|
+
|
|
631
|
+
return { id, sections: [...block.secItems], submessages: [...block.subItems] };
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
/** Remove a previously-added block (by the `id` passed to its `add*()`/`set*()` call) from the message. Throws if `id` isn't registered. */
|
|
635
|
+
delete(id) {
|
|
636
|
+
const block = this._blocks.get(id);
|
|
637
|
+
if (!block) throw new Error(`delete(id): no block registered with id "${id}"`);
|
|
638
|
+
|
|
639
|
+
for (const item of block.subItems) {
|
|
640
|
+
const idx = this._submessages.indexOf(item);
|
|
641
|
+
if (idx !== -1) this._submessages.splice(idx, 1);
|
|
642
|
+
}
|
|
643
|
+
for (const item of block.secItems) {
|
|
644
|
+
const idx = this._sections.indexOf(item);
|
|
645
|
+
if (idx !== -1) this._sections.splice(idx, 1);
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
this._blocks.delete(id);
|
|
649
|
+
return this;
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
/**
|
|
653
|
+
* Retroactively assign an id to the block most recently added by an add*()/set*() call that
|
|
654
|
+
* didn't pass its own `{id}`.
|
|
655
|
+
* @param {string} id
|
|
656
|
+
*/
|
|
657
|
+
assignId(id) {
|
|
658
|
+
if (typeof id !== 'string' || !id) throw new TypeError('assignId(id) requires a non-empty string');
|
|
659
|
+
if (this._blocks.has(id)) throw new Error(`assignId: id "${id}" is already registered`);
|
|
660
|
+
if (!this._lastBlock) throw new Error('assignId: no untracked block to assign — call it immediately after an add*()/set*() that was passed no {id}');
|
|
661
|
+
|
|
662
|
+
this._blocks.set(id, this._lastBlock);
|
|
663
|
+
this._lastBlock = null;
|
|
664
|
+
return this;
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
// --- content -------------------------------------------------------------------------------
|
|
668
|
+
|
|
669
|
+
/** Push validated section(s)/submessage(s) onto the tracked arrays. `{ id, insertAt, replace }` bookkeeping is handled by the constructor's withBlockTracking() proxy — this only needs to append. */
|
|
670
|
+
_addContent(section, submessage) {
|
|
671
|
+
const sections = section === undefined || section === null ? [] : Array.isArray(section) ? section : [section];
|
|
672
|
+
const submessages = submessage === undefined || submessage === null ? [] : Array.isArray(submessage) ? submessage : [submessage];
|
|
673
|
+
|
|
674
|
+
if (!sections.length) {
|
|
675
|
+
throw new ContentValidationError('At least one section is required');
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
for (const item of sections) {
|
|
679
|
+
if (!item || typeof item !== 'object' || Array.isArray(item)) {
|
|
680
|
+
throw new ContentValidationError('Sections must be plain objects');
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
for (const item of submessages) {
|
|
685
|
+
if (!item || typeof item !== 'object' || Array.isArray(item)) {
|
|
686
|
+
throw new ContentValidationError('Submessages must be plain objects');
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
this._sections.push(...sections);
|
|
691
|
+
this._submessages.push(...submessages);
|
|
692
|
+
|
|
693
|
+
return this;
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
/** Push a raw pre-built section wrapper (escape hatch for shapes not covered by the add*() helpers). */
|
|
697
|
+
addSection(section, options = {}) {
|
|
698
|
+
return this._addContent(section, undefined, options);
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
/** Push a raw pre-built submessage block (escape hatch for shapes not covered by the add*() helpers). */
|
|
702
|
+
addSubmessage(submessage, options = {}) {
|
|
703
|
+
if (submessage === undefined || submessage === null) {
|
|
704
|
+
throw new ContentValidationError('At least one submessage is required');
|
|
705
|
+
}
|
|
706
|
+
return this._addContent(undefined, submessage, options);
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
addText(text, { hyperlink = true, citation = true, latex = true, id, replace, insertAt } = {}) {
|
|
710
|
+
if (typeof text !== 'string') {
|
|
711
|
+
throw new TypeError('Text must be a string');
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
const { text: extractedText, inline_entities } = extractIE(text, { hyperlink, citation, latex });
|
|
715
|
+
|
|
716
|
+
const section = AIRich.newLayout('Single', {
|
|
717
|
+
text: extractedText,
|
|
718
|
+
...(inline_entities.length && { inline_entities }),
|
|
719
|
+
__typename: 'GenAIMarkdownTextUXPrimitive',
|
|
720
|
+
});
|
|
721
|
+
|
|
722
|
+
const submessages = [{ messageType: 2, messageText: text }];
|
|
723
|
+
|
|
724
|
+
return this._addContent(section, submessages, { id, replace, insertAt });
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
addFOAText(text, { id, replace, insertAt } = {}) {
|
|
728
|
+
if (typeof text !== 'string') {
|
|
729
|
+
throw new TypeError('Text must be a string');
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
const section = AIRich.newLayout('Single', { text, __typename: 'FOATextPrimitive' });
|
|
733
|
+
const submessages = [{ messageType: 2, messageText: text }];
|
|
734
|
+
|
|
735
|
+
return this._addContent(section, submessages, { id, replace, insertAt });
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
addCode(language, code, { id, replace, insertAt } = {}) {
|
|
739
|
+
if (typeof language !== 'string' || typeof code !== 'string') {
|
|
740
|
+
throw new TypeError('Language and code must be a string');
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
const meta = tokenizeCodeForRich(code, language);
|
|
744
|
+
|
|
745
|
+
const section = AIRich.newLayout('Single', {
|
|
746
|
+
language,
|
|
747
|
+
code_blocks: meta.unified_codeBlock,
|
|
748
|
+
__typename: 'GenAICodeUXPrimitive',
|
|
749
|
+
});
|
|
750
|
+
|
|
751
|
+
const submessages = [
|
|
752
|
+
{
|
|
753
|
+
messageType: 5,
|
|
754
|
+
codeMetadata: { codeLanguage: language, codeBlocks: meta.codeBlock },
|
|
755
|
+
},
|
|
756
|
+
];
|
|
757
|
+
|
|
758
|
+
return this._addContent(section, submessages, { id, replace, insertAt });
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
addTable(table, { hyperlink = true, citation = true, latex = true, id, replace, insertAt } = {}) {
|
|
762
|
+
if (!Array.isArray(table)) {
|
|
763
|
+
throw new TypeError('Table must be an array');
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
const meta = AIRich.toTableMetadata(table, { hyperlink, citation, latex });
|
|
767
|
+
|
|
768
|
+
const section = AIRich.newLayout('Single', { rows: meta.unified_rows, __typename: 'GenATableUXPrimitive' });
|
|
769
|
+
|
|
770
|
+
const submessages = [
|
|
771
|
+
{
|
|
772
|
+
messageType: 4,
|
|
773
|
+
tableMetadata: { title: meta.title, rows: meta.rows },
|
|
774
|
+
},
|
|
775
|
+
];
|
|
776
|
+
|
|
777
|
+
return this._addContent(section, submessages, { id, replace, insertAt });
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
addSource(sources = [], { id, replace, insertAt } = {}) {
|
|
781
|
+
if (!Array.isArray(sources)) {
|
|
782
|
+
throw new TypeError('Sources must be an array of strings, arrays, or objects');
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
const isStringArray = sources.every((item) => typeof item === 'string');
|
|
786
|
+
const isArrayFormat = sources.every((item) => Array.isArray(item) && item.every((value) => typeof value === 'string'));
|
|
787
|
+
const isObjectFormat = sources.every((item) => item && typeof item === 'object' && !Array.isArray(item));
|
|
788
|
+
|
|
789
|
+
if (!isStringArray && !isArrayFormat && !isObjectFormat) {
|
|
790
|
+
throw new TypeError('Sources must be a string array, array of string arrays, or array of objects');
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
if (isStringArray) sources = [sources];
|
|
794
|
+
|
|
795
|
+
const normalizedSources = sources.map((source) => {
|
|
796
|
+
if (Array.isArray(source)) {
|
|
797
|
+
const [icon, url, title, subtitle] = source;
|
|
798
|
+
return { icon, url, title, subtitle };
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
return {
|
|
802
|
+
icon: source.favicon ?? source.icon ?? '',
|
|
803
|
+
url: source.url ?? '',
|
|
804
|
+
title: source.title ?? '',
|
|
805
|
+
subtitle: source.subtitle ?? '',
|
|
806
|
+
};
|
|
807
|
+
});
|
|
808
|
+
|
|
809
|
+
const source = normalizedSources.map(({ icon, url, title, subtitle }) => ({
|
|
810
|
+
source_type: 'THIRD_PARTY',
|
|
811
|
+
source_display_name: title,
|
|
812
|
+
source_subtitle: subtitle,
|
|
813
|
+
source_url: url,
|
|
814
|
+
favicon: {
|
|
815
|
+
url: Toolkit.resolveMedia(this.#client, icon, 'image'),
|
|
816
|
+
mime_type: 'image/jpeg',
|
|
817
|
+
width: 16,
|
|
818
|
+
height: 16,
|
|
819
|
+
},
|
|
820
|
+
}));
|
|
821
|
+
|
|
822
|
+
const submessage = this.createAlert('GenAISearchResultPrimitive');
|
|
823
|
+
|
|
824
|
+
const section = AIRich.newLayout('Single', { sources: source, __typename: 'GenAISearchResultPrimitive' });
|
|
825
|
+
|
|
826
|
+
return this._addContent(section, submessage, { id, replace, insertAt });
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
addReels(reelsItems = [], { id, replace, insertAt } = {}) {
|
|
830
|
+
if (
|
|
831
|
+
!(
|
|
832
|
+
(reelsItems && typeof reelsItems === 'object' && !Array.isArray(reelsItems)) ||
|
|
833
|
+
(Array.isArray(reelsItems) && reelsItems.every((item) => item && typeof item === 'object' && !Array.isArray(item)))
|
|
834
|
+
)
|
|
835
|
+
) {
|
|
836
|
+
throw new TypeError('Reels items must be an object or an array of objects');
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
const items = Array.isArray(reelsItems) ? reelsItems : [reelsItems];
|
|
840
|
+
|
|
841
|
+
const reels = items.map((item) => ({
|
|
842
|
+
...item,
|
|
843
|
+
_avatar: Toolkit.resolveMedia(this.#client, item.profileIconUrl ?? item.profile_url ?? item.profile ?? '', 'image'),
|
|
844
|
+
_thumbnail: Toolkit.resolveMedia(this.#client, item.thumbnailUrl ?? item.thumbnail ?? '', 'image'),
|
|
845
|
+
}));
|
|
846
|
+
|
|
847
|
+
const section = AIRich.newLayout(
|
|
848
|
+
'HScroll',
|
|
849
|
+
reels.map((item) => ({
|
|
850
|
+
reels_url: item.videoUrl ?? item.url ?? '',
|
|
851
|
+
thumbnail_url: item._thumbnail,
|
|
852
|
+
creator: item.username ?? item.title ?? '',
|
|
853
|
+
avatar_url: item._avatar,
|
|
854
|
+
reels_title: item.reels_title ?? item.title ?? '',
|
|
855
|
+
likes_count: item.likes_count ?? item.like ?? 0,
|
|
856
|
+
shares_count: item.shares_count ?? item.share ?? 0,
|
|
857
|
+
view_count: item.view_count ?? item.view ?? 0,
|
|
858
|
+
reel_source: item.reel_source ?? item.source ?? 'IG',
|
|
859
|
+
is_verified: !!(item.is_verified || item.verified),
|
|
860
|
+
__typename: 'GenAIReelPrimitive',
|
|
861
|
+
}))
|
|
862
|
+
);
|
|
863
|
+
|
|
864
|
+
const submessages = [
|
|
865
|
+
{
|
|
866
|
+
messageType: 9,
|
|
867
|
+
contentItemsMetadata: {
|
|
868
|
+
contentType: 1,
|
|
869
|
+
itemsMetadata: reels.map((item) => ({
|
|
870
|
+
reelItem: {
|
|
871
|
+
title: item.username ?? '',
|
|
872
|
+
profileIconUrl: item._avatar,
|
|
873
|
+
thumbnailUrl: item._thumbnail,
|
|
874
|
+
videoUrl: item.videoUrl ?? item.url ?? '',
|
|
875
|
+
},
|
|
876
|
+
})),
|
|
877
|
+
},
|
|
878
|
+
},
|
|
879
|
+
];
|
|
880
|
+
|
|
881
|
+
return this._addContent(section, submessages, { id, replace, insertAt });
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
addImage(imageUrl, { width, height, status = 'READY', update_text, resolveUrl = false, id, replace, insertAt } = {}) {
|
|
885
|
+
if (!(typeof imageUrl === 'string' || Buffer.isBuffer(imageUrl) || (Array.isArray(imageUrl) && imageUrl.every((v) => typeof v === 'string' || Buffer.isBuffer(v))))) {
|
|
886
|
+
throw new TypeError('imageUrl must be string | buffer | array of string/buffer');
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
const list = Array.isArray(imageUrl)
|
|
890
|
+
? imageUrl.map((v) => {
|
|
891
|
+
const url = Toolkit.resolveMedia(this.#client, v, 'image', { resolveUrl });
|
|
892
|
+
return { imagePreviewUrl: url, imageHighResUrl: url, sourceUrl: url };
|
|
893
|
+
})
|
|
894
|
+
: (() => {
|
|
895
|
+
const url = Toolkit.resolveMedia(this.#client, imageUrl, 'image', { resolveUrl });
|
|
896
|
+
return [{ imagePreviewUrl: url, imageHighResUrl: url, sourceUrl: url }];
|
|
897
|
+
})();
|
|
898
|
+
|
|
899
|
+
const sections = list.map(({ imagePreviewUrl }) =>
|
|
900
|
+
AIRich.newLayout('Single', {
|
|
901
|
+
media: { url: imagePreviewUrl, mime_type: 'image/png', width, height },
|
|
902
|
+
imagine_type: 'IMAGE',
|
|
903
|
+
status: { status, update_text },
|
|
904
|
+
__typename: 'GenAIImaginePrimitive',
|
|
905
|
+
})
|
|
906
|
+
);
|
|
907
|
+
|
|
908
|
+
const submessage = {
|
|
909
|
+
messageType: 1,
|
|
910
|
+
gridImageMetadata: {
|
|
911
|
+
gridImageUrl: { imagePreviewUrl: list[0]?.imagePreviewUrl },
|
|
912
|
+
imageUrls: list,
|
|
913
|
+
},
|
|
914
|
+
};
|
|
915
|
+
|
|
916
|
+
return this._addContent(sections, submessage, { id, replace, insertAt });
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
addVideo(videoUrl, { autoFill = true, status = 'READY', estimatedTime, id, replace, insertAt } = {}) {
|
|
920
|
+
const isObjectVideo = (v) => v && typeof v === 'object' && !Array.isArray(v) && v.url;
|
|
921
|
+
|
|
922
|
+
const isValidPrimitive =
|
|
923
|
+
typeof videoUrl === 'string' ||
|
|
924
|
+
Buffer.isBuffer(videoUrl) ||
|
|
925
|
+
isObjectVideo(videoUrl) ||
|
|
926
|
+
(Array.isArray(videoUrl) && videoUrl.every((v) => typeof v === 'string' || Buffer.isBuffer(v) || isObjectVideo(v)));
|
|
927
|
+
|
|
928
|
+
if (!isValidPrimitive) {
|
|
929
|
+
throw new TypeError('videoUrl must be string | buffer | object | array');
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
const items = Array.isArray(videoUrl) ? videoUrl : [videoUrl];
|
|
933
|
+
|
|
934
|
+
const alert = this.createAlert('GenAIImaginePrimitive (ANIMATE)');
|
|
935
|
+
|
|
936
|
+
const sections = [];
|
|
937
|
+
const submessages = [];
|
|
938
|
+
|
|
939
|
+
for (const item of items) {
|
|
940
|
+
const isObject = isObjectVideo(item);
|
|
941
|
+
|
|
942
|
+
const url = isObject ? Toolkit.resolveMedia(this.#client, item.url ?? '', 'video') : Toolkit.resolveMedia(this.#client, item, 'video');
|
|
943
|
+
|
|
944
|
+
const bufferPromise = autoFill ? Promise.resolve(url).then((u) => Toolkit.fetchBuffer(u)) : null;
|
|
945
|
+
|
|
946
|
+
const file_length = isObject && item.file_length != null ? item.file_length : autoFill ? bufferPromise.then((b) => b?.length ?? 0) : 0;
|
|
947
|
+
|
|
948
|
+
const duration =
|
|
949
|
+
isObject && item.duration != null
|
|
950
|
+
? item.duration
|
|
951
|
+
: autoFill
|
|
952
|
+
? bufferPromise.then((b) => Toolkit.getMp4Duration(b, { silent: true }))
|
|
953
|
+
: 0;
|
|
954
|
+
|
|
955
|
+
const thumbnail =
|
|
956
|
+
isObject && item.thumbnail
|
|
957
|
+
? Toolkit.resolveMedia(this.#client, item.thumbnail, 'image', { result: 'base64', resize: true, width: 300, height: 300 })
|
|
958
|
+
: autoFill
|
|
959
|
+
? bufferPromise?.then((b) => Toolkit.getMp4Preview(b, { time: 0, result: 'base64' }))
|
|
960
|
+
: null;
|
|
961
|
+
|
|
962
|
+
sections.push(
|
|
963
|
+
AIRich.newLayout('Single', {
|
|
964
|
+
media: {
|
|
965
|
+
url,
|
|
966
|
+
mime_type: isObject ? (item.mime_type ?? 'video/mp4') : 'video/mp4',
|
|
967
|
+
file_length,
|
|
968
|
+
duration,
|
|
969
|
+
},
|
|
970
|
+
imagine_type: 'ANIMATE',
|
|
971
|
+
status: {
|
|
972
|
+
status,
|
|
973
|
+
estimated_completion_time: estimatedTime != null ? Math.floor((Date.now() + estimatedTime) / 1000) : undefined,
|
|
974
|
+
},
|
|
975
|
+
thumbnail: { raw_media: thumbnail },
|
|
976
|
+
__typename: 'GenAIImaginePrimitive',
|
|
977
|
+
})
|
|
978
|
+
);
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
if (alert !== undefined) submessages.push(alert);
|
|
982
|
+
|
|
983
|
+
if (submessages.length > 1) {
|
|
984
|
+
throw new Error('Video content can only have one submessage');
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
return this._addContent(sections, submessages[0], { id, replace, insertAt });
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
addProduct(data = {}, { id, replace, insertAt } = {}) {
|
|
991
|
+
if (!((data && typeof data === 'object' && !Array.isArray(data)) || (Array.isArray(data) && data.every((item) => item && typeof item === 'object' && !Array.isArray(item))))) {
|
|
992
|
+
throw new TypeError('Product items must be an object or an array of objects');
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
const items = Array.isArray(data) ? data : [data];
|
|
996
|
+
|
|
997
|
+
const product = items.map((item) => ({
|
|
998
|
+
title: item.title,
|
|
999
|
+
brand: item.brand,
|
|
1000
|
+
price: item.price,
|
|
1001
|
+
sale_price: item.sale_price,
|
|
1002
|
+
product_url: item.product_url ?? item.url,
|
|
1003
|
+
image: { url: Toolkit.resolveMedia(this.#client, item.image_url ?? item.image, 'image') },
|
|
1004
|
+
additional_images: [{ url: Toolkit.resolveMedia(this.#client, item.icon_url ?? item.icon, 'image') }],
|
|
1005
|
+
__typename: 'GenAIProductItemCardPrimitive',
|
|
1006
|
+
}));
|
|
1007
|
+
|
|
1008
|
+
const section = AIRich.newLayout(Array.isArray(data) ? 'HScroll' : 'Single', Array.isArray(data) ? product : product[0]);
|
|
1009
|
+
|
|
1010
|
+
const submessage = this.createAlert('GenAIProductItemCardPrimitive');
|
|
1011
|
+
|
|
1012
|
+
return this._addContent(section, submessage, { id, replace, insertAt });
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
addPost(data = {}, { id, replace, insertAt } = {}) {
|
|
1016
|
+
if (!((data && typeof data === 'object' && !Array.isArray(data)) || (Array.isArray(data) && data.every((item) => item && typeof item === 'object' && !Array.isArray(item))))) {
|
|
1017
|
+
throw new TypeError('Post items must be an object or an array of objects');
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
const posts = Array.isArray(data) ? data : [data];
|
|
1021
|
+
|
|
1022
|
+
const primitives = posts.map((p) => ({
|
|
1023
|
+
title: p.title ?? '',
|
|
1024
|
+
subtitle: p.subtitle ?? '',
|
|
1025
|
+
username: p.username ?? '',
|
|
1026
|
+
profile_picture_url: Toolkit.resolveMedia(this.#client, p.profile_picture_url ?? p.profile_url ?? p.profile ?? '', 'image'),
|
|
1027
|
+
is_verified: !!(p.is_verified || p.verified),
|
|
1028
|
+
thumbnail_url: Toolkit.resolveMedia(this.#client, p.thumbnail_url ?? p.thumbnail ?? '', 'image'),
|
|
1029
|
+
post_caption: p.post_caption ?? p.caption ?? '',
|
|
1030
|
+
likes_count: p.likes_count ?? p.like ?? 0,
|
|
1031
|
+
comments_count: p.comments_count ?? p.comment ?? 0,
|
|
1032
|
+
shares_count: p.shares_count ?? p.share ?? 0,
|
|
1033
|
+
post_url: p.post_url ?? p.url ?? '',
|
|
1034
|
+
post_deeplink: p.post_deeplink ?? p.deeplink ?? '',
|
|
1035
|
+
source_app: p.source_app || p.source || 'INSTAGRAM',
|
|
1036
|
+
footer_label: p.footer_label ?? p.footer ?? '',
|
|
1037
|
+
footer_icon: Toolkit.resolveMedia(this.#client, p.footer_icon ?? p.icon ?? '', 'image'),
|
|
1038
|
+
is_carousel: posts.length > 1,
|
|
1039
|
+
orientation: p.orientation ?? 'LANDSCAPE',
|
|
1040
|
+
post_type: p.post_type ?? 'VIDEO',
|
|
1041
|
+
__typename: 'GenAIPostPrimitive',
|
|
1042
|
+
}));
|
|
1043
|
+
|
|
1044
|
+
const section = AIRich.newLayout('HScroll', primitives);
|
|
1045
|
+
|
|
1046
|
+
const submessage = this.createAlert('GenAIPostPrimitive');
|
|
1047
|
+
|
|
1048
|
+
return this._addContent(section, submessage, { id, replace, insertAt });
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
addMetadata(text, { id, replace, insertAt } = {}) {
|
|
1052
|
+
if (typeof text !== 'string') {
|
|
1053
|
+
throw new TypeError('Text must be a string');
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
const section = AIRich.newLayout('Single', { text, __typename: 'GenAIMetadataTextPrimitive' });
|
|
1057
|
+
const submessage = { messageType: 2, messageText: text };
|
|
1058
|
+
|
|
1059
|
+
return this._addContent(section, submessage, { id, replace, insertAt });
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
addTip(text, { id, replace, insertAt } = {}) {
|
|
1063
|
+
if (typeof text !== 'string') {
|
|
1064
|
+
throw new TypeError('Text must be a string');
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
const section = AIRich.newLayout('Single', { text: 'ⓘ ' + text, __typename: 'GenAIMetadataTextPrimitive' });
|
|
1068
|
+
const submessage = { messageType: 2, messageText: text };
|
|
1069
|
+
|
|
1070
|
+
return this._addContent(section, submessage, { id, replace, insertAt });
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
addWidget(data, { layout, id, replace, insertAt, ...options } = {}) {
|
|
1074
|
+
if (!((data && typeof data === 'object' && !Array.isArray(data)) || (Array.isArray(data) && data.every((item) => item && typeof item === 'object' && !Array.isArray(item))))) {
|
|
1075
|
+
throw new TypeError('Widget must be an object or an array of objects');
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
const isArray = Array.isArray(data);
|
|
1079
|
+
const items = isArray ? data : [data];
|
|
1080
|
+
|
|
1081
|
+
const widgets = items.map((item) => ({
|
|
1082
|
+
__typename: 'GenAI3PExtWidgetPrimitive',
|
|
1083
|
+
header: { __typename: 'GenAI3PExtWidgetStandardHeader', title: item.title ?? '', ...(item.header ?? {}) },
|
|
1084
|
+
body: {
|
|
1085
|
+
__typename: 'GenAI3PExtCalendarEventList',
|
|
1086
|
+
sections: item.sections ?? [],
|
|
1087
|
+
ctas: (item.actions ?? []).map((action) => ({
|
|
1088
|
+
__typename: 'GenAI3PExtWidgetCTA',
|
|
1089
|
+
label: action.label ?? '',
|
|
1090
|
+
state: action.state ?? 'PENDING',
|
|
1091
|
+
kind: action.kind ?? 'OTHER',
|
|
1092
|
+
tool_call_id: action.tool_call_id ?? action.id ?? '',
|
|
1093
|
+
...(action.toast && { toast: { __typename: 'GenAI3PExtWidgetToast', label: action.toast.label ?? action.label ?? '' } }),
|
|
1094
|
+
})),
|
|
1095
|
+
...(item.body ?? {}),
|
|
1096
|
+
},
|
|
1097
|
+
}));
|
|
1098
|
+
|
|
1099
|
+
const section = AIRich.newLayout(layout ?? (isArray ? 'HScroll' : 'Single'), isArray ? widgets : widgets[0], options);
|
|
1100
|
+
|
|
1101
|
+
const submessage = this.createAlert('GenAI3PExtWidgetStandardHeader');
|
|
1102
|
+
|
|
1103
|
+
return this._addContent(section, submessage, { id, replace, insertAt });
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
addFooterAction(data, { layout, id, replace, insertAt, ...options } = {}) {
|
|
1107
|
+
if (!((data && typeof data === 'object' && !Array.isArray(data)) || (Array.isArray(data) && data.every((item) => item && typeof item === 'object' && !Array.isArray(item))))) {
|
|
1108
|
+
throw new TypeError('Footer action must be an object or an array of objects');
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
const isArray = Array.isArray(data);
|
|
1112
|
+
const items = isArray ? data : [data];
|
|
1113
|
+
|
|
1114
|
+
const actions = items.map((item) => ({
|
|
1115
|
+
__typename: 'GenAIFooterActionPrimitive',
|
|
1116
|
+
cta_text: item.text ?? item.cta_text ?? '',
|
|
1117
|
+
cta_type: item.type ?? item.cta_type ?? 'OPEN_URL',
|
|
1118
|
+
cta_url: item.url ?? item.cta_url ?? '',
|
|
1119
|
+
}));
|
|
1120
|
+
|
|
1121
|
+
const section = AIRich.newLayout(layout ?? (isArray ? 'HScroll' : 'Single'), isArray ? actions : actions[0], options);
|
|
1122
|
+
|
|
1123
|
+
const submessage = this.createAlert('GenAIFooterActionPrimitive');
|
|
1124
|
+
|
|
1125
|
+
return this._addContent(section, submessage, { id, replace, insertAt });
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
addSuggest(suggestion, { scroll = true, layout, id, replace, insertAt } = {}) {
|
|
1129
|
+
if (!(typeof suggestion === 'string' || (Array.isArray(suggestion) && suggestion.every((v) => typeof v === 'string')))) {
|
|
1130
|
+
throw new TypeError('Suggestion must be a string or array of strings');
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
const suggest = Array.isArray(suggestion)
|
|
1134
|
+
? suggestion.map((text) => ({ prompt_text: text, prompt_type: 'SUGGESTED_PROMPT', __typename: 'GenAIFollowUpSuggestionPillPrimitive' }))
|
|
1135
|
+
: [{ prompt_text: suggestion, prompt_type: 'SUGGESTED_PROMPT', __typename: 'GenAIFollowUpSuggestionPillPrimitive' }];
|
|
1136
|
+
|
|
1137
|
+
const type = layout ?? (suggest.length === 1 ? 'Single' : scroll ? 'HScroll' : 'ActionRow');
|
|
1138
|
+
|
|
1139
|
+
const section = AIRich.newLayout(type, type === 'Single' ? suggest[0] : suggest, { __typename: 'GenAIUnifiedResponseSection' });
|
|
1140
|
+
|
|
1141
|
+
const submessage = this.createAlert('GenAIFollowUpSuggestionPillPrimitive');
|
|
1142
|
+
|
|
1143
|
+
return this._addContent(section, submessage, { id, replace, insertAt });
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
// --- v2.1.13: reinstated / ported from the Go reference (MessageBuilderv2) ----------------------
|
|
1147
|
+
|
|
1148
|
+
/**
|
|
1149
|
+
* Image card with independent preview and full-resolution urls (`gridImageMetadata` + `GenAIImaginePrimitive`,
|
|
1150
|
+
* the same layout `addImage()` uses). Reinstated in 2.1.13 — it was documented in the README but missing from source.
|
|
1151
|
+
* @param {string|Buffer} previewUrl Thumbnail shown in the card.
|
|
1152
|
+
* @param {string|Buffer} [fullUrl] Full-resolution image (defaults to `previewUrl`).
|
|
1153
|
+
*/
|
|
1154
|
+
addImageCard(previewUrl, fullUrl, { width, height, status = 'READY', update_text, resolveUrl = false, id, replace, insertAt } = {}) {
|
|
1155
|
+
const ok = (v) => typeof v === 'string' || Buffer.isBuffer(v);
|
|
1156
|
+
if (!ok(previewUrl)) throw new TypeError('addImageCard(previewUrl, fullUrl) requires previewUrl as string | Buffer');
|
|
1157
|
+
if (fullUrl != null && !ok(fullUrl)) throw new TypeError('addImageCard(previewUrl, fullUrl): fullUrl must be string | Buffer');
|
|
1158
|
+
|
|
1159
|
+
const resolveImage = (value) => {
|
|
1160
|
+
if (!resolveUrl && typeof value === 'string' && /^https?:\/\//i.test(value)) {
|
|
1161
|
+
return value;
|
|
1162
|
+
}
|
|
1163
|
+
return Toolkit.resolveMedia(this.#client, value, 'image', { resolveUrl });
|
|
1164
|
+
};
|
|
1165
|
+
|
|
1166
|
+
const preview = resolveImage(previewUrl);
|
|
1167
|
+
const full = fullUrl == null ? preview : resolveImage(fullUrl);
|
|
1168
|
+
|
|
1169
|
+
const section = AIRich.newLayout('Single', {
|
|
1170
|
+
media: { url: preview, mime_type: 'image/png', width, height },
|
|
1171
|
+
imagine_type: 'IMAGE',
|
|
1172
|
+
status: { status, update_text },
|
|
1173
|
+
__typename: 'GenAIImaginePrimitive',
|
|
1174
|
+
});
|
|
1175
|
+
|
|
1176
|
+
const submessage = {
|
|
1177
|
+
messageType: 1,
|
|
1178
|
+
gridImageMetadata: {
|
|
1179
|
+
gridImageUrl: { imagePreviewUrl: preview },
|
|
1180
|
+
imageUrls: [{ imagePreviewUrl: preview, imageHighResUrl: full, sourceUrl: full }],
|
|
1181
|
+
},
|
|
1182
|
+
};
|
|
1183
|
+
|
|
1184
|
+
return this._addContent([section], submessage, { id, replace, insertAt });
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
/**
|
|
1188
|
+
* Single comment line. Text-only rendering (markdown), same as the Go reference.
|
|
1189
|
+
* @param {{actorName?: string, actor_name?: string, commentText?: string, comment_text?: string, subtitle?: string}} comment
|
|
1190
|
+
*/
|
|
1191
|
+
addComment(comment = {}, { id, replace, insertAt } = {}) {
|
|
1192
|
+
const actor = comment.actorName ?? comment.actor_name ?? '';
|
|
1193
|
+
const body = comment.commentText ?? comment.comment_text ?? '';
|
|
1194
|
+
if (!actor && !body) throw new TypeError('addComment({ actorName?, commentText? }) requires at least one of them');
|
|
1195
|
+
let text = `💬 ${actor ? `${actor}: ` : ''}${body}`;
|
|
1196
|
+
if (comment.subtitle) text += `\n_${comment.subtitle}_`;
|
|
1197
|
+
return this.addText(text, { id, replace, insertAt });
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
/**
|
|
1201
|
+
* List of actions/links. Text-only rendering (`▸ title — subtitle` lines), same as the Go reference — the
|
|
1202
|
+
* native `GenAIActionListPrimitive` wire shape has never been captured for this fork.
|
|
1203
|
+
* @param {{title: string, subtitle?: string}[]} rows
|
|
1204
|
+
*/
|
|
1205
|
+
addActionList(rows, { id, replace, insertAt } = {}) {
|
|
1206
|
+
if (!Array.isArray(rows) || !rows.length || !rows.every((r) => r && typeof r.title === 'string' && r.title)) {
|
|
1207
|
+
throw new TypeError('addActionList(rows) requires a non-empty array of { title, subtitle? }');
|
|
1208
|
+
}
|
|
1209
|
+
return this.addText(rows.map((r) => `▸ ${r.title}${r.subtitle ? ` — ${r.subtitle}` : ''}`).join('\n'), { id, replace, insertAt });
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
/** One reasoning step (bold title + body), text-only rendering like the Go reference. */
|
|
1213
|
+
addChainOfThoughtStep(title, body, { id, replace, insertAt } = {}) {
|
|
1214
|
+
if (typeof title !== 'string' || !title) throw new TypeError('addChainOfThoughtStep(title, body?) requires a non-empty title');
|
|
1215
|
+
return this.addText(body ? `**${title}**\n${body}` : title, { id, replace, insertAt });
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
/**
|
|
1219
|
+
* "Thinking" banner using the `GenAIBotProgressStatusPrimitive` shape (icon `THINKING`) — the same primitive
|
|
1220
|
+
* `addProgressStatus()` uses (confirmed rendering), unlike `addThinkingStatus()`'s own typename which stock
|
|
1221
|
+
* WA clients render blank. Matches the Go reference's `AddThinking`.
|
|
1222
|
+
*/
|
|
1223
|
+
addThinking(title = 'Thinking', { icon = 'THINKING', is_in_progress = true, id, replace, insertAt } = {}) {
|
|
1224
|
+
if (typeof title !== 'string' || !title) throw new TypeError('addThinking(title) requires a non-empty string');
|
|
1225
|
+
return this.addProgressStatus(title, { icon, is_in_progress, id, replace, insertAt });
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
/**
|
|
1229
|
+
* Generic escape hatch: push any `GenAI*` primitive as a `Single` layout (empty/null fields are dropped so
|
|
1230
|
+
* they can't crash the client). Use for captured shapes that don't have a dedicated method yet, e.g.
|
|
1231
|
+
* `addPrimitive('GenAISearchPlannerStepsPrimitive', {...})`.
|
|
1232
|
+
* @param {string} typeName The primitive's `__typename`.
|
|
1233
|
+
* @param {object} payload
|
|
1234
|
+
*/
|
|
1235
|
+
addPrimitive(typeName, payload, { id, replace, insertAt } = {}) {
|
|
1236
|
+
if (typeof typeName !== 'string' || !typeName) throw new TypeError('addPrimitive(typeName, payload) requires a non-empty typeName');
|
|
1237
|
+
if (!payload || typeof payload !== 'object' || Array.isArray(payload)) throw new TypeError('addPrimitive(typeName, payload) requires a plain-object payload');
|
|
1238
|
+
return this._addSinglePrimitive(typeName, payload, { id, replace, insertAt });
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
/** Public door to `_addContent()` — insert a raw section (or sections) plus optional submessage(s), as-is. */
|
|
1242
|
+
addContentRaw(section, submessage, { id, replace, insertAt } = {}) {
|
|
1243
|
+
if (!section) throw new TypeError('addContentRaw(section, submessage?) requires a section');
|
|
1244
|
+
return this._addContent(section, submessage, { id, replace, insertAt });
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
// --- experimental primitives (v2.1.12) --------------------------------------------------------
|
|
1248
|
+
// Original implementations of wire shapes observed in captured Meta-AI traffic. As with the other
|
|
1249
|
+
// captured-traffic primitives (see the batch-render notes), the WA client/account decides whether
|
|
1250
|
+
// each one actually renders — test them one at a time on a real device, not in a 120-method batch.
|
|
1251
|
+
|
|
1252
|
+
/** @private Push one `GenAI*` primitive as a `Single` layout, with the usual unsupported-type alert submessage. */
|
|
1253
|
+
_addSinglePrimitive(typeName, payload, { id, replace, insertAt } = {}) {
|
|
1254
|
+
const clean = AIRich._omitEmpty(payload);
|
|
1255
|
+
const section = AIRich.newLayout('Single', { ...clean, __typename: typeName }, { __typename: 'GenAIUnifiedResponseSection' });
|
|
1256
|
+
return this._addContent(section, this.createAlert(typeName), { id, replace, insertAt });
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
/** @private Drop undefined/null/'' recursively so we never send explicit nulls (they can crash the WA renderer). */
|
|
1260
|
+
static _omitEmpty(value) {
|
|
1261
|
+
if (Array.isArray(value)) return value.map((v) => AIRich._omitEmpty(v));
|
|
1262
|
+
if (value && typeof value === 'object') {
|
|
1263
|
+
const out = {};
|
|
1264
|
+
for (const [k, v] of Object.entries(value)) {
|
|
1265
|
+
if (v === undefined || v === null || v === '') continue;
|
|
1266
|
+
const c = AIRich._omitEmpty(v);
|
|
1267
|
+
if (c && typeof c === 'object' && !Array.isArray(c) && !Object.keys(c).length) continue;
|
|
1268
|
+
out[k] = c;
|
|
1269
|
+
}
|
|
1270
|
+
return out;
|
|
1271
|
+
}
|
|
1272
|
+
return value;
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
/**
|
|
1276
|
+
* Follow-up suggestion card with a title/image (richer sibling of `addSuggest()`), `GenAIChainingSuggestionPrimitive`.
|
|
1277
|
+
* @param {{promptText: string, title?: string, imageUri?: string, externalConversationId?: string, topic?: string, cardId?: string}} options
|
|
1278
|
+
*/
|
|
1279
|
+
addChainingSuggestionV2({ promptText, title, imageUri, externalConversationId, topic, cardId, id, replace, insertAt } = {}) {
|
|
1280
|
+
if (typeof promptText !== 'string' || !promptText) throw new TypeError('addChainingSuggestionV2({ promptText }) requires a non-empty string');
|
|
1281
|
+
return this._addSinglePrimitive(
|
|
1282
|
+
'GenAIChainingSuggestionPrimitive',
|
|
1283
|
+
{ prompt_text: promptText, title, image_uri: imageUri, external_conversation_id: externalConversationId, topic, card_id: cardId },
|
|
1284
|
+
{ id, replace, insertAt }
|
|
1285
|
+
);
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
/**
|
|
1289
|
+
* Place card (`GenAIPlaceEntityItem`).
|
|
1290
|
+
* @param {{placeId?: string, name: string, imageUrl?: string, motivation?: string, key?: string, latitude?: number|string, longitude?: number|string, itemType?: string, address?: {streetAddress?: string, region?: string, country?: string, postalCode?: string, locality?: string, street?: string}, category?: {displayName?: string, categoryId?: string}, rating?: number|string}} place
|
|
1291
|
+
*/
|
|
1292
|
+
addPlaceEntity({ placeId, name, imageUrl, motivation, key, latitude, longitude, itemType, address = {}, category = {}, rating, id, replace, insertAt } = {}) {
|
|
1293
|
+
if (typeof name !== 'string' || !name) throw new TypeError('addPlaceEntity({ name }) requires a non-empty string');
|
|
1294
|
+
return this._addSinglePrimitive(
|
|
1295
|
+
'GenAIPlaceEntityItem',
|
|
1296
|
+
{
|
|
1297
|
+
place_id: placeId,
|
|
1298
|
+
name,
|
|
1299
|
+
image_url: imageUrl,
|
|
1300
|
+
motivation,
|
|
1301
|
+
key,
|
|
1302
|
+
latitude: latitude === undefined ? undefined : String(latitude),
|
|
1303
|
+
longitude: longitude === undefined ? undefined : String(longitude),
|
|
1304
|
+
item_type: itemType,
|
|
1305
|
+
address: {
|
|
1306
|
+
street_address: address.streetAddress,
|
|
1307
|
+
region: address.region,
|
|
1308
|
+
country: address.country,
|
|
1309
|
+
postal_code: address.postalCode,
|
|
1310
|
+
locality: address.locality,
|
|
1311
|
+
street: address.street,
|
|
1312
|
+
},
|
|
1313
|
+
category: { display_name: category.displayName, category_id: category.categoryId },
|
|
1314
|
+
rating: rating === undefined ? undefined : { avg_rating: String(rating) },
|
|
1315
|
+
},
|
|
1316
|
+
{ id, replace, insertAt }
|
|
1317
|
+
);
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
/**
|
|
1321
|
+
* Social-search result card (`GenAISearchResultV2Primitive`).
|
|
1322
|
+
* @param {{searchQuery?: string, metadata?: string, postId?: string, reelsUrl?: string, reelsDeeplink?: string, reelsTitle?: string, thumbnailUrl?: string, avatarUrl?: string, creator?: string, isVerified?: boolean, sourceApp?: string, timestamp?: string, originalWidth?: number, originalHeight?: number, primitives?: object[]}} result
|
|
1323
|
+
*/
|
|
1324
|
+
addSearchResultV2({ searchQuery, metadata, postId, reelsUrl, reelsDeeplink, reelsTitle, thumbnailUrl, avatarUrl, creator, isVerified, sourceApp, timestamp, originalWidth, originalHeight, primitives, id, replace, insertAt } = {}) {
|
|
1325
|
+
if (!searchQuery && !reelsUrl && !postId && !(primitives && primitives.length)) {
|
|
1326
|
+
throw new TypeError('addSearchResultV2() needs at least one of: searchQuery, reelsUrl, postId, primitives');
|
|
1327
|
+
}
|
|
1328
|
+
return this._addSinglePrimitive(
|
|
1329
|
+
'GenAISearchResultV2Primitive',
|
|
1330
|
+
{
|
|
1331
|
+
metadata,
|
|
1332
|
+
search_query: searchQuery,
|
|
1333
|
+
primitives,
|
|
1334
|
+
post_id: postId,
|
|
1335
|
+
reels_url: reelsUrl,
|
|
1336
|
+
reels_deeplink: reelsDeeplink,
|
|
1337
|
+
thumbnail_url: thumbnailUrl,
|
|
1338
|
+
avatar_url: avatarUrl,
|
|
1339
|
+
creator,
|
|
1340
|
+
reels_title: reelsTitle,
|
|
1341
|
+
is_verified: isVerified,
|
|
1342
|
+
source_app: sourceApp,
|
|
1343
|
+
timestamp,
|
|
1344
|
+
original_width: originalWidth,
|
|
1345
|
+
original_height: originalHeight,
|
|
1346
|
+
},
|
|
1347
|
+
{ id, replace, insertAt }
|
|
1348
|
+
);
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
/**
|
|
1352
|
+
* Compact "sources" chip row (`GenAIContextualSourcesViewModel`).
|
|
1353
|
+
* @param {Array<{faviconUri?: string, displayName: string, uri: string}>} sources
|
|
1354
|
+
* @param {string|object} [contextualSources] Optional primary-source payload (objects are JSON-stringified).
|
|
1355
|
+
*/
|
|
1356
|
+
addContextualSources(sources, contextualSources, { id, replace, insertAt } = {}) {
|
|
1357
|
+
if (!Array.isArray(sources) || !sources.length) throw new TypeError('addContextualSources(sources) requires a non-empty array');
|
|
1358
|
+
const list = sources.map((s, i) => {
|
|
1359
|
+
if (!s || typeof s.uri !== 'string' || typeof s.displayName !== 'string') {
|
|
1360
|
+
throw new TypeError(`addContextualSources: sources[${i}] needs { displayName, uri }`);
|
|
1361
|
+
}
|
|
1362
|
+
return { favicon_uri: s.faviconUri, display_name: s.displayName, uri: s.uri };
|
|
1363
|
+
});
|
|
1364
|
+
const contextual = contextualSources === undefined ? undefined : typeof contextualSources === 'string' ? contextualSources : JSON.stringify(contextualSources);
|
|
1365
|
+
return this._addSinglePrimitive('GenAIContextualSourcesViewModel', { sources: list, contextual_sources: contextual }, { id, replace, insertAt });
|
|
1366
|
+
}
|
|
1367
|
+
|
|
1368
|
+
/**
|
|
1369
|
+
* Raw media item (`GenAIMediaItem`) — for media you already uploaded/host, addressed by url and/or `media_id`.
|
|
1370
|
+
* @param {{url?: string, mimeType?: string, urlFallback?: string, mediaId?: string, fileLength?: number, width?: number, height?: number, durationMs?: number, expirationTimestampMs?: number, thumbnail?: string, encryption?: {mediaKey: string, directPath: string, fileSha256: string, fileEncSha256: string, scansSidecar?: string, scanLengths?: number[], mediaKeyTimestamp?: number, fileLength?: number}}} media
|
|
1371
|
+
*/
|
|
1372
|
+
addMediaItem({ url, mimeType, urlFallback, mediaId, fileLength, width, height, durationMs, expirationTimestampMs, thumbnail, encryption, id, replace, insertAt } = {}) {
|
|
1373
|
+
if (!url && !mediaId) throw new TypeError('addMediaItem() requires url and/or mediaId');
|
|
1374
|
+
const media = {
|
|
1375
|
+
url,
|
|
1376
|
+
mime_type: mimeType,
|
|
1377
|
+
url_fallback: urlFallback,
|
|
1378
|
+
media_id: mediaId,
|
|
1379
|
+
file_length: fileLength,
|
|
1380
|
+
width,
|
|
1381
|
+
height,
|
|
1382
|
+
duration: durationMs,
|
|
1383
|
+
expiration_timestamp_ms: expirationTimestampMs,
|
|
1384
|
+
thumbnail: thumbnail ? { raw_media: thumbnail } : undefined,
|
|
1385
|
+
encryption_data: encryption
|
|
1386
|
+
? {
|
|
1387
|
+
media_key: encryption.mediaKey,
|
|
1388
|
+
direct_path: encryption.directPath,
|
|
1389
|
+
file_sha256: encryption.fileSha256,
|
|
1390
|
+
file_enc_sha256: encryption.fileEncSha256,
|
|
1391
|
+
scans_sidecar: encryption.scansSidecar,
|
|
1392
|
+
scan_lengths: encryption.scanLengths,
|
|
1393
|
+
media_key_timestamp: encryption.mediaKeyTimestamp,
|
|
1394
|
+
file_length: encryption.fileLength,
|
|
1395
|
+
}
|
|
1396
|
+
: undefined,
|
|
1397
|
+
};
|
|
1398
|
+
return this._addSinglePrimitive('GenAIMediaItem', { media }, { id, replace, insertAt });
|
|
1399
|
+
}
|
|
1400
|
+
|
|
1401
|
+
// --- build / send ----------------------------------------------------------------------------
|
|
1402
|
+
|
|
1403
|
+
/**
|
|
1404
|
+
* Build (but don't send) the full AI-rich message content, wrapped and ready for `relayMessage()`.
|
|
1405
|
+
* @param {string} jid Destination chat/group jid.
|
|
1406
|
+
*/
|
|
1407
|
+
async build(
|
|
1408
|
+
jid,
|
|
1409
|
+
{ forwarded = this._forwarded, notification = this._notification, notificationText, verificationMetadata = this._verification, includesUnifiedResponse = this._includesUnifiedResponse, includesSubmessages = this._includesSubmessages, quoted = this._quoted, quotedParticipant = this._quotedParticipant, noContextInfo = this._noContextInfo, messageId, ...options } = {}
|
|
1410
|
+
) {
|
|
1411
|
+
if (this._deferred.length) {
|
|
1412
|
+
const jobs = this._deferred.splice(0);
|
|
1413
|
+
await Promise.all(jobs.map((fn) => fn()));
|
|
1414
|
+
}
|
|
1415
|
+
|
|
1416
|
+
const forward = forwarded
|
|
1417
|
+
? {
|
|
1418
|
+
forwardingScore: 1,
|
|
1419
|
+
isForwarded: true,
|
|
1420
|
+
forwardedAiBotMessageInfo: { botJid: this._forwardBotJid },
|
|
1421
|
+
forwardOrigin: 4,
|
|
1422
|
+
}
|
|
1423
|
+
: {};
|
|
1424
|
+
|
|
1425
|
+
// disclaimerText is user-visible to the recipient: never hardcode a credit line here (credits live in NOTICE.md).
|
|
1426
|
+
const disclaimer = notificationText ?? this._notificationText ?? (this._title || undefined);
|
|
1427
|
+
const notif = notification
|
|
1428
|
+
? {
|
|
1429
|
+
sessionTransparencyMetadata: {
|
|
1430
|
+
...(disclaimer ? { disclaimerText: disclaimer } : {}),
|
|
1431
|
+
hcaId: `hca_${Date.now()}`,
|
|
1432
|
+
sessionTransparencyType: 1,
|
|
1433
|
+
},
|
|
1434
|
+
}
|
|
1435
|
+
: {};
|
|
1436
|
+
|
|
1437
|
+
const qObj = quoted
|
|
1438
|
+
? {
|
|
1439
|
+
stanzaId: quoted?.key?.id || quoted?.id,
|
|
1440
|
+
participant: quotedParticipant || quoted?.key?.participant || quoted?.participant || quoted?.key?.remoteJid,
|
|
1441
|
+
quotedType: 0,
|
|
1442
|
+
quotedMessage: typeof quoted === 'object' && quoted !== null ? (quoted.message ?? quoted) : undefined,
|
|
1443
|
+
}
|
|
1444
|
+
: {};
|
|
1445
|
+
|
|
1446
|
+
const sections = this._footer
|
|
1447
|
+
? [...(await waitAllPromises(this._sections)), AIRich.newLayout('Single', { text: this._footer, __typename: 'GenAIMetadataTextPrimitive' })]
|
|
1448
|
+
: [...(await waitAllPromises(this._sections))];
|
|
1449
|
+
|
|
1450
|
+
const embeddedScreens = await waitAllPromises(this._embeddedScreens);
|
|
1451
|
+
|
|
1452
|
+
if (this._dynamic) {
|
|
1453
|
+
this.refreshResponseId();
|
|
1454
|
+
this.refreshBotResponseId();
|
|
1455
|
+
}
|
|
1456
|
+
|
|
1457
|
+
return generateWAMessageFromContent(
|
|
1458
|
+
jid,
|
|
1459
|
+
{
|
|
1460
|
+
messageContextInfo: {
|
|
1461
|
+
deviceListMetadata: {},
|
|
1462
|
+
deviceListMetadataVersion: 2,
|
|
1463
|
+
...(this._messageSecret ? { messageSecret: crypto.randomBytes(32) } : {}),
|
|
1464
|
+
botMetadata: {
|
|
1465
|
+
messageDisclaimerText: this._title,
|
|
1466
|
+
...(this._rawEnvelope ? {} : notif),
|
|
1467
|
+
...(verificationMetadata
|
|
1468
|
+
? {
|
|
1469
|
+
verificationMetadata: {
|
|
1470
|
+
proofs: [
|
|
1471
|
+
{
|
|
1472
|
+
certificateChain: [botMetadataCertificate(), botMetadataCertificate(892)],
|
|
1473
|
+
version: 1,
|
|
1474
|
+
useCase: 1,
|
|
1475
|
+
signature: botMetadataSignature(),
|
|
1476
|
+
},
|
|
1477
|
+
],
|
|
1478
|
+
},
|
|
1479
|
+
}
|
|
1480
|
+
: {}),
|
|
1481
|
+
botResponseId: this._botResponseId,
|
|
1482
|
+
...this._botMetadataExtra,
|
|
1483
|
+
},
|
|
1484
|
+
},
|
|
1485
|
+
...this._extraPayload,
|
|
1486
|
+
botForwardedMessage: {
|
|
1487
|
+
message: {
|
|
1488
|
+
richResponseMessage: {
|
|
1489
|
+
messageType: 1,
|
|
1490
|
+
submessages: includesSubmessages ? await waitAllPromises(this._submessages) : [],
|
|
1491
|
+
unifiedResponse: {
|
|
1492
|
+
data: includesUnifiedResponse
|
|
1493
|
+
? this._unifiedResponseData != null
|
|
1494
|
+
? Buffer.from(typeof this._unifiedResponseData === 'object' && !Buffer.isBuffer(this._unifiedResponseData) ? JSON.stringify(this._unifiedResponseData) : this._unifiedResponseData).toString('base64')
|
|
1495
|
+
: Buffer.from(
|
|
1496
|
+
Toolkit.stringifyEscaped({
|
|
1497
|
+
response_id: this._responseId,
|
|
1498
|
+
sections,
|
|
1499
|
+
...(embeddedScreens.length ? { embedded_screens: embeddedScreens } : {}),
|
|
1500
|
+
})
|
|
1501
|
+
).toString('base64')
|
|
1502
|
+
: '',
|
|
1503
|
+
},
|
|
1504
|
+
...(noContextInfo ? {} : { contextInfo: { ...forward, ...qObj, ...(this._mentions.length ? { mentionedJid: this._mentions } : {}), ...this._contextInfo } }),
|
|
1505
|
+
},
|
|
1506
|
+
},
|
|
1507
|
+
},
|
|
1508
|
+
},
|
|
1509
|
+
{ messageId: messageId || generateMessageIDV2(), ...options }
|
|
1510
|
+
);
|
|
1511
|
+
}
|
|
1512
|
+
|
|
1513
|
+
/**
|
|
1514
|
+
* Build a `protocolMessage` (type EDIT) that patches an already-sent AIRich message in place.
|
|
1515
|
+
* @param {string} targetJid Chat the original message lives in.
|
|
1516
|
+
* @param {string} targetId `key.id` of the original message.
|
|
1517
|
+
* @param {object} [opts] Pass `{ msg }` to reuse an already-built message content instead of rebuilding via build().
|
|
1518
|
+
*/
|
|
1519
|
+
async buildEdit(targetJid, targetId, { msg, messageId, ...options } = {}) {
|
|
1520
|
+
if (!msg) {
|
|
1521
|
+
msg = (await this.build(targetJid, options)).message;
|
|
1522
|
+
}
|
|
1523
|
+
|
|
1524
|
+
const editedMessage = msg;
|
|
1525
|
+
|
|
1526
|
+
if (!editedMessage) {
|
|
1527
|
+
throw new Error('buildEdit: msg does not contain botForwardedMessage');
|
|
1528
|
+
}
|
|
1529
|
+
|
|
1530
|
+
return generateWAMessageFromContent(
|
|
1531
|
+
targetJid,
|
|
1532
|
+
{
|
|
1533
|
+
botForwardedMessage: {
|
|
1534
|
+
message: {
|
|
1535
|
+
protocolMessage: {
|
|
1536
|
+
key: { remoteJid: targetJid, fromMe: true, id: targetId },
|
|
1537
|
+
type: 14, // MESSAGE_EDIT
|
|
1538
|
+
editedMessage,
|
|
1539
|
+
},
|
|
1540
|
+
},
|
|
1541
|
+
},
|
|
1542
|
+
},
|
|
1543
|
+
{ messageId: messageId || generateMessageIDV2(), ...options }
|
|
1544
|
+
);
|
|
1545
|
+
}
|
|
1546
|
+
|
|
1547
|
+
/**
|
|
1548
|
+
* Rebuild this AIRich message's current content and patch it into an already-sent message in
|
|
1549
|
+
* place (WA edits the bubble instead of showing a new one). With no args, edits the message from
|
|
1550
|
+
* the last send()/sendEdit() call.
|
|
1551
|
+
* @param {string} [jid] Defaults to the jid from the last send()/sendEdit().
|
|
1552
|
+
* @param {string} [id] Defaults to the message id from the last send()/sendEdit().
|
|
1553
|
+
*/
|
|
1554
|
+
async sendEdit(jid, id, { msg, messageId, additionalNodes = [], ...options } = {}) {
|
|
1555
|
+
jid = jid ?? this._lastMessageKey?.remoteJid;
|
|
1556
|
+
id = id ?? this._lastMessageKey?.id;
|
|
1557
|
+
|
|
1558
|
+
if (!jid) throw new Error('JID is required');
|
|
1559
|
+
if (!id) throw new Error('Message id is required');
|
|
1560
|
+
|
|
1561
|
+
const msgEdit = await this.buildEdit(jid, id, { msg, messageId: messageId || generateMessageIDV2(), ...options });
|
|
1562
|
+
|
|
1563
|
+
await this.#client.relayMessage(jid, msgEdit.message, { messageId: msgEdit.key.id, additionalNodes });
|
|
1564
|
+
|
|
1565
|
+
return msgEdit;
|
|
1566
|
+
}
|
|
1567
|
+
|
|
1568
|
+
/**
|
|
1569
|
+
* Build a *native-format* edit: a top-level `protocolMessage` (type EDIT) instead of the
|
|
1570
|
+
* `botForwardedMessage`-wrapped edit that `buildEdit()` produces, with the bot-metadata
|
|
1571
|
+
* (`botResponseId`, CLIPPY infrastructure backend) and non-E2EE account attestation a real Meta-AI
|
|
1572
|
+
* edit carries. Use it when an edit made with `sendEdit()` doesn't re-render the bubble.
|
|
1573
|
+
* @param {string} targetJid Chat the original message lives in.
|
|
1574
|
+
* @param {string} targetId `key.id` of the original message.
|
|
1575
|
+
* @param {object} [opts] Pass `{ msg }` to reuse an already-built message content instead of rebuilding via build().
|
|
1576
|
+
*/
|
|
1577
|
+
async buildEditNative(targetJid, targetId, { msg, messageId, ...options } = {}) {
|
|
1578
|
+
if (!msg) {
|
|
1579
|
+
msg = (await this.build(targetJid, options)).message;
|
|
1580
|
+
}
|
|
1581
|
+
|
|
1582
|
+
if (!msg) {
|
|
1583
|
+
throw new Error('buildEditNative: could not build message content');
|
|
1584
|
+
}
|
|
1585
|
+
|
|
1586
|
+
return generateWAMessageFromContent(
|
|
1587
|
+
targetJid,
|
|
1588
|
+
{
|
|
1589
|
+
protocolMessage: {
|
|
1590
|
+
key: { remoteJid: targetJid, fromMe: true, id: targetId },
|
|
1591
|
+
type: 14, // MESSAGE_EDIT
|
|
1592
|
+
editedMessage: msg,
|
|
1593
|
+
timestampMs: Date.now(),
|
|
1594
|
+
},
|
|
1595
|
+
messageContextInfo: {
|
|
1596
|
+
botMetadata: {
|
|
1597
|
+
botResponseId: this._botResponseId,
|
|
1598
|
+
botInfrastructureDiagnostics: { botBackend: 1 }, // CLIPPY
|
|
1599
|
+
},
|
|
1600
|
+
accountEncryptionAttestation: { accountType: 2 }, // NON_E2EE
|
|
1601
|
+
},
|
|
1602
|
+
},
|
|
1603
|
+
{ messageId: messageId || generateMessageIDV2(), ...options }
|
|
1604
|
+
);
|
|
1605
|
+
}
|
|
1606
|
+
|
|
1607
|
+
/** Same as `sendEdit()` but sends the native-format edit from `buildEditNative()`. Defaults to the last send()'s jid/id. */
|
|
1608
|
+
async sendEditNative(jid, id, { msg, messageId, additionalNodes = [], ...options } = {}) {
|
|
1609
|
+
jid = jid ?? this._lastMessageKey?.remoteJid;
|
|
1610
|
+
id = id ?? this._lastMessageKey?.id;
|
|
1611
|
+
|
|
1612
|
+
if (!jid) throw new Error('JID is required');
|
|
1613
|
+
if (!id) throw new Error('Message id is required');
|
|
1614
|
+
|
|
1615
|
+
const msgEdit = await this.buildEditNative(jid, id, { msg, messageId: messageId || generateMessageIDV2(), ...options });
|
|
1616
|
+
|
|
1617
|
+
await this.#client.relayMessage(jid, msgEdit.message, { messageId: msgEdit.key.id, additionalNodes });
|
|
1618
|
+
|
|
1619
|
+
return msgEdit;
|
|
1620
|
+
}
|
|
1621
|
+
|
|
1622
|
+
/** Build and send this AI-rich message. @param {string} jid Destination chat/group jid. */
|
|
1623
|
+
async send(jid, { bypassDownload = this._bypassDownload, forwarded = this._forwarded, notification = this._notification, includesUnifiedResponse = this._includesUnifiedResponse, includesSubmessages = this._includesSubmessages, messageId, additionalNodes = [], skipImageFallback = false, nativeEdit = false, ...options } = {}) {
|
|
1624
|
+
// Inline-image fallback (ported from japofc): WA does not render AIRichResponseInlineImageMetadata for
|
|
1625
|
+
// third-party bots, so every addInlineImage() call is also queued here and re-sent as a plain imageMessage.
|
|
1626
|
+
if (!skipImageFallback && this._inlineImages.length) {
|
|
1627
|
+
for (const { url, caption } of this._inlineImages) {
|
|
1628
|
+
try {
|
|
1629
|
+
await this.#client.sendMessage(jid, { image: { url }, caption }, options.quoted ? { quoted: options.quoted } : {});
|
|
1630
|
+
} catch (err) {
|
|
1631
|
+
this.#client.logger?.warn?.({ err, url }, 'inline image fallback failed, continuing with rich card');
|
|
1632
|
+
}
|
|
1633
|
+
}
|
|
1634
|
+
}
|
|
1635
|
+
|
|
1636
|
+
const msg = await this.build(jid, { forwarded, notification, includesUnifiedResponse, includesSubmessages, messageId, ...options });
|
|
1637
|
+
|
|
1638
|
+
await this.#client.relayMessage(msg.key.remoteJid, msg.message, { messageId: msg.key.id, additionalNodes, ...options });
|
|
1639
|
+
|
|
1640
|
+
if (includesUnifiedResponse && bypassDownload) {
|
|
1641
|
+
await (nativeEdit ? this.sendEditNative(jid, msg.key.id, { msg: msg.message }) : this.sendEdit(jid, msg.key.id, { msg: msg.message }));
|
|
1642
|
+
}
|
|
1643
|
+
|
|
1644
|
+
this._lastMessageKey = msg.key;
|
|
1645
|
+
|
|
1646
|
+
return msg;
|
|
1647
|
+
}
|
|
1648
|
+
|
|
1649
|
+
// --- static helpers ----------------------------------------------------------------------------
|
|
1650
|
+
|
|
1651
|
+
static toTableMetadata(arr, { hyperlink = true, citation = true, latex = true } = {}) {
|
|
1652
|
+
if (!Array.isArray(arr) || !arr.every((row) => Array.isArray(row) && row.every((cell) => typeof cell === 'string'))) {
|
|
1653
|
+
throw new TypeError('Table must be a nested array of strings');
|
|
1654
|
+
}
|
|
1655
|
+
|
|
1656
|
+
const [header, ...rows] = arr;
|
|
1657
|
+
const maxLen = Math.max(header.length, ...rows.map((r) => r.length));
|
|
1658
|
+
const normalize = (r) => [...r, ...Array(maxLen - r.length).fill('')];
|
|
1659
|
+
|
|
1660
|
+
const unified_rows = [{ is_header: true, cells: normalize(header) }, ...rows.map((r) => ({ is_header: false, cells: normalize(r) }))].map((row) => {
|
|
1661
|
+
const markdown_cells = row.cells.map((cell) => {
|
|
1662
|
+
const extracted = extractIE(cell, { hyperlink, citation, latex });
|
|
1663
|
+
return { text: extracted.text, ...(extracted.inline_entities.length ? { inline_entities: extracted.inline_entities } : {}) };
|
|
1664
|
+
});
|
|
1665
|
+
|
|
1666
|
+
return { ...row, ...(markdown_cells.some((c) => c.inline_entities?.length) ? { markdown_cells } : {}) };
|
|
1667
|
+
});
|
|
1668
|
+
|
|
1669
|
+
const rowsMeta = unified_rows.map((r) => ({ items: r.cells, ...(r.is_header ? { isHeading: true } : {}) }));
|
|
1670
|
+
|
|
1671
|
+
return { title: '', rows: rowsMeta, unified_rows };
|
|
1672
|
+
}
|
|
1673
|
+
|
|
1674
|
+
/** Build a raw submessage layout block by name — escape hatch for layouts not covered by the add*() helpers. */
|
|
1675
|
+
static newLayout(name, data, extra = {}) {
|
|
1676
|
+
return {
|
|
1677
|
+
...extra,
|
|
1678
|
+
view_model: {
|
|
1679
|
+
[Array.isArray(data) ? 'primitives' : 'primitive']: data,
|
|
1680
|
+
__typename: `GenAI${name}LayoutViewModel`,
|
|
1681
|
+
},
|
|
1682
|
+
};
|
|
1683
|
+
}
|
|
1684
|
+
|
|
1685
|
+
/** Inline CSS+JS that pins an HTML app's body height and makes it internally scrollable — used by `addHTML()`'s `height` option. Ported alongside `addHTML()` (see that method's header comment for the source). */
|
|
1686
|
+
static lockHeight(height) {
|
|
1687
|
+
const px = Number(height);
|
|
1688
|
+
if (!Number.isFinite(px) || px <= 0) {
|
|
1689
|
+
throw new TypeError('height must be a positive number of pixels');
|
|
1690
|
+
}
|
|
1691
|
+
return (
|
|
1692
|
+
'<style>html,body{margin:0;padding:0;height:' + px + 'px;max-height:' + px + 'px;overflow:hidden}' +
|
|
1693
|
+
'#__wrap{height:' + px + 'px;overflow-y:auto;-webkit-overflow-scrolling:touch;touch-action:pan-y}</style>' +
|
|
1694
|
+
'<script>document.addEventListener("DOMContentLoaded",function(){' +
|
|
1695
|
+
'var w=document.createElement("div");w.id="__wrap";' +
|
|
1696
|
+
'while(document.body.firstChild)w.appendChild(document.body.firstChild);' +
|
|
1697
|
+
'document.body.appendChild(w)});<' + '/script>'
|
|
1698
|
+
);
|
|
1699
|
+
}
|
|
1700
|
+
|
|
1701
|
+
// --- misc getters --------------------------------------------------------------------------
|
|
1702
|
+
|
|
1703
|
+
/** Flatten every primitive pushed into `_sections` so far into one array — lets you build a
|
|
1704
|
+
* card set in one AIRich instance and re-embed it into another via addSection(AIRich.newLayout(...)). */
|
|
1705
|
+
get items() {
|
|
1706
|
+
return this._sections.flatMap((s) => {
|
|
1707
|
+
const vm = s?.view_model;
|
|
1708
|
+
if (!vm) return [];
|
|
1709
|
+
return vm.primitives ?? (vm.primitive !== undefined ? [vm.primitive] : []);
|
|
1710
|
+
});
|
|
1711
|
+
}
|
|
1712
|
+
|
|
1713
|
+
/** The `{ remoteJid, fromMe, id }` of the last message this instance sent/edited, or `null`. */
|
|
1714
|
+
get lastMessageKey() {
|
|
1715
|
+
return this._lastMessageKey ? { ...this._lastMessageKey } : null;
|
|
1716
|
+
}
|
|
1717
|
+
|
|
1718
|
+
// --- ported from japofc AIRich (extra primitives) ---------------------------------------------
|
|
1719
|
+
|
|
1720
|
+
/**
|
|
1721
|
+
* Add a raw Bloks payload (`FOABloksPrimitive`) — Meta's internal UI-description format.
|
|
1722
|
+
* Escape hatch: field meaning beyond what's passed through is undocumented, so this is the
|
|
1723
|
+
* most experimental primitive in this block; pass whatever your captured traffic shows.
|
|
1724
|
+
* @param {{type: string, data: string, uuid?: string, initial_response?: any, versioning_id?: string}} data
|
|
1725
|
+
*/
|
|
1726
|
+
addBloks(data = {}) {
|
|
1727
|
+
if (!data?.type) {
|
|
1728
|
+
throw new TypeError('addBloks() requires a "type"');
|
|
1729
|
+
}
|
|
1730
|
+
this._submessages.push({ messageType: 2, messageText: 'Bloks' });
|
|
1731
|
+
const primitive = {
|
|
1732
|
+
type: data.type,
|
|
1733
|
+
data: data.data ?? '{}',
|
|
1734
|
+
uuid: data.uuid ?? '',
|
|
1735
|
+
versioning_id: data.versioning_id ?? '',
|
|
1736
|
+
__typename: 'FOABloksPrimitive',
|
|
1737
|
+
};
|
|
1738
|
+
// Omit initial_response entirely when unset — same null-field crash as addProgressStatus/addThinkingStatus.
|
|
1739
|
+
if (data.initial_response != null) primitive.initial_response = data.initial_response;
|
|
1740
|
+
this._sections.push(AIRich.newLayout('Single', primitive));
|
|
1741
|
+
// Safety net: FOABloksPrimitive needs a real, client-registered Bloks screen to render
|
|
1742
|
+
// anything — arbitrary/placeholder payloads show up blank. Append a plain text section
|
|
1743
|
+
// so the card isn't empty. Set data.textFallback = false to skip.
|
|
1744
|
+
if (data.textFallback !== false) {
|
|
1745
|
+
this._sections.push(AIRich.newLayout('Single', { text: `Bloks: ${data.type}`, __typename: 'FOATextPrimitive' }));
|
|
1746
|
+
}
|
|
1747
|
+
return this;
|
|
1748
|
+
}
|
|
1749
|
+
|
|
1750
|
+
/**
|
|
1751
|
+
* Add an HTML mini-app card (`GenAIaeacdsnwHtmlPrimitive`, Android-only — see
|
|
1752
|
+
* `AI_RICH_PRIMITIVES_ANDROID_ONLY` below). Confirmed independently in two forks'
|
|
1753
|
+
* `MessageBuilder/extras.js` (yudzxml 7.6.6's `htmlSection()` and elaina 1.3.10's, both
|
|
1754
|
+
* crediting the same original source) as the "proven wire shape": a `GenAIUnifiedResponseSection`
|
|
1755
|
+
* wrapper around the html primitive. `options.url` sets the WebView's base origin (needed for
|
|
1756
|
+
* WebSocket/fetch calls from inside the page to be allowed) — pass a real `https://` origin, not
|
|
1757
|
+
* a data URI. `options.height` pins the page to a fixed height via `AIRich.lockHeight()`.
|
|
1758
|
+
* @param {string} html
|
|
1759
|
+
* @param {{trustedSources?: string[], height?: number, url?: string, textFallback?: boolean}} [options]
|
|
1760
|
+
*/
|
|
1761
|
+
addHTML(html, { trustedSources = [], height, url, textFallback = true } = {}) {
|
|
1762
|
+
if (typeof html !== 'string' || html.trim() === '') {
|
|
1763
|
+
throw new TypeError('addHTML(html) requires a non-empty HTML string');
|
|
1764
|
+
}
|
|
1765
|
+
if (!Array.isArray(trustedSources)) {
|
|
1766
|
+
throw new TypeError('addHTML() trustedSources must be an array of strings');
|
|
1767
|
+
}
|
|
1768
|
+
if (url !== undefined && (typeof url !== 'string' || url.trim() === '')) {
|
|
1769
|
+
throw new TypeError('addHTML() url must be a non-empty string (WebView base origin, e.g. https://example.com)');
|
|
1770
|
+
}
|
|
1771
|
+
const payload = height === undefined ? html : AIRich.lockHeight(height) + html;
|
|
1772
|
+
this._submessages.push({ messageType: 2, messageText: 'Aplikasi HTML' });
|
|
1773
|
+
this._sections.push({
|
|
1774
|
+
__typename: 'GenAIUnifiedResponseSection',
|
|
1775
|
+
...AIRich.newLayout('Single', {
|
|
1776
|
+
payload,
|
|
1777
|
+
...(url ? { url: String(url) } : {}),
|
|
1778
|
+
trusted_sources: trustedSources.map(String),
|
|
1779
|
+
__typename: 'GenAIaeacdsnwHtmlPrimitive',
|
|
1780
|
+
}),
|
|
1781
|
+
});
|
|
1782
|
+
// Safety net: this primitive is confirmed Android-only (see AI_RICH_PRIMITIVES_ANDROID_ONLY
|
|
1783
|
+
// below) — iOS/Web give no visible fallback of their own, so append plain text same as
|
|
1784
|
+
// addTask()/addBloks(). Set options.textFallback = false to skip.
|
|
1785
|
+
if (textFallback) {
|
|
1786
|
+
this._sections.push(AIRich.newLayout('Single', { text: '[ Aplikasi HTML — buka di Android untuk melihat ]', __typename: 'FOATextPrimitive' }));
|
|
1787
|
+
}
|
|
1788
|
+
return this;
|
|
1789
|
+
}
|
|
1790
|
+
|
|
1791
|
+
/**
|
|
1792
|
+
* Add an HTML app inside a tappable bottom-sheet "card" (a title/teaser line in the main
|
|
1793
|
+
* response that opens a tabbed embedded screen containing the HTML), instead of `addHTML()`'s
|
|
1794
|
+
* inline-in-the-response form. Reproduces `sendHtmlApp()`'s `embedded: true` branch (yudzxml
|
|
1795
|
+
* 7.6.6 / elaina 1.3.10's `MessageBuilder/extras.js`) using this class's own `setTitle()` /
|
|
1796
|
+
* `addEmbeddedScreen()` / `addText()` instead of that helper's standalone-function form. Calling
|
|
1797
|
+
* this sets the instance's title via `setTitle()` if `options.title` is given — don't call
|
|
1798
|
+
* `setTitle()` again after this if you want a different top title than the sheet's.
|
|
1799
|
+
* @param {string} html
|
|
1800
|
+
* @param {{title?: string, label?: string, trustedSources?: string[], height?: number, url?: string, screenTitle?: string, tabHeader?: string, textFallback?: boolean}} [options]
|
|
1801
|
+
*/
|
|
1802
|
+
addHTMLCard(html, { title, label, trustedSources = [], height, url, screenTitle, tabHeader, textFallback = true } = {}) {
|
|
1803
|
+
if (typeof html !== 'string' || html.trim() === '') {
|
|
1804
|
+
throw new TypeError('addHTMLCard(html) requires a non-empty HTML string');
|
|
1805
|
+
}
|
|
1806
|
+
if (title) this.setTitle(title);
|
|
1807
|
+
const sheetTitle = screenTitle ?? title ?? 'Preview';
|
|
1808
|
+
const payload = height === undefined ? html : AIRich.lockHeight(height) + html;
|
|
1809
|
+
const htmlSection = {
|
|
1810
|
+
__typename: 'GenAIUnifiedResponseSection',
|
|
1811
|
+
...AIRich.newLayout('Single', {
|
|
1812
|
+
payload,
|
|
1813
|
+
...(url ? { url: String(url) } : {}),
|
|
1814
|
+
trusted_sources: trustedSources.map(String),
|
|
1815
|
+
__typename: 'GenAIaeacdsnwHtmlPrimitive',
|
|
1816
|
+
}),
|
|
1817
|
+
};
|
|
1818
|
+
this.addText(label ?? sheetTitle);
|
|
1819
|
+
this.addEmbeddedScreen({
|
|
1820
|
+
title: sheetTitle,
|
|
1821
|
+
content: [
|
|
1822
|
+
{
|
|
1823
|
+
// NOTE: `FOAIDNixelButtonSheets` is the typename as captured (inherited from the upstream fork lineage,
|
|
1824
|
+
// not a Vanzxy name). Keep it verbatim: the WA client matches on the exact string.
|
|
1825
|
+
__typename: 'FOAIDNixelButtonSheets',
|
|
1826
|
+
tabs: [{ id: 'tab_0', tab_header: tabHeader ?? sheetTitle, sections: [htmlSection], step_entries: [] }],
|
|
1827
|
+
step_entries: [],
|
|
1828
|
+
},
|
|
1829
|
+
],
|
|
1830
|
+
});
|
|
1831
|
+
// Same Android-only caveat as addHTML() — the teaser line from addText() above already
|
|
1832
|
+
// covers iOS/Web with visible text, so no separate fallback needed here.
|
|
1833
|
+
return this;
|
|
1834
|
+
}
|
|
1835
|
+
|
|
1836
|
+
|
|
1837
|
+
/**
|
|
1838
|
+
* Add a downloadable-file card (`GenAIFilePrimitive`). Confirmed in the same two forks'
|
|
1839
|
+
* `fileSection()`/`fileArtifact()` helpers. Takes a plain URL directly (no media upload) — for a
|
|
1840
|
+
* file you're uploading through WhatsApp's own media servers first, use `sock.sendMessage(jid,
|
|
1841
|
+
* { document: buffer, ... })` and attach the result separately; this primitive only points at an
|
|
1842
|
+
* already-hosted URL.
|
|
1843
|
+
* @param {string} url
|
|
1844
|
+
* @param {{title?: string, fileExtension?: string, fileLength?: number, pageCount?: number, previewImage?: object, textFallback?: boolean}} [options]
|
|
1845
|
+
*/
|
|
1846
|
+
addFile(url, { title = '', fileExtension = 'html', fileLength = 0, pageCount, previewImage, textFallback = true } = {}) {
|
|
1847
|
+
if (typeof url !== 'string' || url.trim() === '') {
|
|
1848
|
+
throw new TypeError('addFile(url) requires a non-empty url string');
|
|
1849
|
+
}
|
|
1850
|
+
const primitive = {
|
|
1851
|
+
title: String(title),
|
|
1852
|
+
url,
|
|
1853
|
+
file_extension: String(fileExtension).replace(/^\./, ''),
|
|
1854
|
+
file_length: Number(fileLength) || 0,
|
|
1855
|
+
__typename: 'GenAIFilePrimitive',
|
|
1856
|
+
};
|
|
1857
|
+
if (pageCount !== undefined) primitive.page_count = Number(pageCount) || 0;
|
|
1858
|
+
if (previewImage !== undefined) primitive.preview_image = previewImage;
|
|
1859
|
+
this._submessages.push({ messageType: 2, messageText: title || 'File' });
|
|
1860
|
+
this._sections.push(AIRich.newLayout('Single', primitive));
|
|
1861
|
+
if (textFallback) {
|
|
1862
|
+
this._sections.push(AIRich.newLayout('Single', { text: `File: ${title || url}`, __typename: 'FOATextPrimitive' }));
|
|
1863
|
+
}
|
|
1864
|
+
return this;
|
|
1865
|
+
}
|
|
1866
|
+
|
|
1867
|
+
/**
|
|
1868
|
+
* Upload local content through WhatsApp's own media servers and add it as a file card — the
|
|
1869
|
+
* `prepareFileArtifact()`/`sendHtmlArtifact()` path from yudzxml 7.6.6 / elaina 1.3.10, folded
|
|
1870
|
+
* into this class via `setBotMetadata()` instead of that helper's standalone-function form. Use
|
|
1871
|
+
* this instead of `addFile()` when there's no hosted URL yet — e.g. an HTML app you built
|
|
1872
|
+
* in-process and want to ship as a downloadable file rather than inline via `addHTML()`.
|
|
1873
|
+
* @param {string|Buffer|Uint8Array} content File content (a string is encoded as UTF-8).
|
|
1874
|
+
* @param {{mimetype?: string, fileName?: string, title?: string, id?: string, textFallback?: boolean}} [options]
|
|
1875
|
+
* @returns {Promise<this>}
|
|
1876
|
+
*/
|
|
1877
|
+
async addUploadedFile(content, { mimetype = 'text/html', fileName = 'app.html', title, id, textFallback = true } = {}) {
|
|
1878
|
+
const body = typeof content === 'string' ? Buffer.from(content, 'utf-8') : content;
|
|
1879
|
+
if (!Buffer.isBuffer(body) && !(body instanceof Uint8Array)) {
|
|
1880
|
+
throw new TypeError('addUploadedFile(content) requires a string or a buffer');
|
|
1881
|
+
}
|
|
1882
|
+
const prepared = await prepareWAMessageMedia({ document: Buffer.from(body), mimetype, fileName }, { upload: this.#client.waUploadToServer });
|
|
1883
|
+
const documentMessage = prepared.documentMessage;
|
|
1884
|
+
const mediaId = id ?? crypto.randomUUID();
|
|
1885
|
+
const b64 = (v) => (v === undefined || v === null ? undefined : Buffer.isBuffer(v) || v instanceof Uint8Array ? Buffer.from(v).toString('base64') : String(v));
|
|
1886
|
+
const media = {
|
|
1887
|
+
fileSha256: b64(documentMessage.fileSha256),
|
|
1888
|
+
mediaKey: b64(documentMessage.mediaKey),
|
|
1889
|
+
fileEncSha256: b64(documentMessage.fileEncSha256),
|
|
1890
|
+
directPath: documentMessage.directPath,
|
|
1891
|
+
mediaKeyTimestamp: documentMessage.mediaKeyTimestamp ? Number(documentMessage.mediaKeyTimestamp) : undefined,
|
|
1892
|
+
mimetype: documentMessage.mimetype,
|
|
1893
|
+
};
|
|
1894
|
+
for (const k of Object.keys(media)) if (media[k] === undefined) delete media[k];
|
|
1895
|
+
this.setBotMetadata({ unifiedResponseMutation: { mediaDetailsMetadataList: [{ id: mediaId, previewMedia: media, highResMedia: media }] } });
|
|
1896
|
+
this._submessages.push({ messageType: 2, messageText: title || fileName });
|
|
1897
|
+
this._sections.push(
|
|
1898
|
+
AIRich.newLayout('Single', {
|
|
1899
|
+
title: String(title || fileName),
|
|
1900
|
+
url: documentMessage.url ?? '',
|
|
1901
|
+
file_extension: fileName.includes('.') ? fileName.split('.').pop() : '',
|
|
1902
|
+
file_length: Number(documentMessage.fileLength) || 0,
|
|
1903
|
+
preview_image: { media_id: mediaId, mime_type: mimetype, url: documentMessage.url ?? '', url_fallback: '' },
|
|
1904
|
+
__typename: 'GenAIFilePrimitive',
|
|
1905
|
+
})
|
|
1906
|
+
);
|
|
1907
|
+
if (textFallback) {
|
|
1908
|
+
this._sections.push(AIRich.newLayout('Single', { text: `File: ${title || fileName}`, __typename: 'FOATextPrimitive' }));
|
|
1909
|
+
}
|
|
1910
|
+
return this;
|
|
1911
|
+
}
|
|
1912
|
+
|
|
1913
|
+
|
|
1914
|
+
/**
|
|
1915
|
+
* Wrap one or more already-built primitives (e.g. items from another `AIRich` instance's
|
|
1916
|
+
* `.items` getter, or hand-built `{ ..., __typename }` objects) in an `AddonAction` layout —
|
|
1917
|
+
* a row of action buttons/pills attached to the primitives rather than a primitive itself.
|
|
1918
|
+
* Confirmed in elaina 1.3.10's `metaai.js` (`addonActionSection()` — its own layout name,
|
|
1919
|
+
* `'AddonAction'`, is also listed in `AI_RICH_LAYOUTS` in both forks' `extras.js`).
|
|
1920
|
+
* @param {object[]} primitives Typed primitive objects (each must carry its own `__typename`).
|
|
1921
|
+
* @param {{actionType?: string, alignment?: 'START'|'END'}} [options]
|
|
1922
|
+
*/
|
|
1923
|
+
addAddonAction(primitives = [], { actionType, alignment = 'END' } = {}) {
|
|
1924
|
+
if (!Array.isArray(primitives) || !primitives.length) {
|
|
1925
|
+
throw new TypeError('addAddonAction(primitives) requires a non-empty array of typed primitive objects');
|
|
1926
|
+
}
|
|
1927
|
+
const layout = AIRich.newLayout('AddonAction', primitives);
|
|
1928
|
+
if (actionType !== undefined) layout.view_model.addon_action_type = actionType;
|
|
1929
|
+
layout.view_model.addon_action_alignment = alignment;
|
|
1930
|
+
this._submessages.push({ messageType: 2, messageText: 'Addon Action' });
|
|
1931
|
+
this._sections.push(layout);
|
|
1932
|
+
return this;
|
|
1933
|
+
}
|
|
1934
|
+
|
|
1935
|
+
/** Add a raw rich-response content-items carousel, matching Baileys' `items` field. */
|
|
1936
|
+
addContentItems(items = []) {
|
|
1937
|
+
if (!Array.isArray(items)) throw new TypeError('items must be an array');
|
|
1938
|
+
this._submessages.push({
|
|
1939
|
+
messageType: 9,
|
|
1940
|
+
contentItemsMetadata: { itemsMetadata: items, contentType: 1 },
|
|
1941
|
+
});
|
|
1942
|
+
this._sections.push(AIRich.newLayout('Single', {
|
|
1943
|
+
items,
|
|
1944
|
+
content_type: 1,
|
|
1945
|
+
__typename: 'GenAIContentItemsUXPrimitive',
|
|
1946
|
+
}));
|
|
1947
|
+
return this;
|
|
1948
|
+
}
|
|
1949
|
+
|
|
1950
|
+
/** Add a plain horizontal divider line (`GenAIDividerPrimitive`, no content). */
|
|
1951
|
+
addDivider() {
|
|
1952
|
+
this._submessages.push({ messageType: 2, messageText: '---' });
|
|
1953
|
+
this._sections.push(AIRich.newLayout('Single', { __typename: 'GenAIDividerPrimitive' }));
|
|
1954
|
+
return this;
|
|
1955
|
+
}
|
|
1956
|
+
|
|
1957
|
+
/**
|
|
1958
|
+
* Add an "AI is generating..." placeholder card (`GenAIImaginePrimitive` with status
|
|
1959
|
+
* GENERATING) — distinct from addImage()/addVideo() which always send status READY.
|
|
1960
|
+
* Use this to show a pending-generation state before the real media is ready.
|
|
1961
|
+
* @param {{ imagine_type?: 'IMAGE'|'ANIMATE', estimated_completion_time?: number }} [options]
|
|
1962
|
+
*/
|
|
1963
|
+
addGenerating({ imagine_type = 'IMAGE', estimated_completion_time, textFallback = true } = {}) {
|
|
1964
|
+
this._submessages.push({ messageType: 2, messageText: '[ Processing... ]' });
|
|
1965
|
+
this._sections.push(
|
|
1966
|
+
AIRich.newLayout('Single', {
|
|
1967
|
+
media: { url: '', mime_type: imagine_type === 'ANIMATE' ? 'video/mp4' : 'image/png' },
|
|
1968
|
+
imagine_type,
|
|
1969
|
+
status: {
|
|
1970
|
+
status: 'GENERATING',
|
|
1971
|
+
estimated_completion_time: estimated_completion_time ?? Math.floor(Date.now() / 1000) + 30,
|
|
1972
|
+
},
|
|
1973
|
+
__typename: 'GenAIImaginePrimitive',
|
|
1974
|
+
})
|
|
1975
|
+
);
|
|
1976
|
+
// JAP@Fix 23-08-26 (v4.8) --- empty media.url + GENERATING status has no instant
|
|
1977
|
+
// visual renderer in the stock WA client; previously the card stayed blank until WA
|
|
1978
|
+
// timed out on its own and showed its built-in fallback ("I can't create that image right now...").
|
|
1979
|
+
// Same fix class as addTask/addBloks: append a FOATextPrimitive so there is an
|
|
1980
|
+
// instant fallback instead of waiting for WA's timeout. Set { textFallback: false } to skip.
|
|
1981
|
+
if (textFallback) {
|
|
1982
|
+
this._sections.push(AIRich.newLayout('Single', { text: '[ Processing... ]', __typename: 'FOATextPrimitive' }));
|
|
1983
|
+
}
|
|
1984
|
+
return this;
|
|
1985
|
+
}
|
|
1986
|
+
|
|
1987
|
+
/** Add a large heading-style text block (`FOATextPrimitive`) — visually distinct from `addText()`'s regular paragraph text. */
|
|
1988
|
+
addHeading(text) {
|
|
1989
|
+
if (typeof text !== 'string' || !text) {
|
|
1990
|
+
throw new TypeError('addHeading(text) requires a non-empty string');
|
|
1991
|
+
}
|
|
1992
|
+
|
|
1993
|
+
this._submessages.push({
|
|
1994
|
+
messageType: 2,
|
|
1995
|
+
messageText: text,
|
|
1996
|
+
});
|
|
1997
|
+
|
|
1998
|
+
this._sections.push(
|
|
1999
|
+
AIRich.newLayout('Single', {
|
|
2000
|
+
text,
|
|
2001
|
+
__typename: 'FOATextPrimitive',
|
|
2002
|
+
})
|
|
2003
|
+
);
|
|
2004
|
+
|
|
2005
|
+
return this;
|
|
2006
|
+
}
|
|
2007
|
+
|
|
2008
|
+
// JAP@Fix 15-08-26 (bug 41) --- addImage() only builds GRID_IMAGE (messageType 1).
|
|
2009
|
+
// There was no helper for standalone INLINE_IMAGE (messageType 3): callers were manually
|
|
2010
|
+
// pushing addSubmessage() (correct proto shape) + addSection() (WRONG shape — reused the
|
|
2011
|
+
// GRID_IMAGE/GenAIImaginePrimitive section schema instead of GenAIInlineImageUXPrimitive),
|
|
2012
|
+
// which broke client-side unifiedResponse rendering even though the submessage itself was fine.
|
|
2013
|
+
// Mirrors RichSubMessageType.INLINE_IMAGE handling in rich-message-utils.js's toUnified().
|
|
2014
|
+
/** Add an image inline with the surrounding text flow (falls back to a plain imageMessage on send() if the client can't render inline images — see skipImageFallback). */
|
|
2015
|
+
addInlineImage(imageUrl, { text = '', alignment = 'center', tapLinkUrl = '', resolveUrl = false } = {}) {
|
|
2016
|
+
if (!(typeof imageUrl === 'string' || Buffer.isBuffer(imageUrl) || (imageUrl && typeof imageUrl === 'object'))) {
|
|
2017
|
+
throw new TypeError('imageUrl must be string | buffer | { imagePreviewUrl, imageHighResUrl, sourceUrl }');
|
|
2018
|
+
}
|
|
2019
|
+
|
|
2020
|
+
const ALIGNMENT_ENUM = { leading: 0, trailing: 1, center: 2 };
|
|
2021
|
+
const ALIGNMENT_NAME = ['AI_RICH_RESPONSE_IMAGE_LAYOUT_LEADING_ALIGNED', 'AI_RICH_RESPONSE_IMAGE_LAYOUT_TRAILING_ALIGNED', 'AI_RICH_RESPONSE_IMAGE_LAYOUT_CENTER_ALIGNED'];
|
|
2022
|
+
const alignmentNum = typeof alignment === 'number' ? alignment : (ALIGNMENT_ENUM[String(alignment).toLowerCase()] ?? ALIGNMENT_ENUM.center);
|
|
2023
|
+
|
|
2024
|
+
const url =
|
|
2025
|
+
imageUrl && typeof imageUrl === 'object'
|
|
2026
|
+
? {
|
|
2027
|
+
imagePreviewUrl: imageUrl.imagePreviewUrl || imageUrl.url,
|
|
2028
|
+
imageHighResUrl: imageUrl.imageHighResUrl || imageUrl.url,
|
|
2029
|
+
sourceUrl: imageUrl.sourceUrl || imageUrl.url,
|
|
2030
|
+
}
|
|
2031
|
+
: (() => {
|
|
2032
|
+
const resolved = Toolkit.resolveMedia(this.#client, imageUrl, 'image', { resolveUrl });
|
|
2033
|
+
return { imagePreviewUrl: resolved, imageHighResUrl: resolved, sourceUrl: resolved };
|
|
2034
|
+
})();
|
|
2035
|
+
|
|
2036
|
+
this._submessages.push({
|
|
2037
|
+
messageType: 3,
|
|
2038
|
+
imageMetadata: {
|
|
2039
|
+
imageUrl: url,
|
|
2040
|
+
imageText: text,
|
|
2041
|
+
alignment: alignmentNum,
|
|
2042
|
+
tapLinkUrl,
|
|
2043
|
+
},
|
|
2044
|
+
});
|
|
2045
|
+
|
|
2046
|
+
this._sections.push(
|
|
2047
|
+
AIRich.newLayout('Single', {
|
|
2048
|
+
image_url: {
|
|
2049
|
+
image_preview_url: url.imagePreviewUrl || '',
|
|
2050
|
+
image_high_res_url: url.imageHighResUrl || '',
|
|
2051
|
+
source_url: url.sourceUrl || '',
|
|
2052
|
+
},
|
|
2053
|
+
image_text: text,
|
|
2054
|
+
alignment: ALIGNMENT_NAME[alignmentNum],
|
|
2055
|
+
tap_link_url: tapLinkUrl,
|
|
2056
|
+
__typename: 'GenAIInlineImageUXPrimitive',
|
|
2057
|
+
})
|
|
2058
|
+
);
|
|
2059
|
+
|
|
2060
|
+
// JAP@Fix (bug 42): stash for the imageMessage fallback in send()
|
|
2061
|
+
this._inlineImages.push({
|
|
2062
|
+
url: url.sourceUrl || url.imageHighResUrl || url.imagePreviewUrl,
|
|
2063
|
+
caption: text || undefined,
|
|
2064
|
+
});
|
|
2065
|
+
|
|
2066
|
+
return this;
|
|
2067
|
+
}
|
|
2068
|
+
|
|
2069
|
+
/** Add Baileys-compatible inline-video marker. WhatsApp's current rich-response helper carries this as a text marker. */
|
|
2070
|
+
addInlineVideo() {
|
|
2071
|
+
this._submessages.push({ messageType: 2, messageText: 'INLINE_VIDEO' });
|
|
2072
|
+
this._sections.push(AIRich.newLayout('Single', {
|
|
2073
|
+
text: 'INLINE_VIDEO',
|
|
2074
|
+
__typename: 'GenAIMarkdownTextUXPrimitive',
|
|
2075
|
+
}));
|
|
2076
|
+
return this;
|
|
2077
|
+
}
|
|
2078
|
+
|
|
2079
|
+
/**
|
|
2080
|
+
* Add a rendered LaTeX expression (`GenAILatexUXPrimitive`), with a real `AI_RICH_RESPONSE_LATEX`
|
|
2081
|
+
* submessage (unlike most primitives in this block, this one has a proper proto type).
|
|
2082
|
+
* @param {string} expression LaTeX source, e.g. `'$$E = mc^2$$'`.
|
|
2083
|
+
*/
|
|
2084
|
+
addLatex(expression) {
|
|
2085
|
+
if (typeof expression !== 'string' || !expression) {
|
|
2086
|
+
throw new TypeError('addLatex(expression) requires a non-empty string');
|
|
2087
|
+
}
|
|
2088
|
+
this._submessages.push({
|
|
2089
|
+
messageType: 8,
|
|
2090
|
+
latexMetadata: { text: expression, expressions: [{ latexExpression: expression }] },
|
|
2091
|
+
});
|
|
2092
|
+
this._sections.push(AIRich.newLayout('Single', { latex_expression: expression, __typename: 'GenAILatexUXPrimitive' }));
|
|
2093
|
+
return this;
|
|
2094
|
+
}
|
|
2095
|
+
|
|
2096
|
+
/**
|
|
2097
|
+
* Add a pre-rendered LaTeX expression: `addLatex()` ships the raw expression string for the
|
|
2098
|
+
* client to typeset itself; this ships an already-rendered image of it instead (real proto
|
|
2099
|
+
* fields — `proto.AIRichResponseLatexMetadata.AIRichResponseLatexExpression`: url/width/height/
|
|
2100
|
+
* fontHeight/padding — verified against WAProto/index.js, not guessed), for a caller that
|
|
2101
|
+
* rendered the LaTeX server-side (e.g. via a LaTeX-to-PNG pipeline) and just wants the image shown.
|
|
2102
|
+
* @param {string} expression Raw LaTeX source, still sent alongside the image as a fallback/caption.
|
|
2103
|
+
* @param {{url: string, width?: number, height?: number, fontHeight?: number, topPadding?: number, leadingPadding?: number, bottomPadding?: number, trailingPadding?: number}} image
|
|
2104
|
+
*/
|
|
2105
|
+
addLatexPro(expression, image = {}) {
|
|
2106
|
+
if (typeof expression !== 'string' || !expression) {
|
|
2107
|
+
throw new TypeError('addLatexPro(expression, image) requires a non-empty expression string');
|
|
2108
|
+
}
|
|
2109
|
+
if (!image?.url) {
|
|
2110
|
+
throw new TypeError('addLatexPro(expression, image) requires image.url — use addLatex() for a text-only expression');
|
|
2111
|
+
}
|
|
2112
|
+
const rendered = {
|
|
2113
|
+
latexExpression: expression,
|
|
2114
|
+
url: image.url,
|
|
2115
|
+
};
|
|
2116
|
+
if (image.width != null) rendered.width = image.width;
|
|
2117
|
+
if (image.height != null) rendered.height = image.height;
|
|
2118
|
+
if (image.fontHeight != null) rendered.fontHeight = image.fontHeight;
|
|
2119
|
+
if (image.topPadding != null) rendered.imageTopPadding = image.topPadding;
|
|
2120
|
+
if (image.leadingPadding != null) rendered.imageLeadingPadding = image.leadingPadding;
|
|
2121
|
+
if (image.bottomPadding != null) rendered.imageBottomPadding = image.bottomPadding;
|
|
2122
|
+
if (image.trailingPadding != null) rendered.imageTrailingPadding = image.trailingPadding;
|
|
2123
|
+
this._submessages.push({
|
|
2124
|
+
messageType: 8,
|
|
2125
|
+
latexMetadata: { text: expression, expressions: [rendered] },
|
|
2126
|
+
});
|
|
2127
|
+
this._sections.push(
|
|
2128
|
+
AIRich.newLayout('Single', { latex_expression: expression, image_url: image.url, __typename: 'GenAILatexUXPrimitive' })
|
|
2129
|
+
);
|
|
2130
|
+
return this;
|
|
2131
|
+
}
|
|
2132
|
+
|
|
2133
|
+
/**
|
|
2134
|
+
* Add map annotations (real proto — `proto.AIRichResponseMapMetadata`: centerLatitude/
|
|
2135
|
+
* centerLongitude/latitudeDelta/longitudeDelta/annotations[]/showInfoList — verified against
|
|
2136
|
+
* WAProto/index.js, not guessed).
|
|
2137
|
+
* @param {{number: number, latitude: number, longitude: number, title?: string, body?: string}[]} annotations
|
|
2138
|
+
* @param {{centerLatitude?: number, centerLongitude?: number, latitudeDelta?: number, longitudeDelta?: number, showInfoList?: boolean}} [options]
|
|
2139
|
+
*/
|
|
2140
|
+
addMap(annotations = [], options = {}) {
|
|
2141
|
+
if (!Array.isArray(annotations) || !annotations.length) {
|
|
2142
|
+
throw new TypeError('addMap(annotations) requires a non-empty array');
|
|
2143
|
+
}
|
|
2144
|
+
const mapped = annotations.map((a, i) => {
|
|
2145
|
+
if (typeof a?.latitude !== 'number' || typeof a?.longitude !== 'number') {
|
|
2146
|
+
throw new TypeError(`addMap(): annotation ${i} requires numeric "latitude" and "longitude"`);
|
|
2147
|
+
}
|
|
2148
|
+
return {
|
|
2149
|
+
annotationNumber: a.number ?? i + 1,
|
|
2150
|
+
latitude: a.latitude,
|
|
2151
|
+
longitude: a.longitude,
|
|
2152
|
+
title: a.title ?? '',
|
|
2153
|
+
body: a.body ?? '',
|
|
2154
|
+
};
|
|
2155
|
+
});
|
|
2156
|
+
const mapMetadata = { annotations: mapped };
|
|
2157
|
+
if (options.centerLatitude != null) mapMetadata.centerLatitude = options.centerLatitude;
|
|
2158
|
+
if (options.centerLongitude != null) mapMetadata.centerLongitude = options.centerLongitude;
|
|
2159
|
+
if (options.latitudeDelta != null) mapMetadata.latitudeDelta = options.latitudeDelta;
|
|
2160
|
+
if (options.longitudeDelta != null) mapMetadata.longitudeDelta = options.longitudeDelta;
|
|
2161
|
+
if (options.showInfoList != null) mapMetadata.showInfoList = options.showInfoList;
|
|
2162
|
+
this._submessages.push({ messageType: 7, mapMetadata });
|
|
2163
|
+
// Safety net: GenAIMapUXPrimitive is a custom AI-only component (no confirmed stock-client
|
|
2164
|
+
// renderer for arbitrary bot traffic) — append a plain text fallback listing the pins, same
|
|
2165
|
+
// pattern as addTask()/addBloks(). Set options.textFallback = false to skip.
|
|
2166
|
+
this._sections.push(AIRich.newLayout('Single', { annotations: mapped, __typename: 'GenAIMapUXPrimitive' }));
|
|
2167
|
+
if (options.textFallback !== false) {
|
|
2168
|
+
const list = mapped.map((a) => `${a.title || `Pin ${a.annotationNumber}`}: ${a.latitude}, ${a.longitude}`).join('\n');
|
|
2169
|
+
this._sections.push(AIRich.newLayout('Single', { text: `Peta:\n${list}`, __typename: 'FOATextPrimitive' }));
|
|
2170
|
+
}
|
|
2171
|
+
return this;
|
|
2172
|
+
}
|
|
2173
|
+
|
|
2174
|
+
/**
|
|
2175
|
+
* Add an animated GIF (real proto — `proto.AIRichResponseDynamicMetadata`, `type` = the GIF
|
|
2176
|
+
* value of `AIRichResponseDynamicMetadataType` — verified against WAProto/index.js, not
|
|
2177
|
+
* guessed; `AI_RICH_RESPONSE_DYNAMIC_METADATA_TYPE_GIF` = 2).
|
|
2178
|
+
* @param {string} url
|
|
2179
|
+
* @param {{loopCount?: number, version?: number|string}} [options]
|
|
2180
|
+
*/
|
|
2181
|
+
addGif(url, { loopCount, version, resolveUrl = false } = {}) {
|
|
2182
|
+
if (typeof url !== 'string' || !url) {
|
|
2183
|
+
throw new TypeError('addGif(url) requires a non-empty url string');
|
|
2184
|
+
}
|
|
2185
|
+
const resolved = Toolkit.resolveMedia(this.#client, url, 'video', { resolveUrl });
|
|
2186
|
+
const dynamicMetadata = { type: 2, url: resolved }; // 2 = AI_RICH_RESPONSE_DYNAMIC_METADATA_TYPE_GIF
|
|
2187
|
+
if (loopCount != null) dynamicMetadata.loopCount = loopCount;
|
|
2188
|
+
if (version != null) dynamicMetadata.version = version;
|
|
2189
|
+
this._submessages.push({ messageType: 6, dynamicMetadata });
|
|
2190
|
+
this._sections.push(AIRich.newLayout('Single', { url: resolved, __typename: 'GenAIDynamicUXPrimitive' }));
|
|
2191
|
+
return this;
|
|
2192
|
+
}
|
|
2193
|
+
|
|
2194
|
+
/** Build rich-response citation/link submessages using the same shape as Baileys' `links` content shortcut. */
|
|
2195
|
+
addLinks(links = []) {
|
|
2196
|
+
if (!Array.isArray(links)) throw new TypeError('links must be an array');
|
|
2197
|
+
links.forEach((linkField, index) => {
|
|
2198
|
+
if (!linkField || typeof linkField !== 'object') throw new TypeError('Each link must be an object');
|
|
2199
|
+
const prefix = 'SS_' + index;
|
|
2200
|
+
const url = linkField.url || '';
|
|
2201
|
+
const text = String(linkField.text ?? '');
|
|
2202
|
+
const sources = Array.isArray(linkField.sources) ? linkField.sources.map((sourceField) => ({
|
|
2203
|
+
source_type: 'THIRD_PARTY',
|
|
2204
|
+
source_display_name: sourceField?.displayName || sourceField?.title || 'Source',
|
|
2205
|
+
source_subtitle: sourceField?.subtitle || '',
|
|
2206
|
+
source_url: sourceField?.url || url,
|
|
2207
|
+
})) : [];
|
|
2208
|
+
const entity = {
|
|
2209
|
+
key: prefix,
|
|
2210
|
+
metadata: {
|
|
2211
|
+
reference_id: index + 1,
|
|
2212
|
+
reference_url: url,
|
|
2213
|
+
reference_title: linkField.title || 'Source',
|
|
2214
|
+
reference_display_name: linkField.displayName || linkField.title || 'Source',
|
|
2215
|
+
sources,
|
|
2216
|
+
__typename: 'GenAISearchCitationItem',
|
|
2217
|
+
},
|
|
2218
|
+
};
|
|
2219
|
+
const section = AIRich.newLayout('Single', {
|
|
2220
|
+
text: `${text} {{${prefix}}}${url}{{/${prefix}}}`,
|
|
2221
|
+
inline_entities: [entity],
|
|
2222
|
+
__typename: 'GenAIMarkdownTextUXPrimitive',
|
|
2223
|
+
});
|
|
2224
|
+
this._sections.push(section);
|
|
2225
|
+
this._submessages.push({
|
|
2226
|
+
messageType: 2,
|
|
2227
|
+
messageText: `${text} {{${prefix}}}¹{{/${prefix}}} `,
|
|
2228
|
+
inlineEntities: [entity],
|
|
2229
|
+
});
|
|
2230
|
+
});
|
|
2231
|
+
return this;
|
|
2232
|
+
}
|
|
2233
|
+
|
|
2234
|
+
/**
|
|
2235
|
+
* Add a "searching/working" progress banner (`GenAIBotProgressStatusPrimitive`) — a one-shot
|
|
2236
|
+
* status chip (unlike `addSuggest`, this isn't tappable). Distinct from `addThinkingStatus()`'s
|
|
2237
|
+
* icon/typename.
|
|
2238
|
+
* @param {string} title
|
|
2239
|
+
* @param {{icon?: string, is_in_progress?: boolean}} [options]
|
|
2240
|
+
*/
|
|
2241
|
+
addProgressStatus(title, { icon = 'SEARCH', is_in_progress = true, target_secondary_screen_id, target_secondary_screen_tab_id } = {}) {
|
|
2242
|
+
if (typeof title !== 'string' || !title) {
|
|
2243
|
+
throw new TypeError('addProgressStatus(title) requires a non-empty string');
|
|
2244
|
+
}
|
|
2245
|
+
this._submessages.push({ messageType: 2, messageText: title });
|
|
2246
|
+
const primitive = {
|
|
2247
|
+
title,
|
|
2248
|
+
icon,
|
|
2249
|
+
is_in_progress,
|
|
2250
|
+
meta_search_apps: [],
|
|
2251
|
+
__typename: 'GenAIBotProgressStatusPrimitive',
|
|
2252
|
+
};
|
|
2253
|
+
// NOTE: these two fields must be OMITTED when unset, not sent as `null` —
|
|
2254
|
+
// an explicit null here was reproducibly crashing the WA client renderer
|
|
2255
|
+
// on group-open/media-download. Only include when the caller actually passes one.
|
|
2256
|
+
if (target_secondary_screen_id != null) primitive.target_secondary_screen_id = target_secondary_screen_id;
|
|
2257
|
+
if (target_secondary_screen_tab_id != null) primitive.target_secondary_screen_tab_id = target_secondary_screen_tab_id;
|
|
2258
|
+
this._sections.push(AIRich.newLayout('Single', primitive));
|
|
2259
|
+
return this;
|
|
2260
|
+
}
|
|
2261
|
+
|
|
2262
|
+
/**
|
|
2263
|
+
* Add a subscription-quota-limit upsell card (`GenAIMetaSubsQuotaUpsellPrimitive`).
|
|
2264
|
+
* @param {{title: string, body?: string, body_line1?: string, body_line2?: string, buttons?: {label: string, action?: string, deeplink?: string}[]}} data
|
|
2265
|
+
*/
|
|
2266
|
+
addQuotaUpsell(data = {}) {
|
|
2267
|
+
if (!data?.title) {
|
|
2268
|
+
throw new TypeError('addQuotaUpsell() requires a "title"');
|
|
2269
|
+
}
|
|
2270
|
+
this._submessages.push({ messageType: 2, messageText: data.title });
|
|
2271
|
+
this._sections.push(
|
|
2272
|
+
AIRich.newLayout('Single', {
|
|
2273
|
+
title: data.title,
|
|
2274
|
+
body: data.body ?? '',
|
|
2275
|
+
body_line1: data.body_line1 ?? '',
|
|
2276
|
+
body_line2: data.body_line2 ?? '',
|
|
2277
|
+
buttons: (data.buttons ?? []).map((b) => {
|
|
2278
|
+
const action = b.action ?? 'OPEN_DEEPLINK';
|
|
2279
|
+
const out = { label: b.label ?? '', action };
|
|
2280
|
+
if (action === 'OPEN_URL') out.url = b.url ?? b.deeplink ?? '';
|
|
2281
|
+
else out.deeplink = b.deeplink ?? b.url ?? '';
|
|
2282
|
+
return out;
|
|
2283
|
+
}),
|
|
2284
|
+
__typename: 'GenAIMetaSubsQuotaUpsellPrimitive',
|
|
2285
|
+
})
|
|
2286
|
+
);
|
|
2287
|
+
return this;
|
|
2288
|
+
}
|
|
2289
|
+
|
|
2290
|
+
/**
|
|
2291
|
+
* Add several subscription-quota-limit upsell cards as one carousel-style block (each card is
|
|
2292
|
+
* the same `GenAIMetaSubsQuotaUpsellPrimitive` shape `addQuotaUpsell()` uses — this just
|
|
2293
|
+
* pushes them as one `primitives` array on a single layout instead of one `primitive` each, and
|
|
2294
|
+
* applies the same OPEN_URL/`url` vs OPEN_DEEPLINK/`deeplink` fix per card).
|
|
2295
|
+
* @param {{title: string, body?: string, body_line1?: string, body_line2?: string, buttons?: {label: string, action?: string, deeplink?: string, url?: string}[]}[]} cards
|
|
2296
|
+
*/
|
|
2297
|
+
addQuotaUpsellCards(cards = []) {
|
|
2298
|
+
if (!Array.isArray(cards) || !cards.length) {
|
|
2299
|
+
throw new TypeError('addQuotaUpsellCards(cards) requires a non-empty array');
|
|
2300
|
+
}
|
|
2301
|
+
const primitives = cards.map((data, i) => {
|
|
2302
|
+
if (!data?.title) throw new TypeError(`addQuotaUpsellCards(): card ${i} requires a "title"`);
|
|
2303
|
+
return {
|
|
2304
|
+
title: data.title,
|
|
2305
|
+
body: data.body ?? '',
|
|
2306
|
+
body_line1: data.body_line1 ?? '',
|
|
2307
|
+
body_line2: data.body_line2 ?? '',
|
|
2308
|
+
buttons: (data.buttons ?? []).map((b) => {
|
|
2309
|
+
const action = b.action ?? 'OPEN_DEEPLINK';
|
|
2310
|
+
const out = { label: b.label ?? '', action };
|
|
2311
|
+
if (action === 'OPEN_URL') out.url = b.url ?? b.deeplink ?? '';
|
|
2312
|
+
else out.deeplink = b.deeplink ?? b.url ?? '';
|
|
2313
|
+
return out;
|
|
2314
|
+
}),
|
|
2315
|
+
__typename: 'GenAIMetaSubsQuotaUpsellPrimitive',
|
|
2316
|
+
};
|
|
2317
|
+
});
|
|
2318
|
+
this._submessages.push({ messageType: 2, messageText: cards.map((c) => c.title).join(', ') });
|
|
2319
|
+
this._sections.push(AIRich.newLayout('Single', primitives));
|
|
2320
|
+
return this;
|
|
2321
|
+
}
|
|
2322
|
+
|
|
2323
|
+
// --- v2.1.14: second pass over the Go reference (MessageBuilderv2) ------------------------------
|
|
2324
|
+
// Scope note: the Go reference degrades many of its "primitives" (VStack, FlexibleGrid, SportsWidget, P13N,
|
|
2325
|
+
// ClippyWidget, LocationPermission, ...) to plain markdown text, so only the ones that push a real `GenAI*`
|
|
2326
|
+
// primitive/layout are ported here. Like every captured-traffic primitive in this class, whether the WhatsApp
|
|
2327
|
+
// client actually renders them depends on the client/account -- eval-test one at a time on a real device.
|
|
2328
|
+
|
|
2329
|
+
/** Builder-level default for `forwarded` (the "Forwarded" label + `forwardedAiBotMessageInfo`). `send({ forwarded })` still overrides per call. */
|
|
2330
|
+
setForwarded(enabled = true) {
|
|
2331
|
+
this._forwarded = !!enabled;
|
|
2332
|
+
return this;
|
|
2333
|
+
}
|
|
2334
|
+
|
|
2335
|
+
/** Builder-level default for `includesUnifiedResponse` (the `unifiedResponse` payload + the follow-up edit that gets the card past "download"). */
|
|
2336
|
+
setIncludesUnifiedResponse(enabled = true) {
|
|
2337
|
+
this._includesUnifiedResponse = !!enabled;
|
|
2338
|
+
return this;
|
|
2339
|
+
}
|
|
2340
|
+
|
|
2341
|
+
/** Builder-level default for `includesSubmessages` (the legacy `submessages[]` array). */
|
|
2342
|
+
setIncludesSubmessages(enabled = true) {
|
|
2343
|
+
this._includesSubmessages = !!enabled;
|
|
2344
|
+
return this;
|
|
2345
|
+
}
|
|
2346
|
+
|
|
2347
|
+
/** Markdown text WITHOUT hyperlink/citation/LaTeX entity parsing -- for sending `_italic_` / `**bold**` / `code` exactly as typed. */
|
|
2348
|
+
addTextRaw(text, { id, replace, insertAt } = {}) {
|
|
2349
|
+
if (typeof text !== 'string' || !text) throw new TypeError('addTextRaw(text) requires a non-empty string');
|
|
2350
|
+
return this.addText(text, { hyperlink: false, citation: false, latex: false, id, replace, insertAt });
|
|
2351
|
+
}
|
|
2352
|
+
|
|
2353
|
+
/** "In progress" status card (`GenAIBotProgressStatusPrimitive`); same as `addProgressStatus()` with the Go reference's defaults. */
|
|
2354
|
+
addProgress(title = 'Mencari sumber', icon = 'SEARCH', options = {}) {
|
|
2355
|
+
return this.addProgressStatus(title, { icon, ...options });
|
|
2356
|
+
}
|
|
2357
|
+
|
|
2358
|
+
/** Quota-upsell card with text only (no button). */
|
|
2359
|
+
addQuotaUpsellPlain(title, line1 = '', line2 = '') {
|
|
2360
|
+
return this.addQuotaUpsell({ title, body_line1: line1, body_line2: line2, buttons: [] });
|
|
2361
|
+
}
|
|
2362
|
+
|
|
2363
|
+
/**
|
|
2364
|
+
* Quota-upsell card with one custom button. Unlike the Go reference there is no default target link -- pass `deeplink` or `url`.
|
|
2365
|
+
* @param {{action?: 'OPEN_DEEPLINK'|'OPEN_URL', deeplink?: string, url?: string}} [target]
|
|
2366
|
+
*/
|
|
2367
|
+
addQuotaUpsellCustom(title, line1, line2, label, { action, deeplink, url } = {}) {
|
|
2368
|
+
if (!label) throw new TypeError('addQuotaUpsellCustom() requires a button label');
|
|
2369
|
+
if (!deeplink && !url) throw new TypeError('addQuotaUpsellCustom() requires a deeplink or url for the button');
|
|
2370
|
+
return this.addQuotaUpsell({ title, body_line1: line1, body_line2: line2, buttons: [{ label, action: action ?? (url && !deeplink ? 'OPEN_URL' : 'OPEN_DEEPLINK'), deeplink, url }] });
|
|
2371
|
+
}
|
|
2372
|
+
|
|
2373
|
+
/** Quota-upsell card that is ONLY a button (no title/body). Requires `deeplink` or `url`. */
|
|
2374
|
+
addQuotaUpsellBtnOnly(label = 'Upgrade', { action, deeplink, url } = {}) {
|
|
2375
|
+
if (!deeplink && !url) throw new TypeError('addQuotaUpsellBtnOnly() requires a deeplink or url for the button');
|
|
2376
|
+
const act = action ?? (url && !deeplink ? 'OPEN_URL' : 'OPEN_DEEPLINK');
|
|
2377
|
+
const button = act === 'OPEN_URL' ? { label, action: act, url: url ?? deeplink } : { label, action: act, deeplink: deeplink ?? url };
|
|
2378
|
+
return this._addSinglePrimitive('GenAIMetaSubsQuotaUpsellPrimitive', { buttons: [button] });
|
|
2379
|
+
}
|
|
2380
|
+
|
|
2381
|
+
/**
|
|
2382
|
+
* "Strict" quota-upsell card: only the fields you pass are sent. `body` is the field the client renders as the card's
|
|
2383
|
+
* content (confirmed in the Go reference's strict mode); title and button are optional.
|
|
2384
|
+
*/
|
|
2385
|
+
addQuotaUpsellStrict({ title, body, label, action, deeplink, url } = {}) {
|
|
2386
|
+
if (!title && !body && !label) throw new TypeError('addQuotaUpsellStrict() needs at least one of title/body/label');
|
|
2387
|
+
let buttons;
|
|
2388
|
+
if (label) {
|
|
2389
|
+
if (!deeplink && !url) throw new TypeError('addQuotaUpsellStrict() with a button label requires a deeplink or url');
|
|
2390
|
+
const act = action ?? (url && !deeplink ? 'OPEN_URL' : 'OPEN_DEEPLINK');
|
|
2391
|
+
buttons = [act === 'OPEN_URL' ? { label, action: act, url: url ?? deeplink } : { label, action: act, deeplink: deeplink ?? url }];
|
|
2392
|
+
}
|
|
2393
|
+
const section = AIRich.newLayout('Single', AIRich._omitEmpty({ title, body, buttons, __typename: 'GenAIMetaSubsQuotaUpsellPrimitive' }), { __typename: 'GenAIUnifiedResponseSection' });
|
|
2394
|
+
return this._addContent(section, title ? { messageType: 2, messageText: title } : this.createAlert('GenAIMetaSubsQuotaUpsellPrimitive'));
|
|
2395
|
+
}
|
|
2396
|
+
|
|
2397
|
+
/** Sponsored-story card (`GenAISearchAdPrimitive`). */
|
|
2398
|
+
addSearchAd({ storyId, actorName, actorImageUrl, imageUrl, message } = {}, options = {}) {
|
|
2399
|
+
if (!message && !imageUrl && !actorName) throw new TypeError('addSearchAd() needs at least one of message/imageUrl/actorName');
|
|
2400
|
+
return this._addSinglePrimitive('GenAISearchAdPrimitive', { story_id: storyId, actor_name: actorName, actor_image_url: actorImageUrl, image_url: imageUrl, message }, options);
|
|
2401
|
+
}
|
|
2402
|
+
|
|
2403
|
+
/** Recursive unified-response container (`GenAINestedUnifiedResponse`). Each field is an array of raw section / screen objects. */
|
|
2404
|
+
addNestedUnified({ sections, footerSections, embeddedScreens, nestedResponses } = {}, options = {}) {
|
|
2405
|
+
if (![sections, footerSections, embeddedScreens, nestedResponses].some((v) => Array.isArray(v) && v.length)) {
|
|
2406
|
+
throw new TypeError('addNestedUnified() needs at least one non-empty array');
|
|
2407
|
+
}
|
|
2408
|
+
return this._addSinglePrimitive('GenAINestedUnifiedResponse', { sections, footer_sections: footerSections, embedded_screens: embeddedScreens, nested_responses: nestedResponses }, options);
|
|
2409
|
+
}
|
|
2410
|
+
|
|
2411
|
+
/** Third-party account-linking upsell (`GenAI3PAccountLinkingUpsellPrimitive`). */
|
|
2412
|
+
add3PAccountLinking({ title, subtitle, imageUrl, ctaLabel, ctaUrl, originalPrompt, integrationType, integrationStatus, bottomsheet, apps, label, integrationFbid, integrationSlug } = {}, options = {}) {
|
|
2413
|
+
if (!title && !ctaLabel) throw new TypeError('add3PAccountLinking() needs a title or ctaLabel');
|
|
2414
|
+
return this._addSinglePrimitive(
|
|
2415
|
+
'GenAI3PAccountLinkingUpsellPrimitive',
|
|
2416
|
+
{ title, subtitle, image_url: imageUrl, cta_label: ctaLabel, cta_url: ctaUrl, original_prompt: originalPrompt, integration_type: integrationType, integration_status: integrationStatus, bottomsheet, apps, label, integration_fbid: integrationFbid, integration_slug: integrationSlug },
|
|
2417
|
+
options
|
|
2418
|
+
);
|
|
2419
|
+
}
|
|
2420
|
+
|
|
2421
|
+
/** Search-planner steps card (`GenAISearchPlannerStepsPrimitive`); `data` is the raw primitive body (step shapes vary). */
|
|
2422
|
+
addSearchPlannerSteps(data = {}, options = {}) {
|
|
2423
|
+
return this.addPrimitive('GenAISearchPlannerStepsPrimitive', data, options);
|
|
2424
|
+
}
|
|
2425
|
+
|
|
2426
|
+
/** One search-planner step snippet (`GenAISearchPlannerStepSnippetPrimitive`). @param {string} status @param {string} [header] @param {object[]} [instructions] */
|
|
2427
|
+
addSearchPlannerStepSnippet(status, header, instructions = [], options = {}) {
|
|
2428
|
+
if (!status) throw new TypeError('addSearchPlannerStepSnippet(status) requires a status');
|
|
2429
|
+
return this._addSinglePrimitive('GenAISearchPlannerStepSnippetPrimitive', { status, header, instructions }, options);
|
|
2430
|
+
}
|
|
2431
|
+
|
|
2432
|
+
/** Orchestrator search-engine primitive (`GenAIOrchestratorSearchEngine`); raw body. */
|
|
2433
|
+
addOrchestratorSearchEngine(data = {}, options = {}) {
|
|
2434
|
+
return this.addPrimitive('GenAIOrchestratorSearchEngine', data, options);
|
|
2435
|
+
}
|
|
2436
|
+
|
|
2437
|
+
/** Professional-consent primitive (`GenAIProfessionalConsentPrimitive`); raw body (many label/url fields). */
|
|
2438
|
+
addProfessionalConsent(data = {}, options = {}) {
|
|
2439
|
+
return this.addPrimitive('GenAIProfessionalConsentPrimitive', data, options);
|
|
2440
|
+
}
|
|
2441
|
+
|
|
2442
|
+
/** Embedded contextual screen (`GenAIEmbeddedContextualScreenContent`, the "bloks_attachment" payload). */
|
|
2443
|
+
addEmbeddedContextualScreen(type, data = {}, { uuid, versioningId, initialResponse } = {}, options = {}) {
|
|
2444
|
+
if (!type) throw new TypeError('addEmbeddedContextualScreen(type, data) requires a type');
|
|
2445
|
+
return this._addSinglePrimitive('GenAIEmbeddedContextualScreenContent', { type, data, uuid, versioning_id: versioningId, initial_response: initialResponse }, options);
|
|
2446
|
+
}
|
|
2447
|
+
|
|
2448
|
+
/**
|
|
2449
|
+
* Experimental real layout container: push several already-built `GenAI*` primitives as ONE layout section
|
|
2450
|
+
* (`GenAI<name>LayoutViewModel`, `primitives[]`). `addMultipleResponse` / `addFlexibleGrid` / `addVStack` are named
|
|
2451
|
+
* shortcuts. (The Go reference only flattens these to text; this sends the real layout -- eval-test before relying on it.)
|
|
2452
|
+
* @param {string} name Layout name, e.g. 'HScroll', 'Grid', 'MultipleResponse', 'FlexibleCountGrid', 'VStack'.
|
|
2453
|
+
* @param {object[]} primitives Primitive objects, each with its own `__typename`.
|
|
2454
|
+
*/
|
|
2455
|
+
addLayout(name, primitives, { text, id, replace, insertAt } = {}) {
|
|
2456
|
+
if (typeof name !== 'string' || !name) throw new TypeError('addLayout(name, primitives) requires a layout name');
|
|
2457
|
+
if (!Array.isArray(primitives) || !primitives.length) throw new TypeError('addLayout(name, primitives) requires a non-empty primitives array');
|
|
2458
|
+
const section = AIRich.newLayout(name, primitives, { __typename: 'GenAIUnifiedResponseSection' });
|
|
2459
|
+
const fallback = text ?? primitives.map((p) => p?.text ?? p?.title ?? p?.prompt_text ?? '').filter(Boolean).join('\n');
|
|
2460
|
+
return this._addContent(section, fallback ? { messageType: 2, messageText: fallback } : this.createAlert(primitives[0]?.__typename ?? 'GenAILayout'), { id, replace, insertAt });
|
|
2461
|
+
}
|
|
2462
|
+
|
|
2463
|
+
/** `GenAIMultipleResponseLayoutViewModel`. */
|
|
2464
|
+
addMultipleResponse(primitives, options = {}) {
|
|
2465
|
+
return this.addLayout('MultipleResponse', primitives, options);
|
|
2466
|
+
}
|
|
2467
|
+
|
|
2468
|
+
/** `GenAIFlexibleCountGridLayoutViewModel`. */
|
|
2469
|
+
addFlexibleGrid(primitives, options = {}) {
|
|
2470
|
+
return this.addLayout('FlexibleCountGrid', primitives, options);
|
|
2471
|
+
}
|
|
2472
|
+
|
|
2473
|
+
/** `GenAIVStackLayoutViewModel` (experimental). */
|
|
2474
|
+
addVStack(primitives, options = {}) {
|
|
2475
|
+
return this.addLayout('VStack', primitives, options);
|
|
2476
|
+
}
|
|
2477
|
+
|
|
2478
|
+
/**
|
|
2479
|
+
* Pending options for the NEXT `add*()` / `set*()` call (Go `Opts`). Accepts `{ id, insertAt, replace }` or the positional
|
|
2480
|
+
* `(id, insertAt, replace)` form. Inline options on that call take priority; the pending options are consumed either way.
|
|
2481
|
+
*/
|
|
2482
|
+
opts(idOrOpts, insertAt, replace) {
|
|
2483
|
+
const o = idOrOpts && typeof idOrOpts === 'object' ? { ...idOrOpts } : { id: idOrOpts || undefined, insertAt: insertAt || undefined, replace: replace || undefined };
|
|
2484
|
+
this._pendingOpts = Object.fromEntries(Object.entries(o).filter(([, v]) => v !== undefined && v !== ''));
|
|
2485
|
+
return this;
|
|
2486
|
+
}
|
|
2487
|
+
|
|
2488
|
+
/** Like `opts()` with an insert offset (Go `OptsAt`): `offset` 0 = after the `insertAtId` block, negative = before it (-1 = directly before). */
|
|
2489
|
+
optsAt(id, insertAtId, offset = 0, replace) {
|
|
2490
|
+
if (!insertAtId) throw new TypeError('optsAt(id, insertAtId, offset) requires insertAtId');
|
|
2491
|
+
this._pendingOpts = Object.fromEntries(Object.entries({ id, insertAt: insertAtId, insertAtOffset: offset, replace }).filter(([, v]) => v !== undefined && v !== ''));
|
|
2492
|
+
return this;
|
|
2493
|
+
}
|
|
2494
|
+
|
|
2495
|
+
/** JSON snapshot for `JSON.stringify(rich)` (Go `MarshalJSON`): response id, title/footer, sections, submessages, embedded screens. */
|
|
2496
|
+
toJSON() {
|
|
2497
|
+
return { response_id: this._responseId, bot_response_id: this._botResponseId, title: this._title, footer: this._footer, sections: this._sections, submessages: this._submessages, embedded_screens: this._embeddedScreens };
|
|
2498
|
+
}
|
|
2499
|
+
|
|
2500
|
+
// --- v2.1.14 (part 2): remaining helpers from the Go reference ---------------------------------
|
|
2501
|
+
// Ports of the Go helpers that were not covered in part 1. Several are thin aliases because this class
|
|
2502
|
+
// already had the capability under another name (noted per method); the text-card helpers reproduce what
|
|
2503
|
+
// the Go reference does for primitives it could not render natively (plain markdown cards).
|
|
2504
|
+
|
|
2505
|
+
/** Minimal botMetadata envelope: drops the notification fields so only disclaimer / response id / verification / extras remain (Go `SetRawEnvelope`). */
|
|
2506
|
+
setRawEnvelope(enabled = true) {
|
|
2507
|
+
this._rawEnvelope = !!enabled;
|
|
2508
|
+
return this;
|
|
2509
|
+
}
|
|
2510
|
+
|
|
2511
|
+
/** Register async work (fetch, resolve, ...) that runs concurrently at the start of `build()` (Go `Defer`). @param {() => any} fn */
|
|
2512
|
+
defer(fn) {
|
|
2513
|
+
if (typeof fn !== 'function') throw new TypeError('defer(fn) requires a function');
|
|
2514
|
+
this._deferred.push(fn);
|
|
2515
|
+
return this;
|
|
2516
|
+
}
|
|
2517
|
+
|
|
2518
|
+
/** Remove a block by id; unlike `delete(id)` this is a silent no-op when the id is unknown (Go `DeleteSection`). */
|
|
2519
|
+
deleteSection(id) {
|
|
2520
|
+
return this.hasId(id) ? this.delete(id) : this;
|
|
2521
|
+
}
|
|
2522
|
+
|
|
2523
|
+
/** Current unified-response id. */
|
|
2524
|
+
get responseId() {
|
|
2525
|
+
return this._responseId;
|
|
2526
|
+
}
|
|
2527
|
+
|
|
2528
|
+
/** Current bot response id. */
|
|
2529
|
+
get botResponseId() {
|
|
2530
|
+
return this._botResponseId;
|
|
2531
|
+
}
|
|
2532
|
+
|
|
2533
|
+
/** Key of the last message sent through `send()`, or `null`. */
|
|
2534
|
+
get lastMessageKey() {
|
|
2535
|
+
return this._lastMessageKey;
|
|
2536
|
+
}
|
|
2537
|
+
|
|
2538
|
+
/** Shallow copies of the current sections and submessages (Go `Items`). */
|
|
2539
|
+
items() {
|
|
2540
|
+
return { sections: [...this._sections], submessages: [...this._submessages] };
|
|
2541
|
+
}
|
|
2542
|
+
|
|
2543
|
+
/** Option-object form of `addImage()` (Go `AddImageOpts`). @param {{urls: string|Buffer|Array<string|Buffer>, width?: number, height?: number, status?: string, updateText?: string}} o */
|
|
2544
|
+
addImageOpts({ urls, width, height, status, updateText, ...rest } = {}) {
|
|
2545
|
+
if (!urls || (Array.isArray(urls) && !urls.length)) throw new TypeError('addImageOpts({ urls }) requires at least one url');
|
|
2546
|
+
return this.addImage(urls, { width, height, ...(status ? { status } : {}), update_text: updateText, ...rest });
|
|
2547
|
+
}
|
|
2548
|
+
|
|
2549
|
+
/** Option-object form of `addVideo()` (Go `AddVideoOpts`). @param {string|Buffer|{url: string, file_length?: number, duration?: number, thumbnail?: any, mime_type?: string, status?: string, estimatedTime?: number}} v */
|
|
2550
|
+
addVideoOpts(v, extra = {}) {
|
|
2551
|
+
const { status, estimatedTime, ...video } = v && typeof v === 'object' && !Buffer.isBuffer(v) ? v : { url: v };
|
|
2552
|
+
return this.addVideo(video, { ...(status ? { status } : {}), ...(estimatedTime != null ? { estimatedTime } : {}), ...extra });
|
|
2553
|
+
}
|
|
2554
|
+
|
|
2555
|
+
/** Several videos in ONE block with a single alert (Go `AddVideos`). Same as passing an array to `addVideo()`. */
|
|
2556
|
+
addVideos(videos, options = {}) {
|
|
2557
|
+
if (!Array.isArray(videos) || !videos.length) throw new TypeError('addVideos(videos) requires a non-empty array');
|
|
2558
|
+
return this.addVideo(videos, options);
|
|
2559
|
+
}
|
|
2560
|
+
|
|
2561
|
+
/** Follow-up pills with an explicit layout (Go `AddSuggestLayout`). Same as `addSuggest(list, { scroll, layout })`. */
|
|
2562
|
+
addSuggestLayout(suggestions, scroll = true, layout) {
|
|
2563
|
+
if (!Array.isArray(suggestions) || !suggestions.length) throw new TypeError('addSuggestLayout(suggestions) requires a non-empty array of strings');
|
|
2564
|
+
return this.addSuggest(suggestions, { scroll, layout });
|
|
2565
|
+
}
|
|
2566
|
+
|
|
2567
|
+
/** Single follow-up pill (Go `AddChainingSuggestion`; the Go prompt-id argument is unused there too). */
|
|
2568
|
+
addChainingSuggestion(prompt, options = {}) {
|
|
2569
|
+
if (typeof prompt !== 'string' || !prompt) throw new TypeError('addChainingSuggestion(prompt) requires a non-empty string');
|
|
2570
|
+
return this.addSuggest(prompt, { layout: 'Single', ...options });
|
|
2571
|
+
}
|
|
2572
|
+
|
|
2573
|
+
/** Raw progress/thinking primitive: any extra fields pass through; `__typename` is forced to `GenAIBotProgressStatusPrimitive` (Go `AddThinkingFull`). */
|
|
2574
|
+
addThinkingFull(primitive = {}, { id, replace, insertAt } = {}) {
|
|
2575
|
+
if (!primitive || typeof primitive !== 'object' || Array.isArray(primitive)) throw new TypeError('addThinkingFull(primitive) requires a plain object');
|
|
2576
|
+
return this._addContent(AIRich.newLayout('Single', { ...primitive, __typename: 'GenAIBotProgressStatusPrimitive' }), undefined, { id, replace, insertAt });
|
|
2577
|
+
}
|
|
2578
|
+
|
|
2579
|
+
/** AI "thinking" status line (Go `AddBotThinking`). `status` THINKING/GENERATING render as in-progress, anything else as done. */
|
|
2580
|
+
addBotThinking(status = 'COMPLETED', updateText = 'Berpikir', { id, replace, insertAt } = {}) {
|
|
2581
|
+
return this.addThinkingFull({ title: updateText, is_in_progress: status === 'THINKING' || status === 'GENERATING', icon: 'THINKING' }, { id, replace, insertAt });
|
|
2582
|
+
}
|
|
2583
|
+
|
|
2584
|
+
/** Source list with per-source subtitle (Go `AddSourceFull`). @param {{title: string, subtitle?: string, url: string, icon?: string|Buffer}[]} sources */
|
|
2585
|
+
addSourceFull(sources = [], { id, replace, insertAt } = {}) {
|
|
2586
|
+
if (!Array.isArray(sources) || !sources.length) throw new TypeError('addSourceFull(sources) requires a non-empty array');
|
|
2587
|
+
const list = sources.map((src, i) => {
|
|
2588
|
+
if (!src?.title || !src?.url) throw new TypeError(`addSourceFull(): source ${i} requires a title and a url`);
|
|
2589
|
+
return {
|
|
2590
|
+
source_type: 'THIRD_PARTY',
|
|
2591
|
+
source_display_name: src.title,
|
|
2592
|
+
source_subtitle: src.subtitle ?? '',
|
|
2593
|
+
source_url: src.url,
|
|
2594
|
+
favicon: { url: src.icon ? Toolkit.resolveMedia(this.#client, src.icon, 'image') : '', mime_type: 'image/jpeg', width: 16, height: 16 },
|
|
2595
|
+
};
|
|
2596
|
+
});
|
|
2597
|
+
return this._addContent(AIRich.newLayout('Single', { sources: list, __typename: 'GenAISearchResultPrimitive' }), this.createAlert('GenAISearchResultPrimitive'), { id, replace, insertAt });
|
|
2598
|
+
}
|
|
2599
|
+
|
|
2600
|
+
/**
|
|
2601
|
+
* Compact entity card (`GenAICompactEntityPrimitive`: title / subtitle / secondary_subtitle / image / entity id+url+type / action type).
|
|
2602
|
+
* Pass `{ asText: true }` for the Go reference's markdown fallback card instead (renders everywhere).
|
|
2603
|
+
*/
|
|
2604
|
+
addCompactEntity({ title, subtitle, secondarySubtitle, image, entityId, entityUrl, entityType, actionType, isVerified } = {}, { asText = false, id, replace, insertAt } = {}) {
|
|
2605
|
+
if (!title) throw new TypeError('addCompactEntity() requires a title');
|
|
2606
|
+
if (asText) {
|
|
2607
|
+
return this.addText(`🏷 **${title}**${subtitle ? `\n${subtitle}` : ''}${secondarySubtitle ? ` • ${secondarySubtitle}` : ''}`, { hyperlink: false, citation: false, latex: false, id, replace, insertAt });
|
|
2608
|
+
}
|
|
2609
|
+
return this._addSinglePrimitive(
|
|
2610
|
+
'GenAICompactEntityPrimitive',
|
|
2611
|
+
{ title, subtitle, secondary_subtitle: secondarySubtitle, image: image ? { url: Toolkit.resolveMedia(this.#client, image, 'image') } : undefined, entity_id: entityId, entity_url: entityUrl, entity_type: entityType, action_type: actionType, is_verified: isVerified },
|
|
2612
|
+
{ id, replace, insertAt }
|
|
2613
|
+
);
|
|
2614
|
+
}
|
|
2615
|
+
|
|
2616
|
+
/** "Generating..." image placeholder (`GenAIImaginePrimitive`, imagine_type IMAGINE, status GENERATING; Go `AddGenerate`). Also sets `botMetadata.imagineMetadata`. */
|
|
2617
|
+
addGenerate(updateText = 'Generating...', { id, replace, insertAt } = {}) {
|
|
2618
|
+
this.setImagineType('IMAGINE');
|
|
2619
|
+
return this._addContent(AIRich.newLayout('Single', { media: {}, imagine_type: 'IMAGINE', status: { status: 'GENERATING', update_text: updateText }, __typename: 'GenAIImaginePrimitive' }), undefined, { id, replace, insertAt });
|
|
2620
|
+
}
|
|
2621
|
+
|
|
2622
|
+
/**
|
|
2623
|
+
* Video via `GenAIVideoPrimitive` (media + optional music clip + delivery urls + lip-sync flag) -- distinct from `addVideo()`,
|
|
2624
|
+
* which uses `GenAIImaginePrimitive`/ANIMATE (Go `AddVideoPrimitive`). Experimental.
|
|
2625
|
+
* @param {{url?: string|Buffer, mimeType?: string, fileLength?: number, durationMs?: number, width?: number, height?: number, thumbnail?: string|Buffer,
|
|
2626
|
+
* isLipSync?: boolean, musicClip?: {hasMusic?: boolean, songTitle?: string, artistName?: string, audioAssetId?: string, progressiveUri?: string, coverImageUri?: string},
|
|
2627
|
+
* delivery?: {progressiveUrls?: string[], dashManifests?: string[], manifestXml?: string}}} v
|
|
2628
|
+
*/
|
|
2629
|
+
addVideoPrimitive({ url, mimeType, fileLength, durationMs, width, height, thumbnail, isLipSync = false, musicClip, delivery } = {}, options = {}) {
|
|
2630
|
+
if (!url && !delivery) throw new TypeError('addVideoPrimitive() needs a url or delivery urls');
|
|
2631
|
+
const media = { url: url ? Toolkit.resolveMedia(this.#client, url, 'video') : undefined, mime_type: mimeType, file_length: fileLength, duration: durationMs, width, height, thumbnail: thumbnail ? { raw_media: Toolkit.resolveMedia(this.#client, thumbnail, 'image') } : undefined };
|
|
2632
|
+
const payload = { media, is_lip_sync: isLipSync };
|
|
2633
|
+
if (musicClip) payload.music_clip_info = { has_music: musicClip.hasMusic, song_title: musicClip.songTitle, artist_name: musicClip.artistName, audio_asset_id: musicClip.audioAssetId, progressive_uri: musicClip.progressiveUri, cover_image_uri: musicClip.coverImageUri };
|
|
2634
|
+
if (delivery) payload.video_delivery_response = { progressive_urls: delivery.progressiveUrls, dash_manifests: delivery.dashManifests, manifest_xml: delivery.manifestXml };
|
|
2635
|
+
return this._addSinglePrimitive('GenAIVideoPrimitive', payload, options);
|
|
2636
|
+
}
|
|
2637
|
+
|
|
2638
|
+
/**
|
|
2639
|
+
* Pure-primitive LaTeX run (`GenAILatexUXPrimitive`: media + latex_expression + font_height + padding; Go `AddLatexUX`). Experimental --
|
|
2640
|
+
* needs a pre-rendered equation image `url`; for the inline-entity mechanism that is known to render, use `addLatex()`.
|
|
2641
|
+
*/
|
|
2642
|
+
addLatexUX({ expression, url, mimeType = 'image/png', fontHeight, padding } = {}, options = {}) {
|
|
2643
|
+
if (!expression || !url) throw new TypeError('addLatexUX() requires an expression and a pre-rendered image url');
|
|
2644
|
+
return this._addContent(
|
|
2645
|
+
AIRich.newLayout('Single', { latex_expression: expression, font_height: fontHeight, padding, media: { url: Toolkit.resolveMedia(this.#client, url, 'image'), mime_type: mimeType }, __typename: 'GenAILatexUXPrimitive' }),
|
|
2646
|
+
undefined,
|
|
2647
|
+
options
|
|
2648
|
+
);
|
|
2649
|
+
}
|
|
2650
|
+
|
|
2651
|
+
// -- Text-card helpers: the Go reference renders these as plain markdown cards (no native primitive); same here. --
|
|
2652
|
+
|
|
2653
|
+
/** Sports match card as formatted text (Go `AddSportsWidget`). */
|
|
2654
|
+
addSportsWidget({ name, abbreviation, league, status, venue } = {}, options = {}) {
|
|
2655
|
+
if (!name) throw new TypeError('addSportsWidget() requires a name');
|
|
2656
|
+
let text = `⚽ **${name}${abbreviation ? ` (${abbreviation})` : ''}**`;
|
|
2657
|
+
if (league) text += `\n${league}`;
|
|
2658
|
+
if (status) text += ` • ${status}`;
|
|
2659
|
+
if (venue) text += `\n🏟 ${venue}`;
|
|
2660
|
+
return this.addText(text, { hyperlink: false, citation: false, latex: false, ...options });
|
|
2661
|
+
}
|
|
2662
|
+
|
|
2663
|
+
/** Follow-up prompt card as text (Go `AddClippyFollowUp`). */
|
|
2664
|
+
addClippyFollowUp({ promptText, intentCategory } = {}, options = {}) {
|
|
2665
|
+
if (!promptText) throw new TypeError('addClippyFollowUp() requires promptText');
|
|
2666
|
+
return this.addText(`📎 ${promptText}${intentCategory ? `\n_${intentCategory}_` : ''}`, { hyperlink: false, citation: false, latex: false, ...options });
|
|
2667
|
+
}
|
|
2668
|
+
|
|
2669
|
+
/** Pinned header/subtitle card as text (Go `AddClippyWidget`). */
|
|
2670
|
+
addClippyWidget(header, subtitle = '', options = {}) {
|
|
2671
|
+
if (!header) throw new TypeError('addClippyWidget(header) requires a header');
|
|
2672
|
+
return this.addText(`📌 ${header}${subtitle ? `\n${subtitle}` : ''}`, { hyperlink: false, citation: false, latex: false, ...options });
|
|
2673
|
+
}
|
|
2674
|
+
|
|
2675
|
+
/** Privacy/transparency note as text (Go `AddP13N`). */
|
|
2676
|
+
addP13N(text = 'Privasi dihormati', options = {}) {
|
|
2677
|
+
return this.addText(`🔒 ${text}`, { hyperlink: false, citation: false, latex: false, ...options });
|
|
2678
|
+
}
|
|
2679
|
+
|
|
2680
|
+
/** Location-permission prompt line as text (Go `AddLocationPermission`). */
|
|
2681
|
+
addLocationPermission(placeholder = 'Untuk hasil terbaik, bagikan lokasi', options = {}) {
|
|
2682
|
+
return this.addText(`📍 ${placeholder}`, { hyperlink: false, citation: false, latex: false, ...options });
|
|
2683
|
+
}
|
|
2684
|
+
|
|
2685
|
+
/** Timestamp placeholder line as text (Go `AddTimestampPlaceholder`). */
|
|
2686
|
+
addTimestampPlaceholder(placeholder = '…', options = {}) {
|
|
2687
|
+
return this.addText(`_⏱ ${placeholder}_`, { hyperlink: false, citation: false, latex: false, ...options });
|
|
2688
|
+
}
|
|
2689
|
+
|
|
2690
|
+
/** Deep-link line as a markdown link (Go `AddDeeplinkItem`); falls back to the Android deeplink when `deepLinkUrl` is empty. */
|
|
2691
|
+
addDeeplinkItem(deepLinkUrl, androidDeeplinkUrl, { label = '🔗 Buka deep-link', ...options } = {}) {
|
|
2692
|
+
const target = deepLinkUrl || androidDeeplinkUrl;
|
|
2693
|
+
if (!target) throw new TypeError('addDeeplinkItem() requires a deep link url');
|
|
2694
|
+
return this.addText(`[${label}](${target})`, options);
|
|
2695
|
+
}
|
|
2696
|
+
|
|
2697
|
+
/** Go-name alias of `loadFrom()`. */
|
|
2698
|
+
loadFromMessage(msg) {
|
|
2699
|
+
return this.loadFrom(msg);
|
|
2700
|
+
}
|
|
2701
|
+
|
|
2702
|
+
/** Add blank vertical spacing (`GenAISpacerPrimitive`). @param {number} [spacing=1] Spacing unit, per observed traffic. */
|
|
2703
|
+
addSpacer(spacing = 1) {
|
|
2704
|
+
if (typeof spacing !== 'number' || spacing < 0) {
|
|
2705
|
+
throw new TypeError('addSpacer(spacing) requires a non-negative number');
|
|
2706
|
+
}
|
|
2707
|
+
this._submessages.push({ messageType: 2, messageText: `spasi ${spacing}` });
|
|
2708
|
+
this._sections.push(AIRich.newLayout('Single', { spacing, __typename: 'GenAISpacerPrimitive' }));
|
|
2709
|
+
return this;
|
|
2710
|
+
}
|
|
2711
|
+
|
|
2712
|
+
/**
|
|
2713
|
+
* Add a task/checklist card (`GenAITaskPrimitive`).
|
|
2714
|
+
* @param {{task_id?: string, title: string, subtitle?: string, status?: string}} data
|
|
2715
|
+
*/
|
|
2716
|
+
addTask(data = {}) {
|
|
2717
|
+
if (!data?.title) {
|
|
2718
|
+
throw new TypeError('addTask() requires a "title"');
|
|
2719
|
+
}
|
|
2720
|
+
this._submessages.push({ messageType: 2, messageText: `Tugas: ${data.title}` });
|
|
2721
|
+
this._sections.push(
|
|
2722
|
+
AIRich.newLayout('Single', {
|
|
2723
|
+
task_id: data.task_id ?? '',
|
|
2724
|
+
title: data.title,
|
|
2725
|
+
subtitle: data.subtitle ?? '',
|
|
2726
|
+
status: data.status ?? 'IN_PROGRESS',
|
|
2727
|
+
__typename: 'GenAITaskPrimitive',
|
|
2728
|
+
})
|
|
2729
|
+
);
|
|
2730
|
+
// Safety net: GenAITaskPrimitive is a custom AI-only component the stock WA client
|
|
2731
|
+
// doesn't render visibly. Append a plain text section so the task is still visible.
|
|
2732
|
+
// Set data.textFallback = false to skip.
|
|
2733
|
+
if (data.textFallback !== false) {
|
|
2734
|
+
const fallbackText = data.subtitle ? `${data.title} — ${data.subtitle}` : data.title;
|
|
2735
|
+
this._sections.push(AIRich.newLayout('Single', { text: `Tugas: ${fallbackText}`, __typename: 'FOATextPrimitive' }));
|
|
2736
|
+
}
|
|
2737
|
+
return this;
|
|
2738
|
+
}
|
|
2739
|
+
|
|
2740
|
+
/**
|
|
2741
|
+
* Batch version of `addTask()` — one or more task/checklist cards (`GenAITaskPrimitive`) pushed
|
|
2742
|
+
* as a single `primitives` array on one layout, instead of one `addTask()` call (and one
|
|
2743
|
+
* `_sections` entry) per task.
|
|
2744
|
+
* @param {{task_id?: string, title: string, subtitle?: string, status?: string}[]} tasks
|
|
2745
|
+
* @param {{textFallback?: boolean}} [options]
|
|
2746
|
+
*/
|
|
2747
|
+
addTasks(tasks = [], { textFallback = true } = {}) {
|
|
2748
|
+
if (!Array.isArray(tasks) || !tasks.length) {
|
|
2749
|
+
throw new TypeError('addTasks(tasks) requires a non-empty array');
|
|
2750
|
+
}
|
|
2751
|
+
const primitives = tasks.map((data, i) => {
|
|
2752
|
+
if (!data?.title) throw new TypeError(`addTasks(): task ${i} requires a "title"`);
|
|
2753
|
+
return {
|
|
2754
|
+
task_id: data.task_id ?? '',
|
|
2755
|
+
title: data.title,
|
|
2756
|
+
subtitle: data.subtitle ?? '',
|
|
2757
|
+
status: data.status ?? 'IN_PROGRESS',
|
|
2758
|
+
__typename: 'GenAITaskPrimitive',
|
|
2759
|
+
};
|
|
2760
|
+
});
|
|
2761
|
+
this._submessages.push({ messageType: 2, messageText: `Tugas: ${tasks.map((t) => t.title).join(', ')}` });
|
|
2762
|
+
this._sections.push(AIRich.newLayout('Single', primitives));
|
|
2763
|
+
// Same safety net as addTask(): stock client doesn't render GenAITaskPrimitive visibly.
|
|
2764
|
+
if (textFallback) {
|
|
2765
|
+
const fallbackText = tasks.map((t) => (t.subtitle ? `${t.title} — ${t.subtitle}` : t.title)).join('\n');
|
|
2766
|
+
this._sections.push(AIRich.newLayout('Single', { text: `Tugas:\n${fallbackText}`, __typename: 'FOATextPrimitive' }));
|
|
2767
|
+
}
|
|
2768
|
+
return this;
|
|
2769
|
+
}
|
|
2770
|
+
|
|
2771
|
+
/** Add a "thinking" status banner (`GenAIBotThinkingStatusPrimitive`). See `addProgressStatus()`. */
|
|
2772
|
+
addThinkingStatus(title, { icon = 'THINKING', is_in_progress = true, target_secondary_screen_id, target_secondary_screen_tab_id, textFallback = true } = {}) {
|
|
2773
|
+
if (typeof title !== 'string' || !title) {
|
|
2774
|
+
throw new TypeError('addThinkingStatus(title) requires a non-empty string');
|
|
2775
|
+
}
|
|
2776
|
+
this._submessages.push({ messageType: 2, messageText: title });
|
|
2777
|
+
const primitive = {
|
|
2778
|
+
title,
|
|
2779
|
+
icon,
|
|
2780
|
+
is_in_progress,
|
|
2781
|
+
meta_search_apps: [],
|
|
2782
|
+
__typename: 'GenAIBotThinkingStatusPrimitive',
|
|
2783
|
+
};
|
|
2784
|
+
// Same crash-avoidance rule as addProgressStatus(): omit, never null.
|
|
2785
|
+
if (target_secondary_screen_id != null) primitive.target_secondary_screen_id = target_secondary_screen_id;
|
|
2786
|
+
if (target_secondary_screen_tab_id != null) primitive.target_secondary_screen_tab_id = target_secondary_screen_tab_id;
|
|
2787
|
+
this._sections.push(AIRich.newLayout('Single', primitive));
|
|
2788
|
+
// Safety net: stock WA client doesn't render this primitive's own view (it's meant
|
|
2789
|
+
// as a transient spinner in the official app), so the card shows blank when forwarded.
|
|
2790
|
+
// Append a plain text section so the title is still visible. Set { textFallback: false } to skip.
|
|
2791
|
+
if (textFallback) {
|
|
2792
|
+
this._sections.push(AIRich.newLayout('Single', { text: title, __typename: 'FOATextPrimitive' }));
|
|
2793
|
+
}
|
|
2794
|
+
return this;
|
|
2795
|
+
}
|
|
2796
|
+
|
|
2797
|
+
/**
|
|
2798
|
+
* Send an image and video as one paired-media unit (image sent first, video linked to it via
|
|
2799
|
+
* `messageAssociation`). Distinct from a plain album — the client treats them as a single group.
|
|
2800
|
+
* @param {import('../../WAProto/index.js').WASocket} client
|
|
2801
|
+
* @param {string} jid
|
|
2802
|
+
* @param {{ image: string|Buffer, video: string|Buffer }} media
|
|
2803
|
+
*/
|
|
2804
|
+
static async sendPairedMedia(client, jid, { image, video } = {}) {
|
|
2805
|
+
if (!client) throw new Error('Socket is required');
|
|
2806
|
+
if (!image || !video) throw new TypeError('sendPairedMedia() requires both "image" and "video"');
|
|
2807
|
+
|
|
2808
|
+
const imagePrepared = await prepareWAMessageMedia(
|
|
2809
|
+
{ image: typeof image === 'string' ? { url: image } : image },
|
|
2810
|
+
{ upload: client.waUploadToServer }
|
|
2811
|
+
);
|
|
2812
|
+
const videoPrepared = await prepareWAMessageMedia(
|
|
2813
|
+
{ video: typeof video === 'string' ? { url: video } : video },
|
|
2814
|
+
{ upload: client.waUploadToServer }
|
|
2815
|
+
);
|
|
2816
|
+
|
|
2817
|
+
const imageMsg = generateWAMessageFromContent(
|
|
2818
|
+
jid,
|
|
2819
|
+
{
|
|
2820
|
+
imageMessage: {
|
|
2821
|
+
...imagePrepared.imageMessage,
|
|
2822
|
+
contextInfo: { pairedMediaType: 5, statusSourceType: 0 },
|
|
2823
|
+
},
|
|
2824
|
+
},
|
|
2825
|
+
{}
|
|
2826
|
+
);
|
|
2827
|
+
|
|
2828
|
+
await client.relayMessage(jid, imageMsg.message, { messageId: imageMsg.key.id });
|
|
2829
|
+
|
|
2830
|
+
await client.relayMessage(
|
|
2831
|
+
jid,
|
|
2832
|
+
{
|
|
2833
|
+
videoMessage: {
|
|
2834
|
+
...videoPrepared.videoMessage,
|
|
2835
|
+
contextInfo: { pairedMediaType: 6, statusSourceType: 0 },
|
|
2836
|
+
},
|
|
2837
|
+
messageContextInfo: {
|
|
2838
|
+
messageAssociation: { associationType: 12, parentMessageKey: imageMsg.key },
|
|
2839
|
+
},
|
|
2840
|
+
},
|
|
2841
|
+
{}
|
|
2842
|
+
);
|
|
2843
|
+
|
|
2844
|
+
return imageMsg.key;
|
|
2845
|
+
}
|
|
2846
|
+
|
|
2847
|
+
/**
|
|
2848
|
+
* Send a support-ticket marker message (`messageContextInfo.supportPayload`) — a plain
|
|
2849
|
+
* conversation message tagged as an AI/support-bot ticket, distinct from richResponseMessage.
|
|
2850
|
+
* @param {import('../../WAProto/index.js').WASocket} client
|
|
2851
|
+
* @param {string} jid
|
|
2852
|
+
* @param {string} text
|
|
2853
|
+
* @param {{ ticketId?: string, isAiMessage?: boolean, shouldShowSystemMessage?: boolean, version?: number }} [options]
|
|
2854
|
+
*/
|
|
2855
|
+
static async sendSupportPayload(client, jid, text, { ticketId = crypto.randomUUID(), isAiMessage = true, shouldShowSystemMessage = true, version = 1 } = {}) {
|
|
2856
|
+
if (!client) throw new Error('Socket is required');
|
|
2857
|
+
if (typeof text !== 'string' || !text) throw new TypeError('sendSupportPayload(client, jid, text) requires a non-empty string text');
|
|
2858
|
+
|
|
2859
|
+
const msg = {
|
|
2860
|
+
conversation: text,
|
|
2861
|
+
messageContextInfo: {
|
|
2862
|
+
messageSecret: crypto.randomBytes(32),
|
|
2863
|
+
supportPayload: JSON.stringify({
|
|
2864
|
+
version,
|
|
2865
|
+
is_ai_message: isAiMessage,
|
|
2866
|
+
should_show_system_message: shouldShowSystemMessage,
|
|
2867
|
+
ticket_id: ticketId,
|
|
2868
|
+
}),
|
|
2869
|
+
},
|
|
2870
|
+
};
|
|
2871
|
+
|
|
2872
|
+
return client.relayMessage(jid, msg, {
|
|
2873
|
+
additionalNodes: [
|
|
2874
|
+
{ tag: 'bot', attrs: { biz_bot: '1' } },
|
|
2875
|
+
{ tag: 'biz', attrs: {} },
|
|
2876
|
+
],
|
|
2877
|
+
});
|
|
2878
|
+
}
|
|
2879
|
+
|
|
2880
|
+
/** The `unifiedResponse.response_id` this instance is currently pinned to. */
|
|
2881
|
+
get responseId() {
|
|
2882
|
+
return this._responseId;
|
|
2883
|
+
}
|
|
2884
|
+
}
|
|
2885
|
+
|
|
2886
|
+
/** Thin no-op subclass of `AIRich` — kept for drop-in compatibility with code ported from ourin-baileys that references `ORich` by name. */
|
|
2887
|
+
class ORich extends AIRich {}
|
|
2888
|
+
|
|
2889
|
+
export { AIRich, ORich };
|