@vanzxy/baileys 1.6.2 → 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 +332 -46
- 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
|
@@ -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
|
|
@@ -704,19 +753,44 @@ class Button extends BaseBuilder {
|
|
|
704
753
|
// error-prone (dangling ids, ordering), so this accepts a plain nested tree instead —
|
|
705
754
|
// { component, ...props, children: [...] } / { component, ...props, child: {...} } — and
|
|
706
755
|
// flattens it into that array itself, auto-assigning ids.
|
|
707
|
-
|
|
756
|
+
//
|
|
757
|
+
// Vanz@Fix 26-08-26 (v2) --- Modal is a different shape: `trigger`/`content` point to a
|
|
758
|
+
// SIBLING node by id (e.g. the Button that opens it), not a fresh child — captured traffic
|
|
759
|
+
// confirmed `{ trigger: "<id of an existing Button node>", content: "<id of its body>" }`.
|
|
760
|
+
// Nesting it as a plain `child`/`children` would duplicate the trigger node. So:
|
|
761
|
+
// - any prop whose value is `{ component: ..., ... }` is now auto-flattened as a nested
|
|
762
|
+
// node too (not just `child`/`children`) — covers Modal.content directly.
|
|
763
|
+
// - a node can carry a builder-only `ref: 'name'` tag; another prop can then point back
|
|
764
|
+
// at it with `{ $ref: 'name' }`, resolved to that node's real id after the whole tree
|
|
765
|
+
// is walked (order-independent). `ref` itself is stripped and never reaches the wire.
|
|
766
|
+
#flattenBloks(tree, out, ctx = { n: 0, refs: new Map(), pending: [] }, id = 'root') {
|
|
708
767
|
if (!tree || typeof tree !== 'object') throw new TypeError('setBloksWidget: every node needs a "component" type');
|
|
709
|
-
const { component, children, child, ...
|
|
768
|
+
const { component, children, child, ref, ...rest } = tree;
|
|
710
769
|
if (typeof component !== 'string' || !component) throw new TypeError('setBloksWidget: every node needs a "component" type');
|
|
711
770
|
|
|
712
|
-
const
|
|
771
|
+
const isTreeNode = (v) => v && typeof v === 'object' && !Array.isArray(v) && typeof v.component === 'string';
|
|
772
|
+
const isRefMarker = (v) => v && typeof v === 'object' && !Array.isArray(v) && typeof v.$ref === 'string' && Object.keys(v).length === 1;
|
|
773
|
+
|
|
774
|
+
const node = { id, component };
|
|
775
|
+
for (const [k, v] of Object.entries(rest)) {
|
|
776
|
+
if (isRefMarker(v)) {
|
|
777
|
+
node[k] = null; // resolved once the full tree (and its `ref` tags) has been walked
|
|
778
|
+
ctx.pending.push({ node, key: k, refName: v.$ref });
|
|
779
|
+
} else if (isTreeNode(v)) {
|
|
780
|
+
node[k] = this.#flattenBloks(v, out, ctx, `n${ctx.n++}`);
|
|
781
|
+
} else {
|
|
782
|
+
node[k] = v;
|
|
783
|
+
}
|
|
784
|
+
}
|
|
713
785
|
|
|
714
786
|
if (Array.isArray(children)) {
|
|
715
|
-
node.children = children.map((c) => this.#flattenBloks(c, out,
|
|
787
|
+
node.children = children.map((c) => this.#flattenBloks(c, out, ctx, `n${ctx.n++}`));
|
|
716
788
|
} else if (child) {
|
|
717
|
-
node.child = this.#flattenBloks(child, out,
|
|
789
|
+
node.child = this.#flattenBloks(child, out, ctx, `n${ctx.n++}`);
|
|
718
790
|
}
|
|
719
791
|
|
|
792
|
+
if (ref) ctx.refs.set(ref, id);
|
|
793
|
+
|
|
720
794
|
out.push(node);
|
|
721
795
|
return id;
|
|
722
796
|
}
|
|
@@ -725,12 +799,23 @@ class Button extends BaseBuilder {
|
|
|
725
799
|
* Set a Bloks/A2UI native widget (`bloksWidget`, `type: "im_a2ui"`) — a real interactive
|
|
726
800
|
* screen (images, video, checkboxes, text fields, buttons that fire an `action`), not a
|
|
727
801
|
* static card. Pass a nested tree; ids are assigned automatically.
|
|
802
|
+
*
|
|
803
|
+
* For components that reference a SIBLING node instead of nesting one — currently just
|
|
804
|
+
* `Modal.trigger` — tag the source node with `ref: 'someName'` and point at it with
|
|
805
|
+
* `{ $ref: 'someName' }`. Everything else (including `Modal.content`) can just be nested
|
|
806
|
+
* directly, no special key needed.
|
|
728
807
|
* @param {Record<string, any>} tree Root node, e.g. `{ component: 'Column', children: [...] }`.
|
|
729
808
|
* @param {{uuid?: string, catalogId?: string, surfaceId?: string, version?: string}} [options]
|
|
730
809
|
*/
|
|
731
810
|
setBloksWidget(tree, { uuid = crypto.randomUUID(), catalogId = 'https://a2ui.org/specification/v0_9/catalogs/basic/catalog.json', surfaceId, version = 'v0.9' } = {}) {
|
|
732
811
|
const components = [];
|
|
733
|
-
|
|
812
|
+
const ctx = { n: 0, refs: new Map(), pending: [] };
|
|
813
|
+
this.#flattenBloks(tree, components, ctx);
|
|
814
|
+
for (const { node, key, refName } of ctx.pending) {
|
|
815
|
+
const resolved = ctx.refs.get(refName);
|
|
816
|
+
if (!resolved) throw new Error(`setBloksWidget: ref "${refName}" (used on "${key}") was never declared with ref: "${refName}" on any node`);
|
|
817
|
+
node[key] = resolved;
|
|
818
|
+
}
|
|
734
819
|
|
|
735
820
|
this._bloksWidget = {
|
|
736
821
|
uuid,
|
|
@@ -1165,13 +1250,23 @@ class Button extends BaseBuilder {
|
|
|
1165
1250
|
return this;
|
|
1166
1251
|
}
|
|
1167
1252
|
|
|
1168
|
-
/** WhatsApp Flows
|
|
1253
|
+
/** WhatsApp Flows shortcut. Requires a real registered Flow. */
|
|
1169
1254
|
addFlow(flow = {}, display_text = '') {
|
|
1170
1255
|
if (typeof flow !== 'object' || flow === null || Array.isArray(flow) || !flow.id) throw new TypeError('addFlow(flow) requires a plain object with flow.id');
|
|
1171
1256
|
this._buttons.push({
|
|
1172
|
-
|
|
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',
|
|
1173
1263
|
buttonParamsJson: JSON.stringify({
|
|
1174
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(),
|
|
1175
1270
|
flow_id: flow.id,
|
|
1176
1271
|
flow_cta: display_text || flow.cta || 'Continue',
|
|
1177
1272
|
flow_action: flow.action || 'navigate',
|
|
@@ -1369,50 +1464,29 @@ class Button extends BaseBuilder {
|
|
|
1369
1464
|
);
|
|
1370
1465
|
}
|
|
1371
1466
|
|
|
1372
|
-
// Vanz@Add 22-08-26 (v4.7) --- picks the native_flow node variant for the first
|
|
1373
|
-
// button's name, per Button.#SPECIAL_FLOW. Falls back to the generic mixed node
|
|
1374
|
-
// (previous unconditional behaviour) for anything not in that map.
|
|
1375
|
-
#buildNativeFlowNode() {
|
|
1376
|
-
const special = Button.#SPECIAL_FLOW[this._buttons[0]?.name];
|
|
1377
|
-
return special ? { tag: 'native_flow', attrs: special } : { tag: 'native_flow', attrs: { v: '9', name: 'mixed' } };
|
|
1378
|
-
}
|
|
1379
|
-
|
|
1380
1467
|
/** Build and send this interactive message. @param {string} jid Destination chat/group jid. */
|
|
1381
1468
|
async send(jid, { ...options } = {}) {
|
|
1382
1469
|
const msg = await this.build(jid, options);
|
|
1383
1470
|
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
tag: 'quality_control',
|
|
1400
|
-
attrs: { decision_id: crypto.randomUUID().replace(/-/g, ''), source_type: 'third_party' },
|
|
1401
|
-
content: [{ tag: 'decision_source', attrs: { value: 'df' } }],
|
|
1402
|
-
},
|
|
1403
|
-
]
|
|
1404
|
-
: []),
|
|
1405
|
-
];
|
|
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);
|
|
1406
1486
|
|
|
1407
1487
|
await this.#client.relayMessage(msg.key.remoteJid, msg.message, {
|
|
1408
1488
|
messageId: msg.key.id,
|
|
1409
|
-
additionalNodes: [
|
|
1410
|
-
{
|
|
1411
|
-
tag: 'biz',
|
|
1412
|
-
attrs: {},
|
|
1413
|
-
content: bizContent,
|
|
1414
|
-
},
|
|
1415
|
-
],
|
|
1489
|
+
additionalNodes: [bizNode],
|
|
1416
1490
|
...options,
|
|
1417
1491
|
});
|
|
1418
1492
|
return msg;
|
|
@@ -1478,6 +1552,24 @@ class ButtonV2 extends BaseBuilder {
|
|
|
1478
1552
|
return this;
|
|
1479
1553
|
}
|
|
1480
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
|
+
|
|
1481
1573
|
// Vanz@Fix 22-08-26 (v4.7) --- _thumbnail was computed unconditionally (fetch + resize) even
|
|
1482
1574
|
// when setMedia() is used, in which case the location-fallback header (the only place
|
|
1483
1575
|
// _thumbnail is used) never runs at all — wasted network/CPU work on every build() call.
|
|
@@ -1542,6 +1634,193 @@ class ButtonV2 extends BaseBuilder {
|
|
|
1542
1634
|
}
|
|
1543
1635
|
}
|
|
1544
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
|
+
|
|
1545
1824
|
/** Carousel of interactive cards (each with its own header media + optional buttons), scrollable horizontally in-chat. */
|
|
1546
1825
|
class Carousel extends BaseBuilder {
|
|
1547
1826
|
#client;
|
|
@@ -4018,6 +4297,9 @@ class AIRich extends BaseBuilder {
|
|
|
4018
4297
|
}
|
|
4019
4298
|
}
|
|
4020
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
|
+
|
|
4021
4303
|
// Vanz@Alias --- AIRich diekspos ulang pake nama sendiri. Implementasi & referensi
|
|
4022
4304
|
// internal (AIRich.newLayout/tokenizer/toTableMetadata) TETAP pake nama class asli
|
|
4023
4305
|
// biar nggak perlu rewrite ratusan pemanggilan; ini cuma nge-alias binding exportnya.
|
|
@@ -4027,9 +4309,13 @@ export {
|
|
|
4027
4309
|
MESSAGE_BUILDER_VERSION,
|
|
4028
4310
|
Button,
|
|
4029
4311
|
ButtonV2,
|
|
4312
|
+
ButtonV3,
|
|
4313
|
+
RowBuilder,
|
|
4314
|
+
CardBuilder,
|
|
4030
4315
|
Carousel,
|
|
4031
4316
|
Poll,
|
|
4032
4317
|
AIRich,
|
|
4318
|
+
ORich,
|
|
4033
4319
|
AIRich as AIVanzxy,
|
|
4034
4320
|
AIRich as LeafRich,
|
|
4035
4321
|
AIRich as VanzxyAI,
|
|
@@ -104,9 +104,27 @@ export class Button extends BaseBuilder {
|
|
|
104
104
|
static menu?: (client: any, options?: Record<string, any>) => Button;
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
+
export class RowBuilder {
|
|
108
|
+
constructor();
|
|
109
|
+
buttons: Record<string, any>[];
|
|
110
|
+
button(displayText: string, buttonId?: string): this;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export class CardBuilder {
|
|
114
|
+
constructor(client: any);
|
|
115
|
+
title(text: string): this;
|
|
116
|
+
text(text: string): this;
|
|
117
|
+
image(path: string | Buffer, options?: Record<string, any>): this;
|
|
118
|
+
button(displayText: string, buttonId?: string): this;
|
|
119
|
+
}
|
|
120
|
+
|
|
107
121
|
export class ButtonV2 extends BaseBuilder {
|
|
108
122
|
constructor(client: any);
|
|
109
123
|
addButton(displayText: string, buttonId?: string): this;
|
|
124
|
+
/** Alias for addButton(). */
|
|
125
|
+
button(displayText: string, buttonId?: string): this;
|
|
126
|
+
/** Fluent row helper — group buttons via a callback instead of chaining addButton(). */
|
|
127
|
+
row(cb: (row: RowBuilder) => void): this;
|
|
110
128
|
addRawButton(obj: Record<string, any>): this;
|
|
111
129
|
setThumbnail(path: string | Buffer): this;
|
|
112
130
|
setMedia(obj: Record<string, any>): this;
|
|
@@ -115,6 +133,30 @@ export class ButtonV2 extends BaseBuilder {
|
|
|
115
133
|
send(jid: string, options?: Record<string, any> & { viewOnce?: boolean }): Promise<any>;
|
|
116
134
|
}
|
|
117
135
|
|
|
136
|
+
/**
|
|
137
|
+
* Legacy `templateMessage` / `hydratedFourRowTemplate` builder — WA's Generation-1
|
|
138
|
+
* button protocol (predates the nativeFlow format Button/ButtonV2 use). Capped at
|
|
139
|
+
* 3 buttons (quickReply/url/call only), no interactive list/flow support.
|
|
140
|
+
*/
|
|
141
|
+
export class ButtonV3 extends BaseBuilder {
|
|
142
|
+
constructor(client: any);
|
|
143
|
+
/** Load an existing templateMessage (e.g. from a fetched/quoted message) for editing. */
|
|
144
|
+
loadFrom(msg: Record<string, any>): this;
|
|
145
|
+
setImage(path: string | Buffer, options?: Record<string, any>): this;
|
|
146
|
+
setVideo(path: string | Buffer, options?: Record<string, any>): this;
|
|
147
|
+
setDocument(path: string | Buffer, options?: Record<string, any>): this;
|
|
148
|
+
setMedia(obj: Record<string, any>): this;
|
|
149
|
+
clearButtons(): this;
|
|
150
|
+
/** Max 3 buttons — throws past the limit. */
|
|
151
|
+
addButton(hydratedButton: Record<string, any>): this;
|
|
152
|
+
addReply(display_text?: string, id?: string): this;
|
|
153
|
+
addUrl(display_text?: string, url?: string, options?: Record<string, any>): this;
|
|
154
|
+
addCall(display_text?: string, phone_number?: string): this;
|
|
155
|
+
toTemplate(): Promise<Record<string, any>>;
|
|
156
|
+
build(jid: string, options?: Record<string, any>): Promise<Record<string, any>>;
|
|
157
|
+
send(jid: string, options?: Record<string, any>): Promise<any>;
|
|
158
|
+
}
|
|
159
|
+
|
|
118
160
|
export class Carousel extends BaseBuilder {
|
|
119
161
|
constructor(client: any);
|
|
120
162
|
/** WhatsApp caps carousels at this many cards (10); addCard() throws past it. */
|
|
@@ -224,6 +266,9 @@ export class AIRich extends BaseBuilder {
|
|
|
224
266
|
// MessageBuilder.js). All four are structurally identical to AIRich.
|
|
225
267
|
export { AIRich as AIVanzxy, AIRich as LeafRich, AIRich as VanzxyAI, AIRich as VanzxyRich };
|
|
226
268
|
|
|
269
|
+
/** `class ORich extends AIRich {}` — plain re-export alias, no additional members. */
|
|
270
|
+
export class ORich extends AIRich {}
|
|
271
|
+
|
|
227
272
|
export class Toolkit {
|
|
228
273
|
static extractIE(text: string, options?: AddTextOptions): Record<string, any>;
|
|
229
274
|
static getMp4Duration(buffer: Buffer, options?: { silent?: boolean }): Promise<number>;
|