@vanzxy/baileys 2.0.1 → 2.0.3
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 +1 -2
- package/README.md +46 -3
- package/lib/Builders/AIRich.js +1143 -1103
- package/lib/Builders/Album.js +71 -0
- package/lib/Builders/Contact.js +83 -0
- package/lib/Builders/LinkPreview.js +74 -0
- package/lib/Builders/Location.js +151 -0
- package/lib/Builders/NativeFlow.js +210 -0
- package/lib/Builders/VanzxyBaileys.js +40 -1
- package/lib/Builders/index.js +6 -1
- package/package.json +1 -1
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { BaseBuilder, generateWAMessageFromContent, prepareWAMessageMedia, generateMessageIDV2, crypto } from './shared.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Album/media-group builder — bundles 2+ images/videos into one WhatsApp
|
|
5
|
+
* album. Fully handled by the existing `content.album` shorthand at the
|
|
6
|
+
* socket layer (see Socket/messages-send.js, `if ('album' in content)`):
|
|
7
|
+
* it sends the `albumMessage` cover then each item as a linked follow-up
|
|
8
|
+
* with a configurable delay, so this builder only needs to shape the array.
|
|
9
|
+
*/
|
|
10
|
+
class Album extends BaseBuilder {
|
|
11
|
+
#client;
|
|
12
|
+
|
|
13
|
+
/** @param {import('../../WAProto/index.js').WASocket} client Active Baileys socket (must expose `sendMessage`). */
|
|
14
|
+
constructor(client) {
|
|
15
|
+
super();
|
|
16
|
+
if (!client) throw new Error('Socket is required');
|
|
17
|
+
this.#client = client;
|
|
18
|
+
|
|
19
|
+
this._items = [];
|
|
20
|
+
this._delayMs;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Append an image. @param {string|Buffer} path Url or buffer. */
|
|
24
|
+
addImage(path, { caption, ...options } = {}) {
|
|
25
|
+
if (!path) throw new Error('Url or buffer needed');
|
|
26
|
+
const image = Buffer.isBuffer(path) ? path : { url: path };
|
|
27
|
+
this._items.push({ image, ...(caption !== undefined && { caption }), ...options });
|
|
28
|
+
return this;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Append a video. @param {string|Buffer} path Url or buffer. */
|
|
32
|
+
addVideo(path, { caption, ...options } = {}) {
|
|
33
|
+
if (!path) throw new Error('Url or buffer needed');
|
|
34
|
+
const video = Buffer.isBuffer(path) ? path : { url: path };
|
|
35
|
+
this._items.push({ video, ...(caption !== undefined && { caption }), ...options });
|
|
36
|
+
return this;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Append several images/videos at once, in `{image|video, caption?}` shape (bypasses addImage()/addVideo()'s shorthand). */
|
|
40
|
+
addItems(items) {
|
|
41
|
+
if (!Array.isArray(items) || !items.length) throw new TypeError('addItems(items) requires a non-empty array');
|
|
42
|
+
items.forEach((item) => this._items.push(item));
|
|
43
|
+
return this;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Remove every item added so far. */
|
|
47
|
+
clearItems() {
|
|
48
|
+
this._items = [];
|
|
49
|
+
return this;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Delay in ms between each follow-up item after the album cover (default: `config.albumDelayMs` or 1500ms). */
|
|
53
|
+
setDelay(ms) {
|
|
54
|
+
if (typeof ms !== 'number' || ms < 0) throw new TypeError('setDelay(ms) requires a non-negative number');
|
|
55
|
+
this._delayMs = ms;
|
|
56
|
+
return this;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** @returns {{album: Record<string, any>[]}} The `sendMessage()`-shaped payload, without sending it. */
|
|
60
|
+
build() {
|
|
61
|
+
if (this._items.length < 2) throw new Error('Album requires at least 2 items (use addImage()/addVideo(), images+videos combined)');
|
|
62
|
+
return { album: this._items };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Build and send via the socket's `sendMessage()`. */
|
|
66
|
+
async send(jid, options = {}) {
|
|
67
|
+
return this.#client.sendMessage(jid, this.build(), { ...(this._delayMs !== undefined && { delayMs: this._delayMs }), ...options });
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export { Album };
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { BaseBuilder, generateWAMessageFromContent, prepareWAMessageMedia, generateMessageIDV2, crypto } from './shared.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Contact-card message builder — one contact sends as `contactMessage`, two or
|
|
5
|
+
* more as `contactsArrayMessage` (both handled by the existing `message.contacts`
|
|
6
|
+
* shorthand in Utils/messages.js). Accepts either a raw vCard string via
|
|
7
|
+
* `addRawVcard()` or plain fields via `addContact()`, which builds a minimal
|
|
8
|
+
* VCARD 3.0 block itself.
|
|
9
|
+
*/
|
|
10
|
+
class Contact extends BaseBuilder {
|
|
11
|
+
#client;
|
|
12
|
+
|
|
13
|
+
/** @param {import('../../WAProto/index.js').WASocket} client Active Baileys socket (must expose `sendMessage`). */
|
|
14
|
+
constructor(client) {
|
|
15
|
+
super();
|
|
16
|
+
if (!client) throw new Error('Socket is required');
|
|
17
|
+
this.#client = client;
|
|
18
|
+
|
|
19
|
+
this._contacts = [];
|
|
20
|
+
this._displayName;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Name shown on the card when sending more than one contact (`contactsArrayMessage.displayName`). Ignored for a single contact. */
|
|
24
|
+
setDisplayName(name) {
|
|
25
|
+
this._displayName = name;
|
|
26
|
+
return this;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Add a contact from plain fields — builds a minimal VCARD 3.0 block.
|
|
31
|
+
* @param {{name: string, phone?: string|string[], org?: string, email?: string, waid?: string}} fields
|
|
32
|
+
* `waid` (digits-only, no `+`) makes the number tappable to chat/call on WhatsApp; without it the number is still shown but may not be tappable on every client.
|
|
33
|
+
*/
|
|
34
|
+
addContact({ name, phone, org, email, waid } = {}) {
|
|
35
|
+
if (typeof name !== 'string' || !name) throw new TypeError('addContact({name, ...}) requires a non-empty name');
|
|
36
|
+
|
|
37
|
+
const phones = phone === undefined ? [] : Array.isArray(phone) ? phone : [phone];
|
|
38
|
+
const lines = ['BEGIN:VCARD', 'VERSION:3.0', `FN:${name}`, `N:${name};;;;`];
|
|
39
|
+
phones.forEach((number, i) => {
|
|
40
|
+
const type = waid && i === 0 ? `TYPE=CELL,WAID=${waid}` : 'TYPE=CELL';
|
|
41
|
+
lines.push(`TEL;${type}:${number}`);
|
|
42
|
+
});
|
|
43
|
+
if (org) lines.push(`ORG:${org}`);
|
|
44
|
+
if (email) lines.push(`EMAIL:${email}`);
|
|
45
|
+
lines.push('END:VCARD');
|
|
46
|
+
|
|
47
|
+
this._contacts.push({ displayName: name, vcard: lines.join('\n') });
|
|
48
|
+
return this;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Add a contact from a pre-built vCard string (bypasses `addContact()`'s auto-generation). */
|
|
52
|
+
addRawVcard(displayName, vcard) {
|
|
53
|
+
if (typeof displayName !== 'string' || !displayName) throw new TypeError('addRawVcard(displayName, vcard) requires a non-empty displayName');
|
|
54
|
+
if (typeof vcard !== 'string' || !vcard) throw new TypeError('addRawVcard(displayName, vcard) requires a non-empty vcard string');
|
|
55
|
+
this._contacts.push({ displayName, vcard });
|
|
56
|
+
return this;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Remove every contact added so far. */
|
|
60
|
+
clearContacts() {
|
|
61
|
+
this._contacts = [];
|
|
62
|
+
return this;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** @returns {{contacts: Record<string, any>}} The `sendMessage()`-shaped payload, without sending it. */
|
|
66
|
+
build() {
|
|
67
|
+
if (!this._contacts.length) throw new Error('Contact requires at least one contact (use addContact()/addRawVcard())');
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
contacts: {
|
|
71
|
+
...(this._displayName !== undefined && { displayName: this._displayName }),
|
|
72
|
+
contacts: this._contacts,
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Build and send via the socket's `sendMessage()`. */
|
|
78
|
+
async send(jid, options = {}) {
|
|
79
|
+
return this.#client.sendMessage(jid, this.build(), options);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export { Contact };
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { prepareWAMessageMedia } from './shared.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Binds `sendLinkPreview` onto a Baileys socket instance.
|
|
5
|
+
*
|
|
6
|
+
* Ported from MessageBuilder.js's `bind()` helper (present upstream in
|
|
7
|
+
* MessageBuilderV4.7 / "mb"'s build, but missing from this fork).
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* import { bindLinkPreview } from '@vanzxy/baileys';
|
|
11
|
+
* const sock = bindLinkPreview(makeWASocket({ ... }));
|
|
12
|
+
* await sock.sendLinkPreview(jid, 'check this out', 'https://example.com', 'Example Title');
|
|
13
|
+
*/
|
|
14
|
+
function bindLinkPreview(client) {
|
|
15
|
+
if (!client) {
|
|
16
|
+
throw new Error('Socket is required');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return Object.defineProperties(client, {
|
|
20
|
+
sendLinkPreview: {
|
|
21
|
+
configurable: true,
|
|
22
|
+
writable: true,
|
|
23
|
+
async value(jid, text, link, title, description, thumbnail, options = {}) {
|
|
24
|
+
if (typeof jid !== 'string') {
|
|
25
|
+
throw new TypeError('jid is not string');
|
|
26
|
+
}
|
|
27
|
+
if (typeof text !== 'string') {
|
|
28
|
+
throw new TypeError('text is not string');
|
|
29
|
+
}
|
|
30
|
+
if (typeof link !== 'string') {
|
|
31
|
+
throw new TypeError('link is not string');
|
|
32
|
+
}
|
|
33
|
+
if (typeof title !== 'string') {
|
|
34
|
+
throw new TypeError('title is not string');
|
|
35
|
+
}
|
|
36
|
+
if (description && typeof description !== 'string') {
|
|
37
|
+
throw new TypeError('description is not string');
|
|
38
|
+
}
|
|
39
|
+
if (thumbnail && !Buffer.isBuffer(thumbnail) && typeof thumbnail.url !== 'string') {
|
|
40
|
+
throw new TypeError('thumbnail must be Buffer or object with url key');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const image = thumbnail
|
|
44
|
+
? await prepareWAMessageMedia(
|
|
45
|
+
{ image: thumbnail },
|
|
46
|
+
{
|
|
47
|
+
upload: client.waUploadToServer,
|
|
48
|
+
mediaTypeOverride: 'thumbnail-link',
|
|
49
|
+
}
|
|
50
|
+
).then((v) => v.imageMessage)
|
|
51
|
+
: undefined;
|
|
52
|
+
|
|
53
|
+
text = text.includes(link) ? text : `${link}\n${text}`;
|
|
54
|
+
|
|
55
|
+
return await client.sendMessage(
|
|
56
|
+
jid,
|
|
57
|
+
{
|
|
58
|
+
text,
|
|
59
|
+
linkPreview: {
|
|
60
|
+
'matched-text': link,
|
|
61
|
+
title,
|
|
62
|
+
description,
|
|
63
|
+
jpegThumbnail: image?.jpegThumbnail,
|
|
64
|
+
highQualityThumbnail: image,
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
options
|
|
68
|
+
);
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export { bindLinkPreview };
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { BaseBuilder, generateWAMessageFromContent, prepareWAMessageMedia, generateMessageIDV2, crypto } from './shared.js';
|
|
2
|
+
import { proto } from '../../WAProto/index.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Location message builder — static pin via the existing `message.location`
|
|
6
|
+
* shorthand (see Utils/messages.js, `hasNonNullishProperty(message, 'location')`),
|
|
7
|
+
* or a live/moving location. There is no `liveLocation` shorthand in
|
|
8
|
+
* `generateWAMessageContent` yet, so the live path goes through the `raw: true`
|
|
9
|
+
* escape hatch (same file, top of `generateWAMessageContent`) with a hand-built
|
|
10
|
+
* `liveLocationMessage`.
|
|
11
|
+
*/
|
|
12
|
+
class Location extends BaseBuilder {
|
|
13
|
+
#client;
|
|
14
|
+
|
|
15
|
+
/** @param {import('../../WAProto/index.js').WASocket} client Active Baileys socket (must expose `sendMessage`). */
|
|
16
|
+
constructor(client) {
|
|
17
|
+
super();
|
|
18
|
+
if (!client) throw new Error('Socket is required');
|
|
19
|
+
this.#client = client;
|
|
20
|
+
|
|
21
|
+
this._lat;
|
|
22
|
+
this._lng;
|
|
23
|
+
this._name;
|
|
24
|
+
this._address;
|
|
25
|
+
this._url;
|
|
26
|
+
this._thumbnail;
|
|
27
|
+
this._live = false;
|
|
28
|
+
this._accuracy;
|
|
29
|
+
this._speed;
|
|
30
|
+
this._degrees;
|
|
31
|
+
this._caption;
|
|
32
|
+
this._sequence;
|
|
33
|
+
this._timeOffset;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Set the pin's coordinates. Required. */
|
|
37
|
+
setCoordinates(lat, lng) {
|
|
38
|
+
if (typeof lat !== 'number' || typeof lng !== 'number') throw new TypeError('setCoordinates(lat, lng) requires two numbers');
|
|
39
|
+
this._lat = lat;
|
|
40
|
+
this._lng = lng;
|
|
41
|
+
return this;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Place name shown above the address (static location only). */
|
|
45
|
+
setName(name) {
|
|
46
|
+
this._name = name;
|
|
47
|
+
return this;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Address line shown under the name (static location only). */
|
|
51
|
+
setAddress(address) {
|
|
52
|
+
this._address = address;
|
|
53
|
+
return this;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** External map URL attached to the pin (static location only). */
|
|
57
|
+
setUrl(url) {
|
|
58
|
+
this._url = url;
|
|
59
|
+
return this;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Raw jpeg thumbnail bytes for the map preview. */
|
|
63
|
+
setThumbnail(buffer) {
|
|
64
|
+
this._thumbnail = buffer;
|
|
65
|
+
return this;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Switch between a static pin (default) and a live/moving location. */
|
|
69
|
+
setLive(isLive = true) {
|
|
70
|
+
this._live = isLive;
|
|
71
|
+
return this;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** GPS accuracy radius in meters (live location only). */
|
|
75
|
+
setAccuracy(meters) {
|
|
76
|
+
this._accuracy = meters;
|
|
77
|
+
return this;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Current speed in meters/second (live location only). */
|
|
81
|
+
setSpeed(mps) {
|
|
82
|
+
this._speed = mps;
|
|
83
|
+
return this;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Heading in degrees clockwise from magnetic north (live location only). */
|
|
87
|
+
setDegrees(degreesClockwiseFromMagneticNorth) {
|
|
88
|
+
this._degrees = degreesClockwiseFromMagneticNorth;
|
|
89
|
+
return this;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Caption text shown with a live location update. */
|
|
93
|
+
setCaption(text) {
|
|
94
|
+
this._caption = text;
|
|
95
|
+
return this;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Ordinal of this update within a live-location session — bump it on each subsequent send so clients render the latest pin instead of stacking old ones. */
|
|
99
|
+
setSequence(n) {
|
|
100
|
+
this._sequence = n;
|
|
101
|
+
return this;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Seconds since the live-location session started. */
|
|
105
|
+
setTimeOffset(n) {
|
|
106
|
+
this._timeOffset = n;
|
|
107
|
+
return this;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** @returns {Record<string, any>} The `sendMessage()`-shaped payload, without sending it. */
|
|
111
|
+
build() {
|
|
112
|
+
if (typeof this._lat !== 'number' || typeof this._lng !== 'number') {
|
|
113
|
+
throw new Error('Location requires setCoordinates(lat, lng)');
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (!this._live) {
|
|
117
|
+
return {
|
|
118
|
+
location: {
|
|
119
|
+
degreesLatitude: this._lat,
|
|
120
|
+
degreesLongitude: this._lng,
|
|
121
|
+
...(this._name !== undefined && { name: this._name }),
|
|
122
|
+
...(this._address !== undefined && { address: this._address }),
|
|
123
|
+
...(this._url !== undefined && { url: this._url }),
|
|
124
|
+
...(this._thumbnail !== undefined && { jpegThumbnail: this._thumbnail }),
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return {
|
|
130
|
+
raw: true,
|
|
131
|
+
liveLocationMessage: proto.Message.LiveLocationMessage.create({
|
|
132
|
+
degreesLatitude: this._lat,
|
|
133
|
+
degreesLongitude: this._lng,
|
|
134
|
+
...(this._accuracy !== undefined && { accuracyInMeters: this._accuracy }),
|
|
135
|
+
...(this._speed !== undefined && { speedInMps: this._speed }),
|
|
136
|
+
...(this._degrees !== undefined && { degreesClockwiseFromMagneticNorth: this._degrees }),
|
|
137
|
+
...(this._caption !== undefined && { caption: this._caption }),
|
|
138
|
+
...(this._sequence !== undefined && { sequenceNumber: this._sequence }),
|
|
139
|
+
...(this._timeOffset !== undefined && { timeOffset: this._timeOffset }),
|
|
140
|
+
...(this._thumbnail !== undefined && { jpegThumbnail: this._thumbnail }),
|
|
141
|
+
}),
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** Build and send via the socket's `sendMessage()`. Call again with a higher `setSequence()` to push live-location updates. */
|
|
146
|
+
async send(jid, options = {}) {
|
|
147
|
+
return this.#client.sendMessage(jid, this.build(), options);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export { Location };
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { BaseBuilder, generateWAMessageFromContent, prepareWAMessageMedia, generateMessageIDV2, crypto } from './shared.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Interactive/nativeFlow message builder — the modern replacement for the legacy
|
|
5
|
+
* hydrated-template buttons in `ButtonV3`. Produces the `interactiveMessage` shape
|
|
6
|
+
* (`cta_url`, `cta_copy`, `cta_call`, `quick_reply`, `single_select`) that
|
|
7
|
+
* `generateWAMessageContent` already understands via the `message.nativeFlow`
|
|
8
|
+
* shorthand (see Utils/messages.js, `hasNonNullishProperty(message, 'nativeFlow')`).
|
|
9
|
+
*/
|
|
10
|
+
class NativeFlow extends BaseBuilder {
|
|
11
|
+
#client;
|
|
12
|
+
|
|
13
|
+
/** @param {import('../../WAProto/index.js').WASocket} client Active Baileys socket (must expose `sendMessage`). */
|
|
14
|
+
constructor(client) {
|
|
15
|
+
super();
|
|
16
|
+
if (!client) throw new Error('Socket is required');
|
|
17
|
+
this.#client = client;
|
|
18
|
+
|
|
19
|
+
this._buttons = [];
|
|
20
|
+
this._text;
|
|
21
|
+
this._caption;
|
|
22
|
+
this._title;
|
|
23
|
+
this._subtitle;
|
|
24
|
+
this._footer;
|
|
25
|
+
this._audioFooter;
|
|
26
|
+
this._thumbnail;
|
|
27
|
+
this._media;
|
|
28
|
+
this._offer;
|
|
29
|
+
this._optionTitle;
|
|
30
|
+
this._optionText;
|
|
31
|
+
this._bizJid;
|
|
32
|
+
this._shopSurface;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Plain text body (no header). Mutually exclusive with `setCaption()`. */
|
|
36
|
+
setText(text) {
|
|
37
|
+
if (typeof text !== 'string' || !text) throw new TypeError('setText(text) requires a non-empty string');
|
|
38
|
+
this._text = text;
|
|
39
|
+
return this;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Body text shown together with a media/title header — pair with `setHeader()` and/or `setImage()/setVideo()/setDocument()`. */
|
|
43
|
+
setCaption(caption) {
|
|
44
|
+
if (typeof caption !== 'string' || !caption) throw new TypeError('setCaption(caption) requires a non-empty string');
|
|
45
|
+
this._caption = caption;
|
|
46
|
+
return this;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Header title/subtitle shown above the caption. Requires a media header (see `setImage()` etc.) or leave media unset for a text-only header. */
|
|
50
|
+
setHeader(title = '', subtitle = '') {
|
|
51
|
+
this._title = title;
|
|
52
|
+
this._subtitle = subtitle;
|
|
53
|
+
return this;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Footer text under the buttons. Mutually exclusive with `setAudioFooter()`. */
|
|
57
|
+
setFooter(text) {
|
|
58
|
+
this._footer = text;
|
|
59
|
+
return this;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Voice-note footer instead of text. @param {string|Buffer} path Url or buffer. */
|
|
63
|
+
setAudioFooter(path) {
|
|
64
|
+
if (!path) throw new Error('Url or buffer needed');
|
|
65
|
+
this._audioFooter = path;
|
|
66
|
+
return this;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Raw jpeg thumbnail bytes for the header (used when the header media itself isn't re-uploaded). */
|
|
70
|
+
setThumbnail(buffer) {
|
|
71
|
+
this._thumbnail = buffer;
|
|
72
|
+
return this;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Attach an image as the header. @param {string|Buffer} path Url or buffer. */
|
|
76
|
+
setImage(path, options = {}) {
|
|
77
|
+
if (!path) throw new Error('Url or buffer needed');
|
|
78
|
+
this._media = Buffer.isBuffer(path) ? { image: path, ...options } : { image: { url: path }, ...options };
|
|
79
|
+
return this;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Attach a video as the header. @param {string|Buffer} path Url or buffer. */
|
|
83
|
+
setVideo(path, options = {}) {
|
|
84
|
+
if (!path) throw new Error('Url or buffer needed');
|
|
85
|
+
this._media = Buffer.isBuffer(path) ? { video: path, ...options } : { video: { url: path }, ...options };
|
|
86
|
+
return this;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Attach a document as the header. @param {string|Buffer} path Url or buffer. */
|
|
90
|
+
setDocument(path, options = {}) {
|
|
91
|
+
if (!path) throw new Error('Url or buffer needed');
|
|
92
|
+
this._media = Buffer.isBuffer(path) ? { document: path, ...options } : { document: { url: path }, ...options };
|
|
93
|
+
return this;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** Set a raw pre-built header media object (bypasses setImage/setVideo/setDocument shorthands). */
|
|
97
|
+
setMedia(obj) {
|
|
98
|
+
if (typeof obj !== 'object' || obj === null || Array.isArray(obj)) throw new TypeError('Media must be a plain object');
|
|
99
|
+
this._media = obj;
|
|
100
|
+
return this;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** Remove every button added so far (keeps text/header/footer/media). */
|
|
104
|
+
clearButtons() {
|
|
105
|
+
this._buttons = [];
|
|
106
|
+
return this;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** `quick_reply` button — fires a regular button-response callback. */
|
|
110
|
+
addQuickReply(displayText, id = crypto.randomUUID(), { icon } = {}) {
|
|
111
|
+
if (typeof displayText !== 'string' || !displayText) throw new TypeError('addQuickReply(displayText, id) requires a non-empty displayText');
|
|
112
|
+
this._buttons.push({ text: displayText, id, icon });
|
|
113
|
+
return this;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** `cta_url` button — opens a link, optionally inside WhatsApp's in-app webview. */
|
|
117
|
+
addUrl(displayText, url, { useWebview = false, icon } = {}) {
|
|
118
|
+
if (typeof url !== 'string' || !url) throw new TypeError('addUrl(displayText, url) requires a non-empty url');
|
|
119
|
+
this._buttons.push({ text: displayText, url, useWebview, icon });
|
|
120
|
+
return this;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** `cta_copy` button — copies text/code to the recipient's clipboard. */
|
|
124
|
+
addCopy(displayText, copyCode, { icon } = {}) {
|
|
125
|
+
if (typeof copyCode !== 'string' || !copyCode) throw new TypeError('addCopy(displayText, copyCode) requires a non-empty copyCode');
|
|
126
|
+
this._buttons.push({ text: displayText, copy: copyCode, icon });
|
|
127
|
+
return this;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** `cta_call` button — dials a phone number. */
|
|
131
|
+
addCall(displayText, phoneNumber, { icon } = {}) {
|
|
132
|
+
if (typeof phoneNumber !== 'string' || !phoneNumber) throw new TypeError('addCall(displayText, phoneNumber) requires a non-empty phoneNumber');
|
|
133
|
+
this._buttons.push({ text: displayText, call: phoneNumber, icon });
|
|
134
|
+
return this;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* `single_select` button — opens a list picker.
|
|
139
|
+
* @param {string} title Text shown on the button that opens the list.
|
|
140
|
+
* @param {{title: string, rows: {title: string, description?: string, id: string}[]}[]} sections
|
|
141
|
+
*/
|
|
142
|
+
addSingleSelect(title, sections) {
|
|
143
|
+
if (typeof title !== 'string' || !title) throw new TypeError('addSingleSelect(title, sections) requires a non-empty title');
|
|
144
|
+
if (!Array.isArray(sections) || !sections.length) throw new TypeError('addSingleSelect(title, sections) requires a non-empty sections array');
|
|
145
|
+
this._buttons.push({ text: title, sections });
|
|
146
|
+
return this;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** Append a raw pre-built button object (bypasses the add*() shorthands). */
|
|
150
|
+
addRawButton(obj) {
|
|
151
|
+
if (typeof obj !== 'object' || obj === null) throw new TypeError('addRawButton(obj) requires a plain object');
|
|
152
|
+
this._buttons.push(obj);
|
|
153
|
+
return this;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** Attach the "limited time offer" banner above the buttons. */
|
|
157
|
+
setOffer({ text, url, code, expiration } = {}) {
|
|
158
|
+
this._offer = { text, url, code, expiration };
|
|
159
|
+
return this;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** Collapse buttons beyond the in-thread limit into a "..." bottom-sheet menu. */
|
|
163
|
+
setOptionsMenu(buttonText, title = '📄 Select Options') {
|
|
164
|
+
if (typeof buttonText !== 'string' || !buttonText) throw new TypeError('setOptionsMenu(buttonText) requires a non-empty string');
|
|
165
|
+
this._optionText = buttonText;
|
|
166
|
+
this._optionTitle = title;
|
|
167
|
+
return this;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/** Mark this as a catalog/collection-backed message tied to a business JID. */
|
|
171
|
+
setBizJid(jid) {
|
|
172
|
+
this._bizJid = jid;
|
|
173
|
+
return this;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** Mark this as a shop-storefront-backed message. */
|
|
177
|
+
setShopSurface(surface) {
|
|
178
|
+
this._shopSurface = surface;
|
|
179
|
+
return this;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/** @returns {Record<string, any>} The `sendMessage()`-shaped payload, without sending it. */
|
|
183
|
+
build() {
|
|
184
|
+
if (!this._text && !this._caption) throw new Error('NativeFlow requires setText() or setCaption()');
|
|
185
|
+
if (!this._buttons.length) throw new Error('NativeFlow requires at least one button (addUrl()/addCopy()/addCall()/addQuickReply()/addSingleSelect())');
|
|
186
|
+
if (this._caption && this._media === undefined && !this._title && !this._subtitle) {
|
|
187
|
+
throw new Error('setCaption() requires setHeader() and/or a media header (setImage()/setVideo()/setDocument())');
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return {
|
|
191
|
+
...(this._media || {}),
|
|
192
|
+
...(this._text ? { text: this._text } : { caption: this._caption, title: this._title, subtitle: this._subtitle }),
|
|
193
|
+
...(this._footer !== undefined && { footer: this._footer }),
|
|
194
|
+
...(this._audioFooter !== undefined && { audioFooter: this._audioFooter }),
|
|
195
|
+
...(this._thumbnail !== undefined && { thumbnail: this._thumbnail }),
|
|
196
|
+
...(this._offer && { offerText: this._offer.text, offerUrl: this._offer.url, offerCode: this._offer.code, offerExpiration: this._offer.expiration }),
|
|
197
|
+
...(this._optionText !== undefined && { optionText: this._optionText, optionTitle: this._optionTitle }),
|
|
198
|
+
...(this._bizJid !== undefined && { bizJid: this._bizJid }),
|
|
199
|
+
...(this._shopSurface !== undefined && { shopSurface: this._shopSurface }),
|
|
200
|
+
nativeFlow: this._buttons,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/** Build and send via the socket's `sendMessage()`. */
|
|
205
|
+
async send(jid, options = {}) {
|
|
206
|
+
return this.#client.sendMessage(jid, this.build(), options);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export { NativeFlow };
|
|
@@ -5,6 +5,11 @@ import { ButtonV3 } from './ButtonV3.js'
|
|
|
5
5
|
import { Carousel } from './Carousel.js'
|
|
6
6
|
import { Poll } from './Poll.js'
|
|
7
7
|
import { A2UI } from './A2UI.js'
|
|
8
|
+
import { NativeFlow } from './NativeFlow.js'
|
|
9
|
+
import { Location } from './Location.js'
|
|
10
|
+
import { Contact } from './Contact.js'
|
|
11
|
+
import { Album } from './Album.js'
|
|
12
|
+
import { bindLinkPreview } from './LinkPreview.js'
|
|
8
13
|
|
|
9
14
|
/**
|
|
10
15
|
* VanzxyBaileys — unified builder hub.
|
|
@@ -20,7 +25,7 @@ class VanzxyBaileys {
|
|
|
20
25
|
)
|
|
21
26
|
}
|
|
22
27
|
|
|
23
|
-
this.client = client
|
|
28
|
+
this.client = bindLinkPreview(client)
|
|
24
29
|
}
|
|
25
30
|
|
|
26
31
|
// ===== AIRICH =====
|
|
@@ -75,6 +80,24 @@ class VanzxyBaileys {
|
|
|
75
80
|
return new Poll(this.client)
|
|
76
81
|
}
|
|
77
82
|
|
|
83
|
+
// ===== NATIVE FLOW =====
|
|
84
|
+
|
|
85
|
+
nativeFlow() {
|
|
86
|
+
return new NativeFlow(this.client)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
location() {
|
|
90
|
+
return new Location(this.client)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
contact() {
|
|
94
|
+
return new Contact(this.client)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
album() {
|
|
98
|
+
return new Album(this.client)
|
|
99
|
+
}
|
|
100
|
+
|
|
78
101
|
// ===== A2UI / BLOKS =====
|
|
79
102
|
|
|
80
103
|
a2ui() {
|
|
@@ -100,6 +123,22 @@ class VanzxyBaileys {
|
|
|
100
123
|
return this.poll()
|
|
101
124
|
}
|
|
102
125
|
|
|
126
|
+
NativeFlow() {
|
|
127
|
+
return this.nativeFlow()
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
Location() {
|
|
131
|
+
return this.location()
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
Contact() {
|
|
135
|
+
return this.contact()
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
Album() {
|
|
139
|
+
return this.album()
|
|
140
|
+
}
|
|
141
|
+
|
|
103
142
|
A2UI() {
|
|
104
143
|
return this.a2ui()
|
|
105
144
|
}
|
package/lib/Builders/index.js
CHANGED
|
@@ -4,8 +4,13 @@ export { ButtonV2 } from './ButtonV2.js';
|
|
|
4
4
|
export { ButtonV3 } from './ButtonV3.js';
|
|
5
5
|
export { Carousel } from './Carousel.js';
|
|
6
6
|
export { Poll } from './Poll.js';
|
|
7
|
+
export { NativeFlow } from './NativeFlow.js';
|
|
8
|
+
export { Location } from './Location.js';
|
|
9
|
+
export { Contact } from './Contact.js';
|
|
10
|
+
export { Album } from './Album.js';
|
|
7
11
|
export { AIRich, ORich } from './AIRich.js';
|
|
8
12
|
export { A2UI, sendA2UIWidget } from './A2UI.js';
|
|
9
13
|
export { AIRich as AIVanzxy, AIRich as LeafRich, AIRich as VanzxyAI, AIRich as VanzxyRich, AIRich as RichVanzxy } from './AIRich.js';
|
|
10
|
-
export const MESSAGE_BUILDER_VERSION = '
|
|
14
|
+
export const MESSAGE_BUILDER_VERSION = '5.0';
|
|
11
15
|
export { VanzxyBaileys } from './VanzxyBaileys.js';
|
|
16
|
+
export { bindLinkPreview } from './LinkPreview.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vanzxy/baileys",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.3",
|
|
4
4
|
"description": "Enhanced Baileys fork by Vanzxy — based on @itsliaaa/baileys + @whiskeysockets/baileys with fixes for audio group status and clean media without newsletter button.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|