@codexverified/baileys 2.14.14 → 2.17.17

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 CHANGED
@@ -1,11 +1,35 @@
1
- # @codexverified/baileys
1
+ <div align="center">
2
2
 
3
- [![npm version](https://img.shields.io/npm/v/@codexverified/baileys.svg)](https://www.npmjs.com/package/@codexverified/baileys)
4
- [![node](https://img.shields.io/badge/node-%3E%3D20-brightgreen)](#at-a-glance)
5
- [![license](https://img.shields.io/badge/license-MIT-blue.svg)](#license-and-maintenance)
6
- [![types](https://img.shields.io/badge/types-included-blue)](#at-a-glance)
3
+ # BAILEYS
7
4
 
8
- > A readable, carefully maintained Baileys distribution for building reliable WhatsApp integrations with codexverified/baileys.
5
+ ### **The intelligent interactive and productivity layer for WhatsApp protocols.**
6
+
7
+ <img src="https://readme-typing-svg.demolab.com?font=Space+Mono&size=22&pause=1200&color=00FFF0&center=true&vCenter=true&width=900&height=70&repeat=true&lines=BAILEYS+PROTOCOL.;NODE.JS+%2B+TYPESCRIPT.;RICH+MESSAGES+%2B+MEDIA.;BUILD+RELIABLE+INTEGRATIONS." alt="CodexVerified Baileys animated keywords" />
8
+
9
+ <img src="https://i.imgur.com/dBaSKWF.gif" height="16" width="88%" alt="animated coloured divider" />
10
+
11
+ <img src="https://raw.githubusercontent.com/codexverified/CODEX-AI/main/assets/rolling-circle.svg" width="90" alt="CodexVerified rolling circle" />
12
+
13
+ [![Version](https://img.shields.io/badge/version-2.14.14-00FFF0?style=for-the-badge&labelColor=07111F)](https://www.npmjs.com/package/@codexverified/baileys)
14
+ [![Developer CODEX](https://img.shields.io/badge/Developer-CODEX-B88CFF?style=for-the-badge&logo=telegram&labelColor=07111F)](https://t.me/codexverified)
15
+ [![Node.js](https://img.shields.io/badge/node-%3E%3D20-7CFFB2?style=for-the-badge&logo=node.js&logoColor=white&labelColor=07111F)](https://nodejs.org/)
16
+ [![Powered by codexverified/baileys](https://img.shields.io/badge/powered%20by-codexverified%2Fbaileys-25D366?style=for-the-badge&logo=whatsapp&logoColor=white&labelColor=07111F)](https://www.npmjs.com/package/@codexverified/baileys)
17
+ [![License](https://img.shields.io/badge/license-MIT-B88CFF?style=for-the-badge&labelColor=07111F)](#license-and-maintenance)
18
+
19
+ </div>
20
+
21
+ <div align="center">
22
+
23
+ [![Main WhatsApp Channel](https://img.shields.io/badge/WhatsApp%20Channel-Follow-25D366?style=for-the-badge&logo=whatsapp)](https://whatsapp.com/channel/0029Vb78BHmL2AU7fsANSH2y)
24
+ [![Backup WhatsApp Channel](https://img.shields.io/badge/Backup%20Channel-Follow-25D366?style=for-the-badge&logo=whatsapp)](https://whatsapp.com/channel/0029Vb6sMEy96H4VI2w3I50F)
25
+ [![Support Group](https://img.shields.io/badge/Support%20Group-Join-25D366?style=for-the-badge&logo=whatsapp)](https://chat.whatsapp.com/If0d4XKHITO2NUf6YvQ3Eg?s=cl&p=a&mlu=4&ilr=4)
26
+ [![Telegram Channel](https://img.shields.io/badge/Telegram%20Channel-Join-26A5E4?style=for-the-badge&logo=telegram)](https://t.me/codex_tech_innovation)
27
+ [![Telegram Group](https://img.shields.io/badge/Telegram%20Group-Join-26A5E4?style=for-the-badge&logo=telegram)](https://t.me/CODEXV3)
28
+ [![Website](https://img.shields.io/badge/Website-Visit-00FFF0?style=for-the-badge&logo=vercel&logoColor=black)](https://codex-ai.site)
29
+
30
+ </div>
31
+
32
+ > A readable, carefully maintained Baileys distribution for building reliable WhatsApp integrations. built by codex
9
33
 
10
34
  `@codexverified/baileys` is a Node.js and TypeScript library for creating WhatsApp automations over the Baileys protocol. It provides a socket-oriented API for authentication, messaging, media, interactive content, rich responses, newsletters, groups, communities, business features, account utilities, and carefully documented extensions.
11
35
 
@@ -40,7 +64,7 @@ The package is maintained by **Codex** under the **CodexVerified** organization.
40
64
  | Property | Value |
41
65
  | --- | --- |
42
66
  | Package | `@codexverified/baileys` |
43
- | Version | `2.14.14` |
67
+ | Version | `2.17.17` |
44
68
  | Runtime | Node.js 20 or newer |
45
69
  | Module format | ESM, with CommonJS compatibility where supported |
46
70
  | Type declarations | Included at `lib/index.d.ts` |
@@ -54,7 +78,7 @@ The package is maintained by **Codex** under the **CodexVerified** organization.
54
78
  | Method | Command | Notes |
55
79
  | --- | --- | --- |
56
80
  | From npm | `npm install @codexverified/baileys` | Standard install when published under your organization |
57
- | From local archive | `npm install ./codex-baileys-2.14.14.tgz` | Place the `.tgz` beside your application |
81
+ | From local archive | `npm install ./codex-baileys-2.17.17.tgz` | Place the `.tgz` beside your application |
58
82
  | Source audit (no scripts) | `npm install --ignore-scripts` | Skips install hooks for local review |
59
83
 
60
84
  Both npm and archive installs use the same import name:
@@ -663,6 +687,134 @@ await sock.sendBotCommand(jid, {
663
687
  ```
664
688
 
665
689
 
690
+
691
+
692
+ ### Full-width stacked albums
693
+
694
+ `sendAlbumMessage` sends a rich vertical album rather than the legacy parent/child album collection. Each image or video is rendered as one large media item on its own row. Provide at least two publicly fetchable media URLs. Both `{ image: { url } }` and the typed `{ type: 'image', data: { url } }` forms are accepted.
695
+
696
+ ```js
697
+ await sock.sendAlbumMessage(jid, [
698
+ { image: { url: 'https://example.com/one.jpg' } },
699
+ { image: { url: 'https://example.com/two.jpg' } },
700
+ { image: { url: 'https://example.com/three.jpg' } }
701
+ ], { caption: 'Codex album' })
702
+ ```
703
+
704
+ The rich album path is URL-based and does not upload local buffers. Remove accidental whitespace from URLs before sending; the implementation trims URL values, but clean URLs are recommended.
705
+
706
+ ### A2UI command menu
707
+
708
+ `sendA2UICommandMenu` sends the large command/function menu UI: an optional image header, title, command rows, URL buttons, and a native-flow fallback. The `rows` entries use `[command, description]` tuples, matching the menu layout shown in the WhatsApp client.
709
+
710
+ ```js
711
+ await sock.sendA2UICommandMenu(jid, {
712
+ image: { url: 'https://example.com/menu.jpg' },
713
+ title: 'Speed menu',
714
+ rows: [
715
+ ['.menu', 'Main menu'],
716
+ ['.ping', 'Speed check']
717
+ ],
718
+ buttons: [
719
+ { text: 'Website', url: 'https://example.com' },
720
+ { text: 'Support', url: 'https://example.com/support' }
721
+ ]
722
+ })
723
+ ```
724
+
725
+
726
+ ### AIRich command menu and video grid
727
+
728
+ The compatibility update also exposes the two AIRich layouts that were not already present in Codex. `sendAIRichCommandMenu` sends the manually serialized command card with an image, Markdown command table, optional widget CTAs, and footer URL actions. The image must be a publicly fetchable URL.
729
+
730
+ ```ts
731
+ await sock.sendAIRichCommandMenu(jid, {
732
+ image: 'https://example.com/menu.png',
733
+ title: 'CODEX AI',
734
+ subtitle: 'Choose a command',
735
+ rows: [
736
+ ['.menu', 'Show the main menu'],
737
+ ['.ping', 'Check service latency']
738
+ ],
739
+ buttons: [
740
+ { id: 'docs', text: 'Documentation', url: 'https://example.com/docs' }
741
+ ],
742
+ footer: 'Developed by - CODEX TECHNOLOGY'
743
+ })
744
+ ```
745
+
746
+ `sendVideoGridMessage` sends at least two video URL items as a `GenAIGridLayoutViewModel` containing `GenAIVideoPrimitive` entries. It is separate from `sendAlbumMessage`, which uses the vertical album layout.
747
+
748
+ ```ts
749
+ await sock.sendVideoGridMessage(jid, [
750
+ {
751
+ url: 'https://cdn.example.com/first.mp4',
752
+ title: 'First clip',
753
+ thumbnailUrl: 'https://cdn.example.com/first.jpg',
754
+ progressiveUrls: ['https://cdn.example.com/first-720p.mp4']
755
+ },
756
+ {
757
+ url: 'https://cdn.example.com/second.mp4',
758
+ title: 'Second clip',
759
+ thumbnailUrl: 'https://cdn.example.com/second.jpg'
760
+ }
761
+ ])
762
+ ```
763
+
764
+ Both layouts are experimental WhatsApp protocol structures. Client rendering can change, and callers should provide a plain-message fallback where reliability matters.
765
+
766
+
767
+ ### Bot planning responses
768
+
769
+ `sendBotPlanning` sends a bot-planning rich response with a summary, ordered step text, and optional planning/query-plan capabilities.
770
+
771
+ ```ts
772
+ await sock.sendBotPlanning(jid, {
773
+ text: 'Preparing the deployment',
774
+ steps: [
775
+ { title: 'Build', body: 'Compile the application' },
776
+ { title: 'Verify', body: 'Run the checks' },
777
+ 'Publish the release'
778
+ ],
779
+ queryPlan: true
780
+ })
781
+ ```
782
+
783
+ ### Newsletter invite metadata
784
+
785
+ `newsletterGetInviteInfo` accepts either a channel invite code or a WhatsApp channel invite URL and returns the resolved newsletter metadata, or `null` when WhatsApp does not return a matching record.
786
+
787
+ ```ts
788
+ const info = await sock.newsletterGetInviteInfo('https://whatsapp.com/channel/INVITE_CODE')
789
+ console.log(info?.id, info?.name, info?.invite)
790
+ ```
791
+
792
+ The lookup is an explicit network request and does not follow, join, or subscribe to the channel.
793
+
794
+
795
+ ### Carousel, compatibility aliases, and source search
796
+
797
+ `sendCarousel` normalizes card media, captions, footers, and buttons before sending a carousel message.
798
+
799
+ ```ts
800
+ await sock.sendCarousel(jid, [
801
+ {
802
+ title: 'Documentation',
803
+ body: { text: 'Read the API guide' },
804
+ media: { image: { url: 'https://example.com/docs.jpg' } },
805
+ buttons: [{ text: 'Open', id: 'docs' }]
806
+ }
807
+ ])
808
+ ```
809
+
810
+ `HoldOn` is retained as a compatibility alias for `sendRichButtonGrid`. New applications should prefer the descriptive Codex-named method.
811
+
812
+ `grepCode(pattern, options)` searches installed package source files and returns matching file, line, column, and source-line information. It is an explicit diagnostic helper and performs no network access.
813
+
814
+ ```ts
815
+ const matches = sock.grepCode('prepareVideoGridMessage', { includeReadme: true })
816
+ ```
817
+
666
818
  ### Interactive rich HTML
667
819
 
668
820
  The compatibility update also exposes `sendRichHtmlMessage`, which uses the interactive GenAI HTML primitive and supports embedded tabs. Only pass trusted, sanitized HTML; this is not a general-purpose HTML sanitizer.
@@ -677,3 +829,44 @@ await sock.sendRichHtmlMessage(jid, {
677
829
  ```
678
830
 
679
831
  Multiple tabs can be supplied with `tabs`; each tab may provide its own `html`, `url`, and `trustedSources`. The structure is experimental and depends on WhatsApp client support.
832
+
833
+
834
+ ## Codex unified responses and the built-in mini-app
835
+
836
+ `sendCodexMessage` accepts one object containing any supported combination of text, links, media, products, reels, tables, code, LaTeX, or suggestions. Unsupported optional sections are ignored by the rich-response serializer, so callers can use one stable entry point for heterogeneous response data.
837
+
838
+ ```js
839
+ await sock.sendCodexMessage(jid, {
840
+ text: 'Here is your result',
841
+ links: [{ text: 'Open the docs', url: 'https://example.com/docs' }],
842
+ table: {
843
+ title: 'Summary',
844
+ headers: ['Name', 'Status'],
845
+ rows: [['Codex', 'Ready']]
846
+ }
847
+ })
848
+ ```
849
+
850
+ `sendMiniApp` sends the bundled interactive mini-app as a trusted rich HTML screen. The helper does not fetch or execute caller-provided remote code; pass only presentation options such as `title` and keep authentication or business actions on your own server.
851
+
852
+ ```js
853
+ await sock.sendMiniApp(jid, {
854
+ title: 'Codex mini-app'
855
+ }, {
856
+ quoted: message
857
+ })
858
+ ```
859
+
860
+ The package also exports `BotCapabilityType`, `normalizeBotCapabilities`, `buildBotCapabilityMetadata`, and `buildBotRichResponse` for applications that need to construct compatible bot metadata.
861
+
862
+ <div align="center">
863
+
864
+ <sub>Thanks for reading my README.</sub>
865
+
866
+ <img src="https://i.imgur.com/dBaSKWF.gif" height="16" width="88%" alt="animated coloured footer line" />
867
+
868
+ <br><br>
869
+
870
+ <img src="https://capsule-render.vercel.app/api?type=waving&color=00FFF0&height=100&section=footer" alt="@codexverified/baileys footer" />
871
+
872
+ </div>
@@ -12,12 +12,14 @@ import { areJidsSameUser, getBinaryNodeChild, getBinaryNodeChildren, getBizBinar
12
12
  import { USyncQuery, USyncUser } from '../WAUSync/index.js';
13
13
  import { captureUnifiedResponse, generateButtonGridContent, generateCodeBlockContent, generateLatexContent, generateListContent, generateRichMenuContent, generateTableContent, generateUnifiedResponseContent } from '../Utils/message-composer.js';
14
14
  import { Baron } from './interactive-handler.js';
15
- import { prepareRichResponseMessage, prepareRichGenerationMessage, prepareRichTextMessage, prepareRichImageMessage, prepareRichLinkMessage, prepareHtmlMessage, prepareRichHtmlMessage } from '../Utils/rich-message-utils.js';
15
+ import { prepareRichResponseMessage, prepareRichGenerationMessage, prepareRichTextMessage, prepareRichImageMessage, prepareRichLinkMessage, prepareA2UICommandMenu, prepareAIRichCommandMenu, prepareVideoGridMessage, prepareVerticalAlbumMessage, prepareHtmlMessage, prepareRichHtmlMessage } from '../Utils/rich-message-utils.js';
16
16
  import { generateSlotMachineHtml } from '../Utils/games/slot-machine.js';
17
+ import { generateMiniAppHtml } from '../Utils/games/miniapp.js';
17
18
  import { generateWebsitePreviewHtml, generateLinkPreviewHtml } from '../Utils/website-preview.js';
18
19
  import { isViewOnceMessage, downloadViewOnceMedia, createRegularMessageFromViewOnce, parseViewOnceInfo } from '../Utils/view-once-utils.js';
19
20
  import { collectMetaAIBotParticipantJids, isMetaAIBotResponse, META_AI_BOT_JID } from './aigroups.js';
20
21
  import { makeUsernameSocket } from './username.js';
22
+ import { grepSource } from '../Utils/source-grep.js';
21
23
 
22
24
  const STATUS_JID = 'status@broadcast';
23
25
  export const normalizeStatusJidList = (value) => {
@@ -1242,11 +1244,38 @@ export const makeMessagesSocket = (config) => {
1242
1244
  if (!content.copyText) throw new Boom('sendQuoteCopy requires copyText content', { statusCode: 400 });
1243
1245
  return socket.sendRichTextCopy(jid, content, options);
1244
1246
  },
1247
+ /** Send a full-width stacked AIRich album with one large image/video per row. */
1245
1248
  sendAlbumMessage: async (jid, medias = [], options = {}) => {
1246
1249
  if (!jid) throw new Boom('sendAlbumMessage requires a target JID', { statusCode: 400 });
1247
1250
  if (!Array.isArray(medias) || medias.length < 2) throw new Boom('sendAlbumMessage requires at least two media items', { statusCode: 400 });
1248
- const album = medias.map((media, index) => { const normalized = media?.type && media?.data ? { [media.type]: media.data } : { ...media }; if (index === 0 && options.caption && !normalized.caption) normalized.caption = options.caption; return normalized; });
1249
- return socket.sendMessage(jid, { album }, options);
1251
+ const albumItems = medias.map((media) => {
1252
+ const raw = media?.type && media?.data ? media.data : (media?.image ?? media);
1253
+ if (media?.video || media?.type === 'video') {
1254
+ const video = media?.video || raw;
1255
+ const sourceUrl = typeof video === 'string' ? video.trim() : String(video?.url || video?.videoUrl || '').trim();
1256
+ if (!sourceUrl) throw new Boom('Each video album item requires a video URL', { statusCode: 400 });
1257
+ return { type: 'video', video: { ...video, url: sourceUrl } };
1258
+ }
1259
+ const image = typeof raw === 'string' ? raw.trim() : { ...raw, url: String(raw?.url || '').trim() };
1260
+ if (!image || (typeof image === 'object' && !image.url && !image.sourceUrl)) {
1261
+ throw new Boom('Each image album item requires an image URL', { statusCode: 400 });
1262
+ }
1263
+ return { type: 'image', image };
1264
+ });
1265
+ const wrapped = prepareVerticalAlbumMessage({ items: albumItems, verificationMetadata: options.verificationMetadata });
1266
+ const fullMsg = await generateWAMessageFromContent(jid, wrapped, {
1267
+ logger,
1268
+ userJid: authState.creds.me?.id,
1269
+ messageId: options.messageId || generateMessageIDV2(authState.creds.me?.id || jid),
1270
+ ...options
1271
+ });
1272
+ await relayMessage(jid, fullMsg.message, { messageId: fullMsg.key.id, ...options });
1273
+ if (config.emitOwnEvents) {
1274
+ process.nextTick(async () => {
1275
+ await messageMutex.mutex(() => upsertMessage(fullMsg, 'append'));
1276
+ });
1277
+ }
1278
+ return fullMsg;
1250
1279
  },
1251
1280
  sendBotDiagnostics: async (jid, content = {}, options = {}) => {
1252
1281
  if (!jid) throw new Boom('sendBotDiagnostics requires a target JID', { statusCode: 400 });
@@ -1258,6 +1287,29 @@ export const makeMessagesSocket = (config) => {
1258
1287
  const { text = 'Bot command test', commandName = '', commandDescription = '', commandPrompt = '', botJid = '867051314767696@bot' } = content;
1259
1288
  return socket.sendRichGeneration(jid, { text, botMetadata: { commandMetadata: { commandName, commandDescription, commandPrompt } }, botJid }, undefined, options);
1260
1289
  },
1290
+ /** Sends a planning/execution-plan rich response with WAProto capabilities. */
1291
+ sendBotPlanning: async (jid, content = {}, options = {}) => {
1292
+ if (!jid) throw new Boom('sendBotPlanning requires a target JID', { statusCode: 400 });
1293
+ const { text = 'Execution plan', steps = [], queryPlan = true, botJid = '867051314767696@bot' } = content;
1294
+ const submessages = [
1295
+ { messageType: proto.AIRichResponseSubMessageType.AI_RICH_RESPONSE_TEXT, messageText: text },
1296
+ ...steps.map(step => ({
1297
+ messageType: proto.AIRichResponseSubMessageType.AI_RICH_RESPONSE_TEXT,
1298
+ messageText: typeof step === 'string' ? step : `${step.title || step.name || 'Step'}${step.body ? `: ${step.body}` : ''}`
1299
+ }))
1300
+ ];
1301
+ const wrapped = buildBotRichResponse({
1302
+ botJid,
1303
+ submessages,
1304
+ capabilities: [
1305
+ BotCapabilityType.AGENTIC_PLANNING,
1306
+ ...(queryPlan ? [BotCapabilityType.QUERY_PLAN] : [])
1307
+ ]
1308
+ });
1309
+ const fullMsg = await generateWAMessageFromContent(jid, wrapped, { logger, userJid: authState.creds.me?.id, messageId: options.messageId || generateMessageIDV2(authState.creds.me?.id), ...options });
1310
+ await relayRichMessage(jid, fullMsg, options);
1311
+ return fullMsg;
1312
+ },
1261
1313
  sendBotToolResult: async (jid, content = {}, options = {}) => {
1262
1314
  if (!jid) throw new Boom('sendBotToolResult requires a target JID', { statusCode: 400 });
1263
1315
  const { text = 'Bot tool result test', toolCallId = 'tool-' + Date.now(), resolutionData, resolutionDataSerialized, botJid = '867051314767696@bot' } = content;
@@ -1270,6 +1322,48 @@ export const makeMessagesSocket = (config) => {
1270
1322
  await relayMessage(jid, fullMsg.message, { messageId: fullMsg.key.id, useCachedGroupMetadata: options.useCachedGroupMetadata, statusJidList: options.statusJidList });
1271
1323
  return fullMsg;
1272
1324
  },
1325
+ /** Send the large A2UI command menu with an uploaded image header, rows, and URL buttons. */
1326
+ /** Sends the manually serialized AIRich command card shown in the screenshot. */
1327
+ sendAIRichCommandMenu: async (jid, content = {}, options = {}) => {
1328
+ if (!jid) throw new Boom('sendAIRichCommandMenu requires a target JID', { statusCode: 400 });
1329
+ const wrapped = prepareAIRichCommandMenu(content);
1330
+ const fullMsg = await generateWAMessageFromContent(jid, wrapped, {
1331
+ logger,
1332
+ userJid: authState.creds.me?.id,
1333
+ messageId: options.messageId || generateMessageIDV2(authState.creds.me?.id || jid),
1334
+ ...options,
1335
+ });
1336
+ await relayRichMessage(jid, fullMsg, options);
1337
+ if (config.emitOwnEvents) {
1338
+ process.nextTick(async () => {
1339
+ await messageMutex.mutex(() => upsertMessage(fullMsg, 'append'));
1340
+ });
1341
+ }
1342
+ return fullMsg;
1343
+ },
1344
+ sendA2UICommandMenu: async (jid, content = {}, options = {}) => {
1345
+ if (!jid) throw new Boom('sendA2UICommandMenu requires a target JID', { statusCode: 400 });
1346
+ const { image, ...menuContent } = content || {};
1347
+ let imageMessage;
1348
+ if (image) {
1349
+ const prepared = await prepareWAMessageMedia({ image }, { upload: waUploadToServer });
1350
+ imageMessage = prepared.imageMessage;
1351
+ }
1352
+ const wrapped = prepareA2UICommandMenu({ ...menuContent, imageMessage });
1353
+ const fullMsg = await generateWAMessageFromContent(jid, wrapped, {
1354
+ logger,
1355
+ userJid: authState.creds.me?.id,
1356
+ messageId: options.messageId || generateMessageIDV2(authState.creds.me?.id || jid),
1357
+ ...options
1358
+ });
1359
+ await relayMessage(jid, fullMsg.message, { messageId: fullMsg.key.id, ...options.relayOptions });
1360
+ if (config.emitOwnEvents) {
1361
+ process.nextTick(async () => {
1362
+ await messageMutex.mutex(() => upsertMessage(fullMsg, 'append'));
1363
+ });
1364
+ }
1365
+ return fullMsg;
1366
+ },
1273
1367
  sendRichButtonGrid: async (jid, grid, options = {}) => {
1274
1368
  const message = generateButtonGridContent(grid, options.quoted, options);
1275
1369
  const fullMsg = await generateWAMessageFromContent(jid, message, { logger, userJid: authState.creds.me?.id, messageId: options.messageId || generateMessageIDV2(authState.creds.me?.id), ...options });
@@ -1289,6 +1383,8 @@ export const makeMessagesSocket = (config) => {
1289
1383
  const lines = [title, headers.length ? `| ${headers.map(String).join(' | ')} |` : '', headers.length ? `| ${headers.map(() => '---').join(' | ')} |` : '', ...rows.map(row => `| ${(Array.isArray(row) ? row : [row]).map(value => String(value ?? '')).join(' | ')} |`)].filter(Boolean);
1290
1384
  return sock.sendMessage(jid, { ...(image ? { image, caption: lines.join('\\n') } : { text: lines.join('\\n') }), buttons, ...(footer ? { footer } : {}) }, options);
1291
1385
  },
1386
+ /** Compatibility alias for the former Gen4/grid menu entry point. */
1387
+ HoldOn: async (jid, grid, options = {}) => socket.sendRichButtonGrid(jid, grid, options),
1292
1388
  sendWhatsAppFlow: async (jid, flow = {}, options = {}) => {
1293
1389
  const { text, footer, image, caption, flowId, flowToken, flowName = 'flow', flowAction = 'navigate', flowActionPayload = {} } = flow;
1294
1390
  if (!flowId || !flowToken) throw new Boom('sendWhatsAppFlow requires flowId and flowToken', { statusCode: 400 });
@@ -1313,6 +1409,38 @@ export const makeMessagesSocket = (config) => {
1313
1409
  await relayMessage(jid, message, { messageId });
1314
1410
  return { message, messageId };
1315
1411
  },
1412
+ sendCarousel: async (jid, cards, options = {}) => {
1413
+ if (!Array.isArray(cards) || cards.length === 0) {
1414
+ throw new Boom('sendCarousel requires a non-empty cards array', { statusCode: 400 });
1415
+ }
1416
+ const normalizedCards = cards.map(card => ({
1417
+ ...card,
1418
+ text: card.text ?? card.body?.text,
1419
+ caption: card.caption ?? card.body?.text,
1420
+ footer: card.footer?.text ?? card.footer,
1421
+ buttons: card.buttons ?? card.nativeFlowMessage?.buttons,
1422
+ ...(card.media?.image ? { image: card.media.image } : {}),
1423
+ ...(card.media?.video ? { video: card.media.video } : {}),
1424
+ ...(card.media?.document ? { document: card.media.document } : {})
1425
+ }));
1426
+ const { text, footer, ...messageOptions } = options;
1427
+ const fullMsg = await generateWAMessage(jid, {
1428
+ ...messageOptions,
1429
+ cards: normalizedCards,
1430
+ ...(text !== undefined ? { text } : {}),
1431
+ ...(footer !== undefined ? { footer } : {})
1432
+ }, {
1433
+ logger,
1434
+ userJid: authState.creds.me?.id,
1435
+ upload: waUploadToServer,
1436
+ mediaCache: config.mediaCache,
1437
+ options: config.options,
1438
+ ...options,
1439
+ messageId: options.messageId || generateMessageIDV2(jid)
1440
+ });
1441
+ await relayMessage(jid, fullMsg.message, { messageId: fullMsg.key.id, ...options });
1442
+ return fullMsg;
1443
+ },
1316
1444
  sendLatex: async (jid, quoted, options) => {
1317
1445
  const { message, messageId } = generateLatexContent(quoted, options);
1318
1446
  await relayMessage(jid, message, { messageId });
@@ -1519,6 +1647,31 @@ export const makeMessagesSocket = (config) => {
1519
1647
  await relayMessage(jid, message, { messageId });
1520
1648
  return { message, messageId };
1521
1649
  },
1650
+ /** Send one tolerant AIRich response containing every supported section supplied in content. */
1651
+ sendCodexMessage: async (jid, content = {}, quoted, options = {}) => {
1652
+ if (!jid) throw new Boom('sendCodexMessage requires a target JID', { statusCode: 400 });
1653
+ if (!content || typeof content !== 'object' || Array.isArray(content)) {
1654
+ throw new Boom('sendCodexMessage content must be an object', { statusCode: 400 });
1655
+ }
1656
+ const message = prepareRichResponseMessage({
1657
+ ...content,
1658
+ noDonation: options.noDonation ?? true,
1659
+ });
1660
+ const fullMsg = generateWAMessageFromContent(jid, message, {
1661
+ logger,
1662
+ userJid: authState.creds.me?.id,
1663
+ messageId: options.messageId || generateMessageIDV2(authState.creds.me?.id || jid),
1664
+ quoted,
1665
+ ...options,
1666
+ });
1667
+ await relayMessage(jid, fullMsg, options);
1668
+ if (config.emitOwnEvents) {
1669
+ process.nextTick(async () => {
1670
+ await messageMutex.mutex(() => upsertMessage(fullMsg, 'append'));
1671
+ });
1672
+ }
1673
+ return fullMsg;
1674
+ },
1522
1675
  captureAndResendUnifiedResponse: async (jid, metaAiMsg, quoted) => {
1523
1676
  const captured = captureUnifiedResponse(metaAiMsg);
1524
1677
  if (!captured) {
@@ -2259,6 +2412,35 @@ export const makeMessagesSocket = (config) => {
2259
2412
  return { message, messageId };
2260
2413
  };
2261
2414
 
2415
+ /**
2416
+ * Sends a unified-response video grid. This uses GenAIVideoPrimitive items
2417
+ * inside GenAIGridLayoutViewModel; it is not the image-only GRID_IMAGE type.
2418
+ */
2419
+ socket.sendVideoGridMessage = async (jid, videos = [], options = {}) => {
2420
+ if (!jid) throw new Boom('sendVideoGridMessage requires a target JID', { statusCode: 400 });
2421
+ if (!Array.isArray(videos) || videos.length < 2) {
2422
+ throw new Boom('sendVideoGridMessage requires at least two videos', { statusCode: 400 });
2423
+ }
2424
+ const wrapped = prepareVideoGridMessage({
2425
+ videos,
2426
+ verificationMetadata: options.verificationMetadata
2427
+ });
2428
+ const fullMsg = generateWAMessageFromContent(jid, wrapped, {
2429
+ logger,
2430
+ userJid: authState.creds.me?.id,
2431
+ messageId: options.messageId || generateMessageIDV2(authState.creds.me?.id || jid),
2432
+ ...options
2433
+ });
2434
+ await relayRichMessage(jid, fullMsg, options);
2435
+ if (config.emitOwnEvents) {
2436
+ process.nextTick(async () => {
2437
+ await messageMutex.mutex(() => upsertMessage(fullMsg, 'append'));
2438
+ });
2439
+ }
2440
+ return fullMsg;
2441
+ };
2442
+
2443
+
2262
2444
  socket.sendHtmlMessage = async (jid, content = {}, options = {}) => {
2263
2445
  if (!jid) throw new Boom('sendHtmlMessage requires a target JID', { statusCode: 400 });
2264
2446
  if (!content.html) throw new Boom('sendHtmlMessage requires html content', { statusCode: 400 });
@@ -2276,10 +2458,23 @@ export const makeMessagesSocket = (config) => {
2276
2458
  await relayMessage(jid, fullMsg.message, { messageId: fullMsg.key.id, useCachedGroupMetadata: options.useCachedGroupMetadata, statusJidList: options.statusJidList });
2277
2459
  return fullMsg;
2278
2460
  };
2461
+ /** Find a word or expression in the installed package source. */
2462
+ socket.grepCode = (pattern, options = {}) => grepSource(pattern, options);
2463
+
2279
2464
  socket.sendSlotMachine = async (jid, options = {}, sendOptions = {}) => {
2280
2465
  return socket.sendHtmlMessage(jid, { html: generateSlotMachineHtml(options) }, sendOptions);
2281
2466
  };
2282
2467
 
2468
+ /** Send the built-in interactive Codex mini-app through the rich HTML envelope. */
2469
+ socket.sendMiniApp = async (jid, options = {}, sendOptions = {}) => {
2470
+ if (!jid) throw new Boom('sendMiniApp requires a target JID', { statusCode: 400 });
2471
+ return socket.sendRichHtmlMessage(jid, {
2472
+ html: generateMiniAppHtml(options),
2473
+ title: options.title || 'mini-app',
2474
+ url: 'https://codex-ai.site',
2475
+ trustedSources: ['codex-ai.site']
2476
+ }, sendOptions);
2477
+ };
2283
2478
  socket.sendWebsitePreview = async (jid, url, options = {}, sendOptions = {}) => {
2284
2479
  if (!url) throw new Boom('sendWebsitePreview requires a URL', { statusCode: 400 });
2285
2480
  return socket.sendHtmlMessage(jid, { html: await generateWebsitePreviewHtml(url, options) }, sendOptions);
@@ -1,3 +1,4 @@
1
+ import { Boom } from '@hapi/boom';
1
2
  import { proto } from '../../WAProto/index.js';
2
3
  import { QueryIds, XWAPaths } from '../Types/index.js';
3
4
  import { generateProfilePicture } from '../Utils/messages-media.js';
@@ -34,6 +35,15 @@ const parseNewsletterMetadata = (result) => {
34
35
  }
35
36
  return null;
36
37
  };
38
+ const normalizeNewsletterInviteCode = (value) => {
39
+ if (typeof value !== 'string' || !value.trim()) {
40
+ throw new Boom('newsletterGetInviteInfo requires a non-empty invite code or URL', { statusCode: 400 });
41
+ }
42
+ return value.trim()
43
+ .replace(/^https?:\/\/(?:www\.)?whatsapp\.com\/channel\//i, '')
44
+ .replace(/^https?:\/\/wa\.me\/channel\//i, '')
45
+ .split(/[/?#]/, 1)[0];
46
+ };
37
47
  export const makeNewsletterSocket = (config) => {
38
48
  const sock = makeAIGroupsSocket(config);
39
49
  const { query, generateMessageTag } = sock;
@@ -84,6 +94,14 @@ export const makeNewsletterSocket = (config) => {
84
94
  const result = await executeWMexQuery(variables, QueryIds.METADATA, XWAPaths.xwa2_newsletter_metadata);
85
95
  return parseNewsletterMetadata(result);
86
96
  },
97
+ newsletterGetInviteInfo: async (inviteCodeOrUrl) => {
98
+ const inviteCode = normalizeNewsletterInviteCode(inviteCodeOrUrl);
99
+ return executeWMexQuery(
100
+ { input: { key: inviteCode, type: 'INVITE' }, fetch_creation_time: true, fetch_full_image: true, fetch_viewer_metadata: true },
101
+ QueryIds.METADATA,
102
+ XWAPaths.xwa2_newsletter_metadata
103
+ ).then(parseNewsletterMetadata);
104
+ },
87
105
  newsletterFollow: (jid) => {
88
106
  return executeWMexQuery({ newsletter_id: jid }, QueryIds.FOLLOW, XWAPaths.xwa2_newsletter_join_v2);
89
107
  },
@@ -224,3 +242,4 @@ export const makeNewsletterSocket = (config) => {
224
242
  }
225
243
  };
226
244
  };
245
+
@@ -0,0 +1,84 @@
1
+ import { proto } from '../../WAProto/index.js';
2
+
3
+ /**
4
+ * Numeric capability values exposed by WAProto.BotCapabilityMetadata.
5
+ * Keep this derived from the generated protobuf so callers cannot drift from
6
+ * the checked-in schema.
7
+ */
8
+ export const BotCapabilityType = proto.BotCapabilityMetadata.BotCapabilityType;
9
+
10
+ const capabilityNames = new Set(
11
+ Object.keys(BotCapabilityType).filter(name => Number.isNaN(Number(name)))
12
+ );
13
+
14
+ /** Normalize enum names/numbers and reject unknown capability values. */
15
+ export const normalizeBotCapabilities = (capabilities = []) => {
16
+ if (!Array.isArray(capabilities)) {
17
+ throw new TypeError('capabilities must be an array');
18
+ }
19
+ const values = capabilities.map(capability => {
20
+ if (typeof capability === 'string') {
21
+ if (!capabilityNames.has(capability)) {
22
+ throw new TypeError(`Unknown bot capability: ${capability}`);
23
+ }
24
+ return BotCapabilityType[capability];
25
+ }
26
+ if (Number.isInteger(capability) && BotCapabilityType[capability] !== undefined) {
27
+ return capability;
28
+ }
29
+ throw new TypeError(`Invalid bot capability: ${String(capability)}`);
30
+ });
31
+ return [...new Set(values)];
32
+ };
33
+
34
+ export const buildBotCapabilityMetadata = (capabilities = []) => ({
35
+ capabilities: normalizeBotCapabilities(capabilities)
36
+ });
37
+
38
+ /**
39
+ * Build the canonical wrapper used by WhatsApp's bot rich-response messages.
40
+ * The capability list is deliberately explicit; unknown or duplicate values
41
+ * are never put on the wire.
42
+ */
43
+ export const buildBotRichResponse = ({
44
+ text,
45
+ submessages,
46
+ capabilities = [],
47
+ botMetadata = {},
48
+ botJid = '867051314767696@bot',
49
+ richResponse = {}
50
+ } = {}) => {
51
+ const resolvedSubmessages = submessages || (text == null ? [] : [{
52
+ messageType: proto.AIRichResponseSubMessageType.AI_RICH_RESPONSE_TEXT,
53
+ messageText: String(text)
54
+ }]);
55
+ return {
56
+ messageContextInfo: {
57
+ botMetadata: {
58
+ ...botMetadata,
59
+ capabilityMetadata: buildBotCapabilityMetadata(capabilities)
60
+ }
61
+ },
62
+ botForwardedMessage: {
63
+ message: {
64
+ richResponseMessage: {
65
+ messageType: proto.AIRichResponseMessageType.AI_RICH_RESPONSE_TYPE_STANDARD,
66
+ submessages: resolvedSubmessages,
67
+ ...richResponse,
68
+ contextInfo: {
69
+ isForwarded: true,
70
+ forwardingScore: 1,
71
+ forwardedAiBotMessageInfo: { botJid },
72
+ forwardOrigin: 4,
73
+ ...(richResponse.contextInfo || {})
74
+ }
75
+ }
76
+ }
77
+ }
78
+ };
79
+ };
80
+
81
+ export const capability = (name) => {
82
+ if (!capabilityNames.has(name)) throw new TypeError(`Unknown bot capability: ${name}`);
83
+ return BotCapabilityType[name];
84
+ };
@@ -0,0 +1,5 @@
1
+ /** Generate the attached interactive mini-app HTML game. */
2
+ export const generateMiniAppHtml = (options = {}) => {
3
+ const html = "<style>*{-webkit-tap-highlight-color:transparent;-webkit-user-select:none;user-select:none;-webkit-touch-callout:none}</style>\n<body style=\"margin:0;background:transparent;font-family:Arial,sans-serif;color:#eee;touch-action:manipulation;cursor:pointer\">\n<div style=\"width:100%;max-width:620px;margin:auto;padding:16px;box-sizing:border-box\">\n<div style=\"background:rgba(255,255,255,.06);backdrop-filter:blur(14px);-webkit-backdrop-filter:blur(14px);border:1px solid rgba(255,255,255,.15);border-radius:16px;overflow:hidden;box-shadow:0 8px 32px rgba(0,0,0,.35)\">\n<div style=\"padding:18px 20px;border-bottom:1px solid rgba(255,255,255,.12);display:flex;justify-content:space-between;align-items:center\">\n<div><div style=\"font-size:11px;letter-spacing:1.5px;color:rgba(255,255,255,.45)\">𝑶𝒓𝒊𝒈𝒊𝒏✦</div><div style=\"font-size:21px;font-weight:bold;color:#fff\">mini-app</div></div>\n<div style=\"text-align:right\"><div id=\"score\" style=\"font-size:18px;font-weight:bold;color:#fff;text-shadow:0 0 10px rgba(108,92,231,.85);transition:transform .15s\">00000</div><div id=\"best\" style=\"font-size:10px;color:rgba(255,255,255,.4);margin-top:2px\">BEST 00000</div></div>\n</div>\n<div style=\"padding:18px\">\n<canvas id=\"game\" width=\"560\" height=\"190\" style=\"width:100%;height:auto;background:rgba(255,255,255,.03);border:1px solid rgba(255,255,255,.12);border-radius:12px;display:block\"></canvas>\n<div id=\"status\" style=\"text-align:center;margin-top:10px;font-size:12px;color:rgba(255,255,255,.55)\">Velocità 5.0x</div>\n</div></div></div>\n<script>\nconst c=document.getElementById('game'),x=c.getContext('2d'),scoreEl=document.getElementById('score'),bestEl=document.getElementById('best'),statusEl=document.getElementById('status');\nconst GY=170;\nlet d,o,clouds,particles,ambient,trail,score,best=0,speed,gameOver,last,shake,flash,runT,spawnTimer,milestone,squash;\nfunction loadBest(){\nlet vals=[];\ntry{let v=localStorage.getItem('dino_best');if(v)vals.push(parseInt(v,10))}catch(e){}\ntry{let v=sessionStorage.getItem('dino_best');if(v)vals.push(parseInt(v,10))}catch(e){}\ntry{let m=document.cookie.match(/(?:^|;\\s*)dino_best=(\\d+)/);if(m)vals.push(parseInt(m[1],10))}catch(e){}\nreturn vals.length?Math.max(...vals.filter(v=>!isNaN(v))):0\n}\nfunction saveBest(v){\nlet val=String(Math.floor(v));\ntry{localStorage.setItem('dino_best',val)}catch(e){}\ntry{sessionStorage.setItem('dino_best',val)}catch(e){}\ntry{document.cookie='dino_best='+val+';max-age=31536000;path=/'}catch(e){}\ntry{\nlet rq=indexedDB.open('dino_db',1);\nrq.onupgradeneeded=()=>{rq.result.createObjectStore('kv')};\nrq.onsuccess=()=>{try{rq.result.transaction('kv','readwrite').objectStore('kv').put(val,'dino_best')}catch(e){}}\n}catch(e){}\n}\nfunction loadBestAsync(cb){\ntry{\nlet rq=indexedDB.open('dino_db',1);\nrq.onupgradeneeded=()=>{rq.result.createObjectStore('kv')};\nrq.onsuccess=()=>{\ntry{\nlet gr=rq.result.transaction('kv','readonly').objectStore('kv').get('dino_best');\ngr.onsuccess=()=>{if(gr.result)cb(parseInt(gr.result,10))}\n}catch(e){}\n}\n}catch(e){}\n}\nbest=loadBest();\nloadBestAsync(v=>{if(!isNaN(v)&&v>best){best=v;bestEl.textContent='BEST '+String(Math.floor(best)).padStart(5,'0')}});\nfunction reset(){\nd={x:55,y:132,w:27,h:30,vy:0,jumping:false};\no=[];\nclouds=[{x:120,y:32,w:44,s:.35},{x:300,y:52,w:60,s:.22},{x:460,y:26,w:36,s:.4},{x:560,y:70,w:50,s:.18}];\nparticles=[];\ntrail=[];\nif(!ambient){ambient=[];for(let i=0;i<18;i++)ambient.push({x:Math.random()*c.width,y:Math.random()*c.height,r:.5+Math.random()*1.5,vx:.1+Math.random()*.3,ph:Math.random()*10})}\nscore=0;speed=5;gameOver=false;last=0;shake=0;flash=0;runT=0;milestone=0;squash=1;\nspawnTimer=70+Math.random()*30;\nbestEl.textContent='BEST '+String(Math.floor(best)).padStart(5,'0');\nstatusEl.textContent='Speed 5.0x'\n}\nfunction burst(px,py,n,col,spd){for(let i=0;i<n;i++)particles.push({x:px,y:py,vx:(Math.random()-.5)*spd,vy:-Math.random()*spd,life:1,col,size:2+Math.random()*2})}\nfunction jumpDino(){\nif(gameOver){reset();return}\nif(!d.jumping){d.jumping=true;d.vy=-13;squash=.7;burst(d.x+13,d.y+30,10,'255,255,255',4)}\n}\nfunction cactus(){\nlet h=24+Math.random()*24;\no.push({x:c.width+20,y:GY-h,w:16+Math.random()*6,h});\nif(Math.random()<.22){o.push({x:c.width+20+34+Math.random()*10,y:GY-(20+Math.random()*18),w:16,h:20+Math.random()*18})}\n}\nfunction hit(a,b){return a.x+4<b.x+b.w&&a.x+a.w-4>b.x&&a.y+4<b.y+b.h&&a.y+a.h>b.y}\nfunction drawTrail(){\ntrail.forEach((p,i)=>{x.fillStyle='rgba(108,92,231,'+(.25*(i/trail.length))+')';x.fillRect(p.x,p.y,27,30)})\n}\nfunction drawDino(){\nx.save();\nlet cx=d.x+13,cy=d.y+30;\nx.translate(cx,cy);\nx.scale(1/squash,squash);\nx.translate(-cx,-cy);\nlet legOff=d.jumping?0:Math.sin(runT*.5)*5;\nx.fillStyle='#eaeaea';\nx.fillRect(d.x,d.y,27,30);\nx.fillRect(d.x+22,d.y+5,13,18);\nx.fillStyle='#6c5ce7';\nx.fillRect(d.x+29,d.y+8,4,4);\nx.fillStyle='#eaeaea';\nx.fillRect(d.x+5,d.y+30,6,8+legOff);\nx.fillRect(d.x+20,d.y+30,6,8-legOff);\nx.restore()\n}\nfunction drawCactus(q){\nx.save();\nx.shadowColor='rgba(255,90,90,.35)';x.shadowBlur=10;\nx.fillStyle='#e17a7a';\nx.fillRect(q.x,q.y,q.w,q.h);\nx.fillRect(q.x-7,q.y+10,7,6);\nx.fillRect(q.x-7,q.y+4,6,12);\nx.fillRect(q.x+q.w,q.y+18,7,6);\nx.fillRect(q.x+q.w+1,q.y+12,6,12);\nx.restore()\n}\nfunction drawParticles(){\nparticles.forEach(p=>{x.fillStyle='rgba('+p.col+','+Math.max(p.life,0)+')';x.fillRect(p.x,p.y,p.size,p.size)})\n}\nfunction drawAmbient(){\nambient.forEach(p=>{let a=.15+Math.sin(runT*.05+p.ph)*.1;x.fillStyle='rgba(180,160,255,'+a+')';x.beginPath();x.arc(p.x,p.y,p.r,0,7);x.fill()})\n}\nfunction draw(){\nx.clearRect(0,0,c.width,c.height);\nx.save();\nif(shake>0)x.translate((Math.random()-.5)*shake,(Math.random()-.5)*shake);\ndrawAmbient();\nx.fillStyle='rgba(255,255,255,.35)';\nclouds.forEach(q=>{let b=Math.sin(runT*.03+q.x)*2;x.fillRect(q.x,q.y+b,q.w,5);x.fillRect(q.x+10,q.y+b-5,q.w*.45,10)});\nx.strokeStyle='rgba(255,255,255,.25)';\nx.lineWidth=2;\nx.setLineDash([10,8]);\nx.lineDashOffset=-runT*speed*.6;\nx.beginPath();x.moveTo(0,GY);x.lineTo(c.width,GY);x.stroke();\nx.setLineDash([]);\ndrawTrail();\ndrawDino();\no.forEach(drawCactus);\ndrawParticles();\nif(flash>0){x.fillStyle='rgba(255,60,60,'+(flash*.35)+')';x.fillRect(0,0,c.width,c.height)}\nx.restore();\nif(gameOver){\nx.fillStyle='rgba(15,15,25,.55)';x.fillRect(0,0,c.width,c.height);\nx.fillStyle='#fff';x.textAlign='center';\nx.font='bold 24px Arial';x.fillText('GAME OVER',c.width/2,85);\nx.font='14px Arial';x.fillText('Tocca per giocare di nuovo',c.width/2,112);\nx.textAlign='left'\n}\n}\nfunction loop(t){\nif(!last)last=t;\nlet dt=Math.min((t-last)/16.67,2);\nlast=t;\nrunT+=dt;\nif(!gameOver){\nd.y+=d.vy*dt;d.vy+=.75*dt;\nif(d.y>=132){\nif(d.jumping){burst(d.x+13,GY,10,'255,255,255',3.5);squash=1.35}\nd.y=132;d.vy=0;d.jumping=false\n}\nif(d.jumping)trail.push({x:d.x,y:d.y});\nif(trail.length>6)trail.shift();\nif(!d.jumping)trail.length=0;\nsquash+=(1-squash)*.18*dt;\nif(!d.jumping&&Math.floor(runT)%8===0&&Math.random()<.4)burst(d.x+6,GY-2,1,'255,255,255',1.5);\nambient.forEach(p=>{p.x-=p.vx*dt;if(p.x<-4)p.x=c.width+4});\nspawnTimer-=dt;\nif(spawnTimer<=0){cactus();spawnTimer=Math.max(38,62-speed*1.4)+Math.random()*30}\no.forEach(q=>q.x-=speed*dt);\no=o.filter(q=>q.x>-40);\nclouds.forEach(q=>{q.x-=q.s*dt;if(q.x<-80)q.x=c.width+Math.random()*100});\nparticles.forEach(p=>{p.x+=p.vx*dt;p.y+=p.vy*dt;p.vy+=.3*dt;p.life-=.03*dt});\nparticles=particles.filter(p=>p.life>0);\nspeed=Math.min(11,speed+.0018*dt);\nscore+=dt*.6;\nif(score>best)best=score;\nif(Math.floor(score/500)>milestone){\nmilestone=Math.floor(score/500);\nscoreEl.style.transform='scale(1.35)';\nsetTimeout(()=>scoreEl.style.transform='scale(1)',150)\n}\nscoreEl.textContent=String(Math.floor(score)).padStart(5,'0');\nbestEl.textContent='BEST '+String(Math.floor(best)).padStart(5,'0');\nstatusEl.textContent='Speed '+speed.toFixed(1)+'x';\nfor(const q of o)if(hit(d,q)){\ngameOver=true;shake=14;flash=1;\nsaveBest(best);\nburst(d.x+13,d.y+15,18,'255,90,90',5)\n}\n}\nif(shake>0)shake=Math.max(0,shake-.6*dt);\nif(flash>0)flash=Math.max(0,flash-.05*dt);\ndraw();\nrequestAnimationFrame(loop)\n}\ndocument.addEventListener('pointerdown',e=>{e.preventDefault();jumpDino()});\ndocument.addEventListener('keydown',e=>{if(e.code==='Space'){e.preventDefault();jumpDino()}});\nreset();\nrequestAnimationFrame(loop);\n</script></body>";
4
+ return options.title ? html.replace('mini-app', String(options.title)) : html;
5
+ };
@@ -6,6 +6,7 @@ export * from './messages-media.js';
6
6
  export * from './rich-message-utils.js';
7
7
  export * from './meta-compositing.js';
8
8
  export * from './bot-planning-replay.js';
9
+ export * from './bot-capabilities.js';
9
10
  export * from './welcome-flow.js';
10
11
  export * from './message-composer.js';
11
12
  export * from './consumer-application.js';
@@ -38,3 +39,6 @@ export * from './native-flow.js';
38
39
  export * from './view-once-utils.js';
39
40
  export * from './website-preview.js';
40
41
  export * from './portable-compat.js';
42
+ export * from './games/miniapp.js';
43
+ export { prepareVerticalAlbumMessage } from './rich-message-utils.js';
44
+ export { prepareAIRichCommandMenu, prepareVideoGridMessage } from './rich-message-utils.js';
@@ -152,6 +152,64 @@ export const buildAdditionalBotMetadataContext = (submessages) => {
152
152
  }
153
153
  return { sources, mediaDetailsMetadataList };
154
154
  }
155
+ export const prepareVideoGridMessage = ({ videos = [], verificationMetadata } = {}) => {
156
+ if (!Array.isArray(videos) || videos.length < 2) {
157
+ throw new Error('Video grid requires at least two videos');
158
+ }
159
+ const primitives = videos.map((video) => {
160
+ const url = typeof video === 'string' ? video : (video?.url || video?.videoUrl || video?.reelsUrl || '');
161
+ if (!url) throw new Error('Each video-grid item requires a video URL');
162
+ const item = typeof video === 'string' ? {} : video;
163
+ return {
164
+ post_id: item.postId == null ? undefined : String(item.postId),
165
+ reels_url: url,
166
+ reels_title: item.title,
167
+ thumbnail_url: item.thumbnailUrl || item.thumbnail,
168
+ creator: item.creator,
169
+ avatar_url: item.avatarUrl,
170
+ content_hash: item.contentHash,
171
+ likes_count: item.likes,
172
+ comments_count: item.comments,
173
+ shares_count: item.shares,
174
+ is_verified: item.isVerified,
175
+ source_app: item.sourceApp,
176
+ video_delivery_response: {
177
+ progressive_urls: (item.progressiveUrls || []).map((value) => ({
178
+ progressive_url: value,
179
+ __typename: 'GenAIProgressiveUrlResponse'
180
+ })),
181
+ dash_manifests: (item.dashManifests || []).map((value) => ({
182
+ manifest_xml: value,
183
+ __typename: 'GenAIDashManifestResponse'
184
+ })),
185
+ __typename: 'GenAIVideoDeliveryResponse'
186
+ },
187
+ __typename: 'GenAIVideoPrimitive'
188
+ };
189
+ });
190
+ const unified = {
191
+ response_id: randomUUID(),
192
+ sections: [{
193
+ view_model: {
194
+ primitives,
195
+ __typename: 'GenAIGridLayoutViewModel'
196
+ }
197
+ }]
198
+ };
199
+ const richResponseMessage = proto.AIRichResponseMessage.create({
200
+ submessages: [],
201
+ messageType: proto.AIRichResponseMessageType.AI_RICH_RESPONSE_TYPE_STANDARD,
202
+ unifiedResponse: { data: Buffer.from(JSON.stringify(unified)) },
203
+ contextInfo: {
204
+ isForwarded: true,
205
+ forwardingScore: 1,
206
+ forwardedAiBotMessageInfo: { botJid: '867051314767696@bot' },
207
+ forwardOrigin: 4
208
+ }
209
+ });
210
+ return wrapToBotForwardedMessage(richResponseMessage, verificationMetadata);
211
+ };
212
+
155
213
  export const prepareRichResponseMessage = (content) => {
156
214
  const { alignment, code, contentText, disclaimerText, footerText, headerText, imageText, inlineImage, inlineVideo, items, language, latex, links, noDonation, noHeading, posts, products, suggested, richResponse, table, tapLinkUrl, title } = content;
157
215
  let submessages = [];
@@ -444,6 +502,78 @@ export const wrapToBotForwardedMessage = (richResponseMessage) => ({
444
502
  * @param {{text?: string, label?: string, alignment?: 'END'|'START'|'CENTER'}} options
445
503
  * @returns {{messageContextInfo: object, botForwardedMessage: object}}
446
504
  */
505
+ /** Build the A2UI interactive command menu used by the full-size card layout. */
506
+ /** Build the manually serialized AIRich command menu shown in the screenshot. */
507
+ export const prepareAIRichCommandMenu = ({ image, title = 'CODY AI', subtitle = '', code = '', rows = [], buttons = [], footer = 'Developed by - CODY AI', latexImage, latexExpression = '' } = {}) => {
508
+ if (!image) throw new Error('AIRich command menu requires an image URL');
509
+ if (!Array.isArray(rows)) throw new Error('AIRich command-menu rows must be an array');
510
+ if (!Array.isArray(buttons)) throw new Error('AIRich command-menu buttons must be an array');
511
+ const safeRows = rows.map((row, index) => ({ command: String(row?.[0] ?? row?.command ?? `command_${index + 1}`), description: String(row?.[1] ?? row?.description ?? '') }));
512
+ const table = ['Command | Function', '--- | ---', ...safeRows.map((row) => `${row.command} | ${row.description}`)].join('\n');
513
+ const inlineEntities = latexImage ? [{ key: 'NGA', metadata: { latex_expression: String(latexExpression), latex_image: { url: String(latexImage), width: 400, height: 400 }, font_height: 83.333, padding: 15, __typename: 'GenAILatexItem' } }] : [];
514
+ const text = [String(title), subtitle && String(subtitle), code && `Code: ${String(code)}`, '## Command List', table, footer && String(footer)].filter(Boolean).join('\n');
515
+ const imagePrimitive = { __typename: 'GenAIImagePrimitive', preview_image: { __typename: 'GenAIMediaItem', mime_type: 'image/png', url: String(image) }, full_image: { __typename: 'GenAIMediaItem', mime_type: 'image/png', url: String(image) } };
516
+ const ctas = buttons.map((button, index) => ({ label: String(button?.text || button?.label || `Button ${index + 1}`), state: 'PENDING', kind: 'OTHER', tool_call_id: String(button?.id || `airich_menu_${index + 1}`), toast: { label: String(button?.toast || ''), __typename: 'GenAIExtWidgetToast' }, __typename: 'GenAI3PExtWidgetCTA' }));
517
+ const footerPrimitives = buttons.map((button, index) => ({ cta_text: String(button?.text || button?.label || `Button ${index + 1}`), cta_type: 'OPEN_URL', cta_url: String(button?.url || ''), __typename: 'GenAIFooterActionPrimitive' }));
518
+ footerPrimitives.push({ cta_text: '☷ OPEN MENU', cta_type: 'OPEN_URL', cta_url: String(buttons[0]?.url || ''), __typename: 'GenAIFooterActionPrimitive' });
519
+ const sections = [
520
+ { __typename: 'GenAIUnifiedResponseSection', view_model: { __typename: 'GenAISingleLayoutViewModel', primitive: imagePrimitive } },
521
+ ...(ctas.length ? [{ __typename: 'GenAIUnifiedResponseSection', view_model: { primitives: [{ __typename: 'GenAI3PExtWidgetPrimitive', header: { __typename: 'GenAI3PExtWidgetStandardHeader', title: 'Command List' }, body: { __typename: 'GenAIExtCalendarEventList', ctas, sections: [] } }], __typename: 'GenAIActionRowLayoutViewModel' } }] : []),
522
+ { __typename: 'GenAIUnifiedResponseSection', view_model: { primitive: { text: `{{NGA}}${text}{{/NGA}}`, inline_entities: inlineEntities, __typename: 'GenAIMarkdownTextUXPrimitive' }, __typename: 'GenAISingleLayoutViewModel' } },
523
+ { __typename: 'GenAIUnifiedResponseSection', view_model: { primitives: footerPrimitives, __typename: 'GenAIActionRowLayoutViewModel' } }
524
+ ];
525
+ const richResponseMessage = proto.AIRichResponseMessage.create({
526
+ submessages: [{ messageType: RichSubMessageType.TEXT, messageText: text }],
527
+ messageType: proto.AIRichResponseMessageType.AI_RICH_RESPONSE_TYPE_STANDARD,
528
+ unifiedResponse: { data: Buffer.from(JSON.stringify({ response_id: randomUUID(), sections })) },
529
+ contextInfo: { isForwarded: true, forwardingScore: 1, forwardedAiBotMessageInfo: { botJid: '867051314767696@bot' }, forwardOrigin: 4 }
530
+ });
531
+ return wrapToBotForwardedMessage(richResponseMessage);
532
+ };
533
+
534
+ export const prepareA2UICommandMenu = ({ imageMessage, title = 'CODY AI', rows = [], buttons = [], fallback = '' } = {}) => {
535
+ if (!Array.isArray(rows)) throw new Error('A2UI command-menu rows must be an array');
536
+ if (!Array.isArray(buttons)) throw new Error('A2UI command-menu buttons must be an array');
537
+ const safeRows = rows.map((row, index) => ({
538
+ title: String(row?.[0] ?? row?.title ?? `Command ${index + 1}`),
539
+ description: String(row?.[1] ?? row?.description ?? ''),
540
+ id: String(row?.[2] ?? row?.id ?? row?.[0] ?? `command_${index + 1}`)
541
+ }));
542
+ const safeButtons = buttons.map((button, index) => ({ id: `a2ui_button_${index + 1}`, label: String(button?.text || button?.label || `Button ${index + 1}`), url: String(button?.url || '') }));
543
+ const componentIds = ['title', 'divider', 'tableCard', 'footer', ...(safeButtons.length ? ['buttonRow'] : [])];
544
+ const components = [
545
+ { id: 'root', component: 'Column', align: 'center', children: componentIds },
546
+ { id: 'title', component: 'Text', text: String(title), variant: 'h2' },
547
+ { id: 'divider', component: 'Divider' },
548
+ { id: 'tableCard', component: 'Card', child: 'tableColumn' },
549
+ { id: 'tableColumn', component: 'Column', children: ['headerRow', ...safeRows.flatMap((_, index) => [`divider_${index}`, `row_${index}`])] },
550
+ { id: 'headerRow', component: 'Row', children: ['headerCommand', 'headerFunction'] },
551
+ { id: 'headerCommand', component: 'Text', text: 'Command', variant: 'h5' },
552
+ { id: 'headerFunction', component: 'Text', text: 'Function', variant: 'h5' },
553
+ ...safeRows.flatMap((row, index) => [
554
+ { id: `divider_${index}`, component: 'Divider' },
555
+ { id: `row_${index}`, component: 'Row', children: [`command_${index}`, `description_${index}`] },
556
+ { id: `command_${index}`, component: 'Text', text: row.title },
557
+ { id: `description_${index}`, component: 'Text', text: row.description }
558
+ ]),
559
+ { id: 'footer', component: 'Text', text: 'Developed by - CODY AI', variant: 'caption' }
560
+ ];
561
+ if (safeButtons.length) {
562
+ components.push({ id: 'buttonRow', component: 'Row', justify: 'spaceEvenly', children: safeButtons.flatMap((button, index) => [`buttonLabel_${index}`, `button_${index}`]) });
563
+ safeButtons.forEach((button, index) => {
564
+ components.push({ id: `buttonLabel_${index}`, component: 'Text', text: button.label });
565
+ components.push({ id: `button_${index}`, component: 'Button', child: `buttonLabel_${index}`, variant: 'primary', action: { call: 'openUrl', args: { url: button.url } } });
566
+ });
567
+ }
568
+ return {
569
+ interactiveMessage: {
570
+ ...(imageMessage ? { header: { imageMessage, hasMediaAttachment: true } } : {}),
571
+ bloksWidget: { type: 'im_a2ui', data: JSON.stringify({ version: 'v0.9', createSurface: { surfaceId: 'codex-a2ui-command-menu', catalogId: 'https://a2ui.org/specification/v0_9/catalogs/basic/catalog.json', components } }), fallback: String(fallback || title) },
572
+ nativeFlowMessage: { messageParamsJson: JSON.stringify({ limited_time_offer: { text: String(title), url: safeButtons[0]?.url || '', copy_code: String(title), expiration_time: Date.now() * 1000 } }), buttons: [{ name: 'single_select', buttonParamsJson: JSON.stringify({ title: String(title), sections: [{ title: 'Available Commands', rows: safeRows }] }) }] }
573
+ }
574
+ };
575
+ };
576
+
447
577
  export const prepareCopyToClipboardMessage = (options = {}) => {
448
578
  const text = String(options.text ?? '');
449
579
  const label = String(options.label ?? text);
@@ -535,6 +665,63 @@ export const prepareSlotMachineMessage = async (options = {}) => {
535
665
  * The payload is useful for displaying generation progress without making any
536
666
  * network request or enabling a hidden background process.
537
667
  */
668
+ /** Build a full-width vertical album with one large media item per row. */
669
+ export const prepareVerticalAlbumMessage = ({ items = [], verificationMetadata } = {}) => {
670
+ if (!Array.isArray(items) || items.length < 2) {
671
+ throw new Error('Vertical album requires at least two media items');
672
+ }
673
+ const expiration = String(Date.now() + 30 * 24 * 60 * 60 * 1000);
674
+ const primitives = items.map((item) => {
675
+ const kind = item?.type || (item?.video ? 'video' : 'image');
676
+ if (kind === 'video') {
677
+ const video = item.video || item.data || item;
678
+ const url = typeof video === 'string' ? video.trim() : String(video?.url || video?.videoUrl || '').trim();
679
+ if (!url) throw new Error('Each video album item requires a video URL');
680
+ return buildImaginePrimitive({
681
+ mediaType: 'video',
682
+ url,
683
+ thumbnail: video?.thumbnail || video?.thumbnailUrl,
684
+ mimeType: video?.mimeType || video?.mime_type || 'video/mp4',
685
+ fileLength: video?.fileLength || video?.file_length || 0,
686
+ duration: video?.duration || 0,
687
+ status: 'READY'
688
+ });
689
+ }
690
+ const image = item?.image || item?.data || item;
691
+ const sourceUrl = typeof image === 'string' ? image.trim() : String(image?.sourceUrl || image?.url || '').trim();
692
+ if (!sourceUrl) throw new Error('Each image album item requires an image URL');
693
+ const previewUrl = image?.imagePreviewUrl || image?.previewUrl || sourceUrl;
694
+ const fullUrl = image?.imageHighResUrl || image?.highResUrl || image?.url || sourceUrl;
695
+ const width = image?.width || item?.width || 600;
696
+ const height = image?.height || item?.height || 400;
697
+ const mimeType = image?.mimeType || image?.mime_type || 'image/jpeg';
698
+ return {
699
+ preview_image: { url: previewUrl, mime_type: mimeType, expiration_timestamp_ms: expiration, width, height },
700
+ full_image: { url: fullUrl, mime_type: mimeType, expiration_timestamp_ms: expiration, width, height },
701
+ dark_mode_preview_image: { url: image?.darkModePreviewUrl || previewUrl, mime_type: mimeType, expiration_timestamp_ms: expiration, width, height },
702
+ dark_mode_full_image: { url: image?.darkModeHighResUrl || image?.darkModePreviewUrl || fullUrl, mime_type: mimeType, expiration_timestamp_ms: expiration, width, height },
703
+ asset_query_status: 'FETCHED',
704
+ __typename: 'GenAIImagePrimitive'
705
+ };
706
+ });
707
+ const richResponseMessage = proto.AIRichResponseMessage.create({
708
+ submessages: [],
709
+ messageType: proto.AIRichResponseMessageType.AI_RICH_RESPONSE_TYPE_STANDARD,
710
+ unifiedResponse: {
711
+ data: Buffer.from(JSON.stringify({
712
+ response_id: randomUUID(),
713
+ sections: [{ view_model: { primitives, __typename: 'GenAIVStackLayoutViewModel' } }]
714
+ }))
715
+ },
716
+ contextInfo: {
717
+ isForwarded: true,
718
+ forwardingScore: 1,
719
+ forwardedAiBotMessageInfo: { botJid: '867051314767696@bot' },
720
+ forwardOrigin: 4
721
+ }
722
+ });
723
+ return wrapToBotForwardedMessage(richResponseMessage, verificationMetadata);
724
+ };
538
725
  export const buildImaginePrimitive = ({
539
726
  mediaType = 'video',
540
727
  url = '',
@@ -560,4 +747,5 @@ export const buildImaginePrimitive = ({
560
747
  ...(estimatedMs !== undefined ? { estimated_ms: Number(estimatedMs) } : {})
561
748
  };
562
749
  };
750
+
563
751
 
@@ -0,0 +1,46 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+
5
+ const PACKAGE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..');
6
+ const DEFAULT_DIRS = ['lib', 'WAProto'];
7
+ const DEFAULT_EXTENSIONS = new Set(['.js', '.mjs', '.cjs', '.ts', '.d.ts', '.proto', '.md', '.json']);
8
+
9
+ const walk = (directory, files) => {
10
+ if (!fs.existsSync(directory)) return;
11
+ for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
12
+ if (entry.name === 'node_modules' || entry.name.startsWith('.')) continue;
13
+ const fullPath = path.join(directory, entry.name);
14
+ if (entry.isDirectory()) walk(fullPath, files);
15
+ else if (DEFAULT_EXTENSIONS.has(path.extname(entry.name))) files.push(fullPath);
16
+ }
17
+ };
18
+
19
+ /**
20
+ * Find a literal or regular-expression pattern in package source.
21
+ * Returns exact relative file, line, column, and matching source line.
22
+ */
23
+ export const grepSource = (pattern, options = {}) => {
24
+ if (typeof pattern !== 'string' || !pattern.trim()) {
25
+ throw new TypeError('grepSource requires a non-empty search string');
26
+ }
27
+ const expression = options.regex ? new RegExp(pattern, options.flags || 'i') : null;
28
+ const needle = options.caseSensitive ? pattern : pattern.toLowerCase();
29
+ const files = [];
30
+ for (const relativeDir of options.dirs || DEFAULT_DIRS) walk(path.resolve(PACKAGE_ROOT, relativeDir), files);
31
+ if (options.includeReadme) files.push(path.resolve(PACKAGE_ROOT, 'README.md'));
32
+ const matches = [];
33
+ for (const file of files.sort()) {
34
+ const lines = fs.readFileSync(file, 'utf8').split(/\r?\n/);
35
+ lines.forEach((line, index) => {
36
+ const found = expression ? expression.test(line) : (options.caseSensitive ? line.includes(pattern) : line.toLowerCase().includes(needle));
37
+ if (found) {
38
+ const column = expression ? Math.max(0, line.search(expression)) : (options.caseSensitive ? line.indexOf(pattern) : line.toLowerCase().indexOf(needle));
39
+ matches.push({ file: path.relative(PACKAGE_ROOT, file), line: index + 1, column: column + 1, text: line.trim() });
40
+ }
41
+ if (expression) expression.lastIndex = 0;
42
+ });
43
+ }
44
+ return matches.slice(0, Math.max(1, options.maxResults || 200));
45
+ };
46
+
package/lib/index.d.ts CHANGED
@@ -162,6 +162,8 @@ export interface WASocket {
162
162
  sendMessage(jid: string | string[], content: StatusContent | GroupStatusContent | Record<string, unknown>, options?: Partial<StatusOptions> & Record<string, unknown>): Promise<unknown>;
163
163
  /** Relay a prepared message with optional recipient/device-scoping controls. */
164
164
  relayMessage(jid: string, message: Record<string, unknown>, options?: RelayMessageOptions): Promise<unknown>;
165
+ /** Resolve a WhatsApp channel invite code or URL to newsletter metadata. */
166
+ newsletterGetInviteInfo(inviteCodeOrUrl: string): Promise<Record<string, unknown> | null>;
165
167
  deleteGroupStatus(jid: string, key: WAMessageKey): Promise<unknown>;
166
168
  /** Reports the contact and blocks it after WhatsApp accepts the report. */
167
169
  reportContact(jid: string, messageKeys?: WAMessageKey[]): Promise<unknown>;
@@ -301,17 +303,27 @@ export interface WASocket {
301
303
  sendMarkdownQuote(jid: string, content?: { text?: string }, options?: Record<string, unknown>): Promise<unknown>;
302
304
  /** Send a quote plus a COPY_TO_CLIPBOARD action. */
303
305
  sendQuoteCopy(jid: string, content: { text?: string; copyText: string; copyLabel?: string; alignment?: 'END' | 'START' | 'CENTER' }, options?: Record<string, unknown>): Promise<unknown>;
304
- /** Send an experimental album containing at least two image/video items. */
306
+ /** Send a full-width stacked AIRich album with at least two image/video URL items. */
305
307
  sendAlbumMessage(jid: string, medias: Array<Record<string, unknown>>, options?: Record<string, unknown>): Promise<unknown>;
308
+ /** Send the large A2UI command/function menu with an image header and URL buttons. */
309
+ sendA2UICommandMenu(jid: string, content?: { image?: unknown; title?: string; rows?: Array<[string, string, string?] | { title?: string; description?: string; id?: string }>; buttons?: Array<{ text?: string; label?: string; url?: string }>; fallback?: string }, options?: Record<string, unknown>): Promise<unknown>;
310
+ /** Send the manually serialized AIRich command card with image, Markdown, widget CTAs, and footer actions. */
311
+ sendAIRichCommandMenu(jid: string, content: { image: string; title?: string; subtitle?: string; code?: string; rows?: Array<[string, string] | { command?: string; description?: string }>; buttons?: Array<{ id?: string; text?: string; label?: string; url?: string; toast?: string }>; footer?: string; latexImage?: string; latexExpression?: string }, options?: Record<string, unknown>): Promise<unknown>;
312
+ /** Send a GenAI unified-response grid of video primitives. Requires at least two video URLs. */
313
+ sendVideoGridMessage(jid: string, videos: Array<string | { url?: string; videoUrl?: string; reelsUrl?: string; postId?: string | number; title?: string; thumbnailUrl?: string; thumbnail?: string; creator?: string; avatarUrl?: string; contentHash?: string; likes?: number; comments?: number; shares?: number; isVerified?: boolean; sourceApp?: string; progressiveUrls?: string[]; dashManifests?: string[] }>, options?: Record<string, unknown>): Promise<unknown>;
306
314
  /** Send experimental bot diagnostic metadata. */
307
315
  sendBotDiagnostics(jid: string, content?: Record<string, unknown>, options?: Record<string, unknown>): Promise<unknown>;
308
316
  /** Send experimental bot command metadata. */
309
317
  sendBotCommand(jid: string, content?: Record<string, unknown>, options?: Record<string, unknown>): Promise<unknown>;
318
+ /** Send an experimental bot planning/execution-plan rich response. */
319
+ sendBotPlanning(jid: string, content?: { text?: string; steps?: Array<string | { title?: string; name?: string; body?: string }>; queryPlan?: boolean; botJid?: string }, options?: Record<string, unknown>): Promise<unknown>;
310
320
  /** Send experimental resolved tool-call metadata. */
311
321
  sendBotToolResult(jid: string, content?: Record<string, unknown>, options?: Record<string, unknown>): Promise<unknown>;
312
322
  richMenu(jid: string, content?: Record<string, unknown>, options?: Record<string, unknown>): Promise<unknown>;
313
323
  /** Send a rich button-card grid. */
314
324
  sendRichButtonGrid(jid: string, grid: Record<string, unknown>, options?: Record<string, unknown>): Promise<unknown>;
325
+ /** Compatibility alias for sendRichButtonGrid. */
326
+ HoldOn(jid: string, grid: Record<string, unknown>, options?: Record<string, unknown>): Promise<unknown>;
315
327
  /** Send a GenAI-style inline copy-to-clipboard rich response. */
316
328
  sendCopyButton(jid: string, content: { text: string; label?: string; alignment?: 'END' | 'START' | 'CENTER' }, options?: Record<string, unknown>): Promise<unknown>;
317
329
  /** Send a markdown-style interactive table with buttons. */
@@ -320,6 +332,10 @@ export interface WASocket {
320
332
  sendWhatsAppFlow(jid: string, flow: Record<string, unknown>, options?: Record<string, unknown>): Promise<unknown>;
321
333
  /** Send a complete GenAI-style rich response envelope. */
322
334
  sendRichMedia(jid: string, content?: Record<string, unknown>, quoted?: unknown, options?: Record<string, unknown>): Promise<{ message: unknown; messageId: string }>;
335
+ /** Send one tolerant rich response containing every supported section supplied in content. */
336
+ sendCodexMessage(jid: string, content?: Record<string, unknown>, quoted?: unknown, options?: Record<string, unknown>): Promise<unknown>;
337
+ /** Search package source files for a literal or regular-expression pattern. */
338
+ grepCode(pattern: string, options?: { regex?: boolean; flags?: string; caseSensitive?: boolean; dirs?: string[]; includeReadme?: boolean; maxResults?: number }): Array<{ file: string; line: number; column: number; text: string }>;
323
339
  /** Send a native AI image/video generation card. */
324
340
  sendRichGeneration(jid: string, content?: RichGenerationContent, quoted?: unknown, options?: Record<string, unknown>): Promise<RichGenerationResult>;
325
341
  /** Edit an existing rich generation card in place. */
@@ -351,6 +367,8 @@ export interface WASocket {
351
367
  sendHtmlMessage(jid: string, content: { html: string; trustedSources?: string[] }, options?: Record<string, unknown>): Promise<unknown>;
352
368
  /** Send the built-in HTML slot-machine payload. */
353
369
  sendSlotMachine(jid: string, options?: { title?: string; startingCredits?: number }, sendOptions?: Record<string, unknown>): Promise<unknown>;
370
+ /** Send the built-in interactive Codex mini-app. */
371
+ sendMiniApp(jid: string, options?: { title?: string; [key: string]: unknown }, sendOptions?: Record<string, unknown>): Promise<unknown>;
354
372
  /** Send a fetched website preview as an HTML rich response. */
355
373
  sendWebsitePreview(jid: string, url: string, options?: Record<string, unknown>, sendOptions?: Record<string, unknown>): Promise<unknown>;
356
374
  /** Send a lightweight HTML link-preview card. */
@@ -386,6 +404,7 @@ export declare function makeCacheManagerAuthState(store: CacheManagerStore, sess
386
404
  }>;
387
405
 
388
406
  /** Generate a self-contained local slot-machine HTML payload. */
407
+ export declare function generateMiniAppHtml(options?: { title?: string; [key: string]: unknown }): string;
389
408
  export declare function generateSlotMachineHtml(options?: { title?: string; startingCredits?: number }): string;
390
409
  export declare function buildImaginePrimitive(options?: { mediaType?: 'image' | 'video'; url?: string; thumbnail?: string; mimeType?: string; fileLength?: number; duration?: number; status?: string; estimatedMs?: number; imagineType?: string }): Record<string, unknown>;
391
410
  export declare function parseNativeFlowResponse(message: unknown): Record<string, unknown> | null;
@@ -423,3 +442,13 @@ declare function makeWASocket(config: SocketConfig): WASocket;
423
442
  export { makeWASocket };
424
443
  export default makeWASocket;
425
444
 
445
+
446
+ export declare const BotCapabilityType: Record<string, number>;
447
+ export declare function normalizeBotCapabilities(capabilities?: unknown[]): number[];
448
+ export declare function buildBotCapabilityMetadata(capabilities?: unknown[]): Record<string, unknown>;
449
+ export declare function buildBotRichResponse(options?: Record<string, unknown>): Record<string, unknown>;
450
+
451
+ export declare function prepareVerticalAlbumMessage(options?: { items?: Array<Record<string, unknown>>; verificationMetadata?: Record<string, unknown> }): Record<string, unknown>;
452
+ export declare function prepareAIRichCommandMenu(options?: { image?: string; title?: string; subtitle?: string; code?: string; rows?: Array<[string, string] | Record<string, unknown>>; buttons?: Array<Record<string, unknown>>; footer?: string; latexImage?: string; latexExpression?: string }): Record<string, unknown>;
453
+ export declare function prepareVideoGridMessage(options?: { videos?: Array<string | Record<string, unknown>>; verificationMetadata?: Record<string, unknown> }): Record<string, unknown>;
454
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codexverified/baileys",
3
- "version": "2.14.14",
3
+ "version": "2.17.17",
4
4
  "description": "CODEX TECHNOLOGY a premium Baileys developed and built by Dev Codex, with Meta compositing, bot planning replay, welcome flow, rich messages, interactive messages, albums, and more.",
5
5
  "main": "lib/index.js",
6
6
  "type": "module",