@vanzxy/baileys 1.6.3 → 1.6.4
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 +50 -0
- package/lib/Utils/A2UI.js +217 -0
- package/lib/Utils/MessageBuilder.js +290 -40
- package/lib/Utils/MessageBuilder_d.ts +45 -0
- package/lib/Utils/PersistentStore.js +592 -0
- package/lib/Utils/PersistentStore_d.ts +60 -0
- package/lib/Utils/anti-delete.d.ts +68 -0
- package/lib/Utils/anti-delete.js +185 -0
- package/lib/Utils/auto-reply.d.ts +47 -0
- package/lib/Utils/auto-reply.js +155 -0
- package/lib/Utils/button-helper-utils.js +314 -0
- package/lib/Utils/button-sender.js +817 -0
- package/lib/Utils/chat-history-helpers.d.ts +21 -0
- package/lib/Utils/chat-history-helpers.js +71 -0
- package/lib/Utils/index.d.ts +11 -0
- package/lib/Utils/index.js +16 -0
- package/lib/Utils/media-messages.d.ts +18 -0
- package/lib/Utils/media-messages.js +71 -0
- package/lib/Utils/media-set.d.ts +13 -0
- package/lib/Utils/media-set.js +165 -0
- package/lib/Utils/message-kind.js +139 -0
- package/lib/Utils/message-search.d.ts +44 -0
- package/lib/Utils/message-search.js +174 -0
- package/lib/Utils/scheduling.d.ts +42 -0
- package/lib/Utils/scheduling.js +140 -0
- package/lib/Utils/status.d.ts +50 -0
- package/lib/Utils/status.js +108 -0
- package/lib/Utils/stickerpack.d.ts +51 -0
- package/lib/Utils/stickerpack.js +276 -0
- package/lib/Utils/templates.d.ts +76 -0
- package/lib/Utils/templates.js +151 -0
- package/lib/Utils/use-sqlite-auth-state.js +28 -1
- package/lib/Utils/vcard.d.ts +58 -0
- package/lib/Utils/vcard.js +94 -0
- package/lib/VoIP/audio-feeder.d.ts +15 -0
- package/lib/VoIP/audio-feeder.js +132 -0
- package/lib/VoIP/index.js +277 -0
- package/lib/VoIP/relay-transport.d.ts +43 -0
- package/lib/VoIP/relay-transport.js +559 -0
- package/lib/VoIP/signaling.js +624 -0
- package/lib/VoIP/types.d.ts +69 -0
- package/lib/VoIP/types.js +17 -0
- package/lib/VoIP/wasm-engine.d.ts +103 -0
- package/lib/VoIP/wasm-engine.js +1214 -0
- package/lib/VoIP/worker-bootstrap.js +1042 -0
- package/lib/WABinary/generic-utils.js +8 -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/index.js +4 -0
- package/package.json +22 -1
package/NOTICE.md
CHANGED
|
@@ -29,11 +29,61 @@ inline `Vanz@` comments for specifics): lazy-loading of optional
|
|
|
29
29
|
and neutral fallback text (previously referenced the original author's
|
|
30
30
|
name in a small number of user-facing fallback strings).
|
|
31
31
|
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## lib/Utils/message-kind.js — button/list "addon kind" resolution
|
|
35
|
+
|
|
36
|
+
`resolveButtonAddonKind()` is adapted from **zapo-js**
|
|
37
|
+
(`resolveButtonAddonKind` / `resolveNativeFlowAddonKind` in
|
|
38
|
+
`src/message/encode/content.ts`), created by **vinikjkkj**
|
|
39
|
+
<contact@vinicius.email>. <https://github.com/vinikjkkj/zapo>
|
|
40
|
+
|
|
41
|
+
- License: MIT, Copyright (c) 2026 vinikjkkj
|
|
42
|
+
- Ported into `@vanzxy/baileys` 1.6.4, adapted from zapo's internal
|
|
43
|
+
`Proto.IMessage` shape to this fork's message-content shape.
|
|
44
|
+
`parseInteractiveReply()` in the same file is original code written
|
|
45
|
+
for this fork (not from zapo), covering `buttonsResponseMessage` /
|
|
46
|
+
`listResponseMessage` / `interactiveResponseMessage` /
|
|
47
|
+
`templateButtonReplyMessage`.
|
|
48
|
+
|
|
32
49
|
Additional performance/authenticity reference drawn from `arslan-baileys`
|
|
33
50
|
(1.1.0), which shares the same underlying builder API.
|
|
34
51
|
|
|
35
52
|
---
|
|
36
53
|
|
|
54
|
+
## lib/Utils/button-sender.js + lib/Utils/button-helper-utils.js — interactive/native-flow button send layer
|
|
55
|
+
|
|
56
|
+
Ported from `@queenanya/baileys` (fork by **QueenAnya** / OLDUSER 06,
|
|
57
|
+
package `@queenanya/baileys`), files `src/addons/button-sender.ts` and
|
|
58
|
+
`src/addons/message-utils.ts`. That fork's own header credits the base
|
|
59
|
+
implementation as "Ported from `@ryuu-reinzz/button-helper` v2.2.5".
|
|
60
|
+
|
|
61
|
+
- License: MIT (per `@queenanya/baileys` `package.json`)
|
|
62
|
+
- Ported into `@vanzxy/baileys` 1.6.5, converted TS → JS ESM. Adjustments:
|
|
63
|
+
`console.log`/`console.warn` calls replaced with `sock.logger` (pino)
|
|
64
|
+
calls to match this fork's logging convention; default dummy-file
|
|
65
|
+
name/branding strings genericized; import paths rewired to this fork's
|
|
66
|
+
`Utils`/`WABinary`/`Types` layout (`generateWAMessage`,
|
|
67
|
+
`generateWAMessageFromContent`, `normalizeMessageContent`,
|
|
68
|
+
`getUrlFromDirectPath`, `unixTimestampSeconds`, `isJidGroup`,
|
|
69
|
+
`isJidNewsletter`, `QueryIds`, `XWAPaths`, etc. — all already present
|
|
70
|
+
in `@vanzxy/baileys` and reused as-is, no duplication).
|
|
71
|
+
- Adds `sendButtons()`, `sendInteractiveMessage()`,
|
|
72
|
+
`sendInteractiveMessageV2()`, plus supporting validators
|
|
73
|
+
(`validateSendButtonsPayload`, `validateSendInteractiveMessagePayload`,
|
|
74
|
+
`validateInteractiveMessageContent`, `validateAuthoringButtons`),
|
|
75
|
+
`buildInteractiveButtons()`, `convertToInteractiveMessage()`, the
|
|
76
|
+
`InteractiveValidationError` class, and message-introspection helpers
|
|
77
|
+
(`getButtonType`, `getButtonArgs`, `getMediaType`, `getMessageType`,
|
|
78
|
+
`prepareAlbumMessageContent`, `makeMessageExtrasAddon`, etc.). These
|
|
79
|
+
send buttons via `relayMessage` directly (bypassing `sendMessage`'s
|
|
80
|
+
validation path, which doesn't recognise `interactiveMessage`
|
|
81
|
+
payloads) with the correct `biz`/`interactive`/`native_flow` binary
|
|
82
|
+
nodes so buttons render on WhatsApp Messenger + Business, Android +
|
|
83
|
+
iOS. Complements the existing `Button`/`ButtonV2`/`ButtonV3` builder
|
|
84
|
+
classes in `MessageBuilder.js`, which build message *content* — this
|
|
85
|
+
addon handles the *send* layer.
|
|
86
|
+
|
|
37
87
|
## rich-message-utils.js — AIRICH inline-image bug fix
|
|
38
88
|
|
|
39
89
|
Original implementation is this project's own; fixed 2026-08-15 (bad
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/Utils/A2UI.js — A2UI (Bloks) widget builder + sender
|
|
3
|
+
*
|
|
4
|
+
* Part of @vanzxy/baileys. Builds the flat `components` array A2UI/Bloks
|
|
5
|
+
* expects (id-referencing tree: Column/Row hold `children`, Card/Button hold
|
|
6
|
+
* `child`, Modal holds `trigger`/`content`) and sends it as an
|
|
7
|
+
* `interactiveMessage.bloksWidget` via the same `getBizBinaryNode()` path
|
|
8
|
+
* Button/ButtonV2 use, so the wire-level <biz>/<native_flow> node always
|
|
9
|
+
* matches the actual button names sent — no more hand-rolled duplicate of
|
|
10
|
+
* that logic living outside the library.
|
|
11
|
+
*
|
|
12
|
+
* See MessageBuilder.js's `Button.setBloksWidget()` for the declarative
|
|
13
|
+
* (nested-tree) alternative to this imperative (id-returning-factory) API;
|
|
14
|
+
* both produce the same wire format and can be mixed freely.
|
|
15
|
+
*/
|
|
16
|
+
"use strict";
|
|
17
|
+
|
|
18
|
+
import crypto from "crypto";
|
|
19
|
+
import { generateWAMessageFromContent } from "./messages.js";
|
|
20
|
+
import { getBizBinaryNode } from "../WABinary/index.js";
|
|
21
|
+
|
|
22
|
+
class A2UI {
|
|
23
|
+
constructor({ catalogId = "https://a2ui.org/specification/v0_9/catalogs/basic/catalog.json", version = "v0.9" } = {}) {
|
|
24
|
+
this._version = version;
|
|
25
|
+
this._catalogId = catalogId;
|
|
26
|
+
this._components = new Map();
|
|
27
|
+
this._counter = 0;
|
|
28
|
+
this._rootChildren = [];
|
|
29
|
+
}
|
|
30
|
+
#nextId(prefix) {
|
|
31
|
+
return `${prefix}_${(this._counter++).toString(36)}`;
|
|
32
|
+
}
|
|
33
|
+
#reg(id, component, extra = {}) {
|
|
34
|
+
id ??= this.#nextId(component.toLowerCase());
|
|
35
|
+
if (id === "root") throw new Error(`Component id "root" is reserved for the implicit root wrapper`);
|
|
36
|
+
if (this._components.has(id)) throw new Error(`Component id "${id}" already used`);
|
|
37
|
+
this._components.set(id, { id, component, ...extra });
|
|
38
|
+
return id;
|
|
39
|
+
}
|
|
40
|
+
text(text, { id, variant = "body" } = {}) {
|
|
41
|
+
return this.#reg(id, "Text", { text, variant });
|
|
42
|
+
}
|
|
43
|
+
image(url, { id, variant, fit = "cover" } = {}) {
|
|
44
|
+
return this.#reg(id, "Image", { url, ...(variant ? { variant } : {}), fit });
|
|
45
|
+
}
|
|
46
|
+
video(url, { id } = {}) {
|
|
47
|
+
return this.#reg(id, "Video", { url });
|
|
48
|
+
}
|
|
49
|
+
checkbox(label, { id, value = false } = {}) {
|
|
50
|
+
return this.#reg(id, "CheckBox", { label, value });
|
|
51
|
+
}
|
|
52
|
+
textField(label, { id, variant = "text" } = {}) {
|
|
53
|
+
return this.#reg(id, "TextField", { label, variant });
|
|
54
|
+
}
|
|
55
|
+
button(childId, { id, variant = "primary", action } = {}) {
|
|
56
|
+
if (!childId) throw new TypeError("button(childId) requires the id of a child component (e.g. from .text())");
|
|
57
|
+
return this.#reg(id, "Button", { child: childId, variant, ...(action ? { action } : {}) });
|
|
58
|
+
}
|
|
59
|
+
card(childId, { id } = {}) {
|
|
60
|
+
return this.#reg(id, "Card", { child: childId });
|
|
61
|
+
}
|
|
62
|
+
// trigger: id of the component that opens the modal (e.g. a Button); content: id of the
|
|
63
|
+
// component shown inside it. Both are ids of already-registered SIBLING components, not
|
|
64
|
+
// nested children — that's how the wire format expects Modal to reference them.
|
|
65
|
+
modal(triggerId, contentId, { id } = {}) {
|
|
66
|
+
if (!triggerId) throw new TypeError("modal(triggerId, contentId) requires the id of the trigger component");
|
|
67
|
+
if (!contentId) throw new TypeError("modal(triggerId, contentId) requires the id of the content component");
|
|
68
|
+
return this.#reg(id, "Modal", { trigger: triggerId, content: contentId });
|
|
69
|
+
}
|
|
70
|
+
// Escape hatch for any catalog component without a dedicated method yet (Divider, Slider,
|
|
71
|
+
// Switch, List, etc). props is merged as-is into the registered node.
|
|
72
|
+
raw(component, props = {}, { id } = {}) {
|
|
73
|
+
if (typeof component !== "string" || !component) throw new TypeError("raw(component, props) requires a non-empty component type string");
|
|
74
|
+
return this.#reg(id, component, props);
|
|
75
|
+
}
|
|
76
|
+
column(children = [], { id } = {}) {
|
|
77
|
+
if (!children.length) throw new TypeError("column(children) requires at least one child id");
|
|
78
|
+
return this.#reg(id, "Column", { children });
|
|
79
|
+
}
|
|
80
|
+
row(children = [], { id } = {}) {
|
|
81
|
+
if (!children.length) throw new TypeError("row(children) requires at least one child id");
|
|
82
|
+
return this.#reg(id, "Row", { children });
|
|
83
|
+
}
|
|
84
|
+
divider({ id } = {}) {
|
|
85
|
+
return this.#reg(id, "Divider", {});
|
|
86
|
+
}
|
|
87
|
+
choicePicker(label, options, { id, variant = "mutuallyExclusive", value, displayStyle = "checkbox", filterable = false } = {}) {
|
|
88
|
+
if (!Array.isArray(options) || !options.length) {
|
|
89
|
+
throw new TypeError("choicePicker(label, options) requires a non-empty options array of {label, value}");
|
|
90
|
+
}
|
|
91
|
+
return this.#reg(id, "ChoicePicker", {
|
|
92
|
+
label,
|
|
93
|
+
variant,
|
|
94
|
+
...(value !== undefined ? { value } : {}),
|
|
95
|
+
options,
|
|
96
|
+
displayStyle,
|
|
97
|
+
filterable
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
root(children) {
|
|
101
|
+
if (!Array.isArray(children) || !children.length) {
|
|
102
|
+
throw new TypeError("root(children) requires a non-empty array of top-level component ids");
|
|
103
|
+
}
|
|
104
|
+
this._rootChildren = children;
|
|
105
|
+
return this;
|
|
106
|
+
}
|
|
107
|
+
// Vanz@Add 29-08-26 --- Product/list carousel card. This is a DIFFERENT wire
|
|
108
|
+
// payload shape from the Column/Row component-tree the rest of this class
|
|
109
|
+
// builds (type: 'list_card', flat items array) — not a catalog component, so
|
|
110
|
+
// it doesn't go through #reg()/root(). Call this instead of root()+build();
|
|
111
|
+
// build() returns the list_card payload directly when this was set.
|
|
112
|
+
listCard({ title, items, fallbackText, uuid = crypto.randomUUID() } = {}) {
|
|
113
|
+
if (!title) throw new TypeError("listCard requires a title");
|
|
114
|
+
if (!Array.isArray(items) || !items.length) {
|
|
115
|
+
throw new TypeError("listCard requires a non-empty items array");
|
|
116
|
+
}
|
|
117
|
+
this._listCardPayload = {
|
|
118
|
+
uuid,
|
|
119
|
+
data: JSON.stringify({
|
|
120
|
+
type: "list_card",
|
|
121
|
+
title,
|
|
122
|
+
fallback_text: fallbackText ?? "",
|
|
123
|
+
items: items.map((it) => ({
|
|
124
|
+
asset_id: it.assetId ?? crypto.randomUUID().replace(/-/g, "").slice(0, 17),
|
|
125
|
+
asset_type: it.assetType ?? "PRODUCT_ITEM",
|
|
126
|
+
title: it.title,
|
|
127
|
+
trailing_label: it.price ?? it.trailingLabel ?? "",
|
|
128
|
+
trailing_emphasis: it.emphasis ?? "strong"
|
|
129
|
+
}))
|
|
130
|
+
}),
|
|
131
|
+
type: "im_a2ui",
|
|
132
|
+
fallback: fallbackText ?? ""
|
|
133
|
+
};
|
|
134
|
+
return this;
|
|
135
|
+
}
|
|
136
|
+
build({ uuid = crypto.randomUUID(), surfaceId, type = "im_a2ui", wrapped = true } = {}) {
|
|
137
|
+
if (this._listCardPayload) return this._listCardPayload;
|
|
138
|
+
if (!this._rootChildren.length) throw new Error("Call root([...ids]) before build()");
|
|
139
|
+
const root = { id: "root", component: "Column", children: this._rootChildren };
|
|
140
|
+
const components = [root, ...this._components.values()];
|
|
141
|
+
const data = wrapped
|
|
142
|
+
? {
|
|
143
|
+
version: this._version,
|
|
144
|
+
createSurface: {
|
|
145
|
+
surfaceId: surfaceId ?? `starcore-widget=${uuid}`,
|
|
146
|
+
catalogId: this._catalogId,
|
|
147
|
+
components
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
: { components };
|
|
151
|
+
return {
|
|
152
|
+
uuid,
|
|
153
|
+
data: JSON.stringify(data),
|
|
154
|
+
type
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Build + send an A2UI/Bloks widget as an interactiveMessage.
|
|
161
|
+
* @param {import('../../WAProto/index.js').WASocket} client Active Baileys socket.
|
|
162
|
+
* @param {string} jid Destination chat/group jid.
|
|
163
|
+
*/
|
|
164
|
+
async function sendA2UIWidget(client, jid, {
|
|
165
|
+
a2ui,
|
|
166
|
+
bodyText = "",
|
|
167
|
+
footer = "",
|
|
168
|
+
buttons = [],
|
|
169
|
+
contextInfo = {},
|
|
170
|
+
expiration,
|
|
171
|
+
quoted,
|
|
172
|
+
type = "im_a2ui",
|
|
173
|
+
wrapped = true,
|
|
174
|
+
singleScreen = false
|
|
175
|
+
} = {}) {
|
|
176
|
+
if (!client) throw new Error("Socket is required");
|
|
177
|
+
if (!(a2ui instanceof A2UI)) throw new TypeError("a2ui must be an A2UI instance");
|
|
178
|
+
|
|
179
|
+
const nativeFlowMessage = buttons && buttons.length
|
|
180
|
+
? {
|
|
181
|
+
buttons: buttons.map((b) => ({
|
|
182
|
+
name: b.name ?? "cta_url",
|
|
183
|
+
buttonParamsJson: typeof b.params === "string" ? b.params : JSON.stringify(b.params ?? {}),
|
|
184
|
+
})),
|
|
185
|
+
messageParamsJson: "{}",
|
|
186
|
+
messageVersion: 1
|
|
187
|
+
}
|
|
188
|
+
: { messageParamsJson: "" };
|
|
189
|
+
|
|
190
|
+
const interactiveMessage = singleScreen
|
|
191
|
+
? {
|
|
192
|
+
nativeFlowMessage,
|
|
193
|
+
bloksWidget: a2ui.build({ type, wrapped }),
|
|
194
|
+
...(expiration || Object.keys(contextInfo).length ? { contextInfo: { ...(expiration ? { expiration } : {}), ...contextInfo } } : {})
|
|
195
|
+
}
|
|
196
|
+
: {
|
|
197
|
+
header: { hasMediaAttachment: false },
|
|
198
|
+
body: { text: bodyText },
|
|
199
|
+
...(footer ? { footer: { text: footer } } : {}),
|
|
200
|
+
nativeFlowMessage,
|
|
201
|
+
bloksWidget: a2ui.build({ type, wrapped }),
|
|
202
|
+
...(expiration || Object.keys(contextInfo).length ? { contextInfo: { ...(expiration ? { expiration } : {}), ...contextInfo } } : {})
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
const msg = generateWAMessageFromContent(jid, {
|
|
206
|
+
messageContextInfo: { messageSecret: crypto.randomBytes(32) },
|
|
207
|
+
interactiveMessage
|
|
208
|
+
}, { quoted });
|
|
209
|
+
|
|
210
|
+
await client.relayMessage(msg.key.remoteJid, msg.message, {
|
|
211
|
+
messageId: msg.key.id,
|
|
212
|
+
additionalNodes: [getBizBinaryNode(msg.message)]
|
|
213
|
+
});
|
|
214
|
+
return msg;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
export { A2UI, sendA2UIWidget };
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
|
|
45
45
|
'use strict';
|
|
46
46
|
|
|
47
|
-
const MESSAGE_BUILDER_VERSION = '4.
|
|
47
|
+
const MESSAGE_BUILDER_VERSION = '4.8';
|
|
48
48
|
// Vanz@Fix 24-08-26: use the exported builder version everywhere; the old
|
|
49
49
|
// verification helper referenced an undeclared `VERSION`, making every AIRich.build() fail.
|
|
50
50
|
|
|
@@ -52,6 +52,15 @@ const MESSAGE_BUILDER_VERSION = '4.9.1';
|
|
|
52
52
|
import { generateWAMessageFromContent, prepareWAMessageMedia } from './messages.js';
|
|
53
53
|
import { generateMessageIDV2 } from './generics.js';
|
|
54
54
|
import { botMetadataSignature, botMetadataCertificate } from './rich-message-utils.js';
|
|
55
|
+
// Vanz@Fix 27-08-26: Button.send() was hand-rolling its own <biz> node with
|
|
56
|
+
// attrs: {} instead of reusing the canonical builder. Real client traffic
|
|
57
|
+
// (and the auto-attached biz node in Socket/messages-send.js) always carries
|
|
58
|
+
// actual_actors/host_storage/privacy_mode_ts on every <biz> node, including
|
|
59
|
+
// the one wrapping a lone single_select's <list> node. Missing them made the
|
|
60
|
+
// single_select wire payload diverge from what messages-send.js emits for
|
|
61
|
+
// every other message type -- import the shared helper instead of duplicating
|
|
62
|
+
// (and silently drifting from) its attrs.
|
|
63
|
+
import { getBizBinaryNode } from '../WABinary/index.js';
|
|
55
64
|
import crypto from 'crypto';
|
|
56
65
|
import { PassThrough, Readable } from 'stream';
|
|
57
66
|
// Vanz@Fix 15-08-26 --- sharp/fluent-ffmpeg were statically imported in the blurose source.
|
|
@@ -628,6 +637,46 @@ class BaseBuilder {
|
|
|
628
637
|
}
|
|
629
638
|
}
|
|
630
639
|
|
|
640
|
+
/** Tiny fluent helper for building a single quickReply button row (type 1). Standalone — not tied to Button/ButtonV2. */
|
|
641
|
+
class RowBuilder {
|
|
642
|
+
constructor() {
|
|
643
|
+
this.buttons = [];
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
button(displayText, buttonId) {
|
|
647
|
+
this.buttons.push({ buttonId, buttonText: { displayText }, type: 1 });
|
|
648
|
+
return this;
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
/** Thin fluent wrapper around `Button` for building a single image+title+text+reply "card" in one chain. */
|
|
653
|
+
class CardBuilder {
|
|
654
|
+
/** @param {import('../../WAProto/index.js').WASocket} client Active Baileys socket. */
|
|
655
|
+
constructor(client) {
|
|
656
|
+
this._card = new Button(client);
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
image(url) {
|
|
660
|
+
this._card.setImage(url);
|
|
661
|
+
return this;
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
title(t) {
|
|
665
|
+
this._card.setTitle(t);
|
|
666
|
+
return this;
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
text(t) {
|
|
670
|
+
this._card.setBody(t);
|
|
671
|
+
return this;
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
button(displayText, id) {
|
|
675
|
+
this._card.addReply(displayText, id);
|
|
676
|
+
return this;
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
|
|
631
680
|
/**
|
|
632
681
|
* Interactive (native-flow) message builder — header/body/footer + a mix of
|
|
633
682
|
* buttons (quick_reply, cta_url, cta_call, single_select, ...). Sends via
|
|
@@ -1201,13 +1250,23 @@ class Button extends BaseBuilder {
|
|
|
1201
1250
|
return this;
|
|
1202
1251
|
}
|
|
1203
1252
|
|
|
1204
|
-
/** WhatsApp Flows
|
|
1253
|
+
/** WhatsApp Flows shortcut. Requires a real registered Flow. */
|
|
1205
1254
|
addFlow(flow = {}, display_text = '') {
|
|
1206
1255
|
if (typeof flow !== 'object' || flow === null || Array.isArray(flow) || !flow.id) throw new TypeError('addFlow(flow) requires a plain object with flow.id');
|
|
1207
1256
|
this._buttons.push({
|
|
1208
|
-
|
|
1257
|
+
// Vanz@Fix 27-08-26: the button name was 'flow_action', which is actually
|
|
1258
|
+
// the *field name* inside buttonParamsJson (flow_action: 'navigate' | 'data_exchange'),
|
|
1259
|
+
// not a native_flow name WhatsApp recognises. The real native_flow name for
|
|
1260
|
+
// launching a registered WhatsApp Flow is 'flow' -- client silently ignored
|
|
1261
|
+
// the button because <native_flow name='flow_action'> isn't a thing it renders.
|
|
1262
|
+
name: 'flow',
|
|
1209
1263
|
buttonParamsJson: JSON.stringify({
|
|
1210
1264
|
flow_message_version: flow.version || '3',
|
|
1265
|
+
// flow_token: unique per-send session token WhatsApp Flows requires to
|
|
1266
|
+
// correlate a flow_action data-exchange callback with this specific
|
|
1267
|
+
// message. Was missing entirely -- without it the client can accept the
|
|
1268
|
+
// message but has nothing to key the flow session on.
|
|
1269
|
+
flow_token: flow.token || generateMessageIDV2(),
|
|
1211
1270
|
flow_id: flow.id,
|
|
1212
1271
|
flow_cta: display_text || flow.cta || 'Continue',
|
|
1213
1272
|
flow_action: flow.action || 'navigate',
|
|
@@ -1405,50 +1464,29 @@ class Button extends BaseBuilder {
|
|
|
1405
1464
|
);
|
|
1406
1465
|
}
|
|
1407
1466
|
|
|
1408
|
-
// Vanz@Add 22-08-26 (v4.7) --- picks the native_flow node variant for the first
|
|
1409
|
-
// button's name, per Button.#SPECIAL_FLOW. Falls back to the generic mixed node
|
|
1410
|
-
// (previous unconditional behaviour) for anything not in that map.
|
|
1411
|
-
#buildNativeFlowNode() {
|
|
1412
|
-
const special = Button.#SPECIAL_FLOW[this._buttons[0]?.name];
|
|
1413
|
-
return special ? { tag: 'native_flow', attrs: special } : { tag: 'native_flow', attrs: { v: '9', name: 'mixed' } };
|
|
1414
|
-
}
|
|
1415
|
-
|
|
1416
1467
|
/** Build and send this interactive message. @param {string} jid Destination chat/group jid. */
|
|
1417
1468
|
async send(jid, { ...options } = {}) {
|
|
1418
1469
|
const msg = await this.build(jid, options);
|
|
1419
1470
|
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
tag: 'quality_control',
|
|
1436
|
-
attrs: { decision_id: crypto.randomUUID().replace(/-/g, ''), source_type: 'third_party' },
|
|
1437
|
-
content: [{ tag: 'decision_source', attrs: { value: 'df' } }],
|
|
1438
|
-
},
|
|
1439
|
-
]
|
|
1440
|
-
: []),
|
|
1441
|
-
];
|
|
1471
|
+
// Vanz@Fix 27-08-26: delegate the <biz> node to the shared getBizBinaryNode()
|
|
1472
|
+
// helper instead of hand-rolling one here. It already:
|
|
1473
|
+
// - stamps actual_actors/host_storage/privacy_mode_ts (previously missing,
|
|
1474
|
+
// the likely cause of single_select's wire being flagged for validation)
|
|
1475
|
+
// - picks the correct wrapper per button name (FLOWS_MAP dedicated node,
|
|
1476
|
+
// ORDER_RESPONSE_ALIAS native_flow_name, mixed native_flow, or the
|
|
1477
|
+
// <list v='2' type='product_list'> node for a lone single_select via
|
|
1478
|
+
// message.listMessage) using the exact same table messages-send.js uses
|
|
1479
|
+
// for every non-Button send path, so Button.send() can't silently drift
|
|
1480
|
+
// out of sync with it.
|
|
1481
|
+
// Button.#SPECIAL_FLOW is kept only as documentation for addFlow()/etc.
|
|
1482
|
+
// JSDoc now (see below); getBizBinaryNode()'s own ORDER_RESPONSE_ALIAS /
|
|
1483
|
+
// FLOWS_MAP tables are what actually pick the wire node -- keep those two
|
|
1484
|
+
// tables in sync if a new special-cased button name is ever added.
|
|
1485
|
+
const bizNode = getBizBinaryNode(msg.message);
|
|
1442
1486
|
|
|
1443
1487
|
await this.#client.relayMessage(msg.key.remoteJid, msg.message, {
|
|
1444
1488
|
messageId: msg.key.id,
|
|
1445
|
-
additionalNodes: [
|
|
1446
|
-
{
|
|
1447
|
-
tag: 'biz',
|
|
1448
|
-
attrs: {},
|
|
1449
|
-
content: bizContent,
|
|
1450
|
-
},
|
|
1451
|
-
],
|
|
1489
|
+
additionalNodes: [bizNode],
|
|
1452
1490
|
...options,
|
|
1453
1491
|
});
|
|
1454
1492
|
return msg;
|
|
@@ -1514,6 +1552,24 @@ class ButtonV2 extends BaseBuilder {
|
|
|
1514
1552
|
return this;
|
|
1515
1553
|
}
|
|
1516
1554
|
|
|
1555
|
+
/** Alias for addButton() — shorthand parity with RowBuilder#button(). */
|
|
1556
|
+
button(displayText, buttonId) {
|
|
1557
|
+
return this.addButton(displayText, buttonId);
|
|
1558
|
+
}
|
|
1559
|
+
|
|
1560
|
+
/**
|
|
1561
|
+
* Vanz@Add 29-08-26 --- Fluent row helper ported from the RowBuilder class (already
|
|
1562
|
+
* present but previously unwired into ButtonV2). Lets callers group buttons via a
|
|
1563
|
+
* callback instead of chaining addButton() calls one at a time.
|
|
1564
|
+
* @param {(row: RowBuilder) => void} cb
|
|
1565
|
+
*/
|
|
1566
|
+
row(cb) {
|
|
1567
|
+
const r = new RowBuilder();
|
|
1568
|
+
cb(r);
|
|
1569
|
+
r.buttons.forEach((b) => this._buttons.push(b));
|
|
1570
|
+
return this;
|
|
1571
|
+
}
|
|
1572
|
+
|
|
1517
1573
|
// Vanz@Fix 22-08-26 (v4.7) --- _thumbnail was computed unconditionally (fetch + resize) even
|
|
1518
1574
|
// when setMedia() is used, in which case the location-fallback header (the only place
|
|
1519
1575
|
// _thumbnail is used) never runs at all — wasted network/CPU work on every build() call.
|
|
@@ -1578,6 +1634,193 @@ class ButtonV2 extends BaseBuilder {
|
|
|
1578
1634
|
}
|
|
1579
1635
|
}
|
|
1580
1636
|
|
|
1637
|
+
/**
|
|
1638
|
+
* Legacy `templateMessage` / `hydratedFourRowTemplate` builder — WA's Generation-1
|
|
1639
|
+
* button protocol (predates the nativeFlow format that Button/ButtonV2 use).
|
|
1640
|
+
* Capped at 3 buttons (quickReply/url/call only), no interactive list/flow support.
|
|
1641
|
+
* Ported from MessageBuilderV4.7.
|
|
1642
|
+
*/
|
|
1643
|
+
class ButtonV3 extends BaseBuilder {
|
|
1644
|
+
#client;
|
|
1645
|
+
|
|
1646
|
+
/** @param {import('../../WAProto/index.js').WASocket} client Active Baileys socket. */
|
|
1647
|
+
constructor(client) {
|
|
1648
|
+
super();
|
|
1649
|
+
if (!client) {
|
|
1650
|
+
throw new Error('Socket is required');
|
|
1651
|
+
}
|
|
1652
|
+
|
|
1653
|
+
this.#client = client;
|
|
1654
|
+
this._data;
|
|
1655
|
+
this._mediaHeaderType = null;
|
|
1656
|
+
this._buttons = [];
|
|
1657
|
+
}
|
|
1658
|
+
|
|
1659
|
+
/** Load an existing templateMessage (e.g. from a fetched/quoted message) for editing. */
|
|
1660
|
+
loadFrom(msg) {
|
|
1661
|
+
if (!msg) throw new Error('templateMessage needed');
|
|
1662
|
+
if (!msg.templateMessage) throw new Error('templateMessage not found');
|
|
1663
|
+
|
|
1664
|
+
const { templateMessage, ...extraPayload } = msg;
|
|
1665
|
+
const hft = templateMessage.hydratedFourRowTemplate || {};
|
|
1666
|
+
|
|
1667
|
+
this._title = hft.hydratedTitleText || '';
|
|
1668
|
+
this._body = hft.hydratedContentText || '';
|
|
1669
|
+
this._footer = hft.hydratedFooterText || '';
|
|
1670
|
+
this._contextInfo = templateMessage.contextInfo || {};
|
|
1671
|
+
this._extraPayload = extraPayload;
|
|
1672
|
+
|
|
1673
|
+
this._buttons = Array.isArray(hft.hydratedButtons)
|
|
1674
|
+
? hft.hydratedButtons.map((button) => ({ ...button }))
|
|
1675
|
+
: [];
|
|
1676
|
+
|
|
1677
|
+
if (hft.imageMessage) {
|
|
1678
|
+
this._data = { imageMessage: hft.imageMessage };
|
|
1679
|
+
this._mediaHeaderType = 'imageMessage';
|
|
1680
|
+
} else if (hft.videoMessage) {
|
|
1681
|
+
this._data = { videoMessage: hft.videoMessage };
|
|
1682
|
+
this._mediaHeaderType = 'videoMessage';
|
|
1683
|
+
} else if (hft.documentMessage) {
|
|
1684
|
+
this._data = { documentMessage: hft.documentMessage };
|
|
1685
|
+
this._mediaHeaderType = 'documentMessage';
|
|
1686
|
+
} else if (hft.locationMessage) {
|
|
1687
|
+
this._data = { locationMessage: hft.locationMessage };
|
|
1688
|
+
this._mediaHeaderType = 'locationMessage';
|
|
1689
|
+
} else {
|
|
1690
|
+
this._data = undefined;
|
|
1691
|
+
this._mediaHeaderType = null;
|
|
1692
|
+
}
|
|
1693
|
+
|
|
1694
|
+
return this;
|
|
1695
|
+
}
|
|
1696
|
+
|
|
1697
|
+
setImage(path, options = {}) {
|
|
1698
|
+
if (!path) throw new Error('Url or buffer needed');
|
|
1699
|
+
this._data = Buffer.isBuffer(path)
|
|
1700
|
+
? { image: path, ...options }
|
|
1701
|
+
: { image: { url: path }, ...options };
|
|
1702
|
+
this._mediaHeaderType = 'imageMessage';
|
|
1703
|
+
return this;
|
|
1704
|
+
}
|
|
1705
|
+
|
|
1706
|
+
setVideo(path, options = {}) {
|
|
1707
|
+
if (!path) throw new Error('Url or buffer needed');
|
|
1708
|
+
this._data = Buffer.isBuffer(path)
|
|
1709
|
+
? { video: path, ...options }
|
|
1710
|
+
: { video: { url: path }, ...options };
|
|
1711
|
+
this._mediaHeaderType = 'videoMessage';
|
|
1712
|
+
return this;
|
|
1713
|
+
}
|
|
1714
|
+
|
|
1715
|
+
setDocument(path, options = {}) {
|
|
1716
|
+
if (!path) throw new Error('Url or buffer needed');
|
|
1717
|
+
this._data = Buffer.isBuffer(path)
|
|
1718
|
+
? { document: path, ...options }
|
|
1719
|
+
: { document: { url: path }, ...options };
|
|
1720
|
+
this._mediaHeaderType = 'documentMessage';
|
|
1721
|
+
return this;
|
|
1722
|
+
}
|
|
1723
|
+
|
|
1724
|
+
setMedia(obj) {
|
|
1725
|
+
if (typeof obj !== 'object' || obj === null || Array.isArray(obj)) {
|
|
1726
|
+
throw new TypeError('Media must be a plain object');
|
|
1727
|
+
}
|
|
1728
|
+
this._data = obj;
|
|
1729
|
+
this._mediaHeaderType = null; // caller is expected to pass an already-resolved shape
|
|
1730
|
+
return this;
|
|
1731
|
+
}
|
|
1732
|
+
|
|
1733
|
+
clearButtons() {
|
|
1734
|
+
this._buttons = [];
|
|
1735
|
+
return this;
|
|
1736
|
+
}
|
|
1737
|
+
|
|
1738
|
+
addButton(hydratedButton) {
|
|
1739
|
+
if (this._buttons.length >= 3) {
|
|
1740
|
+
throw new Error('ButtonV3 (TemplateMessage) supports a maximum of 3 buttons');
|
|
1741
|
+
}
|
|
1742
|
+
this._buttons.push({ index: this._buttons.length + 1, ...hydratedButton });
|
|
1743
|
+
return this;
|
|
1744
|
+
}
|
|
1745
|
+
|
|
1746
|
+
addReply(display_text = '', id = '') {
|
|
1747
|
+
return this.addButton({
|
|
1748
|
+
quickReplyButton: { displayText: display_text, id },
|
|
1749
|
+
});
|
|
1750
|
+
}
|
|
1751
|
+
|
|
1752
|
+
addUrl(display_text = '', url = '', options = {}) {
|
|
1753
|
+
return this.addButton({
|
|
1754
|
+
urlButton: { displayText: display_text, url, ...options },
|
|
1755
|
+
});
|
|
1756
|
+
}
|
|
1757
|
+
|
|
1758
|
+
addCall(display_text = '', phone_number = '') {
|
|
1759
|
+
return this.addButton({
|
|
1760
|
+
callButton: { displayText: display_text, phoneNumber: phone_number },
|
|
1761
|
+
});
|
|
1762
|
+
}
|
|
1763
|
+
|
|
1764
|
+
async toTemplate() {
|
|
1765
|
+
let mediaFields = {};
|
|
1766
|
+
|
|
1767
|
+
if (this._data) {
|
|
1768
|
+
const alreadyResolved =
|
|
1769
|
+
this._data.imageMessage || this._data.videoMessage ||
|
|
1770
|
+
this._data.documentMessage || this._data.locationMessage;
|
|
1771
|
+
|
|
1772
|
+
mediaFields = alreadyResolved
|
|
1773
|
+
? this._data
|
|
1774
|
+
: await prepareWAMessageMedia(this._data, {
|
|
1775
|
+
upload: this.#client.waUploadToServer,
|
|
1776
|
+
}).catch((e) => {
|
|
1777
|
+
if (String(e).includes('Invalid media type')) return this._data;
|
|
1778
|
+
throw e;
|
|
1779
|
+
});
|
|
1780
|
+
} else if (this._title) {
|
|
1781
|
+
mediaFields = { hydratedTitleText: this._title };
|
|
1782
|
+
}
|
|
1783
|
+
|
|
1784
|
+
return {
|
|
1785
|
+
hydratedContentText: this._body,
|
|
1786
|
+
hydratedFooterText: this._footer,
|
|
1787
|
+
hydratedButtons: this._buttons,
|
|
1788
|
+
...mediaFields,
|
|
1789
|
+
};
|
|
1790
|
+
}
|
|
1791
|
+
|
|
1792
|
+
async build(jid, { messageId, ...options } = {}) {
|
|
1793
|
+
const hydratedFourRowTemplate = await this.toTemplate();
|
|
1794
|
+
|
|
1795
|
+
return generateWAMessageFromContent(
|
|
1796
|
+
jid,
|
|
1797
|
+
{
|
|
1798
|
+
...this._extraPayload,
|
|
1799
|
+
templateMessage: {
|
|
1800
|
+
hydratedFourRowTemplate,
|
|
1801
|
+
contextInfo: this._contextInfo,
|
|
1802
|
+
},
|
|
1803
|
+
},
|
|
1804
|
+
{ messageId: messageId || generateMessageIDV2(), ...options },
|
|
1805
|
+
);
|
|
1806
|
+
}
|
|
1807
|
+
|
|
1808
|
+
async send(jid, { messageId, additionalNodes = [], ...options } = {}) {
|
|
1809
|
+
if (this._buttons.length < 1)
|
|
1810
|
+
throw new Error('ButtonV3 requires at least one button');
|
|
1811
|
+
const msg = await this.build(jid, { messageId, ...options });
|
|
1812
|
+
|
|
1813
|
+
// TemplateMessage predates the nativeFlow protocol and does not need the
|
|
1814
|
+
// "biz"/"native_flow" additionalNodes hack that Button/ButtonV2 use.
|
|
1815
|
+
await this.#client.relayMessage(msg.key.remoteJid, msg.message, {
|
|
1816
|
+
messageId: msg.key.id,
|
|
1817
|
+
additionalNodes,
|
|
1818
|
+
...options,
|
|
1819
|
+
});
|
|
1820
|
+
return msg;
|
|
1821
|
+
}
|
|
1822
|
+
}
|
|
1823
|
+
|
|
1581
1824
|
/** Carousel of interactive cards (each with its own header media + optional buttons), scrollable horizontally in-chat. */
|
|
1582
1825
|
class Carousel extends BaseBuilder {
|
|
1583
1826
|
#client;
|
|
@@ -4054,6 +4297,9 @@ class AIRich extends BaseBuilder {
|
|
|
4054
4297
|
}
|
|
4055
4298
|
}
|
|
4056
4299
|
|
|
4300
|
+
/** Thin no-op subclass of `AIRich` — kept for drop-in compatibility with code ported from ourin-baileys that references `ORich` by name. */
|
|
4301
|
+
class ORich extends AIRich {}
|
|
4302
|
+
|
|
4057
4303
|
// Vanz@Alias --- AIRich diekspos ulang pake nama sendiri. Implementasi & referensi
|
|
4058
4304
|
// internal (AIRich.newLayout/tokenizer/toTableMetadata) TETAP pake nama class asli
|
|
4059
4305
|
// biar nggak perlu rewrite ratusan pemanggilan; ini cuma nge-alias binding exportnya.
|
|
@@ -4063,9 +4309,13 @@ export {
|
|
|
4063
4309
|
MESSAGE_BUILDER_VERSION,
|
|
4064
4310
|
Button,
|
|
4065
4311
|
ButtonV2,
|
|
4312
|
+
ButtonV3,
|
|
4313
|
+
RowBuilder,
|
|
4314
|
+
CardBuilder,
|
|
4066
4315
|
Carousel,
|
|
4067
4316
|
Poll,
|
|
4068
4317
|
AIRich,
|
|
4318
|
+
ORich,
|
|
4069
4319
|
AIRich as AIVanzxy,
|
|
4070
4320
|
AIRich as LeafRich,
|
|
4071
4321
|
AIRich as VanzxyAI,
|