@japofc/baileys 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/NOTICE.md +111 -0
- package/README.md +1254 -0
- package/WAProto/index.d.ts +9 -0
- package/WAProto/index.js +142276 -0
- package/engine-requirements.js +13 -0
- package/lib/Builders/A2UI.d.ts +242 -0
- package/lib/Builders/A2UI.js +308 -0
- package/lib/Builders/AIRich.d.ts +105 -0
- package/lib/Builders/AIRich.js +2286 -0
- package/lib/Builders/Button.d.ts +103 -0
- package/lib/Builders/Button.js +883 -0
- package/lib/Builders/ButtonV2.d.ts +28 -0
- package/lib/Builders/ButtonV2.js +144 -0
- package/lib/Builders/ButtonV3.d.ts +25 -0
- package/lib/Builders/ButtonV3.js +184 -0
- package/lib/Builders/Carousel.d.ts +22 -0
- package/lib/Builders/Carousel.js +111 -0
- package/lib/Builders/JapBaileys.d.ts +34 -0
- package/lib/Builders/JapBaileys.js +108 -0
- package/lib/Builders/Poll.d.ts +25 -0
- package/lib/Builders/Poll.js +128 -0
- package/lib/Builders/index.d.ts +17 -0
- package/lib/Builders/index.js +11 -0
- package/lib/Builders/shared.d.ts +67 -0
- package/lib/Builders/shared.js +652 -0
- package/lib/Defaults/index.d.ts +154 -0
- package/lib/Defaults/index.js +178 -0
- package/lib/Framework/Bot.d.ts +61 -0
- package/lib/Framework/Bot.js +248 -0
- package/lib/Framework/Context.d.ts +21 -0
- package/lib/Framework/Context.js +89 -0
- package/lib/Framework/MediaManager.d.ts +17 -0
- package/lib/Framework/MediaManager.js +151 -0
- package/lib/Framework/SessionManager.d.ts +13 -0
- package/lib/Framework/SessionManager.js +33 -0
- package/lib/Framework/StatsManager.d.ts +26 -0
- package/lib/Framework/StatsManager.js +119 -0
- package/lib/Framework/Store/SQLiteStore.d.ts +11 -0
- package/lib/Framework/Store/SQLiteStore.js +73 -0
- package/lib/Framework/index.d.ts +9 -0
- package/lib/Framework/index.js +10 -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 +54 -0
- package/lib/Signal/libsignal.js +430 -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 +395 -0
- package/lib/Socket/business.js +379 -0
- package/lib/Socket/chats.d.ts +136 -0
- package/lib/Socket/chats.js +1327 -0
- package/lib/Socket/communities.d.ts +562 -0
- package/lib/Socket/communities.js +439 -0
- package/lib/Socket/dugong.d.ts +117 -0
- package/lib/Socket/dugong.js +798 -0
- package/lib/Socket/groups.d.ts +285 -0
- package/lib/Socket/groups.js +354 -0
- package/lib/Socket/index.d.ts +585 -0
- package/lib/Socket/index.js +52 -0
- package/lib/Socket/messages-recv.d.ts +333 -0
- package/lib/Socket/messages-recv.js +1781 -0
- package/lib/Socket/messages-send.d.ts +353 -0
- package/lib/Socket/messages-send.js +1524 -0
- package/lib/Socket/mex.d.ts +1 -0
- package/lib/Socket/mex.js +41 -0
- package/lib/Socket/newsletter.d.ts +286 -0
- package/lib/Socket/newsletter.js +364 -0
- package/lib/Socket/socket.d.ts +60 -0
- package/lib/Socket/socket.js +1012 -0
- package/lib/Socket/username.d.ts +82 -0
- package/lib/Socket/username.js +159 -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 +428 -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 +117 -0
- package/lib/Types/Auth.js +1 -0
- package/lib/Types/Bussines.d.ts +25 -0
- package/lib/Types/Call.d.ts +15 -0
- package/lib/Types/Call.js +1 -0
- package/lib/Types/Chat.d.ts +124 -0
- package/lib/Types/Chat.js +7 -0
- package/lib/Types/Contact.d.ts +26 -0
- package/lib/Types/Contact.js +1 -0
- package/lib/Types/Events.d.ts +256 -0
- package/lib/Types/Events.js +1 -0
- package/lib/Types/GroupMetadata.d.ts +71 -0
- package/lib/Types/GroupMetadata.js +1 -0
- package/lib/Types/Label.d.ts +5 -0
- package/lib/Types/Label.js +24 -0
- package/lib/Types/LabelAssociation.d.ts +6 -0
- package/lib/Types/LabelAssociation.js +6 -0
- package/lib/Types/Message.d.ts +62 -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 +79 -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 +87 -0
- package/lib/Types/Signal.js +1 -0
- package/lib/Types/Socket.d.ts +138 -0
- package/lib/Types/Socket.js +1 -0
- package/lib/Types/State.d.ts +8 -0
- package/lib/Types/State.js +55 -0
- package/lib/Types/USync.d.ts +26 -0
- package/lib/Types/index.d.ts +68 -0
- package/lib/Types/index.js +27 -0
- package/lib/Utils/MessageBuilder.d.ts +2 -0
- package/lib/Utils/MessageBuilder.js +2 -0
- package/lib/Utils/PersistentStore.d.ts +60 -0
- package/lib/Utils/PersistentStore.js +592 -0
- package/lib/Utils/anti-delete.d.ts +74 -0
- package/lib/Utils/anti-delete.js +221 -0
- package/lib/Utils/auth-secure.d.ts +86 -0
- package/lib/Utils/auth-secure.js +764 -0
- package/lib/Utils/auth-utils.d.ts +57 -0
- package/lib/Utils/auth-utils.js +306 -0
- package/lib/Utils/auto-reply.d.ts +47 -0
- package/lib/Utils/auto-reply.js +157 -0
- package/lib/Utils/baileys-event-stream.d.ts +8 -0
- package/lib/Utils/baileys-event-stream.js +62 -0
- package/lib/Utils/browser-utils.d.ts +8 -0
- package/lib/Utils/browser-utils.js +27 -0
- package/lib/Utils/business.d.ts +49 -0
- package/lib/Utils/business.js +239 -0
- package/lib/Utils/button-helper-utils.d.ts +53 -0
- package/lib/Utils/button-helper-utils.js +312 -0
- package/lib/Utils/button-sender.d.ts +54 -0
- package/lib/Utils/button-sender.js +822 -0
- package/lib/Utils/chat-control.d.ts +159 -0
- package/lib/Utils/chat-control.js +230 -0
- package/lib/Utils/chat-history-helpers.d.ts +21 -0
- package/lib/Utils/chat-history-helpers.js +70 -0
- package/lib/Utils/chat-utils.d.ts +407 -0
- package/lib/Utils/chat-utils.js +885 -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/crypto.d.ts +41 -0
- package/lib/Utils/crypto.js +147 -0
- package/lib/Utils/decode-wa-message.d.ts +83 -0
- package/lib/Utils/decode-wa-message.js +316 -0
- package/lib/Utils/event-buffer.d.ts +12 -0
- package/lib/Utils/event-buffer.js +635 -0
- package/lib/Utils/generics.d.ts +69 -0
- package/lib/Utils/generics.js +438 -0
- package/lib/Utils/history-share.d.ts +11 -0
- package/lib/Utils/history-share.js +70 -0
- package/lib/Utils/history.d.ts +90 -0
- package/lib/Utils/history.js +146 -0
- package/lib/Utils/humanizer.d.ts +12 -0
- package/lib/Utils/humanizer.js +73 -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 +57 -0
- package/lib/Utils/index.js +56 -0
- package/lib/Utils/jid-tools.d.ts +6 -0
- package/lib/Utils/jid-tools.js +77 -0
- package/lib/Utils/link-preview.d.ts +12 -0
- package/lib/Utils/link-preview.js +84 -0
- package/lib/Utils/logger.d.ts +4 -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 +18 -0
- package/lib/Utils/media-messages.js +70 -0
- package/lib/Utils/media-set.d.ts +13 -0
- package/lib/Utils/media-set.js +171 -0
- package/lib/Utils/message-kind.d.ts +32 -0
- package/lib/Utils/message-kind.js +136 -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 +44 -0
- package/lib/Utils/message-search.js +188 -0
- package/lib/Utils/messages-media.d.ts +135 -0
- package/lib/Utils/messages-media.js +868 -0
- package/lib/Utils/messages.d.ts +44 -0
- package/lib/Utils/messages.js +2326 -0
- package/lib/Utils/meta-ai.d.ts +13 -0
- package/lib/Utils/meta-ai.js +74 -0
- package/lib/Utils/mini-app.d.ts +35 -0
- package/lib/Utils/mini-app.js +213 -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/past-participants.d.ts +14 -0
- package/lib/Utils/past-participants.js +40 -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 +82 -0
- package/lib/Utils/process-message.js +808 -0
- package/lib/Utils/qr-render.d.ts +59 -0
- package/lib/Utils/qr-render.js +179 -0
- package/lib/Utils/qrcodegen.d.ts +79 -0
- package/lib/Utils/qrcodegen.js +848 -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 +48 -0
- package/lib/Utils/rich-message-utils.js +444 -0
- package/lib/Utils/router.d.ts +37 -0
- package/lib/Utils/router.js +165 -0
- package/lib/Utils/scheduled-posts.d.ts +30 -0
- package/lib/Utils/scheduled-posts.js +110 -0
- package/lib/Utils/scheduling.d.ts +42 -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.d.ts +50 -0
- package/lib/Utils/status.js +107 -0
- package/lib/Utils/stickerpack.d.ts +51 -0
- package/lib/Utils/stickerpack.js +274 -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 +12 -0
- package/lib/Utils/tag.js +44 -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 +76 -0
- package/lib/Utils/templates.js +150 -0
- package/lib/Utils/transcribe.d.ts +12 -0
- package/lib/Utils/transcribe.js +107 -0
- package/lib/Utils/use-cache-manager-auth-state.d.ts +13 -0
- package/lib/Utils/use-cache-manager-auth-state.js +81 -0
- package/lib/Utils/use-multi-file-auth-state.d.ts +10 -0
- package/lib/Utils/use-multi-file-auth-state.js +166 -0
- package/lib/Utils/use-single-file-auth-state.d.ts +10 -0
- package/lib/Utils/use-single-file-auth-state.js +113 -0
- package/lib/Utils/use-sqlite-auth-state.d.ts +11 -0
- package/lib/Utils/use-sqlite-auth-state.js +169 -0
- package/lib/Utils/validate-connection.d.ts +42 -0
- package/lib/Utils/validate-connection.js +198 -0
- package/lib/Utils/vcard.d.ts +58 -0
- package/lib/Utils/vcard.js +104 -0
- package/lib/Utils/voice-note.d.ts +15 -0
- package/lib/Utils/voice-note.js +63 -0
- package/lib/VoIP/audio-feeder.d.ts +18 -0
- package/lib/VoIP/audio-feeder.js +160 -0
- package/lib/VoIP/call-recorder.d.ts +13 -0
- package/lib/VoIP/call-recorder.js +73 -0
- package/lib/VoIP/index.d.ts +86 -0
- package/lib/VoIP/index.js +611 -0
- package/lib/VoIP/relay-transport.d.ts +43 -0
- package/lib/VoIP/relay-transport.js +563 -0
- package/lib/VoIP/signaling.d.ts +26 -0
- package/lib/VoIP/signaling.js +662 -0
- package/lib/VoIP/types.d.ts +69 -0
- package/lib/VoIP/types.js +17 -0
- package/lib/VoIP/wasm-engine.d.ts +120 -0
- package/lib/VoIP/wasm-engine.js +1358 -0
- package/lib/VoIP/worker-bootstrap.d.ts +4 -0
- package/lib/VoIP/worker-bootstrap.js +1046 -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 +74 -0
- package/lib/WABinary/generic-utils.js +276 -0
- package/lib/WABinary/index.d.ts +5 -0
- package/lib/WABinary/index.js +5 -0
- package/lib/WABinary/jid-utils.d.ts +28 -0
- package/lib/WABinary/jid-utils.js +95 -0
- package/lib/WABinary/types.d.ts +19 -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/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 +62 -0
- package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.d.ts +12 -0
- package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.js +26 -0
- package/lib/WAUSync/Protocols/USyncStatusProtocol.d.ts +12 -0
- package/lib/WAUSync/Protocols/USyncStatusProtocol.js +37 -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 +50 -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 +7 -0
- package/lib/WAUSync/Protocols/index.js +7 -0
- package/lib/WAUSync/USyncQuery.d.ts +20 -0
- package/lib/WAUSync/USyncQuery.js +121 -0
- package/lib/WAUSync/USyncUser.d.ts +16 -0
- package/lib/WAUSync/USyncUser.js +30 -0
- package/lib/WAUSync/index.d.ts +3 -0
- package/lib/WAUSync/index.js +3 -0
- package/lib/assets/wasm/loader.d.ts +5 -0
- package/lib/assets/wasm/loader.js +5 -0
- package/lib/assets/wasm/whatsapp.wasm +0 -0
- package/lib/assets/wasm/worker-modules.d.ts +5 -0
- package/lib/assets/wasm/worker-modules.js +273 -0
- package/lib/index.d.ts +18 -0
- package/lib/index.js +23 -0
- package/package.json +180 -0
- package/postinstall-banner.js +82 -0
|
@@ -0,0 +1,883 @@
|
|
|
1
|
+
import { BaseBuilder, generateWAMessageFromContent, prepareWAMessageMedia, generateMessageIDV2, getBizBinaryNode, crypto } from './shared.js';
|
|
2
|
+
class Button extends BaseBuilder {
|
|
3
|
+
#client;
|
|
4
|
+
|
|
5
|
+
/** @param {import('../../WAProto/index.js').WASocket} client Active Baileys socket (must expose `relayMessage`). */
|
|
6
|
+
constructor(client) {
|
|
7
|
+
super();
|
|
8
|
+
if (!client) {
|
|
9
|
+
throw new Error('Socket is required');
|
|
10
|
+
}
|
|
11
|
+
this.#client = client;
|
|
12
|
+
|
|
13
|
+
this._buttons = [];
|
|
14
|
+
this._data;
|
|
15
|
+
this._currentSelectionIndex = -1;
|
|
16
|
+
this._currentSectionIndex = -1;
|
|
17
|
+
this._params = {};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Attach a video as the interactive header. @param {string|Buffer} path Url or buffer. */
|
|
21
|
+
setVideo(path, options = {}) {
|
|
22
|
+
if (!path) throw new Error('Url or buffer needed');
|
|
23
|
+
Buffer.isBuffer(path) ? (this._data = { video: path, ...options }) : (this._data = { video: { url: path }, ...options });
|
|
24
|
+
return this;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Attach an image as the interactive header. @param {string|Buffer} path Url or buffer. */
|
|
28
|
+
setImage(path, options = {}) {
|
|
29
|
+
if (!path) throw new Error('Url or buffer needed');
|
|
30
|
+
Buffer.isBuffer(path) ? (this._data = { image: path, ...options }) : (this._data = { image: { url: path }, ...options });
|
|
31
|
+
return this;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Attach a document as the interactive header. @param {string|Buffer} path Url or buffer. */
|
|
35
|
+
setDocument(path, options = {}) {
|
|
36
|
+
if (!path) throw new Error('Url or buffer needed');
|
|
37
|
+
Buffer.isBuffer(path) ? (this._data = { document: path, ...options }) : (this._data = { document: { url: path }, ...options });
|
|
38
|
+
return this;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Set a raw pre-built header media object (bypasses setVideo/setImage/setDocument shorthands). */
|
|
42
|
+
setMedia(obj) {
|
|
43
|
+
if (typeof obj !== 'object' || obj === null || Array.isArray(obj)) {
|
|
44
|
+
throw new TypeError('Media must be a plain object');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
this._data = obj;
|
|
48
|
+
return this;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Remove every button added so far (keeps title/body/footer/media). */
|
|
52
|
+
clearButtons() {
|
|
53
|
+
this._buttons = [];
|
|
54
|
+
return this;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Overwrite the message-level `nativeFlowMessage.messageParamsJson` payload wholesale. */
|
|
58
|
+
setParams(obj) {
|
|
59
|
+
this._params = obj;
|
|
60
|
+
return this;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// JAP@Add 26-08-26 --- setBloksWidget(): Meta's Bloks/A2UI format, a sibling field to
|
|
64
|
+
// nativeFlowMessage on interactiveMessage (NOT part of the AIRich rich-response system —
|
|
65
|
+
// this is a real native interactive UI: checkboxes/text fields/buttons actually work).
|
|
66
|
+
// Captured traffic gives the A2UI payload as a flat `components` array where every node has
|
|
67
|
+
// an `id` and children/child reference OTHER nodes by id string. Authoring that by hand is
|
|
68
|
+
// error-prone (dangling ids, ordering), so this accepts a plain nested tree instead —
|
|
69
|
+
// { component, ...props, children: [...] } / { component, ...props, child: {...} } — and
|
|
70
|
+
// flattens it into that array itself, auto-assigning ids.
|
|
71
|
+
//
|
|
72
|
+
// JAP@Fix 26-08-26 (v2) --- Modal is a different shape: `trigger`/`content` point to a
|
|
73
|
+
// SIBLING node by id (e.g. the Button that opens it), not a fresh child — captured traffic
|
|
74
|
+
// confirmed `{ trigger: "<id of an existing Button node>", content: "<id of its body>" }`.
|
|
75
|
+
// Nesting it as a plain `child`/`children` would duplicate the trigger node. So:
|
|
76
|
+
// - any prop whose value is `{ component: ..., ... }` is now auto-flattened as a nested
|
|
77
|
+
// node too (not just `child`/`children`) — covers Modal.content directly.
|
|
78
|
+
// - a node can carry a builder-only `ref: 'name'` tag; another prop can then point back
|
|
79
|
+
// at it with `{ $ref: 'name' }`, resolved to that node's real id after the whole tree
|
|
80
|
+
// is walked (order-independent). `ref` itself is stripped and never reaches the wire.
|
|
81
|
+
//
|
|
82
|
+
// JAP@Add 01-09-26 --- JSDoc typedefs for the "basic" A2UI catalog below (component set +
|
|
83
|
+
// props confirmed from captured traffic — see the fork audit notes). This is NOT the
|
|
84
|
+
// official A2UI spec (there isn't a public one we have access to), just what's been observed
|
|
85
|
+
// on the wire, so BloksNode ends with a permissive `AnyBloksNode` fallback: known components
|
|
86
|
+
// get full editor autocomplete + prop hints, anything else still type-checks and still works
|
|
87
|
+
// at runtime (setBloksWidget()'s own validation only ever requires a "component" string).
|
|
88
|
+
/**
|
|
89
|
+
* @typedef {{ $ref: string }} BloksRef
|
|
90
|
+
* Points back at a sibling node tagged `ref: 'name'` elsewhere in the same tree (currently
|
|
91
|
+
* only needed for `Modal.trigger`/`Modal.content` — everything else nests directly).
|
|
92
|
+
*/
|
|
93
|
+
/**
|
|
94
|
+
* @typedef {Object} BloksNodeBase
|
|
95
|
+
* @property {string} [ref] Builder-only tag so another node can reference this one via `{ $ref: ref }`. Stripped before send.
|
|
96
|
+
*/
|
|
97
|
+
/**
|
|
98
|
+
* @typedef {BloksNodeBase & { component: 'Column'|'Row', weight?: number, justify?: string, children?: BloksNode[] }} ColumnRowNode
|
|
99
|
+
* @typedef {BloksNodeBase & { component: 'Text', text: string, variant?: string }} TextNode
|
|
100
|
+
* @typedef {BloksNodeBase & { component: 'Icon', name: string }} IconNode
|
|
101
|
+
* @typedef {BloksNodeBase & { component: 'Divider' }} DividerNode
|
|
102
|
+
* @typedef {BloksNodeBase & { component: 'Image', url: string, variant?: string, fit?: string }} ImageNode
|
|
103
|
+
* @typedef {BloksNodeBase & { component: 'Video', url: string }} VideoNode
|
|
104
|
+
* @typedef {BloksNodeBase & { component: 'List', children?: BloksNode[] }} ListNode
|
|
105
|
+
* @typedef {BloksNodeBase & { component: 'TextField', label?: string, value?: string, variant?: string }} TextFieldNode
|
|
106
|
+
* @typedef {BloksNodeBase & { component: 'DateTimeInput', label?: string, value?: string, enableDate?: boolean, enableTime?: boolean }} DateTimeInputNode
|
|
107
|
+
* @typedef {BloksNodeBase & { component: 'Slider', label?: string, min?: number, max?: number, value?: number }} SliderNode
|
|
108
|
+
* @typedef {BloksNodeBase & { component: 'CheckBox', label?: string, value?: boolean }} CheckBoxNode
|
|
109
|
+
* @typedef {BloksNodeBase & { component: 'ChoicePicker', label?: string, variant?: string, displayStyle?: string, options?: Array<{label: string, value: string}>, value?: string }} ChoicePickerNode
|
|
110
|
+
* @typedef {BloksNodeBase & { component: 'Button', child?: BloksNode, variant?: string, action?: { call: string, args?: Record<string, any> } }} BloksButtonNode
|
|
111
|
+
* Note: unlike the CTA/native-flow `Button` class elsewhere in this file, an A2UI Button node
|
|
112
|
+
* has no `label`/`type`+`name` — its label comes from a nested `child` (usually a `Text` node),
|
|
113
|
+
* and tapping it fires `action.call` (with `action.args`), not a native-flow button name.
|
|
114
|
+
* @typedef {BloksNodeBase & { component: 'Modal', trigger: string|BloksRef, content: BloksNode|string|BloksRef }} ModalNode
|
|
115
|
+
* @typedef {BloksNodeBase & { component: 'Tabs', tabs: Array<{title: string, child: BloksNode}> }} TabsNode
|
|
116
|
+
* @typedef {BloksNodeBase & { component: 'Card', child?: BloksNode }} CardNode
|
|
117
|
+
* @typedef {BloksNodeBase & { component: 'AudioPlayer', url: string, description?: string }} AudioPlayerNode
|
|
118
|
+
* @typedef {BloksNodeBase & { component: string, [key: string]: any }} AnyBloksNode Fallback for components not yet confirmed on the wire — still works, just no prop-level autocomplete.
|
|
119
|
+
* @typedef {ColumnRowNode|TextNode|IconNode|DividerNode|ImageNode|VideoNode|ListNode|TextFieldNode|DateTimeInputNode|SliderNode|CheckBoxNode|ChoicePickerNode|BloksButtonNode|ModalNode|TabsNode|CardNode|AudioPlayerNode|AnyBloksNode} BloksNode
|
|
120
|
+
*/
|
|
121
|
+
#flattenBloks(tree, out, ctx = { n: 0, refs: new Map(), pending: [] }, id = 'root') {
|
|
122
|
+
if (!tree || typeof tree !== 'object') throw new TypeError('setBloksWidget: every node needs a "component" type');
|
|
123
|
+
const { component, children, child, ref, ...rest } = tree;
|
|
124
|
+
if (typeof component !== 'string' || !component) throw new TypeError('setBloksWidget: every node needs a "component" type');
|
|
125
|
+
|
|
126
|
+
const isTreeNode = (v) => v && typeof v === 'object' && !Array.isArray(v) && typeof v.component === 'string';
|
|
127
|
+
const isRefMarker = (v) => v && typeof v === 'object' && !Array.isArray(v) && typeof v.$ref === 'string' && Object.keys(v).length === 1;
|
|
128
|
+
|
|
129
|
+
const node = { id, component };
|
|
130
|
+
for (const [k, v] of Object.entries(rest)) {
|
|
131
|
+
if (isRefMarker(v)) {
|
|
132
|
+
node[k] = null; // resolved once the full tree (and its `ref` tags) has been walked
|
|
133
|
+
ctx.pending.push({ node, key: k, refName: v.$ref });
|
|
134
|
+
} else if (isTreeNode(v)) {
|
|
135
|
+
node[k] = this.#flattenBloks(v, out, ctx, `n${ctx.n++}`);
|
|
136
|
+
} else {
|
|
137
|
+
node[k] = v;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (Array.isArray(children)) {
|
|
142
|
+
node.children = children.map((c) => this.#flattenBloks(c, out, ctx, `n${ctx.n++}`));
|
|
143
|
+
} else if (child) {
|
|
144
|
+
node.child = this.#flattenBloks(child, out, ctx, `n${ctx.n++}`);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (ref) ctx.refs.set(ref, id);
|
|
148
|
+
|
|
149
|
+
out.push(node);
|
|
150
|
+
return id;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Set a Bloks/A2UI native widget (`bloksWidget`, `type: "im_a2ui"`) — a real interactive
|
|
155
|
+
* screen (images, video, checkboxes, text fields, buttons that fire an `action`), not a
|
|
156
|
+
* static card. Pass a nested tree; ids are assigned automatically.
|
|
157
|
+
*
|
|
158
|
+
* For components that reference a SIBLING node instead of nesting one — currently just
|
|
159
|
+
* `Modal.trigger` — tag the source node with `ref: 'someName'` and point at it with
|
|
160
|
+
* `{ $ref: 'someName' }`. Everything else (including `Modal.content`) can just be nested
|
|
161
|
+
* directly, no special key needed.
|
|
162
|
+
* @param {BloksNode} tree Root node, e.g. `{ component: 'Column', children: [...] }`.
|
|
163
|
+
* @param {{uuid?: string, catalogId?: string, surfaceId?: string, version?: string}} [options]
|
|
164
|
+
*/
|
|
165
|
+
setBloksWidget(tree, { uuid = crypto.randomUUID(), catalogId = 'https://a2ui.org/specification/v0_9/catalogs/basic/catalog.json', surfaceId, version = 'v0.9' } = {}) {
|
|
166
|
+
const components = [];
|
|
167
|
+
const ctx = { n: 0, refs: new Map(), pending: [] };
|
|
168
|
+
this.#flattenBloks(tree, components, ctx);
|
|
169
|
+
for (const { node, key, refName } of ctx.pending) {
|
|
170
|
+
const resolved = ctx.refs.get(refName);
|
|
171
|
+
if (!resolved) throw new Error(`setBloksWidget: ref "${refName}" (used on "${key}") was never declared with ref: "${refName}" on any node`);
|
|
172
|
+
node[key] = resolved;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
this._bloksWidget = {
|
|
176
|
+
uuid,
|
|
177
|
+
data: JSON.stringify({
|
|
178
|
+
version,
|
|
179
|
+
createSurface: {
|
|
180
|
+
surfaceId: surfaceId ?? `starcore-widget=${uuid}`,
|
|
181
|
+
catalogId,
|
|
182
|
+
components,
|
|
183
|
+
},
|
|
184
|
+
}),
|
|
185
|
+
type: 'im_a2ui',
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
return this;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Low-level escape hatch: push a raw native-flow button by name. Prefer the
|
|
193
|
+
* dedicated `add*()` helpers below when one exists — they validate the
|
|
194
|
+
* required keys for that button type.
|
|
195
|
+
* @param {string} name Native-flow button name, e.g. 'quick_reply', 'cta_url'.
|
|
196
|
+
* @param {string|Record<string, any>} params Either a pre-stringified JSON payload or a plain object.
|
|
197
|
+
*/
|
|
198
|
+
addButton(name, params) {
|
|
199
|
+
if (typeof name !== 'string' || !name.trim()) {
|
|
200
|
+
throw new TypeError('addButton(name, params) requires a non-empty string name');
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
this._buttons.push({
|
|
204
|
+
name,
|
|
205
|
+
buttonParamsJson: typeof params === 'string' ? params : JSON.stringify(params),
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
return this;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/** Append a row to the section currently open on the last `addSelection()` (call `makeSection()` first). */
|
|
212
|
+
makeRow(header = '', title = '', description = '', id = '') {
|
|
213
|
+
if (this._currentSelectionIndex === -1 || this._currentSectionIndex === -1) {
|
|
214
|
+
throw new Error('You need to create a selection and a section first');
|
|
215
|
+
}
|
|
216
|
+
if (!title || !id) {
|
|
217
|
+
throw new TypeError('makeRow() requires both a title and an id');
|
|
218
|
+
}
|
|
219
|
+
const buttonParams = JSON.parse(this._buttons[this._currentSelectionIndex].buttonParamsJson);
|
|
220
|
+
buttonParams.sections[this._currentSectionIndex].rows.push({ header, title, description, id });
|
|
221
|
+
this._buttons[this._currentSelectionIndex].buttonParamsJson = JSON.stringify(buttonParams);
|
|
222
|
+
return this;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/** Open a new section on the `single_select` button added by the last `addSelection()` call. */
|
|
226
|
+
makeSection(title = '', highlight_label = '') {
|
|
227
|
+
if (this._currentSelectionIndex === -1) {
|
|
228
|
+
throw new Error('You need to create a selection first');
|
|
229
|
+
}
|
|
230
|
+
const buttonParams = JSON.parse(this._buttons[this._currentSelectionIndex].buttonParamsJson);
|
|
231
|
+
buttonParams.sections.push({ title, highlight_label, rows: [] });
|
|
232
|
+
this._currentSectionIndex = buttonParams.sections.length - 1;
|
|
233
|
+
this._buttons[this._currentSelectionIndex].buttonParamsJson = JSON.stringify(buttonParams);
|
|
234
|
+
return this;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/** Start a `single_select` (in-button picker list) button. Follow with `makeSection()` + `makeRow()`. */
|
|
238
|
+
addSelection(title, options = {}) {
|
|
239
|
+
if (!title) throw new TypeError('addSelection(title) requires a non-empty title');
|
|
240
|
+
this._buttons.push({ ...options, name: 'single_select', buttonParamsJson: JSON.stringify({ title, sections: [] }) });
|
|
241
|
+
this._currentSelectionIndex = this._buttons.length - 1;
|
|
242
|
+
this._currentSectionIndex = -1;
|
|
243
|
+
return this;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Add a `quick_reply` button — sends `id` back as the interactive-response id when tapped.
|
|
248
|
+
* @param {string} display_text Button label.
|
|
249
|
+
* @param {string} id Unique id returned on tap; required, WA silently drops replies without one.
|
|
250
|
+
*/
|
|
251
|
+
addReply(display_text = '', id = '', options = {}) {
|
|
252
|
+
if (!display_text || !id) {
|
|
253
|
+
throw new TypeError('addReply(display_text, id) requires both a label and a unique id');
|
|
254
|
+
}
|
|
255
|
+
this._buttons.push({
|
|
256
|
+
name: 'quick_reply',
|
|
257
|
+
buttonParamsJson: JSON.stringify({
|
|
258
|
+
display_text,
|
|
259
|
+
id,
|
|
260
|
+
...options,
|
|
261
|
+
}),
|
|
262
|
+
});
|
|
263
|
+
return this;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Add a `cta_call` (tap-to-dial) button.
|
|
268
|
+
* @param {string} display_text Button label.
|
|
269
|
+
* @param {string} phone_number Phone number to dial, e.g. '+15551234567'.
|
|
270
|
+
*/
|
|
271
|
+
// JAP@Fix 22-08-26 (v4.7) --- second arg used to be written into buttonParamsJson.id, but
|
|
272
|
+
// the cta_call schema keys on `phone_number` (confirmed against @chatunity/baileys,
|
|
273
|
+
// @neoxr/wb, and a upstream#2626 working example) — `id` is simply ignored
|
|
274
|
+
// by WhatsApp for this button, so every button built with the old addCall() silently
|
|
275
|
+
// rendered with no dial action. Kept the same 2nd-positional-arg call shape so existing
|
|
276
|
+
// call sites keep working; only the wire key changed.
|
|
277
|
+
addCall(display_text = '', phone_number = '', options = {}) {
|
|
278
|
+
if (!display_text || !phone_number) {
|
|
279
|
+
throw new TypeError('addCall(display_text, phone_number) requires both a label and a phone number');
|
|
280
|
+
}
|
|
281
|
+
this._buttons.push({
|
|
282
|
+
name: 'cta_call',
|
|
283
|
+
buttonParamsJson: JSON.stringify({
|
|
284
|
+
display_text,
|
|
285
|
+
phone_number,
|
|
286
|
+
...options,
|
|
287
|
+
}),
|
|
288
|
+
});
|
|
289
|
+
return this;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/** Add a `cta_reminder` button (schedules an in-chat reminder chip). */
|
|
293
|
+
addReminder(display_text = '', id = '', options = {}) {
|
|
294
|
+
if (!display_text || !id) {
|
|
295
|
+
throw new TypeError('addReminder(display_text, id) requires both a label and a unique id');
|
|
296
|
+
}
|
|
297
|
+
this._buttons.push({
|
|
298
|
+
name: 'cta_reminder',
|
|
299
|
+
buttonParamsJson: JSON.stringify({
|
|
300
|
+
display_text,
|
|
301
|
+
id,
|
|
302
|
+
...options,
|
|
303
|
+
}),
|
|
304
|
+
});
|
|
305
|
+
return this;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/** Add a `cta_cancel_reminder` button, pairs with `addReminder()`. */
|
|
309
|
+
addCancelReminder(display_text = '', id = '', options = {}) {
|
|
310
|
+
if (!display_text || !id) {
|
|
311
|
+
throw new TypeError('addCancelReminder(display_text, id) requires both a label and a unique id');
|
|
312
|
+
}
|
|
313
|
+
this._buttons.push({
|
|
314
|
+
name: 'cta_cancel_reminder',
|
|
315
|
+
buttonParamsJson: JSON.stringify({
|
|
316
|
+
display_text,
|
|
317
|
+
id,
|
|
318
|
+
...options,
|
|
319
|
+
}),
|
|
320
|
+
});
|
|
321
|
+
return this;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/** Add an `address_message` button (prompts the user's saved-address picker). */
|
|
325
|
+
addAddress(display_text = '', id = '', options = {}) {
|
|
326
|
+
if (!display_text || !id) {
|
|
327
|
+
throw new TypeError('addAddress(display_text, id) requires both a label and a unique id');
|
|
328
|
+
}
|
|
329
|
+
this._buttons.push({
|
|
330
|
+
name: 'address_message',
|
|
331
|
+
buttonParamsJson: JSON.stringify({
|
|
332
|
+
display_text,
|
|
333
|
+
id,
|
|
334
|
+
...options,
|
|
335
|
+
}),
|
|
336
|
+
});
|
|
337
|
+
return this;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/** Add a `send_location` button (requests the user's live location). */
|
|
341
|
+
addLocation(options = {}) {
|
|
342
|
+
this._buttons.push({
|
|
343
|
+
name: 'send_location',
|
|
344
|
+
buttonParamsJson: JSON.stringify(options),
|
|
345
|
+
});
|
|
346
|
+
return this;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Add a `cta_url` button.
|
|
351
|
+
* @param {string} display_text Button label.
|
|
352
|
+
* @param {string} url Url opened on tap.
|
|
353
|
+
* @param {boolean} webview_interaction Open inside WhatsApp's in-app webview instead of the system browser.
|
|
354
|
+
*/
|
|
355
|
+
// JAP@Add 22-08-26 (v4.7) --- `merchant_url` is present (and equal to `url`) on every
|
|
356
|
+
// cta_url button seen in captured client traffic, alongside `url`. Defaulted here rather
|
|
357
|
+
// than left for the caller to remember; still overridable via options if it should differ.
|
|
358
|
+
addUrl(display_text = '', url = '', webview_interaction = false, options = {}) {
|
|
359
|
+
if (!display_text || !url) {
|
|
360
|
+
throw new TypeError('addUrl(display_text, url) requires both a label and a url');
|
|
361
|
+
}
|
|
362
|
+
this._buttons.push({
|
|
363
|
+
...options,
|
|
364
|
+
name: 'cta_url',
|
|
365
|
+
buttonParamsJson: JSON.stringify({
|
|
366
|
+
display_text,
|
|
367
|
+
url,
|
|
368
|
+
merchant_url: url,
|
|
369
|
+
webview_interaction,
|
|
370
|
+
...options,
|
|
371
|
+
}),
|
|
372
|
+
});
|
|
373
|
+
return this;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/** Add a `cta_copy` button (copies `copy_code` to the user's clipboard on tap). */
|
|
377
|
+
addCopy(display_text = '', copy_code = '', options = {}) {
|
|
378
|
+
if (!display_text || !copy_code) {
|
|
379
|
+
throw new TypeError('addCopy(display_text, copy_code) requires both a label and the text to copy');
|
|
380
|
+
}
|
|
381
|
+
this._buttons.push({
|
|
382
|
+
name: 'cta_copy',
|
|
383
|
+
buttonParamsJson: JSON.stringify({
|
|
384
|
+
display_text,
|
|
385
|
+
copy_code,
|
|
386
|
+
...options,
|
|
387
|
+
}),
|
|
388
|
+
});
|
|
389
|
+
return this;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* Add an `open_webview` button — opens a titled in-app webview (distinct from
|
|
394
|
+
* `cta_url`'s `webview_interaction` flag: this is its own native-flow name).
|
|
395
|
+
* @param {string} title Webview title shown in the header bar.
|
|
396
|
+
* @param {string} url Url loaded inside the webview.
|
|
397
|
+
*/
|
|
398
|
+
addOpenWebview(title = '', url = '', options = {}) {
|
|
399
|
+
if (!title || !url) {
|
|
400
|
+
throw new TypeError('addOpenWebview(title, url) requires both a title and a url');
|
|
401
|
+
}
|
|
402
|
+
this._buttons.push({
|
|
403
|
+
name: 'open_webview',
|
|
404
|
+
buttonParamsJson: JSON.stringify({
|
|
405
|
+
title,
|
|
406
|
+
link: { url },
|
|
407
|
+
...options,
|
|
408
|
+
}),
|
|
409
|
+
});
|
|
410
|
+
return this;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* Add a `cta_catalog` button (opens the sender's WhatsApp Business catalog).
|
|
415
|
+
* JAP@Add 22-08-26 (v4.7) --- WA only shows the catalog action for accounts
|
|
416
|
+
* that actually have a catalog attached; on regular accounts the button may
|
|
417
|
+
* render inert. See Button.#SPECIAL_FLOW for the native-flow node this name requires.
|
|
418
|
+
*/
|
|
419
|
+
addCatalog(display_text = '', options = {}) {
|
|
420
|
+
this._buttons.push({
|
|
421
|
+
name: 'cta_catalog',
|
|
422
|
+
buttonParamsJson: JSON.stringify({
|
|
423
|
+
...(display_text ? { display_text } : {}),
|
|
424
|
+
...options,
|
|
425
|
+
}),
|
|
426
|
+
});
|
|
427
|
+
return this;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/**
|
|
431
|
+
* Add an `automated_greeting_message_view_catalog` button — the "View catalog"
|
|
432
|
+
* action WhatsApp Business shows on the automated greeting message.
|
|
433
|
+
* JAP@Add 22-08-26 (v4.7). Business-account only; see Button.#SPECIAL_FLOW.
|
|
434
|
+
*/
|
|
435
|
+
addViewCatalog(options = {}) {
|
|
436
|
+
this._buttons.push({
|
|
437
|
+
name: 'automated_greeting_message_view_catalog',
|
|
438
|
+
buttonParamsJson: JSON.stringify(options),
|
|
439
|
+
});
|
|
440
|
+
return this;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/**
|
|
444
|
+
* Add a `call_permission_request` button — asks the user to grant call
|
|
445
|
+
* permission before a voice/video call can be placed.
|
|
446
|
+
* JAP@Add 22-08-26 (v4.7). See Button.#SPECIAL_FLOW.
|
|
447
|
+
*/
|
|
448
|
+
addCallPermission(display_text = '', options = {}) {
|
|
449
|
+
this._buttons.push({
|
|
450
|
+
name: 'call_permission_request',
|
|
451
|
+
buttonParamsJson: JSON.stringify({
|
|
452
|
+
...(display_text ? { display_text } : {}),
|
|
453
|
+
...options,
|
|
454
|
+
}),
|
|
455
|
+
});
|
|
456
|
+
return this;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* Add a `payment_info` button carrying a structured payment-settings payload
|
|
461
|
+
* (e.g. a PIX static-code block). Payload shape is dictated by WhatsApp's
|
|
462
|
+
* payment flows and is passed through as given — validate it yourself.
|
|
463
|
+
* JAP@Add 22-08-26 (v4.7). Business/payment-enabled accounts only.
|
|
464
|
+
* @param {Record<string, any>} payload e.g. `{ payment_settings: [{ type: 'pix_static_code', pix_static_code: {...} }] }`.
|
|
465
|
+
*/
|
|
466
|
+
addPaymentInfo(payload = {}) {
|
|
467
|
+
if (typeof payload !== 'object' || payload === null || Array.isArray(payload)) {
|
|
468
|
+
throw new TypeError('addPaymentInfo(payload) requires a plain object');
|
|
469
|
+
}
|
|
470
|
+
this._buttons.push({
|
|
471
|
+
name: 'payment_info',
|
|
472
|
+
buttonParamsJson: JSON.stringify(payload),
|
|
473
|
+
});
|
|
474
|
+
return this;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
/**
|
|
478
|
+
* Add a `review_and_pay` button (order/payment summary flow).
|
|
479
|
+
* JAP@Add 22-08-26 (v4.7). Server-validated by WhatsApp; malformed or
|
|
480
|
+
* unauthorized payloads are typically ignored rather than erroring locally.
|
|
481
|
+
* @param {Record<string, any>} payload Order/payment summary payload.
|
|
482
|
+
*/
|
|
483
|
+
addReviewAndPay(payload = {}) {
|
|
484
|
+
if (typeof payload !== 'object' || payload === null || Array.isArray(payload)) {
|
|
485
|
+
throw new TypeError('addReviewAndPay(payload) requires a plain object');
|
|
486
|
+
}
|
|
487
|
+
this._buttons.push({
|
|
488
|
+
name: 'review_and_pay',
|
|
489
|
+
buttonParamsJson: JSON.stringify(payload),
|
|
490
|
+
});
|
|
491
|
+
return this;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
* Add a `wa_payment_transaction_details` button referencing a prior transaction.
|
|
496
|
+
* JAP@Add 22-08-26 (v4.7). See Button.#SPECIAL_FLOW.
|
|
497
|
+
*/
|
|
498
|
+
addTransactionDetails(payload = {}) {
|
|
499
|
+
if (typeof payload !== 'object' || payload === null || Array.isArray(payload)) {
|
|
500
|
+
throw new TypeError('addTransactionDetails(payload) requires a plain object');
|
|
501
|
+
}
|
|
502
|
+
this._buttons.push({
|
|
503
|
+
name: 'wa_payment_transaction_details',
|
|
504
|
+
buttonParamsJson: JSON.stringify(payload),
|
|
505
|
+
});
|
|
506
|
+
return this;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
/**
|
|
510
|
+
* Add an `mpm` (multi-product message) button referencing a set of catalog items.
|
|
511
|
+
* JAP@Add 22-08-26 (v4.7). Business-catalog accounts only; see Button.#SPECIAL_FLOW.
|
|
512
|
+
*/
|
|
513
|
+
addMultiProduct(payload = {}) {
|
|
514
|
+
if (typeof payload !== 'object' || payload === null || Array.isArray(payload)) {
|
|
515
|
+
throw new TypeError('addMultiProduct(payload) requires a plain object');
|
|
516
|
+
}
|
|
517
|
+
this._buttons.push({
|
|
518
|
+
name: 'mpm',
|
|
519
|
+
buttonParamsJson: JSON.stringify(payload),
|
|
520
|
+
});
|
|
521
|
+
return this;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
|
|
525
|
+
/** Native-flow `payment_key_info` shortcut. Payload is passed through unchanged. */
|
|
526
|
+
addPaymentKeyInfo(payload = {}) {
|
|
527
|
+
if (typeof payload !== 'object' || payload === null || Array.isArray(payload)) throw new TypeError('addPaymentKeyInfo(payload) requires a plain object');
|
|
528
|
+
this._buttons.push({ name: 'payment_key_info', buttonParamsJson: JSON.stringify(payload) });
|
|
529
|
+
return this;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
/** Native-flow `booking_confirmation` shortcut. Payload is passed through unchanged. */
|
|
533
|
+
addBookingConfirmation(payload = {}) {
|
|
534
|
+
if (typeof payload !== 'object' || payload === null || Array.isArray(payload)) throw new TypeError('addBookingConfirmation(payload) requires a plain object');
|
|
535
|
+
this._buttons.push({ name: 'booking_confirmation', buttonParamsJson: JSON.stringify(payload) });
|
|
536
|
+
return this;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
/** Native-flow `card_message` shortcut, matching this fork's prepareNativeFlowButtons(). */
|
|
540
|
+
addCardMessage(payload = {}) {
|
|
541
|
+
if (typeof payload !== 'object' || payload === null || Array.isArray(payload)) throw new TypeError('addCardMessage(payload) requires a plain object');
|
|
542
|
+
this._buttons.push({ name: 'card_message', buttonParamsJson: JSON.stringify(payload) });
|
|
543
|
+
return this;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
/** Native-flow `order_details` shortcut. */
|
|
547
|
+
addOrderDetails(payload = {}) {
|
|
548
|
+
if (typeof payload !== 'object' || payload === null || Array.isArray(payload)) throw new TypeError('addOrderDetails(payload) requires a plain object');
|
|
549
|
+
this._buttons.push({ name: 'order_details', buttonParamsJson: JSON.stringify(payload) });
|
|
550
|
+
return this;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
/** Native-flow `order_status` shortcut. */
|
|
554
|
+
addOrderStatus(payload = {}) {
|
|
555
|
+
if (typeof payload !== 'object' || payload === null || Array.isArray(payload)) throw new TypeError('addOrderStatus(payload) requires a plain object');
|
|
556
|
+
this._buttons.push({ name: 'order_status', buttonParamsJson: JSON.stringify(payload) });
|
|
557
|
+
return this;
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
/** Native-flow `payment_status` shortcut. */
|
|
561
|
+
addPaymentStatus(payload = {}) {
|
|
562
|
+
if (typeof payload !== 'object' || payload === null || Array.isArray(payload)) throw new TypeError('addPaymentStatus(payload) requires a plain object');
|
|
563
|
+
this._buttons.push({ name: 'payment_status', buttonParamsJson: JSON.stringify(payload) });
|
|
564
|
+
return this;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
/** Native-flow `payment_method` shortcut. */
|
|
568
|
+
addPaymentMethod(payload = {}) {
|
|
569
|
+
if (typeof payload !== 'object' || payload === null || Array.isArray(payload)) throw new TypeError('addPaymentMethod(payload) requires a plain object');
|
|
570
|
+
this._buttons.push({ name: 'payment_method', buttonParamsJson: JSON.stringify(payload) });
|
|
571
|
+
return this;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
/** Native-flow `track_order` shortcut. */
|
|
575
|
+
addTrackOrder(id, display_text = '🚚 Track order') {
|
|
576
|
+
if (!id) throw new TypeError('addTrackOrder(id) requires a non-empty id');
|
|
577
|
+
this._buttons.push({ name: 'track_order', buttonParamsJson: JSON.stringify({ id, display_text }) });
|
|
578
|
+
return this;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
/** Native-flow `reorder` shortcut. */
|
|
582
|
+
addReorder(id, display_text = '🔁 Reorder') {
|
|
583
|
+
if (!id) throw new TypeError('addReorder(id) requires a non-empty id');
|
|
584
|
+
this._buttons.push({ name: 'reorder', buttonParamsJson: JSON.stringify({ id, display_text }) });
|
|
585
|
+
return this;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
/** Native-flow `cancel_order` shortcut. */
|
|
589
|
+
addCancelOrder(id, display_text = '❌ Cancel order') {
|
|
590
|
+
if (!id) throw new TypeError('addCancelOrder(id) requires a non-empty id');
|
|
591
|
+
this._buttons.push({ name: 'cancel_order', buttonParamsJson: JSON.stringify({ id, display_text }) });
|
|
592
|
+
return this;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
/** Native-flow `clear_chat` shortcut. */
|
|
596
|
+
addClearChat() {
|
|
597
|
+
this._buttons.push({ name: 'clear_chat', buttonParamsJson: '{}' });
|
|
598
|
+
return this;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
/** Native-flow `navigateToScreen` shortcut. */
|
|
602
|
+
addNavigateToScreen(screen, data = {}) {
|
|
603
|
+
if (!screen) throw new TypeError('addNavigateToScreen(screen) requires a non-empty screen');
|
|
604
|
+
this._buttons.push({ name: 'navigateToScreen', buttonParamsJson: JSON.stringify({ screen_name: screen, data }) });
|
|
605
|
+
return this;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
/** WhatsApp Flows shortcut. Requires a real registered Flow. */
|
|
609
|
+
addFlow(flow = {}, display_text = '') {
|
|
610
|
+
if (typeof flow !== 'object' || flow === null || Array.isArray(flow) || !flow.id) throw new TypeError('addFlow(flow) requires a plain object with flow.id');
|
|
611
|
+
this._buttons.push({
|
|
612
|
+
// JAP@Fix 27-08-26: the button name was 'flow_action', which is actually
|
|
613
|
+
// the *field name* inside buttonParamsJson (flow_action: 'navigate' | 'data_exchange'),
|
|
614
|
+
// not a native_flow name WhatsApp recognises. The real native_flow name for
|
|
615
|
+
// launching a registered WhatsApp Flow is 'flow' -- client silently ignored
|
|
616
|
+
// the button because <native_flow name='flow_action'> isn't a thing it renders.
|
|
617
|
+
name: 'flow',
|
|
618
|
+
buttonParamsJson: JSON.stringify({
|
|
619
|
+
flow_message_version: flow.version || '3',
|
|
620
|
+
// flow_token: unique per-send session token WhatsApp Flows requires to
|
|
621
|
+
// correlate a flow_action data-exchange callback with this specific
|
|
622
|
+
// message. Was missing entirely -- without it the client can accept the
|
|
623
|
+
// message but has nothing to key the flow session on.
|
|
624
|
+
flow_token: flow.token || generateMessageIDV2(),
|
|
625
|
+
flow_id: flow.id,
|
|
626
|
+
flow_cta: display_text || flow.cta || 'Continue',
|
|
627
|
+
flow_action: flow.action || 'navigate',
|
|
628
|
+
flow_action_payload: flow.actionPayload || { screen: flow.screen || 'WELCOME', data: flow.data || {} },
|
|
629
|
+
}),
|
|
630
|
+
});
|
|
631
|
+
return this;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
/** Native-flow `voice_call` shortcut. */
|
|
635
|
+
addVoiceCall(id, display_text = '📞 Voice call') {
|
|
636
|
+
if (!id) throw new TypeError('addVoiceCall(id) requires a non-empty id');
|
|
637
|
+
this._buttons.push({ name: 'voice_call', buttonParamsJson: JSON.stringify({ display_text, id }) });
|
|
638
|
+
return this;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
/** Native-flow `video_call_button` shortcut. */
|
|
642
|
+
addVideoCall(id, display_text = '🎥 Video call') {
|
|
643
|
+
if (!id) throw new TypeError('addVideoCall(id) requires a non-empty id');
|
|
644
|
+
this._buttons.push({ name: 'video_call_button', buttonParamsJson: JSON.stringify({ display_text, id }) });
|
|
645
|
+
return this;
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
// JAP@Fix (bug 43) --- paramsList documented the schema for these 3 message-level native flow
|
|
649
|
+
// params (limited_time_offer / bottom_sheet / tap_target_configuration) but no helper ever wrote
|
|
650
|
+
// them into this._params — only manual setParams() could, with zero validation against the
|
|
651
|
+
// documented schema. Added dedicated setters + a lightweight type check reusing paramsList.
|
|
652
|
+
static #validateAgainstSchema(schema, data, label) {
|
|
653
|
+
for (const [key, type] of Object.entries(schema)) {
|
|
654
|
+
if (data[key] === undefined) continue;
|
|
655
|
+
const expectsArray = Array.isArray(type);
|
|
656
|
+
if (expectsArray) {
|
|
657
|
+
if (!Array.isArray(data[key]) || !data[key].every((v) => typeof v === type[0])) {
|
|
658
|
+
throw new TypeError(`${label}.${key} must be an array of ${type[0]}`);
|
|
659
|
+
}
|
|
660
|
+
} else if (typeof data[key] !== type) {
|
|
661
|
+
throw new TypeError(`${label}.${key} must be a ${type}`);
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
/** Set the message-level "limited time offer" strip (countdown banner above the buttons). */
|
|
667
|
+
setLimitedTimeOffer({ text = '', url = '', copy_code = '', expiration_time } = {}) {
|
|
668
|
+
const data = { text, url, copy_code, expiration_time };
|
|
669
|
+
Button.#validateAgainstSchema(Button.paramsList.limited_time_offer, data, 'limited_time_offer');
|
|
670
|
+
this._params = { ...this._params, limited_time_offer: data };
|
|
671
|
+
return this;
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
/** Configure how many buttons show inline before the rest collapse into a bottom sheet. */
|
|
675
|
+
setBottomSheet({ in_thread_buttons_limit, divider_indices = [], list_title = '', button_title = '' } = {}) {
|
|
676
|
+
const data = { in_thread_buttons_limit, divider_indices, list_title, button_title };
|
|
677
|
+
Button.#validateAgainstSchema(Button.paramsList.bottom_sheet, data, 'bottom_sheet');
|
|
678
|
+
this._params = { ...this._params, bottom_sheet: data };
|
|
679
|
+
return this;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
/** Configure the tap-target callout shown pointing at a specific button by index. */
|
|
683
|
+
setTapTargetConfiguration({ title = '', description = '', canonical_url = '', domain = '', buttonIndex = 0 } = {}) {
|
|
684
|
+
const data = { title, description, canonical_url, domain, buttonIndex };
|
|
685
|
+
Button.#validateAgainstSchema(Button.paramsList.tap_target_configuration, data, 'tap_target_configuration');
|
|
686
|
+
this._params = { ...this._params, tap_target_configuration: data };
|
|
687
|
+
return this;
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
static paramsList = {
|
|
691
|
+
limited_time_offer: {
|
|
692
|
+
text: 'string',
|
|
693
|
+
url: 'string',
|
|
694
|
+
copy_code: 'string',
|
|
695
|
+
expiration_time: 'number',
|
|
696
|
+
},
|
|
697
|
+
bottom_sheet: {
|
|
698
|
+
in_thread_buttons_limit: 'number',
|
|
699
|
+
divider_indices: ['number'],
|
|
700
|
+
list_title: 'string',
|
|
701
|
+
button_title: 'string',
|
|
702
|
+
},
|
|
703
|
+
tap_target_configuration: {
|
|
704
|
+
title: 'string',
|
|
705
|
+
description: 'string',
|
|
706
|
+
canonical_url: 'string',
|
|
707
|
+
domain: 'string',
|
|
708
|
+
buttonIndex: 'number',
|
|
709
|
+
},
|
|
710
|
+
};
|
|
711
|
+
|
|
712
|
+
// JAP@Add 22-08-26 (v4.7) --- native-flow names WA treats specially: the client
|
|
713
|
+
// only recognises these when the *first* button's name matches AND the wrapping
|
|
714
|
+
// <native_flow> biz-node carries the right v/name for that name. Everything not
|
|
715
|
+
// listed here (quick_reply, cta_url, cta_call, cta_copy, single_select mixed with
|
|
716
|
+
// others, etc.) uses the generic v=9 name=mixed node, which is what send() emitted
|
|
717
|
+
// unconditionally before this change. Table cross-checked against the observed
|
|
718
|
+
// wire behaviour documented by zqdevelopers/zq_baileys_helper and @chatunity/baileys.
|
|
719
|
+
static #SPECIAL_FLOW = {
|
|
720
|
+
review_and_pay: { v: '1', name: 'order_details' },
|
|
721
|
+
payment_info: { v: '1', name: 'payment_info' },
|
|
722
|
+
mpm: { v: '2', name: 'mpm' },
|
|
723
|
+
cta_catalog: { v: '2', name: 'cta_catalog' },
|
|
724
|
+
send_location: { v: '2', name: 'send_location' },
|
|
725
|
+
call_permission_request: { v: '2', name: 'call_permission_request' },
|
|
726
|
+
wa_payment_transaction_details: { v: '2', name: 'wa_payment_transaction_details' },
|
|
727
|
+
payment_key_info: { v: '1', name: 'payment_key_info' },
|
|
728
|
+
booking_confirmation: { v: '1', name: 'booking_confirmation' },
|
|
729
|
+
automated_greeting_message_view_catalog: { v: '2', name: 'automated_greeting_message_view_catalog' },
|
|
730
|
+
};
|
|
731
|
+
|
|
732
|
+
/** Render this builder's header/body/footer/media/buttons/params into an `interactiveMessage`-shaped card (without the outer `interactiveMessage` wrapper or contextInfo). */
|
|
733
|
+
async toCard() {
|
|
734
|
+
return {
|
|
735
|
+
body: {
|
|
736
|
+
text: this._body,
|
|
737
|
+
},
|
|
738
|
+
footer: {
|
|
739
|
+
text: this._footer,
|
|
740
|
+
},
|
|
741
|
+
header: {
|
|
742
|
+
title: this._title,
|
|
743
|
+
subtitle: this._subtitle,
|
|
744
|
+
hasMediaAttachment: !!this._data,
|
|
745
|
+
...(this._data
|
|
746
|
+
? await prepareWAMessageMedia(this._data, { upload: this.#client.waUploadToServer }).catch((e) => {
|
|
747
|
+
if (String(e).includes('Invalid media type')) return this._data;
|
|
748
|
+
throw e;
|
|
749
|
+
})
|
|
750
|
+
: {}),
|
|
751
|
+
},
|
|
752
|
+
nativeFlowMessage: {
|
|
753
|
+
messageParamsJson: JSON.stringify(this._params),
|
|
754
|
+
buttons: this._buttons,
|
|
755
|
+
},
|
|
756
|
+
};
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
// JAP@Fix (bug: single_select alone doesn't render) --- WhatsApp only renders a
|
|
760
|
+
// `single_select` native_flow button when it's mixed with other native_flow buttons
|
|
761
|
+
// (biz node <native_flow v='9' name='mixed'>). 'single_select' is NOT in the set of
|
|
762
|
+
// button names WA treats as a standalone native_flow type (confirmed against
|
|
763
|
+
// WABinary/generic-utils.js getBizBinaryNode() — it special-cases
|
|
764
|
+
// message.listMessage separately, with its own biz node <list v='2' type='product_list'>,
|
|
765
|
+
// and never routes 'single_select' through the single-type native_flow path).
|
|
766
|
+
// A lone single_select must be sent as a legacy `listMessage` instead.
|
|
767
|
+
#isLoneSingleSelect() {
|
|
768
|
+
return this._buttons.length === 1 && this._buttons[0].name === 'single_select';
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
#toListMessage() {
|
|
772
|
+
const { title: buttonText, sections } = JSON.parse(this._buttons[0].buttonParamsJson);
|
|
773
|
+
return {
|
|
774
|
+
listMessage: {
|
|
775
|
+
title: this._title || undefined,
|
|
776
|
+
description: this._body || undefined,
|
|
777
|
+
footerText: this._footer || undefined,
|
|
778
|
+
buttonText: buttonText || undefined,
|
|
779
|
+
listType: 1,
|
|
780
|
+
sections: (sections || []).map((s) => ({
|
|
781
|
+
title: s.title,
|
|
782
|
+
rows: (s.rows || []).map((r) => ({
|
|
783
|
+
title: r.title || r.header || '',
|
|
784
|
+
description: r.description || '',
|
|
785
|
+
rowId: r.id || '',
|
|
786
|
+
})),
|
|
787
|
+
})),
|
|
788
|
+
contextInfo: this._contextInfo,
|
|
789
|
+
},
|
|
790
|
+
};
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
/** @returns {Record<string, any>} The final content object, without generating/wrapping a WAMessage. Useful when composing this interactive card into something else (e.g. Carousel). */
|
|
794
|
+
async build(jid, { ...options } = {}) {
|
|
795
|
+
if (this._buttons.length === 0 && !this._bloksWidget) {
|
|
796
|
+
throw new Error('Button requires at least one button (use addReply/addUrl/addCall/addSelection/addButton/...) or a Bloks widget (setBloksWidget())');
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
if (this._buttons.length > 0 && this.#isLoneSingleSelect()) {
|
|
800
|
+
return generateWAMessageFromContent(jid, { ...this._extraPayload, ...this.#toListMessage() }, { ...options });
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
const message = this._buttons.length > 0 ? await this.toCard() : {};
|
|
804
|
+
|
|
805
|
+
return generateWAMessageFromContent(
|
|
806
|
+
jid,
|
|
807
|
+
{
|
|
808
|
+
...(this._bloksWidget && {
|
|
809
|
+
messageContextInfo: { messageSecret: crypto.randomBytes(32) },
|
|
810
|
+
}),
|
|
811
|
+
...this._extraPayload,
|
|
812
|
+
interactiveMessage: {
|
|
813
|
+
...message,
|
|
814
|
+
...(this._bloksWidget && { bloksWidget: this._bloksWidget }),
|
|
815
|
+
contextInfo: this._contextInfo,
|
|
816
|
+
},
|
|
817
|
+
},
|
|
818
|
+
{ ...options }
|
|
819
|
+
);
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
/** Build and send this interactive message. @param {string} jid Destination chat/group jid. */
|
|
823
|
+
async send(jid, { ...options } = {}) {
|
|
824
|
+
const msg = await this.build(jid, options);
|
|
825
|
+
|
|
826
|
+
// JAP@Fix 27-08-26: delegate the <biz> node to the shared getBizBinaryNode()
|
|
827
|
+
// helper instead of hand-rolling one here. It already:
|
|
828
|
+
// - stamps actual_actors/host_storage/privacy_mode_ts (previously missing,
|
|
829
|
+
// the likely cause of single_select's wire being flagged for validation)
|
|
830
|
+
// - picks the correct wrapper per button name (FLOWS_MAP dedicated node,
|
|
831
|
+
// ORDER_RESPONSE_ALIAS native_flow_name, mixed native_flow, or the
|
|
832
|
+
// <list v='2' type='product_list'> node for a lone single_select via
|
|
833
|
+
// message.listMessage) using the exact same table messages-send.js uses
|
|
834
|
+
// for every non-Button send path, so Button.send() can't silently drift
|
|
835
|
+
// out of sync with it.
|
|
836
|
+
// Button.#SPECIAL_FLOW is kept only as documentation for addFlow()/etc.
|
|
837
|
+
// JSDoc now (see below); getBizBinaryNode()'s own ORDER_RESPONSE_ALIAS /
|
|
838
|
+
// FLOWS_MAP tables are what actually pick the wire node -- keep those two
|
|
839
|
+
// tables in sync if a new special-cased button name is ever added.
|
|
840
|
+
const bizNode = getBizBinaryNode(msg.message);
|
|
841
|
+
|
|
842
|
+
await this.#client.relayMessage(msg.key.remoteJid, msg.message, {
|
|
843
|
+
messageId: msg.key.id,
|
|
844
|
+
additionalNodes: [bizNode],
|
|
845
|
+
...options,
|
|
846
|
+
});
|
|
847
|
+
return msg;
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
/**
|
|
852
|
+
* Legacy `buttonsMessage` builder (up to 3 simple quick-reply buttons under a
|
|
853
|
+
* media/location header). Simpler and more universally supported than
|
|
854
|
+
* `Button`'s native-flow messages, but capped to `type: 1` quick replies.
|
|
855
|
+
*/
|
|
856
|
+
class CardBuilder {
|
|
857
|
+
/** @param {import('../../WAProto/index.js').WASocket} client Active Baileys socket. */
|
|
858
|
+
constructor(client) {
|
|
859
|
+
this._card = new Button(client);
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
image(url) {
|
|
863
|
+
this._card.setImage(url);
|
|
864
|
+
return this;
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
title(t) {
|
|
868
|
+
this._card.setTitle(t);
|
|
869
|
+
return this;
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
text(t) {
|
|
873
|
+
this._card.setBody(t);
|
|
874
|
+
return this;
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
button(displayText, id) {
|
|
878
|
+
this._card.addReply(displayText, id);
|
|
879
|
+
return this;
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
export { Button, CardBuilder };
|