@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 +201 -8
- package/lib/Socket/messages-send.js +198 -3
- package/lib/Socket/newsletter.js +19 -0
- package/lib/Utils/bot-capabilities.js +84 -0
- package/lib/Utils/games/miniapp.js +5 -0
- package/lib/Utils/index.js +4 -0
- package/lib/Utils/rich-message-utils.js +188 -0
- package/lib/Utils/source-grep.js +46 -0
- package/lib/index.d.ts +30 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,11 +1,35 @@
|
|
|
1
|
-
|
|
1
|
+
<div align="center">
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
[](#at-a-glance)
|
|
5
|
-
[](#license-and-maintenance)
|
|
6
|
-
[](#at-a-glance)
|
|
3
|
+
# BAILEYS
|
|
7
4
|
|
|
8
|
-
|
|
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¢er=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
|
+
[](https://www.npmjs.com/package/@codexverified/baileys)
|
|
14
|
+
[](https://t.me/codexverified)
|
|
15
|
+
[](https://nodejs.org/)
|
|
16
|
+
[](https://www.npmjs.com/package/@codexverified/baileys)
|
|
17
|
+
[](#license-and-maintenance)
|
|
18
|
+
|
|
19
|
+
</div>
|
|
20
|
+
|
|
21
|
+
<div align="center">
|
|
22
|
+
|
|
23
|
+
[](https://whatsapp.com/channel/0029Vb78BHmL2AU7fsANSH2y)
|
|
24
|
+
[](https://whatsapp.com/channel/0029Vb6sMEy96H4VI2w3I50F)
|
|
25
|
+
[](https://chat.whatsapp.com/If0d4XKHITO2NUf6YvQ3Eg?s=cl&p=a&mlu=4&ilr=4)
|
|
26
|
+
[](https://t.me/codex_tech_innovation)
|
|
27
|
+
[](https://t.me/CODEXV3)
|
|
28
|
+
[](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.
|
|
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.
|
|
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§ion=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
|
|
1249
|
-
|
|
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);
|
package/lib/Socket/newsletter.js
CHANGED
|
@@ -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
|
+
};
|
package/lib/Utils/index.js
CHANGED
|
@@ -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
|
|
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.
|
|
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",
|