@vanzxy/baileys 1.5.2 → 1.5.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.
@@ -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,7 @@
47
47
  const MESSAGE_BUILDER_VERSION = '4.9';
48
48
 
49
49
  import { generateWAMessageFromContent, prepareWAMessageMedia } from './messages.js';
50
+ import { botMetadataSignature, botMetadataCertificate } from './rich-message-utils.js';
50
51
  import crypto from 'crypto';
51
52
  import { PassThrough, Readable } from 'stream';
52
53
  // Vanz@Fix 15-08-26 --- sharp/fluent-ffmpeg were statically imported in the blurose source.
@@ -1180,14 +1181,6 @@ class Button extends BaseBuilder {
1180
1181
  }
1181
1182
 
1182
1183
  /** 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
1184
  async send(jid, { ...options } = {}) {
1192
1185
  const msg = await this.build(jid, options);
1193
1186
 
@@ -1594,9 +1587,17 @@ class AIRich extends BaseBuilder {
1594
1587
  // mediaKey still doesn't render, so it's a trust-chain gate, not a domain/encoding issue).
1595
1588
  // Track every addInlineImage() call here so send() can fall back to a normal imageMessage.
1596
1589
  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 = [];
1590
+
1591
+ // Vanz@Add 24-08-26 --- ported from temen's MessageBuilderV4.7 (setResponseId/setBotResponseId
1592
+ // below), rewritten to fit this fork's conventions. build() used to always mint a fresh
1593
+ // crypto.randomUUID() for both unifiedResponse.response_id and botMetadata.botResponseId on
1594
+ // every call, with no way to reuse one — so a `sendEdit()`-style flow (rebuild the same
1595
+ // message with updated content, same response_id, so WA patches it in place instead of
1596
+ // showing a new message) was never actually possible despite being in the gist example.
1597
+ // null here means "not pinned yet" — build() falls back to a fresh randomUUID() same as before
1598
+ // when neither setResponseId() nor setBotResponseId() has been called.
1599
+ this._responseId = null;
1600
+ this._botResponseId = null;
1600
1601
 
1601
1602
  // Vanz@Add (v4.9.1) --- { id, insertAt } support for every add*()/set*() call, without
1602
1603
  // touching each method's own body/signature. Every add*() call ends up pushing 0-N items
@@ -1915,7 +1916,7 @@ class AIRich extends BaseBuilder {
1915
1916
  * One image, no prompt, nothing to download — use this when you don't need the rich card,
1916
1917
  * just the picture to show up immediately.
1917
1918
  */
1918
- addImage(imageUrl, { resolveUrl = false, instant = true } = {}) {
1919
+ addImage(imageUrl, { resolveUrl = false, instant = false } = {}) {
1919
1920
  if (!(typeof imageUrl === 'string' || Buffer.isBuffer(imageUrl) || (Array.isArray(imageUrl) && imageUrl.every((v) => typeof v === 'string' || Buffer.isBuffer(v))))) {
1920
1921
  throw new TypeError('imageUrl must be string | buffer | array of string/buffer');
1921
1922
  }
@@ -1973,8 +1974,6 @@ class AIRich extends BaseBuilder {
1973
1974
  }
1974
1975
 
1975
1976
  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
1977
  this._inlineImages.push({ url: imagePreviewUrl, caption: undefined });
1979
1978
  }
1980
1979
  });
@@ -2052,7 +2051,7 @@ class AIRich extends BaseBuilder {
2052
2051
  // for before rendering. Mirrors addImage()'s { resolveUrl } — when true, the url is uploaded to
2053
2052
  // WA's own media server first via Toolkit.toUrl() so it renders instantly like WA-native media.
2054
2053
  /** Add a video block. */
2055
- addVideo(videoUrl, { autoFill = false, resolveUrl = false, instant = true } = {}) {
2054
+ addVideo(videoUrl, { autoFill = false, resolveUrl = false } = {}) {
2056
2055
  const isObjectVideo = (v) => v && typeof v === 'object' && v.url;
2057
2056
 
2058
2057
  const isValidPrimitive =
@@ -2129,15 +2128,6 @@ class AIRich extends BaseBuilder {
2129
2128
  __typename: 'GenAIImaginePrimitive',
2130
2129
  })
2131
2130
  );
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
2131
  });
2142
2132
 
2143
2133
  return this;
@@ -2224,6 +2214,56 @@ class AIRich extends BaseBuilder {
2224
2214
  return this;
2225
2215
  }
2226
2216
 
2217
+ // Vanz@Add 24-08-26 --- ported from temen's MessageBuilderV4.7 (setResponseId/setBotResponseId/
2218
+ // refreshResponseId/refreshBotResponseId), rewritten for this fork. Pins the two ids build()
2219
+ // generates (see constructor comment) so a rebuilt message can reuse the same response_id/
2220
+ // botResponseId — needed for editing an already-sent AIRich message in place.
2221
+
2222
+ /** 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. */
2223
+ setResponseId(id) {
2224
+ if (typeof id !== 'string' || !id) throw new TypeError('setResponseId(id) requires a non-empty string');
2225
+ this._responseId = id;
2226
+ return this;
2227
+ }
2228
+
2229
+ /** Un-pin `unifiedResponse.response_id`, generating a fresh crypto.randomUUID() immediately (not deferred to the next build()). */
2230
+ refreshResponseId() {
2231
+ this._responseId = crypto.randomUUID();
2232
+ return this;
2233
+ }
2234
+
2235
+ /** Pin `botMetadata.botResponseId` to a specific value instead of a fresh random one each build(). */
2236
+ setBotResponseId(id) {
2237
+ if (typeof id !== 'string' || !id) throw new TypeError('setBotResponseId(id) requires a non-empty string');
2238
+ this._botResponseId = id;
2239
+ return this;
2240
+ }
2241
+
2242
+ /** Un-pin `botMetadata.botResponseId`, generating a fresh crypto.randomUUID() immediately. */
2243
+ refreshBotResponseId() {
2244
+ this._botResponseId = crypto.randomUUID();
2245
+ return this;
2246
+ }
2247
+
2248
+ /** 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. */
2249
+ addMetadata(text) {
2250
+ if (typeof text !== 'string' || !text) throw new TypeError('addMetadata(text) requires a non-empty string');
2251
+
2252
+ this._submessages.push({
2253
+ messageType: 2,
2254
+ messageText: text,
2255
+ });
2256
+
2257
+ this._sections.push(
2258
+ AIRich.newLayout('Single', {
2259
+ text,
2260
+ __typename: 'GenAIMetadataTextPrimitive',
2261
+ })
2262
+ );
2263
+
2264
+ return this;
2265
+ }
2266
+
2227
2267
  /** Add a small "tip" callout banner. @param {string} text */
2228
2268
  addTip(text) {
2229
2269
  if (typeof text !== 'string' || !text) {
@@ -2301,7 +2341,6 @@ class AIRich extends BaseBuilder {
2301
2341
  __typename: 'GenAIImagePrimitive',
2302
2342
  })
2303
2343
  );
2304
- this._mediaFallbacks.push({ type: 'image', url: full || preview, caption: undefined });
2305
2344
 
2306
2345
  return this;
2307
2346
  }
@@ -2641,6 +2680,18 @@ class AIRich extends BaseBuilder {
2641
2680
  ]
2642
2681
  : [...(await waitAllPromises(this._sections))];
2643
2682
 
2683
+ // Vanz@Merge 15-08-26 --- Neither blurose nor arslan sign the bot metadata with
2684
+ // verificationMetadata (proofs/certificateChain). Backported from this project's own
2685
+ // rich-message-utils.js botMetadataSignature/botMetadataCertificate helpers, plus a
2686
+ // botResponseId tying the signed metadata to unifiedResponse.response_id.
2687
+ // Vanz@Fix 24-08-26 --- was `const responseId = crypto.randomUUID()` shared for BOTH
2688
+ // unifiedResponse.response_id and botMetadata.botResponseId, generated fresh every build()
2689
+ // with no override. Now each has its own id, pinned via setResponseId()/setBotResponseId()
2690
+ // if the caller set one (for sendEdit()-style in-place message updates), otherwise still
2691
+ // defaults to a fresh randomUUID() per build() exactly like before.
2692
+ const responseId = this._responseId ?? crypto.randomUUID();
2693
+ const botResponseId = this._botResponseId ?? crypto.randomUUID();
2694
+
2644
2695
  return {
2645
2696
  messageContextInfo: {
2646
2697
  deviceListMetadata: {},
@@ -2648,6 +2699,17 @@ class AIRich extends BaseBuilder {
2648
2699
  botMetadata: {
2649
2700
  messageDisclaimerText: this._title,
2650
2701
  richResponseSourcesMetadata: { sources: this._richResponseSources },
2702
+ botResponseId: botResponseId,
2703
+ verificationMetadata: {
2704
+ proofs: [
2705
+ {
2706
+ certificateChain: [botMetadataCertificate(), botMetadataCertificate(892)],
2707
+ version: 1,
2708
+ useCase: 1,
2709
+ signature: botMetadataSignature(),
2710
+ },
2711
+ ],
2712
+ },
2651
2713
  ...notif,
2652
2714
  },
2653
2715
  },
@@ -2658,7 +2720,7 @@ class AIRich extends BaseBuilder {
2658
2720
  messageType: 1,
2659
2721
  submessages: includesSubmessages ? await waitAllPromises(this._submessages) : [],
2660
2722
  unifiedResponse: {
2661
- data: includesUnifiedResponse ? Buffer.from(JSON.stringify({ response_id: crypto.randomUUID(), sections })).toString('base64') : '',
2723
+ data: includesUnifiedResponse ? Buffer.from(JSON.stringify({ response_id: responseId, sections })).toString('base64') : '',
2662
2724
  },
2663
2725
  contextInfo: {
2664
2726
  ...forward,
@@ -2679,105 +2741,20 @@ class AIRich extends BaseBuilder {
2679
2741
  // the two calls expect different option shapes, so the fallback now only forwards `quoted`
2680
2742
  // (the one option that clearly applies to both) instead of blindly spreading everything.
2681
2743
  /** 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
- } = {}) {
2744
+ async send(jid, { forwarded, notification, includesUnifiedResponse, includesSubmessages, skipImageFallback = false, quoted, ...options } = {}) {
2692
2745
  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
- }
2720
- }
2721
- }
2722
-
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
- }
2744
-
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
- }
2753
2746
 
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
- }
2766
-
2767
- if (sub.latexMetadata?.text) {
2768
- lines.push(sub.latexMetadata.text);
2747
+ if (!skipImageFallback && this._inlineImages.length) {
2748
+ for (const { url, caption } of this._inlineImages) {
2749
+ try {
2750
+ await this.#client.sendMessage(jid, { image: { url }, caption }, quoted ? { quoted } : {});
2751
+ } catch (err) {
2752
+ // Vanz@Fix: don't let a fallback image failure block the actual rich card from sending
2753
+ this.#client.logger?.warn?.({ err, url }, 'inline image fallback failed, continuing with rich card');
2769
2754
  }
2770
2755
  }
2771
-
2772
- const text = lines.join('\\n\\n').trim();
2773
- if (text) {
2774
- sent.push(await this.#client.sendMessage(jid, { text }, sendOptions));
2775
- }
2776
-
2777
- if (sent.length) return sent.length === 1 ? sent[0] : sent;
2778
2756
  }
2779
2757
 
2780
- // Explicit compatibility mode: preserve the original AIRich rich payload.
2781
2758
  return await this.#client.relayMessage(jid, msg, { ...options });
2782
2759
  }
2783
2760
 
@@ -3452,9 +3429,6 @@ class AIRich extends BaseBuilder {
3452
3429
  if (!Array.isArray(arr) || !arr.every((row) => Array.isArray(row) && row.every((cell) => typeof cell === 'string'))) {
3453
3430
  throw new TypeError('Table must be a nested array of strings');
3454
3431
  }
3455
- if (arr.length === 0 || arr[0].length === 0) {
3456
- throw new TypeError('Table must contain a non-empty header row');
3457
- }
3458
3432
 
3459
3433
  const [header, ...rows] = arr;
3460
3434
 
@@ -3638,7 +3612,6 @@ export {
3638
3612
  AIRich,
3639
3613
  AIRich as AIVanzxy,
3640
3614
  AIRich as LeafRich,
3641
- AIRich as LeafReach,
3642
3615
  AIRich as VanzxyAI,
3643
3616
  AIRich as VanzxyRich,
3644
3617
  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.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
  "module": "./lib/index.js",