@vanzxy/baileys 2.0.0 → 2.0.1
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/README.md +48 -2
- package/lib/{Utils → Builders}/A2UI.js +2 -2
- package/lib/Builders/AIRich.js +2286 -0
- package/lib/Builders/Button.js +883 -0
- package/lib/Builders/ButtonV2.js +144 -0
- package/lib/Builders/ButtonV3.js +184 -0
- package/lib/Builders/Carousel.js +111 -0
- package/lib/Builders/Poll.js +128 -0
- package/lib/Builders/VanzxyBaileys.js +108 -0
- package/lib/Builders/index.js +11 -0
- package/lib/Builders/shared.js +652 -0
- package/lib/Utils/MessageBuilder.js +2 -4405
- package/lib/Utils/index.d.ts +2 -0
- package/lib/Utils/index.js +0 -1
- package/lib/index.js +3 -0
- package/package.json +4 -7
- package/postinstall-banner.js +51 -25
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { BaseBuilder, Toolkit, RowBuilder, generateWAMessageFromContent, prepareWAMessageMedia, generateMessageIDV2, crypto } from './shared.js';
|
|
2
|
+
class ButtonV2 extends BaseBuilder {
|
|
3
|
+
#client;
|
|
4
|
+
|
|
5
|
+
/** @param {import('../../WAProto/index.js').WASocket} client Active Baileys socket. */
|
|
6
|
+
constructor(client) {
|
|
7
|
+
super();
|
|
8
|
+
if (!client) {
|
|
9
|
+
throw new Error('Socket is required');
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
this.#client = client;
|
|
13
|
+
this._image;
|
|
14
|
+
this._data;
|
|
15
|
+
this._buttons = [];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Add a simple quick-reply button. @param {string} displayText Label. @param {string} [buttonId] Defaults to a random uuid. */
|
|
19
|
+
addButton(displayText = '', buttonId = crypto.randomUUID()) {
|
|
20
|
+
if (!displayText) throw new TypeError('addButton(displayText) requires a non-empty label');
|
|
21
|
+
this._buttons.push({
|
|
22
|
+
buttonId,
|
|
23
|
+
buttonText: { displayText },
|
|
24
|
+
type: 1,
|
|
25
|
+
});
|
|
26
|
+
return this;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Push a raw pre-built button object, bypassing the `addButton()` shorthand. */
|
|
30
|
+
addRawButton(obj) {
|
|
31
|
+
if (typeof obj !== 'object' || obj === null || Array.isArray(obj)) {
|
|
32
|
+
throw new TypeError('Buttons must be a plain object');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
this._buttons.push(obj);
|
|
36
|
+
return this;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Set the header thumbnail (used as a fallback location-header image when no `setMedia()` header is given). */
|
|
40
|
+
setThumbnail(path) {
|
|
41
|
+
if (!path) throw new Error('Url or buffer needed');
|
|
42
|
+
this._image = path;
|
|
43
|
+
return this;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Set a raw pre-built header media object for the buttons message. */
|
|
47
|
+
setMedia(obj) {
|
|
48
|
+
if (typeof obj !== 'object' || obj === null || Array.isArray(obj)) {
|
|
49
|
+
throw new TypeError('Media must be a plain object');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
this._data = obj;
|
|
53
|
+
return this;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Alias for addButton() — shorthand parity with RowBuilder#button(). */
|
|
57
|
+
button(displayText, buttonId) {
|
|
58
|
+
return this.addButton(displayText, buttonId);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Vanz@Add 29-08-26 --- Fluent row helper ported from the RowBuilder class (already
|
|
63
|
+
* present but previously unwired into ButtonV2). Lets callers group buttons via a
|
|
64
|
+
* callback instead of chaining addButton() calls one at a time.
|
|
65
|
+
* @param {(row: RowBuilder) => void} cb
|
|
66
|
+
*/
|
|
67
|
+
row(cb) {
|
|
68
|
+
const r = new RowBuilder();
|
|
69
|
+
cb(r);
|
|
70
|
+
r.buttons.forEach((b) => this._buttons.push(b));
|
|
71
|
+
return this;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Vanz@Fix 22-08-26 (v4.7) --- _thumbnail was computed unconditionally (fetch + resize) even
|
|
75
|
+
// when setMedia() is used, in which case the location-fallback header (the only place
|
|
76
|
+
// _thumbnail is used) never runs at all — wasted network/CPU work on every build() call.
|
|
77
|
+
// Now only computed when it'll actually be used. Also: `viewOnce` was hardcoded true with no
|
|
78
|
+
// way to opt out (kept as the default — some clients need it to render legacy buttonsMessage
|
|
79
|
+
// at all — but it's now a `{ viewOnce = true }` option instead of a hardcoded literal).
|
|
80
|
+
/** @returns {Promise<Record<string, any>>} The generated WAMessage (without sending). @param {boolean} [viewOnce] Default true — some clients require this for legacy buttonsMessage to render; pass false to send it as a normal (non-disappearing) message. */
|
|
81
|
+
async build(jid, { viewOnce = true, ...options } = {}) {
|
|
82
|
+
const _thumbnail = !this._data && this._image ? await Toolkit.resize(Buffer.isBuffer(this._image) ? this._image : await Toolkit.fetchBuffer(this._image, {}, { silent: true }), 300, 300) : null;
|
|
83
|
+
const msg = generateWAMessageFromContent(
|
|
84
|
+
jid,
|
|
85
|
+
{
|
|
86
|
+
...this._extraPayload,
|
|
87
|
+
buttonsMessage: {
|
|
88
|
+
contentText: this._body,
|
|
89
|
+
footerText: this._footer,
|
|
90
|
+
...(this._data
|
|
91
|
+
? this._data
|
|
92
|
+
: {
|
|
93
|
+
headerType: 6,
|
|
94
|
+
locationMessage: {
|
|
95
|
+
degreesLatitude: 0,
|
|
96
|
+
degreesLongitude: 0,
|
|
97
|
+
name: this._title,
|
|
98
|
+
address: this._subtitle,
|
|
99
|
+
jpegThumbnail: _thumbnail,
|
|
100
|
+
},
|
|
101
|
+
}),
|
|
102
|
+
viewOnce,
|
|
103
|
+
contextInfo: this._contextInfo,
|
|
104
|
+
buttons: [...this._buttons],
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
{ ...options }
|
|
108
|
+
);
|
|
109
|
+
return msg;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** Build and send this buttons message. @param {string} jid Destination chat/group jid. */
|
|
113
|
+
async send(jid, { ...options } = {}) {
|
|
114
|
+
if (this._buttons.length < 1) throw new Error('ButtonV2 requires at least one button');
|
|
115
|
+
const msg = await this.build(jid, options);
|
|
116
|
+
|
|
117
|
+
await this.#client.relayMessage(msg.key.remoteJid, msg.message, {
|
|
118
|
+
messageId: msg.key.id,
|
|
119
|
+
additionalNodes: [
|
|
120
|
+
{
|
|
121
|
+
tag: 'biz',
|
|
122
|
+
attrs: {},
|
|
123
|
+
content: [
|
|
124
|
+
{
|
|
125
|
+
tag: 'interactive',
|
|
126
|
+
attrs: { type: 'native_flow', v: '1' },
|
|
127
|
+
content: [{ tag: 'native_flow', attrs: { v: '9', name: 'mixed' } }],
|
|
128
|
+
},
|
|
129
|
+
],
|
|
130
|
+
},
|
|
131
|
+
],
|
|
132
|
+
...options,
|
|
133
|
+
});
|
|
134
|
+
return msg;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Legacy `templateMessage` / `hydratedFourRowTemplate` builder — WA's Generation-1
|
|
140
|
+
* button protocol (predates the nativeFlow format that Button/ButtonV2 use).
|
|
141
|
+
* Capped at 3 buttons (quickReply/url/call only), no interactive list/flow support.
|
|
142
|
+
* Ported from MessageBuilderV4.7.
|
|
143
|
+
*/
|
|
144
|
+
export { ButtonV2 };
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { BaseBuilder, generateWAMessageFromContent, prepareWAMessageMedia, generateMessageIDV2, crypto } from './shared.js';
|
|
2
|
+
class ButtonV3 extends BaseBuilder {
|
|
3
|
+
#client;
|
|
4
|
+
|
|
5
|
+
/** @param {import('../../WAProto/index.js').WASocket} client Active Baileys socket. */
|
|
6
|
+
constructor(client) {
|
|
7
|
+
super();
|
|
8
|
+
if (!client) {
|
|
9
|
+
throw new Error('Socket is required');
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
this.#client = client;
|
|
13
|
+
this._data;
|
|
14
|
+
this._mediaHeaderType = null;
|
|
15
|
+
this._buttons = [];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Load an existing templateMessage (e.g. from a fetched/quoted message) for editing. */
|
|
19
|
+
loadFrom(msg) {
|
|
20
|
+
if (!msg) throw new Error('templateMessage needed');
|
|
21
|
+
if (!msg.templateMessage) throw new Error('templateMessage not found');
|
|
22
|
+
|
|
23
|
+
const { templateMessage, ...extraPayload } = msg;
|
|
24
|
+
const hft = templateMessage.hydratedFourRowTemplate || {};
|
|
25
|
+
|
|
26
|
+
this._title = hft.hydratedTitleText || '';
|
|
27
|
+
this._body = hft.hydratedContentText || '';
|
|
28
|
+
this._footer = hft.hydratedFooterText || '';
|
|
29
|
+
this._contextInfo = templateMessage.contextInfo || {};
|
|
30
|
+
this._extraPayload = extraPayload;
|
|
31
|
+
|
|
32
|
+
this._buttons = Array.isArray(hft.hydratedButtons)
|
|
33
|
+
? hft.hydratedButtons.map((button) => ({ ...button }))
|
|
34
|
+
: [];
|
|
35
|
+
|
|
36
|
+
if (hft.imageMessage) {
|
|
37
|
+
this._data = { imageMessage: hft.imageMessage };
|
|
38
|
+
this._mediaHeaderType = 'imageMessage';
|
|
39
|
+
} else if (hft.videoMessage) {
|
|
40
|
+
this._data = { videoMessage: hft.videoMessage };
|
|
41
|
+
this._mediaHeaderType = 'videoMessage';
|
|
42
|
+
} else if (hft.documentMessage) {
|
|
43
|
+
this._data = { documentMessage: hft.documentMessage };
|
|
44
|
+
this._mediaHeaderType = 'documentMessage';
|
|
45
|
+
} else if (hft.locationMessage) {
|
|
46
|
+
this._data = { locationMessage: hft.locationMessage };
|
|
47
|
+
this._mediaHeaderType = 'locationMessage';
|
|
48
|
+
} else {
|
|
49
|
+
this._data = undefined;
|
|
50
|
+
this._mediaHeaderType = null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return this;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
setImage(path, options = {}) {
|
|
57
|
+
if (!path) throw new Error('Url or buffer needed');
|
|
58
|
+
this._data = Buffer.isBuffer(path)
|
|
59
|
+
? { image: path, ...options }
|
|
60
|
+
: { image: { url: path }, ...options };
|
|
61
|
+
this._mediaHeaderType = 'imageMessage';
|
|
62
|
+
return this;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
setVideo(path, options = {}) {
|
|
66
|
+
if (!path) throw new Error('Url or buffer needed');
|
|
67
|
+
this._data = Buffer.isBuffer(path)
|
|
68
|
+
? { video: path, ...options }
|
|
69
|
+
: { video: { url: path }, ...options };
|
|
70
|
+
this._mediaHeaderType = 'videoMessage';
|
|
71
|
+
return this;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
setDocument(path, options = {}) {
|
|
75
|
+
if (!path) throw new Error('Url or buffer needed');
|
|
76
|
+
this._data = Buffer.isBuffer(path)
|
|
77
|
+
? { document: path, ...options }
|
|
78
|
+
: { document: { url: path }, ...options };
|
|
79
|
+
this._mediaHeaderType = 'documentMessage';
|
|
80
|
+
return this;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
setMedia(obj) {
|
|
84
|
+
if (typeof obj !== 'object' || obj === null || Array.isArray(obj)) {
|
|
85
|
+
throw new TypeError('Media must be a plain object');
|
|
86
|
+
}
|
|
87
|
+
this._data = obj;
|
|
88
|
+
this._mediaHeaderType = null; // caller is expected to pass an already-resolved shape
|
|
89
|
+
return this;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
clearButtons() {
|
|
93
|
+
this._buttons = [];
|
|
94
|
+
return this;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
addButton(hydratedButton) {
|
|
98
|
+
if (this._buttons.length >= 3) {
|
|
99
|
+
throw new Error('ButtonV3 (TemplateMessage) supports a maximum of 3 buttons');
|
|
100
|
+
}
|
|
101
|
+
this._buttons.push({ index: this._buttons.length + 1, ...hydratedButton });
|
|
102
|
+
return this;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
addReply(display_text = '', id = '') {
|
|
106
|
+
return this.addButton({
|
|
107
|
+
quickReplyButton: { displayText: display_text, id },
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
addUrl(display_text = '', url = '', options = {}) {
|
|
112
|
+
return this.addButton({
|
|
113
|
+
urlButton: { displayText: display_text, url, ...options },
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
addCall(display_text = '', phone_number = '') {
|
|
118
|
+
return this.addButton({
|
|
119
|
+
callButton: { displayText: display_text, phoneNumber: phone_number },
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async toTemplate() {
|
|
124
|
+
let mediaFields = {};
|
|
125
|
+
|
|
126
|
+
if (this._data) {
|
|
127
|
+
const alreadyResolved =
|
|
128
|
+
this._data.imageMessage || this._data.videoMessage ||
|
|
129
|
+
this._data.documentMessage || this._data.locationMessage;
|
|
130
|
+
|
|
131
|
+
mediaFields = alreadyResolved
|
|
132
|
+
? this._data
|
|
133
|
+
: await prepareWAMessageMedia(this._data, {
|
|
134
|
+
upload: this.#client.waUploadToServer,
|
|
135
|
+
}).catch((e) => {
|
|
136
|
+
if (String(e).includes('Invalid media type')) return this._data;
|
|
137
|
+
throw e;
|
|
138
|
+
});
|
|
139
|
+
} else if (this._title) {
|
|
140
|
+
mediaFields = { hydratedTitleText: this._title };
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return {
|
|
144
|
+
hydratedContentText: this._body,
|
|
145
|
+
hydratedFooterText: this._footer,
|
|
146
|
+
hydratedButtons: this._buttons,
|
|
147
|
+
...mediaFields,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
async build(jid, { messageId, ...options } = {}) {
|
|
152
|
+
const hydratedFourRowTemplate = await this.toTemplate();
|
|
153
|
+
|
|
154
|
+
return generateWAMessageFromContent(
|
|
155
|
+
jid,
|
|
156
|
+
{
|
|
157
|
+
...this._extraPayload,
|
|
158
|
+
templateMessage: {
|
|
159
|
+
hydratedFourRowTemplate,
|
|
160
|
+
contextInfo: this._contextInfo,
|
|
161
|
+
},
|
|
162
|
+
},
|
|
163
|
+
{ messageId: messageId || generateMessageIDV2(), ...options },
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async send(jid, { messageId, additionalNodes = [], ...options } = {}) {
|
|
168
|
+
if (this._buttons.length < 1)
|
|
169
|
+
throw new Error('ButtonV3 requires at least one button');
|
|
170
|
+
const msg = await this.build(jid, { messageId, ...options });
|
|
171
|
+
|
|
172
|
+
// TemplateMessage predates the nativeFlow protocol and does not need the
|
|
173
|
+
// "biz"/"native_flow" additionalNodes hack that Button/ButtonV2 use.
|
|
174
|
+
await this.#client.relayMessage(msg.key.remoteJid, msg.message, {
|
|
175
|
+
messageId: msg.key.id,
|
|
176
|
+
additionalNodes,
|
|
177
|
+
...options,
|
|
178
|
+
});
|
|
179
|
+
return msg;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** Carousel of interactive cards (each with its own header media + optional buttons), scrollable horizontally in-chat. */
|
|
184
|
+
export { ButtonV3 };
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { BaseBuilder, generateWAMessageFromContent, prepareWAMessageMedia, generateMessageIDV2, crypto } from './shared.js';
|
|
2
|
+
class Carousel extends BaseBuilder {
|
|
3
|
+
#client;
|
|
4
|
+
|
|
5
|
+
// Vanz@Add 22-08-26 (v4.7) --- WhatsApp caps carousels at 10 cards; anything beyond
|
|
6
|
+
// that is silently truncated client-side, so failing fast here is more useful than
|
|
7
|
+
// shipping a carousel that quietly loses cards.
|
|
8
|
+
static MAX_CARDS = 10;
|
|
9
|
+
|
|
10
|
+
/** @param {import('../../WAProto/index.js').WASocket} client Active Baileys socket. */
|
|
11
|
+
constructor(client) {
|
|
12
|
+
super();
|
|
13
|
+
if (!client) {
|
|
14
|
+
throw new Error('Socket is required');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
this.#client = client;
|
|
18
|
+
this._cards = [];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Add one card, or an array of cards, to the carousel.
|
|
23
|
+
* @param {Record<string, any>|Record<string, any>[]} card A card (or array of cards) with `header.hasMediaAttachment: true`
|
|
24
|
+
* — typically built via `new Button(client).setImage(...).addUrl(...).toCard()`.
|
|
25
|
+
*/
|
|
26
|
+
addCard(card) {
|
|
27
|
+
const cards = Array.isArray(card) ? card : [card];
|
|
28
|
+
const baseIndex = this._cards.length;
|
|
29
|
+
|
|
30
|
+
for (const [index, c] of cards.entries()) {
|
|
31
|
+
if (!c?.header?.hasMediaAttachment) {
|
|
32
|
+
throw new Error(`Card [${baseIndex + index}] must include an image or video in header`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (this._cards.length + cards.length > Carousel.MAX_CARDS) {
|
|
37
|
+
throw new Error(`Carousel supports at most ${Carousel.MAX_CARDS} cards (got ${this._cards.length + cards.length})`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
this._cards.push(...cards);
|
|
41
|
+
return this;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** @returns {Record<string, any>} The generated WAMessage (without sending). */
|
|
45
|
+
build(jid, { ...options } = {}) {
|
|
46
|
+
return generateWAMessageFromContent(
|
|
47
|
+
jid,
|
|
48
|
+
{
|
|
49
|
+
...this._extraPayload,
|
|
50
|
+
interactiveMessage: {
|
|
51
|
+
header: {
|
|
52
|
+
hasMediaAttachment: false,
|
|
53
|
+
},
|
|
54
|
+
body: { text: this._body },
|
|
55
|
+
footer: { text: this._footer },
|
|
56
|
+
contextInfo: this._contextInfo,
|
|
57
|
+
carouselMessage: {
|
|
58
|
+
cards: this._cards,
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
{ ...options }
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Build and send this carousel. @param {string} jid Destination chat/group jid. */
|
|
67
|
+
async send(jid, { ...options } = {}) {
|
|
68
|
+
if (this._cards.length === 0) throw new Error('Carousel requires at least one card (use addCard())');
|
|
69
|
+
|
|
70
|
+
const msg = this.build(jid, options);
|
|
71
|
+
|
|
72
|
+
await this.#client.relayMessage(msg.key.remoteJid, msg.message, {
|
|
73
|
+
messageId: msg.key.id,
|
|
74
|
+
additionalNodes: [
|
|
75
|
+
{
|
|
76
|
+
tag: 'biz',
|
|
77
|
+
attrs: {},
|
|
78
|
+
content: [
|
|
79
|
+
{
|
|
80
|
+
tag: 'interactive',
|
|
81
|
+
attrs: { type: 'native_flow', v: '1' },
|
|
82
|
+
content: [{ tag: 'native_flow', attrs: { v: '9', name: 'mixed' } }],
|
|
83
|
+
},
|
|
84
|
+
],
|
|
85
|
+
},
|
|
86
|
+
],
|
|
87
|
+
...options,
|
|
88
|
+
});
|
|
89
|
+
return msg;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Vanz@Add (v4.8) --- Chainable poll builder, wrapping the socket's own well-tested
|
|
95
|
+
* `sendMessage({ poll })` path (see messages.js) instead of hand-building
|
|
96
|
+
* pollCreationMessageV3/V5 over relayMessage. Two things from the traffic you sent were
|
|
97
|
+
* deliberately NOT implemented here because they can't be built with confidence:
|
|
98
|
+
* 1. Per-option poll images (`values: [{ name, image }]`) — the proto this fork ships
|
|
99
|
+
* only has a plain `optionName` string per option; an image-poll option isn't a named
|
|
100
|
+
* field anywhere in it. The one place an image-poll concept even appears
|
|
101
|
+
* (`pollCreationOptionImageMessage`) is typed as an opaque `FutureProofMessage` (a
|
|
102
|
+
* forward-compat envelope with no documented inner layout) — there's no field list to
|
|
103
|
+
* target, so adding "support" for it would just be silently dropping the image and
|
|
104
|
+
* guessing at a shape. Flagging instead of faking it.
|
|
105
|
+
* 2. Quiz-mode `correctAnswer.optionHash` built by hand — the one working example you
|
|
106
|
+
* captured had a 65-character hex string where a sha256 digest should be 64, and this
|
|
107
|
+
* builder's target `sendMessage({poll})` path (pollCreationMessageV5) already computes
|
|
108
|
+
* quiz mode correctly from a plain `correctAnswer` string, so `setQuiz()` below defers to
|
|
109
|
+
* that existing, already-tested logic rather than reimplementing the hash.
|
|
110
|
+
*/
|
|
111
|
+
export { Carousel };
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { BaseBuilder, generateWAMessageFromContent, prepareWAMessageMedia, generateMessageIDV2, crypto } from './shared.js';
|
|
2
|
+
class Poll extends BaseBuilder {
|
|
3
|
+
#client;
|
|
4
|
+
|
|
5
|
+
/** @param {import('../../WAProto/index.js').WASocket} client Active Baileys socket (must expose `sendMessage`). */
|
|
6
|
+
constructor(client) {
|
|
7
|
+
super();
|
|
8
|
+
if (!client) throw new Error('Socket is required');
|
|
9
|
+
this.#client = client;
|
|
10
|
+
|
|
11
|
+
this._name = '';
|
|
12
|
+
this._values = [];
|
|
13
|
+
this._selectableCount = 1;
|
|
14
|
+
this._hideVoter = false;
|
|
15
|
+
this._canAddOption = false;
|
|
16
|
+
this._toAnnouncementGroup = false;
|
|
17
|
+
this._correctAnswer;
|
|
18
|
+
this._endDate;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Set the poll question/title. */
|
|
22
|
+
setName(name) {
|
|
23
|
+
if (typeof name !== 'string' || !name) throw new TypeError('setName(name) requires a non-empty string');
|
|
24
|
+
this._name = name;
|
|
25
|
+
return this;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Append one option. Chainable — call repeatedly, or use `addOptions()` for an array. */
|
|
29
|
+
addOption(name) {
|
|
30
|
+
if (typeof name !== 'string' || !name) throw new TypeError('addOption(name) requires a non-empty string');
|
|
31
|
+
this._values.push(name);
|
|
32
|
+
return this;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Append several options at once. @param {string[]} names */
|
|
36
|
+
addOptions(names) {
|
|
37
|
+
if (!Array.isArray(names) || !names.length) throw new TypeError('addOptions(names) requires a non-empty array of strings');
|
|
38
|
+
names.forEach((name) => this.addOption(name));
|
|
39
|
+
return this;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** How many options a voter can pick (default 1). Use `setMultiSelect()` for unlimited. */
|
|
43
|
+
setSelectable(count) {
|
|
44
|
+
if (typeof count !== 'number' || count < 0) throw new TypeError('setSelectable(count) requires a non-negative number');
|
|
45
|
+
this._selectableCount = count;
|
|
46
|
+
return this;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Shortcut for unlimited-choice polls (`selectableCount: 0`). Pass `false` to revert to single-select. */
|
|
50
|
+
setMultiSelect(canSelectMultiple = true) {
|
|
51
|
+
this._selectableCount = canSelectMultiple ? 0 : 1;
|
|
52
|
+
return this;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Hide voter names from other participants (where the client supports it). */
|
|
56
|
+
setHideVoter(hide = true) {
|
|
57
|
+
this._hideVoter = hide;
|
|
58
|
+
return this;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Allow voters to add their own options. */
|
|
62
|
+
setCanAddOption(allow = true) {
|
|
63
|
+
this._canAddOption = allow;
|
|
64
|
+
return this;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Mark this a community-announcement-group poll (pollCreationMessageV2 path). */
|
|
68
|
+
setAnnouncementGroup(isAnnouncement = true) {
|
|
69
|
+
this._toAnnouncementGroup = isAnnouncement;
|
|
70
|
+
return this;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Auto-close the poll at this date/time. */
|
|
74
|
+
setEndDate(date) {
|
|
75
|
+
this._endDate = date instanceof Date ? date : new Date(date);
|
|
76
|
+
return this;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Turn this into a quiz: one option is marked correct. Delegates the actual hash/version
|
|
81
|
+
* wiring to the socket's own `sendMessage({poll:{...correctAnswer}})` handling — see class
|
|
82
|
+
* docblock for why this builder doesn't compute the hash itself.
|
|
83
|
+
* @param {string} correctOptionName Must exactly match one of the strings passed to `addOption()`/`addOptions()`.
|
|
84
|
+
*/
|
|
85
|
+
setQuiz(correctOptionName) {
|
|
86
|
+
if (typeof correctOptionName !== 'string' || !correctOptionName) {
|
|
87
|
+
throw new TypeError('setQuiz(correctOptionName) requires a non-empty string');
|
|
88
|
+
}
|
|
89
|
+
this._correctAnswer = correctOptionName;
|
|
90
|
+
return this;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** @returns {{poll: Record<string, any>}} The `sendMessage()`-shaped poll payload, without sending it. */
|
|
94
|
+
build() {
|
|
95
|
+
if (!this._name) throw new Error('Poll requires a name (use setName())');
|
|
96
|
+
if (this._values.length < 2) throw new Error('Poll requires at least 2 options (use addOption()/addOptions())');
|
|
97
|
+
if (this._correctAnswer && !this._values.includes(this._correctAnswer)) {
|
|
98
|
+
throw new Error('setQuiz(correctOptionName) must match one of the added options exactly');
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return {
|
|
102
|
+
poll: {
|
|
103
|
+
name: this._name,
|
|
104
|
+
values: this._values,
|
|
105
|
+
selectableCount: this._selectableCount,
|
|
106
|
+
toAnnouncementGroup: this._toAnnouncementGroup,
|
|
107
|
+
hideVoter: this._hideVoter,
|
|
108
|
+
canAddOption: this._canAddOption,
|
|
109
|
+
...(this._endDate && { endDate: this._endDate }),
|
|
110
|
+
...(this._correctAnswer && { pollType: 1, correctAnswer: this._correctAnswer }),
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** Build and send via the socket's `sendMessage()`. */
|
|
116
|
+
async send(jid, options = {}) {
|
|
117
|
+
return this.#client.sendMessage(jid, this.build(), options);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* "Rich AI-response" style message builder: text with hyperlink/citation/latex
|
|
123
|
+
* inline entities, code blocks, tables, sources, image/video attachments,
|
|
124
|
+
* inline product/post cards, tip banners and quick-reply suggestions —
|
|
125
|
+
* everything ChatGPT/Gemini-in-WhatsApp-style bots typically render.
|
|
126
|
+
* Also exported as `AIVanzxy` / `LeafRich` / `VanzxyAI` / `VanzxyRich` (identical class, alternate names).
|
|
127
|
+
*/
|
|
128
|
+
export { Poll };
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { AIRich } from './AIRich.js'
|
|
2
|
+
import { Button } from './Button.js'
|
|
3
|
+
import { ButtonV2 } from './ButtonV2.js'
|
|
4
|
+
import { ButtonV3 } from './ButtonV3.js'
|
|
5
|
+
import { Carousel } from './Carousel.js'
|
|
6
|
+
import { Poll } from './Poll.js'
|
|
7
|
+
import { A2UI } from './A2UI.js'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* VanzxyBaileys — unified builder hub.
|
|
11
|
+
*
|
|
12
|
+
* Semua builder tetap berada di class/file masing-masing.
|
|
13
|
+
* Class ini hanya menyediakan satu pintu masuk.
|
|
14
|
+
*/
|
|
15
|
+
class VanzxyBaileys {
|
|
16
|
+
constructor(client) {
|
|
17
|
+
if (!client) {
|
|
18
|
+
throw new TypeError(
|
|
19
|
+
'VanzxyBaileys(client) requires an active Baileys socket/client'
|
|
20
|
+
)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
this.client = client
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// ===== AIRICH =====
|
|
27
|
+
|
|
28
|
+
airich() {
|
|
29
|
+
return new AIRich(this.client)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
vanzxyAI() {
|
|
33
|
+
return new AIRich(this.client)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
aiVanzxy() {
|
|
37
|
+
return new AIRich(this.client)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
leafRich() {
|
|
41
|
+
return new AIRich(this.client)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
vanzxyRich() {
|
|
45
|
+
return new AIRich(this.client)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
richVanzxy() {
|
|
49
|
+
return new AIRich(this.client)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// ===== BUTTON =====
|
|
53
|
+
|
|
54
|
+
button() {
|
|
55
|
+
return new Button(this.client)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
buttonV2() {
|
|
59
|
+
return new ButtonV2(this.client)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
buttonV3() {
|
|
63
|
+
return new ButtonV3(this.client)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// ===== CAROUSEL =====
|
|
67
|
+
|
|
68
|
+
carousel() {
|
|
69
|
+
return new Carousel(this.client)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// ===== POLL =====
|
|
73
|
+
|
|
74
|
+
poll() {
|
|
75
|
+
return new Poll(this.client)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// ===== A2UI / BLOKS =====
|
|
79
|
+
|
|
80
|
+
a2ui() {
|
|
81
|
+
return new A2UI()
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Alias PascalCase untuk developer yang suka naming class.
|
|
86
|
+
*/
|
|
87
|
+
AIRich() {
|
|
88
|
+
return this.airich()
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
Button() {
|
|
92
|
+
return this.button()
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
Carousel() {
|
|
96
|
+
return this.carousel()
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
Poll() {
|
|
100
|
+
return this.poll()
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
A2UI() {
|
|
104
|
+
return this.a2ui()
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export { VanzxyBaileys }
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export { Toolkit, BaseBuilder, RowBuilder } from './shared.js';
|
|
2
|
+
export { Button, CardBuilder } from './Button.js';
|
|
3
|
+
export { ButtonV2 } from './ButtonV2.js';
|
|
4
|
+
export { ButtonV3 } from './ButtonV3.js';
|
|
5
|
+
export { Carousel } from './Carousel.js';
|
|
6
|
+
export { Poll } from './Poll.js';
|
|
7
|
+
export { AIRich, ORich } from './AIRich.js';
|
|
8
|
+
export { A2UI, sendA2UIWidget } from './A2UI.js';
|
|
9
|
+
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 = '4.8';
|
|
11
|
+
export { VanzxyBaileys } from './VanzxyBaileys.js';
|