@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.
Files changed (51) hide show
  1. package/NOTICE.md +50 -0
  2. package/lib/Utils/A2UI.js +217 -0
  3. package/lib/Utils/MessageBuilder.js +290 -40
  4. package/lib/Utils/MessageBuilder_d.ts +45 -0
  5. package/lib/Utils/PersistentStore.js +592 -0
  6. package/lib/Utils/PersistentStore_d.ts +60 -0
  7. package/lib/Utils/anti-delete.d.ts +68 -0
  8. package/lib/Utils/anti-delete.js +185 -0
  9. package/lib/Utils/auto-reply.d.ts +47 -0
  10. package/lib/Utils/auto-reply.js +155 -0
  11. package/lib/Utils/button-helper-utils.js +314 -0
  12. package/lib/Utils/button-sender.js +817 -0
  13. package/lib/Utils/chat-history-helpers.d.ts +21 -0
  14. package/lib/Utils/chat-history-helpers.js +71 -0
  15. package/lib/Utils/index.d.ts +11 -0
  16. package/lib/Utils/index.js +16 -0
  17. package/lib/Utils/media-messages.d.ts +18 -0
  18. package/lib/Utils/media-messages.js +71 -0
  19. package/lib/Utils/media-set.d.ts +13 -0
  20. package/lib/Utils/media-set.js +165 -0
  21. package/lib/Utils/message-kind.js +139 -0
  22. package/lib/Utils/message-search.d.ts +44 -0
  23. package/lib/Utils/message-search.js +174 -0
  24. package/lib/Utils/scheduling.d.ts +42 -0
  25. package/lib/Utils/scheduling.js +140 -0
  26. package/lib/Utils/status.d.ts +50 -0
  27. package/lib/Utils/status.js +108 -0
  28. package/lib/Utils/stickerpack.d.ts +51 -0
  29. package/lib/Utils/stickerpack.js +276 -0
  30. package/lib/Utils/templates.d.ts +76 -0
  31. package/lib/Utils/templates.js +151 -0
  32. package/lib/Utils/use-sqlite-auth-state.js +28 -1
  33. package/lib/Utils/vcard.d.ts +58 -0
  34. package/lib/Utils/vcard.js +94 -0
  35. package/lib/VoIP/audio-feeder.d.ts +15 -0
  36. package/lib/VoIP/audio-feeder.js +132 -0
  37. package/lib/VoIP/index.js +277 -0
  38. package/lib/VoIP/relay-transport.d.ts +43 -0
  39. package/lib/VoIP/relay-transport.js +559 -0
  40. package/lib/VoIP/signaling.js +624 -0
  41. package/lib/VoIP/types.d.ts +69 -0
  42. package/lib/VoIP/types.js +17 -0
  43. package/lib/VoIP/wasm-engine.d.ts +103 -0
  44. package/lib/VoIP/wasm-engine.js +1214 -0
  45. package/lib/VoIP/worker-bootstrap.js +1042 -0
  46. package/lib/WABinary/generic-utils.js +8 -0
  47. package/lib/assets/wasm/loader.js +5 -0
  48. package/lib/assets/wasm/whatsapp.wasm +0 -0
  49. package/lib/assets/wasm/worker-modules.js +273 -0
  50. package/lib/index.js +4 -0
  51. 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.9.1';
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 `flow_action` shortcut. Requires a real registered Flow. */
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
- name: 'flow_action',
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
- const bizContent = this.#isLoneSingleSelect()
1421
- ? [{ tag: 'list', attrs: { v: '2', type: 'product_list' } }]
1422
- : [
1423
- ...(this._buttons.length > 0
1424
- ? [
1425
- {
1426
- tag: 'interactive',
1427
- attrs: { type: 'native_flow', v: '1' },
1428
- content: [this.#buildNativeFlowNode()],
1429
- },
1430
- ]
1431
- : []),
1432
- ...(this._bloksWidget
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,