@vanzxy/baileys 1.6.0 → 1.6.2

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.
@@ -696,6 +696,58 @@ class Button extends BaseBuilder {
696
696
  return this;
697
697
  }
698
698
 
699
+ // Vanz@Add 26-08-26 --- setBloksWidget(): Meta's Bloks/A2UI format, a sibling field to
700
+ // nativeFlowMessage on interactiveMessage (NOT part of the AIRich rich-response system —
701
+ // this is a real native interactive UI: checkboxes/text fields/buttons actually work).
702
+ // Captured traffic gives the A2UI payload as a flat `components` array where every node has
703
+ // an `id` and children/child reference OTHER nodes by id string. Authoring that by hand is
704
+ // error-prone (dangling ids, ordering), so this accepts a plain nested tree instead —
705
+ // { component, ...props, children: [...] } / { component, ...props, child: {...} } — and
706
+ // flattens it into that array itself, auto-assigning ids.
707
+ #flattenBloks(tree, out, counter = { n: 0 }, id = 'root') {
708
+ if (!tree || typeof tree !== 'object') throw new TypeError('setBloksWidget: every node needs a "component" type');
709
+ const { component, children, child, ...props } = tree;
710
+ if (typeof component !== 'string' || !component) throw new TypeError('setBloksWidget: every node needs a "component" type');
711
+
712
+ const node = { id, component, ...props };
713
+
714
+ if (Array.isArray(children)) {
715
+ node.children = children.map((c) => this.#flattenBloks(c, out, counter, `n${counter.n++}`));
716
+ } else if (child) {
717
+ node.child = this.#flattenBloks(child, out, counter, `n${counter.n++}`);
718
+ }
719
+
720
+ out.push(node);
721
+ return id;
722
+ }
723
+
724
+ /**
725
+ * Set a Bloks/A2UI native widget (`bloksWidget`, `type: "im_a2ui"`) — a real interactive
726
+ * screen (images, video, checkboxes, text fields, buttons that fire an `action`), not a
727
+ * static card. Pass a nested tree; ids are assigned automatically.
728
+ * @param {Record<string, any>} tree Root node, e.g. `{ component: 'Column', children: [...] }`.
729
+ * @param {{uuid?: string, catalogId?: string, surfaceId?: string, version?: string}} [options]
730
+ */
731
+ setBloksWidget(tree, { uuid = crypto.randomUUID(), catalogId = 'https://a2ui.org/specification/v0_9/catalogs/basic/catalog.json', surfaceId, version = 'v0.9' } = {}) {
732
+ const components = [];
733
+ this.#flattenBloks(tree, components);
734
+
735
+ this._bloksWidget = {
736
+ uuid,
737
+ data: JSON.stringify({
738
+ version,
739
+ createSurface: {
740
+ surfaceId: surfaceId ?? `starcore-widget=${uuid}`,
741
+ catalogId,
742
+ components,
743
+ },
744
+ }),
745
+ type: 'im_a2ui',
746
+ };
747
+
748
+ return this;
749
+ }
750
+
699
751
  /**
700
752
  * Low-level escape hatch: push a raw native-flow button by name. Prefer the
701
753
  * dedicated `add*()` helpers below when one exists — they validate the
@@ -1290,22 +1342,26 @@ class Button extends BaseBuilder {
1290
1342
 
1291
1343
  /** @returns {Record<string, any>} The final content object, without generating/wrapping a WAMessage. Useful when composing this interactive card into something else (e.g. Carousel). */
1292
1344
  async build(jid, { ...options } = {}) {
1293
- if (this._buttons.length === 0) {
1294
- throw new Error('Button requires at least one button (use addReply/addUrl/addCall/addSelection/addButton/...)');
1345
+ if (this._buttons.length === 0 && !this._bloksWidget) {
1346
+ throw new Error('Button requires at least one button (use addReply/addUrl/addCall/addSelection/addButton/...) or a Bloks widget (setBloksWidget())');
1295
1347
  }
1296
1348
 
1297
- if (this.#isLoneSingleSelect()) {
1349
+ if (this._buttons.length > 0 && this.#isLoneSingleSelect()) {
1298
1350
  return generateWAMessageFromContent(jid, { ...this._extraPayload, ...this.#toListMessage() }, { ...options });
1299
1351
  }
1300
1352
 
1301
- const message = await this.toCard();
1353
+ const message = this._buttons.length > 0 ? await this.toCard() : {};
1302
1354
 
1303
1355
  return generateWAMessageFromContent(
1304
1356
  jid,
1305
1357
  {
1358
+ ...(this._bloksWidget && {
1359
+ messageContextInfo: { messageSecret: crypto.randomBytes(32) },
1360
+ }),
1306
1361
  ...this._extraPayload,
1307
1362
  interactiveMessage: {
1308
1363
  ...message,
1364
+ ...(this._bloksWidget && { bloksWidget: this._bloksWidget }),
1309
1365
  contextInfo: this._contextInfo,
1310
1366
  },
1311
1367
  },
@@ -1328,12 +1384,25 @@ class Button extends BaseBuilder {
1328
1384
  const bizContent = this.#isLoneSingleSelect()
1329
1385
  ? [{ tag: 'list', attrs: { v: '2', type: 'product_list' } }]
1330
1386
  : [
1331
- {
1332
- tag: 'interactive',
1333
- attrs: { type: 'native_flow', v: '1' },
1334
- content: [this.#buildNativeFlowNode()],
1335
- },
1336
- ];
1387
+ ...(this._buttons.length > 0
1388
+ ? [
1389
+ {
1390
+ tag: 'interactive',
1391
+ attrs: { type: 'native_flow', v: '1' },
1392
+ content: [this.#buildNativeFlowNode()],
1393
+ },
1394
+ ]
1395
+ : []),
1396
+ ...(this._bloksWidget
1397
+ ? [
1398
+ {
1399
+ tag: 'quality_control',
1400
+ attrs: { decision_id: crypto.randomUUID().replace(/-/g, ''), source_type: 'third_party' },
1401
+ content: [{ tag: 'decision_source', attrs: { value: 'df' } }],
1402
+ },
1403
+ ]
1404
+ : []),
1405
+ ];
1337
1406
 
1338
1407
  await this.#client.relayMessage(msg.key.remoteJid, msg.message, {
1339
1408
  messageId: msg.key.id,
@@ -1776,9 +1845,10 @@ class AIRich extends BaseBuilder {
1776
1845
  }
1777
1846
 
1778
1847
  return (...args) => {
1779
- const opts = args.find((a) => a && typeof a === 'object' && !Array.isArray(a) && !Buffer.isBuffer(a) && ('id' in a || 'insertAt' in a));
1848
+ const opts = args.find((a) => a && typeof a === 'object' && !Array.isArray(a) && !Buffer.isBuffer(a) && ('id' in a || 'insertAt' in a || 'replace' in a));
1780
1849
  const id = opts?.id;
1781
1850
  const insertAt = opts?.insertAt;
1851
+ const replace = opts?.replace;
1782
1852
 
1783
1853
  const subBefore = target._submessages.length;
1784
1854
  const secBefore = target._sections.length;
@@ -1810,6 +1880,31 @@ class AIRich extends BaseBuilder {
1810
1880
  const lastSec = anchor.secItems[anchor.secItems.length - 1];
1811
1881
  const secIdx = lastSec ? target._sections.indexOf(lastSec) + 1 : target._sections.length;
1812
1882
  target._sections.splice(secIdx, 0, ...secItems);
1883
+ } else if (replace) {
1884
+ // replace: delete the old block's items at their current positions,
1885
+ // then insert new items at the same positions
1886
+ const old = target._blocks.get(replace);
1887
+ if (!old) throw new Error(`replace: no block registered with id "${replace}" (register it first with { id: "${replace}" })`);
1888
+
1889
+ let subIdx = old.subItems.length > 0 ? target._submessages.indexOf(old.subItems[0]) : target._submessages.length;
1890
+ if (subIdx === -1) subIdx = target._submessages.length;
1891
+ for (const item of old.subItems) {
1892
+ const i = target._submessages.indexOf(item);
1893
+ if (i !== -1) target._submessages.splice(i, 1);
1894
+ }
1895
+ target._submessages.splice(subIdx, 0, ...subItems);
1896
+
1897
+ let secIdx = old.secItems.length > 0 ? target._sections.indexOf(old.secItems[0]) : target._sections.length;
1898
+ if (secIdx === -1) secIdx = target._sections.length;
1899
+ for (const item of old.secItems) {
1900
+ const i = target._sections.indexOf(item);
1901
+ if (i !== -1) target._sections.splice(i, 1);
1902
+ }
1903
+ target._sections.splice(secIdx, 0, ...secItems);
1904
+
1905
+ target._blocks.delete(replace);
1906
+ if (id) target._blocks.set(id, { subItems, secItems });
1907
+ else target._blocks.set(replace, { subItems, secItems });
1813
1908
  } else {
1814
1909
  target._submessages.push(...subItems);
1815
1910
  target._sections.push(...secItems);
@@ -2014,21 +2109,38 @@ class AIRich extends BaseBuilder {
2014
2109
  }
2015
2110
 
2016
2111
  addSource(sources = [], { resolveUrl = false } = {}) {
2017
- if (!(Array.isArray(sources) && (sources.every((item) => typeof item === 'string') || sources.every((item) => Array.isArray(item) && item.every((v) => typeof v === 'string'))))) {
2018
- throw new TypeError('Sources must be a string array or an array of string arrays');
2019
- }
2020
-
2021
- if (sources.every((item) => typeof item === 'string')) {
2022
- sources = [sources];
2023
- }
2024
-
2025
- const source = sources.map(([icon, url, text]) => ({
2112
+ // Accept 3 formats:
2113
+ // 1. Array of objects: [{ icon, url, title, subtitle }] from v4.7 example
2114
+ // 2. Array of string arrays: [['iconUrl', 'url', 'text']]
2115
+ // 3. Single string array (shorthand for format 2): ['iconUrl', 'url', 'text']
2116
+ const isObjArray = Array.isArray(sources) && sources.every((item) => item && typeof item === 'object' && !Array.isArray(item));
2117
+ const isStrArrayArray = Array.isArray(sources) && sources.every((item) => Array.isArray(item) && item.every((v) => typeof v === 'string'));
2118
+ const isFlatStrArray = Array.isArray(sources) && sources.every((item) => typeof item === 'string');
2119
+
2120
+ if (!isObjArray && !isStrArrayArray && !isFlatStrArray) {
2121
+ throw new TypeError('addSource(): pass an array of objects { icon, url, title, subtitle } or string arrays [iconUrl, url, text]');
2122
+ }
2123
+
2124
+ let normalized;
2125
+ if (isObjArray) {
2126
+ normalized = sources.map((item) => ({
2127
+ icon: item.icon ?? item.iconUrl ?? item.favicon ?? '',
2128
+ url: item.url ?? '',
2129
+ text: item.title ?? item.displayName ?? item.text ?? '',
2130
+ subtitle: item.subtitle ?? 'AI',
2131
+ }));
2132
+ } else {
2133
+ const arr = isFlatStrArray ? [sources] : sources;
2134
+ normalized = arr.map(([icon = '', url = '', text = '']) => ({ icon, url, text, subtitle: 'AI' }));
2135
+ }
2136
+
2137
+ const source = normalized.map(({ icon, url, text, subtitle }) => ({
2026
2138
  source_type: 'THIRD_PARTY',
2027
- source_display_name: text ?? '',
2028
- source_subtitle: 'AI',
2029
- source_url: url ?? '',
2139
+ source_display_name: text,
2140
+ source_subtitle: subtitle,
2141
+ source_url: url,
2030
2142
  favicon: {
2031
- url: Toolkit.resolveMedia(this.#client, icon ?? '', 'image', { resolveUrl }),
2143
+ url: Toolkit.resolveMedia(this.#client, icon, 'image', { resolveUrl }),
2032
2144
  mime_type: 'image/jpeg',
2033
2145
  width: 16,
2034
2146
  height: 16,
@@ -2587,8 +2699,10 @@ class AIRich extends BaseBuilder {
2587
2699
  const items = Array.isArray(data) ? data : [data];
2588
2700
 
2589
2701
  items.forEach((item, i) => {
2590
- if (!item?.title) {
2591
- throw new TypeError(`addWidget() item[${i}] is missing a required "title"`);
2702
+ // header.title or top-level title required
2703
+ const hasTitle = item?.title || item?.header?.title;
2704
+ if (!hasTitle) {
2705
+ throw new TypeError(`addWidget() item[${i}] is missing a required "title" (or "header.title")`);
2592
2706
  }
2593
2707
  const ctas = item.ctas ?? item.actions;
2594
2708
  if (!Array.isArray(ctas) || !ctas.length) {
@@ -2598,22 +2712,29 @@ class AIRich extends BaseBuilder {
2598
2712
 
2599
2713
  this._submessages.push({
2600
2714
  messageType: 2,
2601
- messageText: items.map((item) => item.title).join(', '),
2715
+ messageText: items.map((item) => item.header?.title ?? item.title).join(', '),
2602
2716
  });
2603
2717
 
2604
2718
  const widgets = items.map((item) => {
2605
2719
  const ctas = item.ctas ?? item.actions;
2720
+ // header accepts either a string title (legacy) or an object { title, subtitle }
2721
+ const headerTitle = item.header?.title ?? item.title;
2722
+ const headerSubtitle = item.header?.subtitle ?? item.subtitle ?? undefined;
2606
2723
  return {
2607
- header: { title: item.title, __typename: 'GenAI3PExtWidgetStandardHeader' },
2724
+ header: {
2725
+ title: headerTitle,
2726
+ ...(headerSubtitle !== undefined && { subtitle: headerSubtitle }),
2727
+ __typename: 'GenAI3PExtWidgetStandardHeader',
2728
+ },
2608
2729
  body: {
2609
2730
  sections: item.sections ?? [],
2610
2731
  ctas: ctas.map((cta, idx) => ({
2611
2732
  label: cta.label ?? '',
2612
2733
  state: cta.state ?? 'PENDING',
2613
2734
  kind: cta.kind ?? 'OTHER',
2614
- tool_call_id: cta.tool_call_id ?? String(idx).padStart(2, '0'),
2735
+ tool_call_id: cta.tool_call_id ?? cta.id ?? String(idx).padStart(2, '0'),
2615
2736
  ...(cta.toast !== false && {
2616
- toast: { label: typeof cta.toast === 'string' ? cta.toast : item.title, __typename: 'GenAI3PExtWidgetToast' },
2737
+ toast: { label: typeof cta.toast === 'string' ? cta.toast : headerTitle, __typename: 'GenAI3PExtWidgetToast' },
2617
2738
  }),
2618
2739
  __typename: 'GenAI3PExtWidgetCTA',
2619
2740
  })),
@@ -45,6 +45,7 @@ export class Button extends BaseBuilder {
45
45
  setMedia(obj: Record<string, any>): this;
46
46
  clearButtons(): this;
47
47
  setParams(obj: Record<string, any>): this;
48
+ setBloksWidget(tree: Record<string, any>, options?: { uuid?: string; catalogId?: string; surfaceId?: string; version?: string }): this;
48
49
  addButton(name: string, params: string | Record<string, any>): this;
49
50
  makeRow(header?: string, title?: string, description?: string, id?: string): this;
50
51
  makeSection(title?: string, highlight_label?: string): this;
@@ -1,4 +1,6 @@
1
1
  import { Boom } from '@hapi/boom';
2
+ import $protobuf from 'protobufjs/minimal.js';
3
+ const { Reader } = $protobuf;
2
4
  import { proto } from '../../WAProto/index.js';
3
5
  import { WAMessageStubType } from '../Types/index.js';
4
6
  import { getContentType, normalizeMessageContent } from '../Utils/messages.js';
@@ -14,6 +16,51 @@ const REAL_MSG_STUB_TYPES = new Set([
14
16
  WAMessageStubType.CALL_MISSED_VOICE
15
17
  ]);
16
18
  const REAL_MSG_REQ_ME_STUB_TYPES = new Set([WAMessageStubType.GROUP_PARTICIPANT_ADD]);
19
+ // Vanz@Fix 26-08-26 --- the 26-08-26 WAProto refresh dropped `LIDMigrationMappingSyncPayload`
20
+ // / `LIDMigrationMapping` from the published schema; only the opaque envelope
21
+ // `LIDMigrationMappingSyncMessage { encodedMappingPayload: bytes }` is generated now.
22
+ // Nothing suggests the *inner* wire layout actually changed (WA's extractor just stopped
23
+ // walking this nested message), so we decode it by hand instead of guessing a new shape.
24
+ // TODO: verify against live traffic on the next audit pass — if Meta did change the inner
25
+ // layout this will start throwing and LID/PN pairs will silently stop syncing.
26
+ function decodeLidMigrationMappingSyncPayload(buf) {
27
+ const r = Reader.create(buf);
28
+ const out = { pnToLidMappings: [], chatDbMigrationTimestamp: undefined };
29
+ while (r.pos < r.len) {
30
+ const tag = r.uint32();
31
+ switch (tag >>> 3) {
32
+ case 1: {
33
+ const len = r.uint32();
34
+ const end = r.pos + len;
35
+ const entry = { pn: undefined, assignedLid: undefined, latestLid: undefined };
36
+ while (r.pos < end) {
37
+ const t2 = r.uint32();
38
+ switch (t2 >>> 3) {
39
+ case 1:
40
+ entry.pn = r.uint64();
41
+ break;
42
+ case 2:
43
+ entry.assignedLid = r.uint64();
44
+ break;
45
+ case 3:
46
+ entry.latestLid = r.uint64();
47
+ break;
48
+ default:
49
+ r.skipType(t2 & 7);
50
+ }
51
+ }
52
+ out.pnToLidMappings.push(entry);
53
+ break;
54
+ }
55
+ case 2:
56
+ out.chatDbMigrationTimestamp = r.uint64();
57
+ break;
58
+ default:
59
+ r.skipType(tag & 7);
60
+ }
61
+ }
62
+ return out;
63
+ }
17
64
  async function storeTcTokensFromHistorySync(chats, signalRepository, keyStore, logger) {
18
65
  const getLIDForPN = signalRepository.lidMapping.getLIDForPN.bind(signalRepository.lidMapping);
19
66
  const candidates = [];
@@ -462,7 +509,7 @@ const processMessage = async (message, { shouldProcessHistoryMsg, placeholderRes
462
509
  break;
463
510
  case proto.Message.ProtocolMessage.Type.LID_MIGRATION_MAPPING_SYNC:
464
511
  const encodedPayload = protocolMsg.lidMigrationMappingSyncMessage?.encodedMappingPayload;
465
- const { pnToLidMappings, chatDbMigrationTimestamp } = proto.LIDMigrationMappingSyncPayload.decode(encodedPayload);
512
+ const { pnToLidMappings, chatDbMigrationTimestamp } = decodeLidMigrationMappingSyncPayload(encodedPayload);
466
513
  logger?.debug({ pnToLidMappings, chatDbMigrationTimestamp }, 'got lid mappings and chat db migration timestamp');
467
514
  const pairs = [];
468
515
  for (const { pn, latestLid, assignedLid } of pnToLidMappings) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vanzxy/baileys",
3
- "version": "1.6.0",
3
+ "version": "1.6.2",
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",