@vanzxy/baileys 1.5.2 → 1.5.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.
@@ -706,12 +706,35 @@ export const makeChatsSocket = (config) => {
706
706
  ev.emit('presence.update', { id: jid, presences: { [participant]: presence } });
707
707
  }
708
708
  };
709
+ // Vanz@Fix 24-08-26 --- appPatch() (used by updateProfileName/archive/mute/pin/etc — anything
710
+ // going through the app-state WRITE path) threw "App state key not present!" immediately if
711
+ // myAppStateKeyId wasn't set yet, with zero retry. The READ path (resyncAppState, above) already
712
+ // has a courtesy retry via blockedCollections + the creds.update listener below — the key
713
+ // arrives async after connect (APP_STATE_SYNC_KEY_SHARE), so calling e.g. updateProfileName()
714
+ // right after connection.update === 'open' would reliably fail on a fresh/early connection.
715
+ // Now appPatch() waits (bounded, 10s default) for the same creds.update event before giving up,
716
+ // instead of failing hard on what's usually just a timing race.
717
+ const waitForAppStateKeyId = (timeoutMs = 10_000) => {
718
+ if (authState.creds.myAppStateKeyId) {
719
+ return Promise.resolve(authState.creds.myAppStateKeyId);
720
+ }
721
+ return new Promise((resolve, reject) => {
722
+ const timeout = setTimeout(() => {
723
+ ev.off('creds.update', onUpdate);
724
+ reject(new Boom('App state key not present!', { statusCode: 400 }));
725
+ }, timeoutMs);
726
+ const onUpdate = ({ myAppStateKeyId }) => {
727
+ if (!myAppStateKeyId) return;
728
+ clearTimeout(timeout);
729
+ ev.off('creds.update', onUpdate);
730
+ resolve(myAppStateKeyId);
731
+ };
732
+ ev.on('creds.update', onUpdate);
733
+ });
734
+ };
709
735
  const appPatch = async (patchCreate) => {
710
736
  const name = patchCreate.type;
711
- const myAppStateKeyId = authState.creds.myAppStateKeyId;
712
- if (!myAppStateKeyId) {
713
- throw new Boom('App state key not present!', { statusCode: 400 });
714
- }
737
+ const myAppStateKeyId = await waitForAppStateKeyId();
715
738
  let initial;
716
739
  let encodeResult;
717
740
  await appStatePatchMutex.mutex(async () => {
@@ -47,6 +47,8 @@
47
47
  const MESSAGE_BUILDER_VERSION = '4.9';
48
48
 
49
49
  import { generateWAMessageFromContent, prepareWAMessageMedia } from './messages.js';
50
+ import { generateMessageIDV2 } from './generics.js';
51
+ import { botMetadataSignature, botMetadataCertificate } from './rich-message-utils.js';
50
52
  import crypto from 'crypto';
51
53
  import { PassThrough, Readable } from 'stream';
52
54
  // Vanz@Fix 15-08-26 --- sharp/fluent-ffmpeg were statically imported in the blurose source.
@@ -292,15 +294,19 @@ class Toolkit {
292
294
  return await waitAllPromises(input);
293
295
  }
294
296
 
295
- /** Fetch `url` into a Buffer. @param {boolean} [silent] Return an empty Buffer instead of throwing on failure. */
296
- static async fetchBuffer(url, options = {}, { silent = true } = {}) {
297
+ /** Fetch `url` into a Buffer. @param {boolean} [silent] Return an empty Buffer instead of throwing on failure. @param {number} [timeout] Abort after this many ms (default 15s) instead of hanging indefinitely on a dead/slow host. */
298
+ static async fetchBuffer(url, options = {}, { silent = true, timeout = 15000 } = {}) {
299
+ const controller = new AbortController();
300
+ const timer = setTimeout(() => controller.abort(), timeout);
297
301
  try {
298
- let response = await fetch(url, options);
302
+ let response = await fetch(url, { ...options, signal: options.signal ?? controller.signal });
299
303
  if (!response.ok) throw Error(`HTTP ${response.status}`);
300
304
  return Buffer.from(await response.arrayBuffer());
301
305
  } catch (error) {
302
306
  if (silent) return Buffer.alloc(0);
303
307
  throw error;
308
+ } finally {
309
+ clearTimeout(timer);
304
310
  }
305
311
  }
306
312
 
@@ -1180,14 +1186,6 @@ class Button extends BaseBuilder {
1180
1186
  }
1181
1187
 
1182
1188
  /** Build and send this interactive message. @param {string} jid Destination chat/group jid. */
1183
- async sendEdit(jid, messageId, { ...options } = {}) {
1184
- if (!messageId) throw new Error('messageId is required');
1185
- const msg = await this.build(jid, options);
1186
- msg.key = { ...(msg.key || {}), remoteJid: jid, id: messageId, fromMe: true };
1187
- await this.#client.relayMessage(jid, msg.message, { messageId, ...options });
1188
- return msg;
1189
- }
1190
-
1191
1189
  async send(jid, { ...options } = {}) {
1192
1190
  const msg = await this.build(jid, options);
1193
1191
 
@@ -1594,9 +1592,21 @@ class AIRich extends BaseBuilder {
1594
1592
  // mediaKey still doesn't render, so it's a trust-chain gate, not a domain/encoding issue).
1595
1593
  // Track every addInlineImage() call here so send() can fall back to a normal imageMessage.
1596
1594
  this._inlineImages = [];
1597
- // Plain WhatsApp-media fallbacks for AIRich primitives that some clients refuse to render inline.
1598
- // Entries are { type: 'image'|'video'|'audio'|'document', url, caption, mimetype, fileName }.
1599
- this._mediaFallbacks = [];
1595
+
1596
+ // Vanz@Add 24-08-26 --- ported from temen's MessageBuilderV4.7 (setResponseId/setBotResponseId
1597
+ // below), rewritten to fit this fork's conventions. build() used to always mint a fresh
1598
+ // crypto.randomUUID() for both unifiedResponse.response_id and botMetadata.botResponseId on
1599
+ // every call, with no way to reuse one — so a `sendEdit()`-style flow (rebuild the same
1600
+ // message with updated content, same response_id, so WA patches it in place instead of
1601
+ // showing a new message) was never actually possible despite being in the gist example.
1602
+ // null here means "not pinned yet" — build() falls back to a fresh randomUUID() same as before
1603
+ // when neither setResponseId() nor setBotResponseId() has been called.
1604
+ this._responseId = null;
1605
+ this._botResponseId = null;
1606
+
1607
+ // Vanz@Add --- set by send()/sendEdit() after every relay so a follow-up sendEdit(), called
1608
+ // with no args, knows which jid/message id to patch in place (matches temen's v4.7 API).
1609
+ this._lastMessageKey = null;
1600
1610
 
1601
1611
  // Vanz@Add (v4.9.1) --- { id, insertAt } support for every add*()/set*() call, without
1602
1612
  // touching each method's own body/signature. Every add*() call ends up pushing 0-N items
@@ -1915,7 +1925,7 @@ class AIRich extends BaseBuilder {
1915
1925
  * One image, no prompt, nothing to download — use this when you don't need the rich card,
1916
1926
  * just the picture to show up immediately.
1917
1927
  */
1918
- addImage(imageUrl, { resolveUrl = false, instant = true } = {}) {
1928
+ addImage(imageUrl, { resolveUrl = false, instant = false } = {}) {
1919
1929
  if (!(typeof imageUrl === 'string' || Buffer.isBuffer(imageUrl) || (Array.isArray(imageUrl) && imageUrl.every((v) => typeof v === 'string' || Buffer.isBuffer(v))))) {
1920
1930
  throw new TypeError('imageUrl must be string | buffer | array of string/buffer');
1921
1931
  }
@@ -1973,8 +1983,6 @@ class AIRich extends BaseBuilder {
1973
1983
  }
1974
1984
 
1975
1985
  if (instant) {
1976
- this._mediaFallbacks.push({ type: 'image', url: imagePreviewUrl, caption: undefined });
1977
- // Keep legacy inline queue in sync for callers that inspect it.
1978
1986
  this._inlineImages.push({ url: imagePreviewUrl, caption: undefined });
1979
1987
  }
1980
1988
  });
@@ -2052,7 +2060,7 @@ class AIRich extends BaseBuilder {
2052
2060
  // for before rendering. Mirrors addImage()'s { resolveUrl } — when true, the url is uploaded to
2053
2061
  // WA's own media server first via Toolkit.toUrl() so it renders instantly like WA-native media.
2054
2062
  /** Add a video block. */
2055
- addVideo(videoUrl, { autoFill = false, resolveUrl = false, instant = true } = {}) {
2063
+ addVideo(videoUrl, { autoFill = false, resolveUrl = false } = {}) {
2056
2064
  const isObjectVideo = (v) => v && typeof v === 'object' && v.url;
2057
2065
 
2058
2066
  const isValidPrimitive =
@@ -2129,15 +2137,6 @@ class AIRich extends BaseBuilder {
2129
2137
  __typename: 'GenAIImaginePrimitive',
2130
2138
  })
2131
2139
  );
2132
-
2133
- if (instant) {
2134
- this._mediaFallbacks.push({
2135
- type: 'video',
2136
- url,
2137
- caption: isObject ? (item.caption ?? '') : '',
2138
- mimetype: isObject ? (item.mime_type ?? 'video/mp4') : 'video/mp4',
2139
- });
2140
- }
2141
2140
  });
2142
2141
 
2143
2142
  return this;
@@ -2224,6 +2223,56 @@ class AIRich extends BaseBuilder {
2224
2223
  return this;
2225
2224
  }
2226
2225
 
2226
+ // Vanz@Add 24-08-26 --- ported from temen's MessageBuilderV4.7 (setResponseId/setBotResponseId/
2227
+ // refreshResponseId/refreshBotResponseId), rewritten for this fork. Pins the two ids build()
2228
+ // generates (see constructor comment) so a rebuilt message can reuse the same response_id/
2229
+ // botResponseId — needed for editing an already-sent AIRich message in place.
2230
+
2231
+ /** Pin `unifiedResponse.response_id` to a specific value instead of a fresh random one each build() — needed to re-send an edited version of an already-sent message in place. */
2232
+ setResponseId(id) {
2233
+ if (typeof id !== 'string' || !id) throw new TypeError('setResponseId(id) requires a non-empty string');
2234
+ this._responseId = id;
2235
+ return this;
2236
+ }
2237
+
2238
+ /** Un-pin `unifiedResponse.response_id`, generating a fresh crypto.randomUUID() immediately (not deferred to the next build()). */
2239
+ refreshResponseId() {
2240
+ this._responseId = crypto.randomUUID();
2241
+ return this;
2242
+ }
2243
+
2244
+ /** Pin `botMetadata.botResponseId` to a specific value instead of a fresh random one each build(). */
2245
+ setBotResponseId(id) {
2246
+ if (typeof id !== 'string' || !id) throw new TypeError('setBotResponseId(id) requires a non-empty string');
2247
+ this._botResponseId = id;
2248
+ return this;
2249
+ }
2250
+
2251
+ /** Un-pin `botMetadata.botResponseId`, generating a fresh crypto.randomUUID() immediately. */
2252
+ refreshBotResponseId() {
2253
+ this._botResponseId = crypto.randomUUID();
2254
+ return this;
2255
+ }
2256
+
2257
+ /** Add a small metadata-style text line (`GenAIMetadataTextPrimitive`) — same visual style as the auto-appended footer/`addTip()`'s callout, but insertable anywhere and without `addTip()`'s icon prefix. */
2258
+ addMetadata(text) {
2259
+ if (typeof text !== 'string' || !text) throw new TypeError('addMetadata(text) requires a non-empty string');
2260
+
2261
+ this._submessages.push({
2262
+ messageType: 2,
2263
+ messageText: text,
2264
+ });
2265
+
2266
+ this._sections.push(
2267
+ AIRich.newLayout('Single', {
2268
+ text,
2269
+ __typename: 'GenAIMetadataTextPrimitive',
2270
+ })
2271
+ );
2272
+
2273
+ return this;
2274
+ }
2275
+
2227
2276
  /** Add a small "tip" callout banner. @param {string} text */
2228
2277
  addTip(text) {
2229
2278
  if (typeof text !== 'string' || !text) {
@@ -2301,7 +2350,6 @@ class AIRich extends BaseBuilder {
2301
2350
  __typename: 'GenAIImagePrimitive',
2302
2351
  })
2303
2352
  );
2304
- this._mediaFallbacks.push({ type: 'image', url: full || preview, caption: undefined });
2305
2353
 
2306
2354
  return this;
2307
2355
  }
@@ -2641,6 +2689,18 @@ class AIRich extends BaseBuilder {
2641
2689
  ]
2642
2690
  : [...(await waitAllPromises(this._sections))];
2643
2691
 
2692
+ // Vanz@Merge 15-08-26 --- Neither blurose nor arslan sign the bot metadata with
2693
+ // verificationMetadata (proofs/certificateChain). Backported from this project's own
2694
+ // rich-message-utils.js botMetadataSignature/botMetadataCertificate helpers, plus a
2695
+ // botResponseId tying the signed metadata to unifiedResponse.response_id.
2696
+ // Vanz@Fix 24-08-26 --- was `const responseId = crypto.randomUUID()` shared for BOTH
2697
+ // unifiedResponse.response_id and botMetadata.botResponseId, generated fresh every build()
2698
+ // with no override. Now each has its own id, pinned via setResponseId()/setBotResponseId()
2699
+ // if the caller set one (for sendEdit()-style in-place message updates), otherwise still
2700
+ // defaults to a fresh randomUUID() per build() exactly like before.
2701
+ const responseId = this._responseId ?? crypto.randomUUID();
2702
+ const botResponseId = this._botResponseId ?? crypto.randomUUID();
2703
+
2644
2704
  return {
2645
2705
  messageContextInfo: {
2646
2706
  deviceListMetadata: {},
@@ -2648,6 +2708,17 @@ class AIRich extends BaseBuilder {
2648
2708
  botMetadata: {
2649
2709
  messageDisclaimerText: this._title,
2650
2710
  richResponseSourcesMetadata: { sources: this._richResponseSources },
2711
+ botResponseId: botResponseId,
2712
+ verificationMetadata: {
2713
+ proofs: [
2714
+ {
2715
+ certificateChain: [botMetadataCertificate(), botMetadataCertificate(892)],
2716
+ version: 1,
2717
+ useCase: 1,
2718
+ signature: botMetadataSignature(),
2719
+ },
2720
+ ],
2721
+ },
2651
2722
  ...notif,
2652
2723
  },
2653
2724
  },
@@ -2658,7 +2729,7 @@ class AIRich extends BaseBuilder {
2658
2729
  messageType: 1,
2659
2730
  submessages: includesSubmessages ? await waitAllPromises(this._submessages) : [],
2660
2731
  unifiedResponse: {
2661
- data: includesUnifiedResponse ? Buffer.from(JSON.stringify({ response_id: crypto.randomUUID(), sections })).toString('base64') : '',
2732
+ data: includesUnifiedResponse ? Buffer.from(JSON.stringify({ response_id: responseId, sections })).toString('base64') : '',
2662
2733
  },
2663
2734
  contextInfo: {
2664
2735
  ...forward,
@@ -2679,106 +2750,96 @@ class AIRich extends BaseBuilder {
2679
2750
  // the two calls expect different option shapes, so the fallback now only forwards `quoted`
2680
2751
  // (the one option that clearly applies to both) instead of blindly spreading everything.
2681
2752
  /** Build and send this AI-rich message. @param {string} jid Destination chat/group jid. @param {boolean} [skipImageFallback] Skip auto-resending inline images as a plain imageMessage. */
2682
- async send(jid, {
2683
- forwarded,
2684
- notification,
2685
- includesUnifiedResponse,
2686
- includesSubmessages,
2687
- skipImageFallback = false,
2688
- nativeFallback = true,
2689
- quoted,
2690
- ...options
2691
- } = {}) {
2753
+ async send(jid, { forwarded, notification, includesUnifiedResponse, includesSubmessages, skipImageFallback = false, quoted, messageId, ...options } = {}) {
2692
2754
  const msg = await this.build({ forwarded, notification, includesUnifiedResponse, includesSubmessages, quoted, ...options });
2693
- const sendOptions = quoted ? { quoted } : {};
2694
-
2695
- // Native fallback is enabled by default because stock/third-party WhatsApp clients
2696
- // do not reliably render botForwardedMessage.richResponseMessage. The rich payload
2697
- // remains available through build()/relayMessage(), while send() uses ordinary WA
2698
- // message types that are known to render immediately.
2699
- if (nativeFallback) {
2700
- const sent = [];
2701
-
2702
- // 1) Send real WhatsApp media first. addImage()/addVideo()/addInlineImage()
2703
- // already populate this queue with resolved URLs/buffers.
2704
- if (!skipImageFallback && this._mediaFallbacks.length) {
2705
- const fallbacks = await waitAllPromises(this._mediaFallbacks);
2706
- for (const item of fallbacks) {
2707
- try {
2708
- const media = Buffer.isBuffer(item.url) ? item.url : { url: item.url };
2709
- const content = { [item.type]: media };
2710
- if (item.caption) content.caption = item.caption;
2711
- if (item.mimetype) content.mimetype = item.mimetype;
2712
- if (item.fileName) content.fileName = item.fileName;
2713
- sent.push(await this.#client.sendMessage(jid, content, sendOptions));
2714
- } catch (err) {
2715
- this.#client.logger?.warn?.(
2716
- { err, url: item.url, type: item.type },
2717
- 'AIRich native media fallback failed'
2718
- );
2719
- }
2755
+
2756
+ if (!skipImageFallback && this._inlineImages.length) {
2757
+ for (const { url, caption } of this._inlineImages) {
2758
+ try {
2759
+ await this.#client.sendMessage(jid, { image: { url }, caption }, quoted ? { quoted } : {});
2760
+ } catch (err) {
2761
+ // Vanz@Fix: don't let a fallback image failure block the actual rich card from sending
2762
+ this.#client.logger?.warn?.({ err, url }, 'inline image fallback failed, continuing with rich card');
2720
2763
  }
2721
2764
  }
2765
+ }
2722
2766
 
2723
- // 2) Convert AIRich submessages into ordinary WhatsApp text. This preserves
2724
- // every primitive's readable content even when the AI-only rich renderer is absent.
2725
- const lines = [];
2726
- for (const sub of await waitAllPromises(this._submessages)) {
2727
- if (!sub) continue;
2728
-
2729
- if (typeof sub.messageText === 'string' && sub.messageText.trim()) {
2730
- const t = sub.messageText.trim();
2731
-
2732
- // Media primitives already have a native media message above; don't emit
2733
- // their internal placeholder text as a duplicate.
2734
- if (
2735
- t === '[ Video tidak dapat dimuat ]' ||
2736
- t === '[ Postingan tidak dapat dimuat ]' ||
2737
- t === '[ Produk tidak dapat dimuat ]' ||
2738
- t === '[ Sedang diproses... ]'
2739
- ) continue;
2740
-
2741
- lines.push(t);
2742
- continue;
2743
- }
2767
+ // Vanz@Add --- pin our own messageId (instead of letting relayMessage mint one internally)
2768
+ // so we know exactly which id was sent, and stash it as _lastMessageKey. That's what lets
2769
+ // sendEdit() be called with no args afterwards and still know which message to patch.
2770
+ messageId = messageId || generateMessageIDV2();
2744
2771
 
2745
- if (sub.codeMetadata) {
2746
- const blocks = sub.codeMetadata.codeBlocks ?? [];
2747
- const code = Array.isArray(blocks)
2748
- ? blocks.map((b) => typeof b === 'string' ? b : (b?.value ?? '')).join('')
2749
- : String(blocks);
2750
- lines.push('```' + (sub.codeMetadata.codeLanguage || '') + '\\n' + code + '\\n```');
2751
- continue;
2752
- }
2772
+ await this.#client.relayMessage(jid, msg, { messageId, ...options });
2753
2773
 
2754
- if (sub.tableMetadata?.rows) {
2755
- const rows = sub.tableMetadata.rows;
2756
- if (Array.isArray(rows)) {
2757
- lines.push(rows.map((row) => {
2758
- const cells = Array.isArray(row) ? row : row?.cells;
2759
- return Array.isArray(cells)
2760
- ? cells.map((c) => typeof c === 'string' ? c : (c?.text ?? c?.value ?? '')).join(' | ')
2761
- : String(row);
2762
- }).join('\\n'));
2763
- }
2764
- continue;
2765
- }
2774
+ this._lastMessageKey = { remoteJid: jid, fromMe: true, id: messageId };
2766
2775
 
2767
- if (sub.latexMetadata?.text) {
2768
- lines.push(sub.latexMetadata.text);
2769
- }
2770
- }
2776
+ return { key: this._lastMessageKey, message: msg };
2777
+ }
2771
2778
 
2772
- const text = lines.join('\\n\\n').trim();
2773
- if (text) {
2774
- sent.push(await this.#client.sendMessage(jid, { text }, sendOptions));
2775
- }
2779
+ /**
2780
+ * Build a `protocolMessage` (type EDIT) that patches an already-sent AIRich message in place.
2781
+ * @param {string} targetJid Chat the original message lives in.
2782
+ * @param {string} targetId `key.id` of the original message (the id `send()`/`sendEdit()` returned).
2783
+ * @param {object} [opts] Pass `{ msg }` to reuse an already-built content object instead of rebuilding via build().
2784
+ */
2785
+ async buildEdit(targetJid, targetId, { msg, messageId, ...options } = {}) {
2786
+ const editedMessage = msg || (await this.build({ ...options }));
2787
+
2788
+ if (!editedMessage) {
2789
+ throw new Error('buildEdit: no message content to edit (build() returned nothing)');
2790
+ }
2776
2791
 
2777
- if (sent.length) return sent.length === 1 ? sent[0] : sent;
2792
+ return generateWAMessageFromContent(
2793
+ targetJid,
2794
+ {
2795
+ protocolMessage: {
2796
+ key: {
2797
+ remoteJid: targetJid,
2798
+ fromMe: true,
2799
+ id: targetId,
2800
+ },
2801
+ type: 14, // MESSAGE_EDIT
2802
+ editedMessage,
2803
+ },
2804
+ },
2805
+ { messageId: messageId || generateMessageIDV2(), ...options }
2806
+ );
2807
+ }
2808
+
2809
+ /**
2810
+ * Rebuild this AIRich message's current content and patch it into an already-sent message in place
2811
+ * (WA edits the bubble instead of showing a new one). With no args, edits the message from the last
2812
+ * send()/sendEdit() call — that's the flow `.addX(...); await rich.sendEdit();` relies on.
2813
+ * @param {string} [jid] Defaults to the jid from the last send()/sendEdit().
2814
+ * @param {string} [id] Defaults to the message id from the last send()/sendEdit().
2815
+ */
2816
+ async sendEdit(jid, id, { msg, messageId, additionalNodes = [], ...options } = {}) {
2817
+ jid = jid ?? this._lastMessageKey?.remoteJid;
2818
+ id = id ?? this._lastMessageKey?.id;
2819
+
2820
+ if (!jid) {
2821
+ throw new Error('sendEdit: no jid — pass one explicitly, or call send() first');
2822
+ }
2823
+
2824
+ if (!id) {
2825
+ throw new Error('sendEdit: no message id — pass one explicitly, or call send() first');
2778
2826
  }
2779
2827
 
2780
- // Explicit compatibility mode: preserve the original AIRich rich payload.
2781
- return await this.#client.relayMessage(jid, msg, { ...options });
2828
+ const msgEdit = await this.buildEdit(jid, id, {
2829
+ msg,
2830
+ messageId: messageId || generateMessageIDV2(),
2831
+ ...options,
2832
+ });
2833
+
2834
+ await this.#client.relayMessage(jid, msgEdit.message, {
2835
+ messageId: msgEdit.key.id,
2836
+ additionalNodes,
2837
+ });
2838
+
2839
+ // Vanz@Note --- deliberately NOT overwriting _lastMessageKey with msgEdit.key here: the
2840
+ // protocolMessage envelope has its own id, but the message the user actually sees (and the
2841
+ // one future sendEdit() calls need to keep patching) is still `id`/`jid` above.
2842
+ return msgEdit;
2782
2843
  }
2783
2844
 
2784
2845
  /** Tokenize `code` into `{ type, value }` spans for syntax highlighting. Covers JS/TS/Python/Java and more; unsupported languages fall back to a single plain-text token. */
@@ -3452,9 +3513,6 @@ class AIRich extends BaseBuilder {
3452
3513
  if (!Array.isArray(arr) || !arr.every((row) => Array.isArray(row) && row.every((cell) => typeof cell === 'string'))) {
3453
3514
  throw new TypeError('Table must be a nested array of strings');
3454
3515
  }
3455
- if (arr.length === 0 || arr[0].length === 0) {
3456
- throw new TypeError('Table must contain a non-empty header row');
3457
- }
3458
3516
 
3459
3517
  const [header, ...rows] = arr;
3460
3518
 
@@ -3638,7 +3696,6 @@ export {
3638
3696
  AIRich,
3639
3697
  AIRich as AIVanzxy,
3640
3698
  AIRich as LeafRich,
3641
- AIRich as LeafReach,
3642
3699
  AIRich as VanzxyAI,
3643
3700
  AIRich as VanzxyRich,
3644
3701
  Toolkit,
@@ -155,6 +155,11 @@ export class AIRich extends BaseBuilder {
155
155
  addProduct(data?: Record<string, any>, options?: { resolveUrl?: boolean }): this;
156
156
  addPost(data?: Record<string, any>, options?: { resolveUrl?: boolean }): this;
157
157
  addTip(text: string): this;
158
+ addMetadata(text: string): this;
159
+ setResponseId(id: string): this;
160
+ refreshResponseId(): this;
161
+ setBotResponseId(id: string): this;
162
+ refreshBotResponseId(): this;
158
163
  /** FOATextPrimitive — large heading text, distinct from addText()'s paragraph text. */
159
164
  addHeading(text: string): this;
160
165
  /** GenAIImagePrimitive — "ready" static image (preview + full-res), distinct from addImage()'s generation-style card. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vanzxy/baileys",
3
- "version": "1.5.2",
3
+ "version": "1.5.4",
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
  "module": "./lib/index.js",