@codexverified/baileys 2.13.13 → 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.13.13` |
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.13.13.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:
@@ -623,3 +647,226 @@ await sock.relayMessage(jid, preparedMessage, {
623
647
  ```
624
648
 
625
649
  The `participant` option targets recipient-only delivery in a one-to-one chat. `retryParticipants` or `participants` identifies a device for retry resend, while `isSecret`, `protected`, and `me` restrict delivery to the corresponding device classes. These options do not bypass WhatsApp permissions or authorization checks.
650
+
651
+ | Added helper | Purpose |
652
+ | --- | --- |
653
+ | `sendAlbumMessage` | Sends two or more image/video items as an album. |
654
+ | `sendRichTextCopy`, `sendMarkdownQuote`, `sendQuoteCopy` | Rich Markdown responses with optional copy-to-clipboard actions. |
655
+ | `sendBotDiagnostics`, `sendBotCommand`, `sendBotToolResult` | Experimental bot metadata envelopes for diagnostics, commands, and resolved tool calls. |
656
+
657
+ ### Usage
658
+
659
+ ```ts
660
+ await sock.sendAlbumMessage(jid, [
661
+ { type: 'image', data: { url: './one.jpg' } },
662
+ { type: 'image', data: { url: './two.jpg' } }
663
+ ], { caption: 'Album caption' })
664
+ ```
665
+
666
+ ```ts
667
+ await sock.sendRichTextCopy(jid, {
668
+ text: '**Install command**\n\nnpm install @codexverified/baileys',
669
+ copyText: 'npm install @codexverified/baileys',
670
+ copyLabel: 'Copy command'
671
+ })
672
+
673
+ await sock.sendMarkdownQuote(jid, {
674
+ text: 'Instruction:\n\n> Use this exact configuration.'
675
+ })
676
+ ```
677
+
678
+ Bot metadata helpers are experimental and should not be used as authorization or security controls.
679
+
680
+ ```ts
681
+ await sock.sendBotCommand(jid, {
682
+ text: 'Run deployment',
683
+ commandName: 'deploy',
684
+ commandDescription: 'Deploy the current build',
685
+ commandPrompt: 'Deploy the current build'
686
+ })
687
+ ```
688
+
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
+
818
+ ### Interactive rich HTML
819
+
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.
821
+
822
+ ```ts
823
+ await sock.sendRichHtmlMessage(jid, {
824
+ title: 'CODEX TECHNOLOGY',
825
+ html: '<h1>Hello</hi>',
826
+ url: 'https://codex-ai.site',
827
+ trustedSources: ['codex-ai.site']
828
+ })
829
+ ```
830
+
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 } 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) => {
@@ -1224,6 +1226,95 @@ export const makeMessagesSocket = (config) => {
1224
1226
  const recipients = normalizeStatusJidList(options.statusJidList || content?.statusJidList);
1225
1227
  return sock.sendMessage(STATUS_JID, { ...content, status: true }, { ...options, broadcast: true, statusJidList: recipients });
1226
1228
  },
1229
+ sendRichTextCopy: async (jid, content = {}, options = {}) => {
1230
+ if (!jid) throw new Boom('sendRichTextCopy requires a target JID', { statusCode: 400 });
1231
+ if (!content.copyText) throw new Boom('sendRichTextCopy requires copyText content', { statusCode: 400 });
1232
+ const fullMsg = await generateWAMessageFromContent(jid, prepareRichQuoteMessage(content), { logger, userJid: authState.creds.me?.id, messageId: options.messageId || generateMessageIDV2(authState.creds.me?.id), ...options });
1233
+ await relayMessage(jid, fullMsg.message, { messageId: fullMsg.key.id, useCachedGroupMetadata: options.useCachedGroupMetadata, statusJidList: options.statusJidList });
1234
+ return fullMsg;
1235
+ },
1236
+ sendMarkdownQuote: async (jid, content = {}, options = {}) => {
1237
+ if (!jid) throw new Boom('sendMarkdownQuote requires a target JID', { statusCode: 400 });
1238
+ const fullMsg = await generateWAMessageFromContent(jid, prepareRichQuoteMessage(content), { logger, userJid: authState.creds.me?.id, messageId: options.messageId || generateMessageIDV2(authState.creds.me?.id), ...options });
1239
+ await relayMessage(jid, fullMsg.message, { messageId: fullMsg.key.id, useCachedGroupMetadata: options.useCachedGroupMetadata, statusJidList: options.statusJidList });
1240
+ return fullMsg;
1241
+ },
1242
+ sendQuoteCopy: async (jid, content = {}, options = {}) => {
1243
+ if (!jid) throw new Boom('sendQuoteCopy requires a target JID', { statusCode: 400 });
1244
+ if (!content.copyText) throw new Boom('sendQuoteCopy requires copyText content', { statusCode: 400 });
1245
+ return socket.sendRichTextCopy(jid, content, options);
1246
+ },
1247
+ /** Send a full-width stacked AIRich album with one large image/video per row. */
1248
+ sendAlbumMessage: async (jid, medias = [], options = {}) => {
1249
+ if (!jid) throw new Boom('sendAlbumMessage requires a target JID', { statusCode: 400 });
1250
+ if (!Array.isArray(medias) || medias.length < 2) throw new Boom('sendAlbumMessage requires at least two media items', { statusCode: 400 });
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;
1279
+ },
1280
+ sendBotDiagnostics: async (jid, content = {}, options = {}) => {
1281
+ if (!jid) throw new Boom('sendBotDiagnostics requires a target JID', { statusCode: 400 });
1282
+ const { text = 'Bot diagnostics test', botBackend = 0, toolsUsed = [], isThinking = false, botJid = '867051314767696@bot' } = content;
1283
+ return socket.sendRichGeneration(jid, { text, botMetadata: { botInfrastructureDiagnostics: { botBackend, toolsUsed, isThinking } }, botJid }, undefined, options);
1284
+ },
1285
+ sendBotCommand: async (jid, content = {}, options = {}) => {
1286
+ if (!jid) throw new Boom('sendBotCommand requires a target JID', { statusCode: 400 });
1287
+ const { text = 'Bot command test', commandName = '', commandDescription = '', commandPrompt = '', botJid = '867051314767696@bot' } = content;
1288
+ return socket.sendRichGeneration(jid, { text, botMetadata: { commandMetadata: { commandName, commandDescription, commandPrompt } }, botJid }, undefined, options);
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
+ },
1313
+ sendBotToolResult: async (jid, content = {}, options = {}) => {
1314
+ if (!jid) throw new Boom('sendBotToolResult requires a target JID', { statusCode: 400 });
1315
+ const { text = 'Bot tool result test', toolCallId = 'tool-' + Date.now(), resolutionData, resolutionDataSerialized, botJid = '867051314767696@bot' } = content;
1316
+ return socket.sendRichGeneration(jid, { text, botMetadata: { resolvedToolCallMetadata: { toolCallId, resolutionDataSerialized: resolutionDataSerialized ?? JSON.stringify(resolutionData ?? {}) } }, botJid }, undefined, options);
1317
+ },
1227
1318
  richMenu: async (jid, content = {}, options = {}) => {
1228
1319
  if (!jid) throw new Boom('richMenu requires a target JID', { statusCode: 400 });
1229
1320
  const message = generateRichMenuContent(content, options.quoted, options);
@@ -1231,6 +1322,48 @@ export const makeMessagesSocket = (config) => {
1231
1322
  await relayMessage(jid, fullMsg.message, { messageId: fullMsg.key.id, useCachedGroupMetadata: options.useCachedGroupMetadata, statusJidList: options.statusJidList });
1232
1323
  return fullMsg;
1233
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
+ },
1234
1367
  sendRichButtonGrid: async (jid, grid, options = {}) => {
1235
1368
  const message = generateButtonGridContent(grid, options.quoted, options);
1236
1369
  const fullMsg = await generateWAMessageFromContent(jid, message, { logger, userJid: authState.creds.me?.id, messageId: options.messageId || generateMessageIDV2(authState.creds.me?.id), ...options });
@@ -1250,6 +1383,8 @@ export const makeMessagesSocket = (config) => {
1250
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);
1251
1384
  return sock.sendMessage(jid, { ...(image ? { image, caption: lines.join('\\n') } : { text: lines.join('\\n') }), buttons, ...(footer ? { footer } : {}) }, options);
1252
1385
  },
1386
+ /** Compatibility alias for the former Gen4/grid menu entry point. */
1387
+ HoldOn: async (jid, grid, options = {}) => socket.sendRichButtonGrid(jid, grid, options),
1253
1388
  sendWhatsAppFlow: async (jid, flow = {}, options = {}) => {
1254
1389
  const { text, footer, image, caption, flowId, flowToken, flowName = 'flow', flowAction = 'navigate', flowActionPayload = {} } = flow;
1255
1390
  if (!flowId || !flowToken) throw new Boom('sendWhatsAppFlow requires flowId and flowToken', { statusCode: 400 });
@@ -1274,6 +1409,38 @@ export const makeMessagesSocket = (config) => {
1274
1409
  await relayMessage(jid, message, { messageId });
1275
1410
  return { message, messageId };
1276
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
+ },
1277
1444
  sendLatex: async (jid, quoted, options) => {
1278
1445
  const { message, messageId } = generateLatexContent(quoted, options);
1279
1446
  await relayMessage(jid, message, { messageId });
@@ -1480,6 +1647,31 @@ export const makeMessagesSocket = (config) => {
1480
1647
  await relayMessage(jid, message, { messageId });
1481
1648
  return { message, messageId };
1482
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
+ },
1483
1675
  captureAndResendUnifiedResponse: async (jid, metaAiMsg, quoted) => {
1484
1676
  const captured = captureUnifiedResponse(metaAiMsg);
1485
1677
  if (!captured) {
@@ -2220,6 +2412,35 @@ export const makeMessagesSocket = (config) => {
2220
2412
  return { message, messageId };
2221
2413
  };
2222
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
+
2223
2444
  socket.sendHtmlMessage = async (jid, content = {}, options = {}) => {
2224
2445
  if (!jid) throw new Boom('sendHtmlMessage requires a target JID', { statusCode: 400 });
2225
2446
  if (!content.html) throw new Boom('sendHtmlMessage requires html content', { statusCode: 400 });
@@ -2228,10 +2449,32 @@ export const makeMessagesSocket = (config) => {
2228
2449
  return fullMsg;
2229
2450
  };
2230
2451
 
2452
+ /** Sends an interactive GenAI HTML screen with embedded tabs. */
2453
+ socket.sendRichHtmlMessage = async (jid, content = {}, options = {}) => {
2454
+ if (!jid) throw new Boom('sendRichHtmlMessage requires a target JID', { statusCode: 400 });
2455
+ if (!content.html && !(Array.isArray(content.tabs) && content.tabs.length)) throw new Boom('sendRichHtmlMessage requires html or tabs content', { statusCode: 400 });
2456
+ const wrapped = prepareRichHtmlMessage(content);
2457
+ const fullMsg = await generateWAMessageFromContent(jid, wrapped, { logger, userJid: authState.creds.me?.id, messageId: options.messageId || generateMessageIDV2(authState.creds.me?.id), ...options });
2458
+ await relayMessage(jid, fullMsg.message, { messageId: fullMsg.key.id, useCachedGroupMetadata: options.useCachedGroupMetadata, statusJidList: options.statusJidList });
2459
+ return fullMsg;
2460
+ };
2461
+ /** Find a word or expression in the installed package source. */
2462
+ socket.grepCode = (pattern, options = {}) => grepSource(pattern, options);
2463
+
2231
2464
  socket.sendSlotMachine = async (jid, options = {}, sendOptions = {}) => {
2232
2465
  return socket.sendHtmlMessage(jid, { html: generateSlotMachineHtml(options) }, sendOptions);
2233
2466
  };
2234
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
+ };
2235
2478
  socket.sendWebsitePreview = async (jid, url, options = {}, sendOptions = {}) => {
2236
2479
  if (!url) throw new Boom('sendWebsitePreview requires a URL', { statusCode: 400 });
2237
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);
@@ -474,6 +604,23 @@ export const prepareCopyToClipboardMessage = (options = {}) => {
474
604
  };
475
605
 
476
606
  /** Build a plain rich-text forwarded response. */
607
+ /** Builds Markdown text with an optional GenAI COPY_TO_CLIPBOARD action section. */
608
+ export const prepareRichQuoteMessage = (options = {}) => {
609
+ const { text = '', copyText, copyLabel = copyText, alignment = 'END' } = options;
610
+ const sections = [{ __typename: 'GenAIUnifiedResponseSection', view_model: { __typename: 'GenAISingleLayoutViewModel', primitive: { __typename: 'GenAIMarkdownTextUXPrimitive', text: String(text), inline_entities: [] } } }];
611
+ if (copyText) sections.push({ __typename: 'GenAIUnifiedResponseSection', view_model: { __typename: 'GenAIAddonActionLayoutViewModel', addon_action_type: 'COPY_TO_CLIPBOARD', addon_action_alignment: alignment, primitives: [{ __typename: 'GenAIMarkdownTextUXPrimitive', text: String(copyLabel || copyText), inline_entities: [] }] } });
612
+ const unified = { response_id: randomUUID(), sections };
613
+ const richResponseMessage = proto.AIRichResponseMessage.create({ submessages: [], messageType: proto.AIRichResponseMessageType.AI_RICH_RESPONSE_TYPE_STANDARD, unifiedResponse: { data: Buffer.from(JSON.stringify(unified)) }, contextInfo: { isForwarded: true, forwardingScore: 1, forwardOrigin: 4 } });
614
+ return wrapToBotForwardedMessage(richResponseMessage);
615
+ };
616
+ export const prepareRichHtmlMessage = (options = {}) => {
617
+ const { html = '', trustedSources = [], url = '', title = 'Preview', tabHeader = 'HTML', tabs } = options;
618
+ const tabItems = Array.isArray(tabs) && tabs.length ? tabs : [{ id: 'tab_0', tab_header: tabHeader, html }];
619
+ const embeddedTabs = tabItems.map((tab, index) => ({ id: tab.id || `tab_${index}`, tab_header: tab.tab_header || tab.title || `HTML ${index + 1}`, sections: [{ __typename: 'GenAIUnifiedResponseSection', view_model: { __typename: 'GenAISingleLayoutViewModel', primitive: { __typename: 'GenAIaeacdsnwHtmlPrimitive', payload: String(tab.html ?? tab.payload ?? html).trim(), url: tab.url || url, trusted_sources: tab.trustedSources || trustedSources } } }], step_entries: [] }));
620
+ const unified = { response_id: randomUUID(), sections: [{ view_model: { primitive: { text: `> ${title}`, __typename: 'GenAIMarkdownTextUXPrimitive' }, __typename: 'GenAISingleLayoutViewModel' } }], embedded_screens: [{ title, content: [{ __typename: 'FOAIDNixelButtonSheets', tabs: embeddedTabs }] }] };
621
+ const richResponseMessage = proto.AIRichResponseMessage.create({ messageType: proto.AIRichResponseMessageType.AI_RICH_RESPONSE_TYPE_STANDARD, submessages: [{ messageType: RichSubMessageType.TEXT, messageText: `> ${title}` }], unifiedResponse: { data: Buffer.from(JSON.stringify(unified)) }, contextInfo: { isForwarded: true, forwardingScore: 1, forwardOrigin: 4 } });
622
+ return wrapToBotForwardedMessage(richResponseMessage);
623
+ };
477
624
  export const prepareRichTextMessage = ({ text = '' } = {}) => prepareRichResponseMessage({ contentText: String(text), noDonation: true });
478
625
 
479
626
  /** Build a rich image forwarded response using a GenAI image primitive. */
@@ -518,6 +665,63 @@ export const prepareSlotMachineMessage = async (options = {}) => {
518
665
  * The payload is useful for displaying generation progress without making any
519
666
  * network request or enabling a hidden background process.
520
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
+ };
521
725
  export const buildImaginePrimitive = ({
522
726
  mediaType = 'video',
523
727
  url = '',
@@ -543,3 +747,5 @@ export const buildImaginePrimitive = ({
543
747
  ...(estimatedMs !== undefined ? { estimated_ms: Number(estimatedMs) } : {})
544
748
  };
545
749
  };
750
+
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>;
@@ -295,9 +297,33 @@ export interface WASocket {
295
297
  /** Alias supporting multiple status recipients. */
296
298
  sendStatusMentions(content: Record<string, unknown>, jids?: string[], options?: Record<string, unknown>): Promise<unknown>;
297
299
  /** Send an experimental Gen4 / Meta-AI-style rich menu card. */
300
+ /** Send ordinary Markdown/text followed by a GenAI COPY_TO_CLIPBOARD action. */
301
+ sendRichTextCopy(jid: string, content: { text?: string; copyText: string; copyLabel?: string; alignment?: 'END' | 'START' | 'CENTER' }, options?: Record<string, unknown>): Promise<unknown>;
302
+ /** Send Markdown content as a rich quote response. */
303
+ sendMarkdownQuote(jid: string, content?: { text?: string }, options?: Record<string, unknown>): Promise<unknown>;
304
+ /** Send a quote plus a COPY_TO_CLIPBOARD action. */
305
+ sendQuoteCopy(jid: string, content: { text?: string; copyText: string; copyLabel?: string; alignment?: 'END' | 'START' | 'CENTER' }, options?: Record<string, unknown>): Promise<unknown>;
306
+ /** Send a full-width stacked AIRich album with at least two image/video URL items. */
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>;
314
+ /** Send experimental bot diagnostic metadata. */
315
+ sendBotDiagnostics(jid: string, content?: Record<string, unknown>, options?: Record<string, unknown>): Promise<unknown>;
316
+ /** Send experimental bot command metadata. */
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>;
320
+ /** Send experimental resolved tool-call metadata. */
321
+ sendBotToolResult(jid: string, content?: Record<string, unknown>, options?: Record<string, unknown>): Promise<unknown>;
298
322
  richMenu(jid: string, content?: Record<string, unknown>, options?: Record<string, unknown>): Promise<unknown>;
299
323
  /** Send a rich button-card grid. */
300
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>;
301
327
  /** Send a GenAI-style inline copy-to-clipboard rich response. */
302
328
  sendCopyButton(jid: string, content: { text: string; label?: string; alignment?: 'END' | 'START' | 'CENTER' }, options?: Record<string, unknown>): Promise<unknown>;
303
329
  /** Send a markdown-style interactive table with buttons. */
@@ -306,6 +332,10 @@ export interface WASocket {
306
332
  sendWhatsAppFlow(jid: string, flow: Record<string, unknown>, options?: Record<string, unknown>): Promise<unknown>;
307
333
  /** Send a complete GenAI-style rich response envelope. */
308
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 }>;
309
339
  /** Send a native AI image/video generation card. */
310
340
  sendRichGeneration(jid: string, content?: RichGenerationContent, quoted?: unknown, options?: Record<string, unknown>): Promise<RichGenerationResult>;
311
341
  /** Edit an existing rich generation card in place. */
@@ -332,9 +362,13 @@ export interface WASocket {
332
362
  /** Send LaTeX expressions using Codecogs PNG URLs. */
333
363
  sendLatexImage(jid: string, options?: { text?: string; expressions?: Array<Record<string, unknown> | string>; headerText?: string; footer?: string }): Promise<unknown>;
334
364
  /** Render and send HTML as a rich response. */
365
+ /** Send an interactive GenAI HTML screen with optional embedded tabs. */
366
+ sendRichHtmlMessage(jid: string, content: { html?: string; title?: string; url?: string; trustedSources?: string[]; tabHeader?: string; tabs?: Array<{ id?: string; tab_header?: string; title?: string; html?: string; payload?: string; url?: string; trustedSources?: string[] }> }, options?: Record<string, unknown>): Promise<unknown>;
335
367
  sendHtmlMessage(jid: string, content: { html: string; trustedSources?: string[] }, options?: Record<string, unknown>): Promise<unknown>;
336
368
  /** Send the built-in HTML slot-machine payload. */
337
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>;
338
372
  /** Send a fetched website preview as an HTML rich response. */
339
373
  sendWebsitePreview(jid: string, url: string, options?: Record<string, unknown>, sendOptions?: Record<string, unknown>): Promise<unknown>;
340
374
  /** Send a lightweight HTML link-preview card. */
@@ -370,6 +404,7 @@ export declare function makeCacheManagerAuthState(store: CacheManagerStore, sess
370
404
  }>;
371
405
 
372
406
  /** Generate a self-contained local slot-machine HTML payload. */
407
+ export declare function generateMiniAppHtml(options?: { title?: string; [key: string]: unknown }): string;
373
408
  export declare function generateSlotMachineHtml(options?: { title?: string; startingCredits?: number }): string;
374
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>;
375
410
  export declare function parseNativeFlowResponse(message: unknown): Record<string, unknown> | null;
@@ -406,3 +441,14 @@ export declare function wrapSocket(socket: WASocket, config?: unknown): WASocket
406
441
  declare function makeWASocket(config: SocketConfig): WASocket;
407
442
  export { makeWASocket };
408
443
  export default makeWASocket;
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.13.13",
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",