plogme 2.0.3 → 2.0.4
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 +120 -4
- package/lib/Socket/messages-send.js +131 -31
- package/lib/Utils/rich-message-utils.js +268 -6
- package/lib/index.d.ts +47 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -291,7 +291,7 @@ await sock.sendMessage(jid, {
|
|
|
291
291
|
|
|
292
292
|
### Albums
|
|
293
293
|
|
|
294
|
-
Albums now use
|
|
294
|
+
Albums now use an AIRich vertical unified-response layout. Provide at least two image or video items; the client receives one forwarded rich message with one large media item per row, matching WhatsApp's full-width stacked album presentation. The old `albumMessage` parent/child collection and compact thumbnail grid are no longer generated by `sendAlbumMessage()`.
|
|
295
295
|
|
|
296
296
|
```js
|
|
297
297
|
await sock.sendAlbumMessage(jid, [
|
|
@@ -305,11 +305,20 @@ await sock.sendAlbumMessage(jid, [
|
|
|
305
305
|
width: 1200,
|
|
306
306
|
height: 800
|
|
307
307
|
}
|
|
308
|
+
},
|
|
309
|
+
{
|
|
310
|
+
type: 'video',
|
|
311
|
+
data: {
|
|
312
|
+
url: 'https://example.com/three.mp4',
|
|
313
|
+
mimeType: 'video/mp4',
|
|
314
|
+
duration: 12,
|
|
315
|
+
thumbnailUrl: 'https://example.com/three.jpg'
|
|
316
|
+
}
|
|
308
317
|
}
|
|
309
318
|
])
|
|
310
319
|
```
|
|
311
320
|
|
|
312
|
-
The general `sendMessage` album form delegates to the same
|
|
321
|
+
The general `sendMessage` album form delegates to the same full-width vertical album implementation:
|
|
313
322
|
|
|
314
323
|
```js
|
|
315
324
|
await sock.sendMessage(jid, {
|
|
@@ -320,7 +329,30 @@ await sock.sendMessage(jid, {
|
|
|
320
329
|
})
|
|
321
330
|
```
|
|
322
331
|
|
|
323
|
-
|
|
332
|
+
`sendAlbumMessage()` accepts fetchable image and video URLs. Local `Buffer`/file inputs are not uploaded by this rich album API. The lower-level `prepareGridImageMessage()` helper remains available when a compact `GRID_IMAGE` layout is specifically required; `sendAlbumMessage()` itself uses the larger `GenAIVStackLayoutViewModel` presentation.
|
|
333
|
+
|
|
334
|
+
### Video grids
|
|
335
|
+
|
|
336
|
+
Video grids use the supported `GenAIVideoPrimitive` inside a unified-response `GenAIGridLayoutViewModel`. They are exposed separately from `sendAlbumMessage()` because WhatsApp's protobuf does not define a `GRID_VIDEO` submessage type. Provide at least two publicly fetchable video URLs:
|
|
337
|
+
|
|
338
|
+
```js
|
|
339
|
+
await sock.sendVideoGridMessage(jid, [
|
|
340
|
+
{
|
|
341
|
+
url: 'https://example.com/first.mp4',
|
|
342
|
+
title: 'First clip',
|
|
343
|
+
thumbnailUrl: 'https://example.com/first.jpg',
|
|
344
|
+
progressiveUrls: ['https://cdn.example.com/first-720p.mp4']
|
|
345
|
+
},
|
|
346
|
+
{
|
|
347
|
+
url: 'https://example.com/second.mp4',
|
|
348
|
+
title: 'Second clip',
|
|
349
|
+
thumbnailUrl: 'https://example.com/second.jpg',
|
|
350
|
+
progressiveUrls: ['https://cdn.example.com/second-720p.mp4']
|
|
351
|
+
}
|
|
352
|
+
])
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
The video-grid API is intentionally URL-based; it does not upload local buffers or files. `sendVideoGridMessage()` also uses the rich-message edit relay, so supported clients receive the unified response without an initial Download-only placeholder.
|
|
324
356
|
|
|
325
357
|
## Interactive messages
|
|
326
358
|
|
|
@@ -524,7 +556,7 @@ const result = await sock.sendPlogmeMessage(jid, {
|
|
|
524
556
|
{ latexExpression: 'E = mc^2', url: 'https://example.com/equation.png' }
|
|
525
557
|
],
|
|
526
558
|
|
|
527
|
-
// Optional
|
|
559
|
+
// Optional citation links.
|
|
528
560
|
links: [
|
|
529
561
|
{
|
|
530
562
|
text: 'Open the deployment logs',
|
|
@@ -533,6 +565,43 @@ const result = await sock.sendPlogmeMessage(jid, {
|
|
|
533
565
|
displayName: 'Logs'
|
|
534
566
|
}
|
|
535
567
|
],
|
|
568
|
+
|
|
569
|
+
// Optional AIRich product card (object = Single layout; array = HScroll).
|
|
570
|
+
products: {
|
|
571
|
+
title: 'CODY AI hosting',
|
|
572
|
+
brand: 'Plogme',
|
|
573
|
+
price: '$10',
|
|
574
|
+
sale_price: '$7',
|
|
575
|
+
url: 'https://example.com/product',
|
|
576
|
+
image: 'https://example.com/product.jpg'
|
|
577
|
+
},
|
|
578
|
+
|
|
579
|
+
// Optional social-post cards (always HScroll).
|
|
580
|
+
posts: [{
|
|
581
|
+
title: 'Release announcement',
|
|
582
|
+
username: 'codyai',
|
|
583
|
+
profile: 'https://example.com/profile.jpg',
|
|
584
|
+
thumbnail: 'https://example.com/post.jpg',
|
|
585
|
+
caption: 'Deployment completed.',
|
|
586
|
+
url: 'https://example.com/post',
|
|
587
|
+
source: 'INSTAGRAM',
|
|
588
|
+
verified: true
|
|
589
|
+
}],
|
|
590
|
+
|
|
591
|
+
// Optional source cards: [favicon URL, source URL, display text].
|
|
592
|
+
sources: [[
|
|
593
|
+
'https://example.com/favicon.jpg',
|
|
594
|
+
'https://example.com/docs',
|
|
595
|
+
'Documentation'
|
|
596
|
+
]],
|
|
597
|
+
|
|
598
|
+
// Optional playable video card. Metadata may be supplied manually.
|
|
599
|
+
inlineVideo: {
|
|
600
|
+
url: 'https://example.com/clip.mp4',
|
|
601
|
+
mimeType: 'video/mp4',
|
|
602
|
+
duration: 12,
|
|
603
|
+
fileLength: 123456
|
|
604
|
+
},
|
|
536
605
|
suggested: true,
|
|
537
606
|
footerText: 'Generated by CODY AI'
|
|
538
607
|
}, undefined, {
|
|
@@ -580,6 +649,53 @@ await sock.richMenu(jid, {
|
|
|
580
649
|
})
|
|
581
650
|
```
|
|
582
651
|
|
|
652
|
+
### Large A2UI command card
|
|
653
|
+
|
|
654
|
+
Use `sendA2UICommandMenu` for the full-size card layout with an uploaded image header, a command/function table, URL buttons, and a native-flow command selector. This is an `interactiveMessage`/A2UI payload, not an AIRich `unifiedResponse`, so it is intentionally separate from `sendPlogmeMessage` and `richMenu`.
|
|
655
|
+
|
|
656
|
+
```js
|
|
657
|
+
await sock.sendA2UICommandMenu(jid, {
|
|
658
|
+
image: 'https://example.com/cover.jpg',
|
|
659
|
+
title: 'CODY AI',
|
|
660
|
+
rows: [
|
|
661
|
+
['.meid', 'Check ID'],
|
|
662
|
+
['.fromid', 'Check Source'],
|
|
663
|
+
['.ping', 'Check Speed']
|
|
664
|
+
],
|
|
665
|
+
buttons: [
|
|
666
|
+
{ text: '1st-Channel', url: 'https://example.com/first' },
|
|
667
|
+
{ text: '2nd-Channel', url: 'https://example.com/second' }
|
|
668
|
+
]
|
|
669
|
+
})
|
|
670
|
+
```
|
|
671
|
+
|
|
672
|
+
The image URL is uploaded as the interactive header media. Rows are also available through the native-flow `single_select` menu, and button URLs are represented as A2UI `openUrl` actions.
|
|
673
|
+
|
|
674
|
+
### Screenshot-style raw AIRich command card
|
|
675
|
+
|
|
676
|
+
Use `sendAIRichCommandMenu` when you want the manually serialized AIRich variant: an image primitive, widget CTAs, Markdown text with optional LaTeX inline metadata, a command table, and footer URL actions. Unlike A2UI, this path is an AIRich unified response and therefore uses the normal relay plus type-14 immediate-render edit.
|
|
677
|
+
|
|
678
|
+
```js
|
|
679
|
+
await sock.sendAIRichCommandMenu(jid, {
|
|
680
|
+
image: 'https://example.com/cover.jpg',
|
|
681
|
+
title: 'CODY AI',
|
|
682
|
+
subtitle: 'Command List',
|
|
683
|
+
code: 'CODY AI',
|
|
684
|
+
rows: [
|
|
685
|
+
['.meid', 'Check ID'],
|
|
686
|
+
['.fromid', 'Check Source'],
|
|
687
|
+
['.ping', 'Check Speed']
|
|
688
|
+
],
|
|
689
|
+
latexImage: 'https://example.com/logo.png',
|
|
690
|
+
latexExpression: 'CODY',
|
|
691
|
+
buttons: [
|
|
692
|
+
{ text: '1st-Channel', url: 'https://example.com/first' },
|
|
693
|
+
{ text: '2nd-Channel', url: 'https://example.com/second' }
|
|
694
|
+
],
|
|
695
|
+
footer: 'Developed by - CODY AI'
|
|
696
|
+
})
|
|
697
|
+
```
|
|
698
|
+
|
|
583
699
|
Cards and carousel layout are supported:
|
|
584
700
|
|
|
585
701
|
```js
|
|
@@ -5,7 +5,7 @@ import { proto } from '../../WAProto/index.js';
|
|
|
5
5
|
import { BIZ_BOT_SUPPORT_PAYLOAD, DEFAULT_CACHE_TTLS, WA_DEFAULT_EPHEMERAL } from '../Defaults/index.js';
|
|
6
6
|
import { aggregateMessageKeysNotFromMe, assertMediaContent, assertMeId, bindWaitForEvent, buildLinkPreview, decryptMediaRetryData, DEF_MEDIA_HOST, delay, encodeNewsletterMessage, encodeSignedDeviceIdentity, encodeWAMessage, encryptMediaRetryRequest, extractDeviceJids, extractImageThumb, generateMessageIDV2, generateParticipantHashV2, generateWAMessage, generateWAMessageFromContent, getStatusCodeForMediaRetry, makeWhatsAppFlowButton, getUrlFromDirectPath, getWAUploadToServer, MessageRetryManager, normalizeMessageContent, parseAndInjectE2ESessions, prepareWAMessageMedia, shouldIncludeBizBinaryNode, unixTimestampSeconds } from '../Utils/index.js';
|
|
7
7
|
import { getUrlInfo } from '../Utils/link-preview.js';
|
|
8
|
-
import { prepareRichMenuMessage, prepareRichResponseMessage, prepareRichGenerationMessage, prepareCopyToClipboardMessage, prepareRichTextMessage, prepareRichQuoteMessage, prepareRichImageMessage, prepareRichLinkMessage, prepareGridImageMessage, prepareHtmlMessage, prepareRichHtmlMessage, attachBotVerificationProofs, hasVerifiableProofs } from '../Utils/rich-message-utils.js';
|
|
8
|
+
import { prepareRichMenuMessage, prepareA2UICommandMenu, prepareAIRichCommandMenu, prepareRichResponseMessage, prepareRichGenerationMessage, prepareCopyToClipboardMessage, prepareRichTextMessage, prepareRichQuoteMessage, prepareRichImageMessage, prepareRichLinkMessage, prepareGridImageMessage, prepareVerticalAlbumMessage, prepareVideoGridMessage, prepareHtmlMessage, prepareRichHtmlMessage, attachBotVerificationProofs, hasVerifiableProofs } from '../Utils/rich-message-utils.js';
|
|
9
9
|
import { generateSlotMachineHtml } from '../Utils/games/slot-machine.js';
|
|
10
10
|
import { generateMiniAppHtml } from '../Utils/games/miniapp.js';
|
|
11
11
|
import { generateWebsitePreviewHtml, generateLinkPreviewHtml } from '../Utils/games/website-preview.js';
|
|
@@ -1276,18 +1276,39 @@ export const makeMessagesSocket = (config) => {
|
|
|
1276
1276
|
},
|
|
1277
1277
|
sendTable: async (jid, title, headers, rows, quoted, options = {}) => {
|
|
1278
1278
|
const { message, messageId } = generateTableContent(title, headers, rows, quoted, options);
|
|
1279
|
-
|
|
1280
|
-
|
|
1279
|
+
const fullMsg = generateWAMessageFromContent(jid, message, {
|
|
1280
|
+
logger,
|
|
1281
|
+
userJid: authState.creds.me?.id,
|
|
1282
|
+
messageId: options.messageId || messageId || generateMessageIDV2(jid),
|
|
1283
|
+
quoted,
|
|
1284
|
+
...options
|
|
1285
|
+
});
|
|
1286
|
+
await relayRichMessage(jid, fullMsg, options);
|
|
1287
|
+
return fullMsg;
|
|
1281
1288
|
},
|
|
1282
1289
|
sendList: async (jid, title, items, quoted, options = {}) => {
|
|
1283
1290
|
const { message, messageId } = generateListContent(title, items, quoted, options);
|
|
1284
|
-
|
|
1285
|
-
|
|
1291
|
+
const fullMsg = generateWAMessageFromContent(jid, message, {
|
|
1292
|
+
logger,
|
|
1293
|
+
userJid: authState.creds.me?.id,
|
|
1294
|
+
messageId: options.messageId || messageId || generateMessageIDV2(jid),
|
|
1295
|
+
quoted,
|
|
1296
|
+
...options
|
|
1297
|
+
});
|
|
1298
|
+
await relayRichMessage(jid, fullMsg, options);
|
|
1299
|
+
return fullMsg;
|
|
1286
1300
|
},
|
|
1287
1301
|
sendCodeBlock: async (jid, code, quoted, options = {}) => {
|
|
1288
1302
|
const { message, messageId } = generateCodeBlockContent(code, quoted, options);
|
|
1289
|
-
|
|
1290
|
-
|
|
1303
|
+
const fullMsg = generateWAMessageFromContent(jid, message, {
|
|
1304
|
+
logger,
|
|
1305
|
+
userJid: authState.creds.me?.id,
|
|
1306
|
+
messageId: options.messageId || messageId || generateMessageIDV2(jid),
|
|
1307
|
+
quoted,
|
|
1308
|
+
...options
|
|
1309
|
+
});
|
|
1310
|
+
await relayRichMessage(jid, fullMsg, options);
|
|
1311
|
+
return fullMsg;
|
|
1291
1312
|
},
|
|
1292
1313
|
sendCarousel: async (jid, cards, options = {}) => {
|
|
1293
1314
|
if (!Array.isArray(cards) || cards.length === 0) {
|
|
@@ -1323,8 +1344,15 @@ export const makeMessagesSocket = (config) => {
|
|
|
1323
1344
|
},
|
|
1324
1345
|
sendLatex: async (jid, quoted, options = {}) => {
|
|
1325
1346
|
const { message, messageId } = generateLatexContent(quoted, { expressions: [], ...options });
|
|
1326
|
-
|
|
1327
|
-
|
|
1347
|
+
const fullMsg = generateWAMessageFromContent(jid, message, {
|
|
1348
|
+
logger,
|
|
1349
|
+
userJid: authState.creds.me?.id,
|
|
1350
|
+
messageId: options.messageId || messageId || generateMessageIDV2(jid),
|
|
1351
|
+
quoted,
|
|
1352
|
+
...options
|
|
1353
|
+
});
|
|
1354
|
+
await relayRichMessage(jid, fullMsg, options);
|
|
1355
|
+
return fullMsg;
|
|
1328
1356
|
},
|
|
1329
1357
|
/**
|
|
1330
1358
|
* Send LaTeX as rendered PNG image (like Meta AI does).
|
|
@@ -1352,8 +1380,14 @@ export const makeMessagesSocket = (config) => {
|
|
|
1352
1380
|
headerText,
|
|
1353
1381
|
footer
|
|
1354
1382
|
});
|
|
1355
|
-
|
|
1356
|
-
|
|
1383
|
+
const fullMsg = generateWAMessageFromContent(jid, message, {
|
|
1384
|
+
logger,
|
|
1385
|
+
userJid: authState.creds.me?.id,
|
|
1386
|
+
messageId: options.messageId || messageId || generateMessageIDV2(jid),
|
|
1387
|
+
...options
|
|
1388
|
+
});
|
|
1389
|
+
await relayRichMessage(jid, fullMsg, options);
|
|
1390
|
+
return fullMsg;
|
|
1357
1391
|
},
|
|
1358
1392
|
sendMetaAI: async (a, b, c = {}) => {
|
|
1359
1393
|
// baron signature: (jid, text, opts) — README signature: (text, opts)
|
|
@@ -2353,6 +2387,52 @@ export const makeMessagesSocket = (config) => {
|
|
|
2353
2387
|
}
|
|
2354
2388
|
return fullMsg;
|
|
2355
2389
|
};
|
|
2390
|
+
/**
|
|
2391
|
+
* Sends the A2UI interactive card/menu format used by WhatsApp's large
|
|
2392
|
+
* command-card presentation. This is intentionally separate from richMenu
|
|
2393
|
+
* because it is an interactiveMessage, not an AIRich unifiedResponse.
|
|
2394
|
+
*/
|
|
2395
|
+
socket.sendA2UICommandMenu = async (jid, content = {}, options = {}) => {
|
|
2396
|
+
if (!jid) throw new Boom('sendA2UICommandMenu requires a target JID', { statusCode: 400 });
|
|
2397
|
+
const { image, ...menuContent } = content || {};
|
|
2398
|
+
let imageMessage;
|
|
2399
|
+
if (image) {
|
|
2400
|
+
const prepared = await prepareWAMessageMedia({ image }, { upload: waUploadToServer });
|
|
2401
|
+
imageMessage = prepared.imageMessage;
|
|
2402
|
+
}
|
|
2403
|
+
const wrapped = prepareA2UICommandMenu({ ...menuContent, imageMessage });
|
|
2404
|
+
const fullMsg = await generateWAMessageFromContent(jid, wrapped, {
|
|
2405
|
+
logger,
|
|
2406
|
+
userJid: authState.creds.me?.id,
|
|
2407
|
+
messageId: options.messageId || generateMessageIDV2(authState.creds.me?.id || jid),
|
|
2408
|
+
...options,
|
|
2409
|
+
});
|
|
2410
|
+
await relayMessage(jid, fullMsg.message, { messageId: fullMsg.key.id, ...options.relayOptions });
|
|
2411
|
+
if (config.emitOwnEvents) {
|
|
2412
|
+
process.nextTick(async () => {
|
|
2413
|
+
await messageMutex.mutex(() => upsertMessage(fullMsg, 'append'));
|
|
2414
|
+
});
|
|
2415
|
+
}
|
|
2416
|
+
return fullMsg;
|
|
2417
|
+
};
|
|
2418
|
+
/** Sends the manually serialized AIRich command card shown in the screenshot. */
|
|
2419
|
+
socket.sendAIRichCommandMenu = async (jid, content = {}, options = {}) => {
|
|
2420
|
+
if (!jid) throw new Boom('sendAIRichCommandMenu requires a target JID', { statusCode: 400 });
|
|
2421
|
+
const wrapped = prepareAIRichCommandMenu(content);
|
|
2422
|
+
const fullMsg = await generateWAMessageFromContent(jid, wrapped, {
|
|
2423
|
+
logger,
|
|
2424
|
+
userJid: authState.creds.me?.id,
|
|
2425
|
+
messageId: options.messageId || generateMessageIDV2(authState.creds.me?.id || jid),
|
|
2426
|
+
...options,
|
|
2427
|
+
});
|
|
2428
|
+
await relayRichMessage(jid, fullMsg, options);
|
|
2429
|
+
if (config.emitOwnEvents) {
|
|
2430
|
+
process.nextTick(async () => {
|
|
2431
|
+
await messageMutex.mutex(() => upsertMessage(fullMsg, 'append'));
|
|
2432
|
+
});
|
|
2433
|
+
}
|
|
2434
|
+
return fullMsg;
|
|
2435
|
+
};
|
|
2356
2436
|
/** Sends a rich response with experimental BotInfrastructureDiagnostics metadata. */
|
|
2357
2437
|
socket.sendBotDiagnostics = async (jid, content = {}, options = {}) => {
|
|
2358
2438
|
if (!jid) throw new Boom('sendBotDiagnostics requires a target JID', { statusCode: 400 });
|
|
@@ -2616,38 +2696,58 @@ export const makeMessagesSocket = (config) => {
|
|
|
2616
2696
|
|
|
2617
2697
|
|
|
2618
2698
|
/**
|
|
2619
|
-
* Sends
|
|
2620
|
-
* collection, this is one
|
|
2621
|
-
*
|
|
2699
|
+
* Sends a full-width vertical AIRich album. Unlike the legacy
|
|
2700
|
+
* albumMessage collection or compact GRID_IMAGE layout, this is one
|
|
2701
|
+
* forwarded unified-response message with one large media item per row.
|
|
2622
2702
|
*/
|
|
2623
2703
|
socket.sendAlbumMessage = async (jid, medias = [], options = {}) => {
|
|
2624
2704
|
if (!jid) throw new Boom('sendAlbumMessage requires a target JID', { statusCode: 400 });
|
|
2625
2705
|
if (!Array.isArray(medias) || medias.length < 2) {
|
|
2626
2706
|
throw new Boom('sendAlbumMessage requires at least two media items', { statusCode: 400 });
|
|
2627
2707
|
}
|
|
2628
|
-
const
|
|
2708
|
+
const albumItems = medias.map((media) => {
|
|
2629
2709
|
const raw = media?.type && media?.data ? media.data : (media?.image ?? media);
|
|
2630
2710
|
if (media?.video || media?.type === 'video') {
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
throw new Boom('Each GRID_IMAGE album item requires an image URL', { statusCode: 400 });
|
|
2711
|
+
const video = media?.video || raw;
|
|
2712
|
+
const sourceUrl = typeof video === 'string' ? video : (video?.url || video?.videoUrl || '');
|
|
2713
|
+
if (!sourceUrl) throw new Boom('Each video album item requires a video URL', { statusCode: 400 });
|
|
2714
|
+
return { type: 'video', video: { ...video, url: sourceUrl } };
|
|
2636
2715
|
}
|
|
2637
2716
|
return {
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
imageHighResUrl: raw?.imageHighResUrl || raw?.highResUrl || raw?.url || sourceUrl,
|
|
2641
|
-
darkModePreviewUrl: raw?.darkModePreviewUrl,
|
|
2642
|
-
darkModeHighResUrl: raw?.darkModeHighResUrl,
|
|
2643
|
-
width: raw?.width || media?.width || 600,
|
|
2644
|
-
height: raw?.height || media?.height || 400,
|
|
2645
|
-
darkWidth: raw?.darkWidth || media?.darkWidth,
|
|
2646
|
-
darkHeight: raw?.darkHeight || media?.darkHeight
|
|
2717
|
+
type: 'image',
|
|
2718
|
+
image: raw
|
|
2647
2719
|
};
|
|
2648
2720
|
});
|
|
2649
|
-
const wrapped =
|
|
2650
|
-
|
|
2721
|
+
const wrapped = prepareVerticalAlbumMessage({
|
|
2722
|
+
items: albumItems,
|
|
2723
|
+
verificationMetadata: options.verificationMetadata
|
|
2724
|
+
});
|
|
2725
|
+
const fullMsg = generateWAMessageFromContent(jid, wrapped, {
|
|
2726
|
+
logger,
|
|
2727
|
+
userJid: authState.creds.me?.id,
|
|
2728
|
+
messageId: options.messageId || generateMessageIDV2(authState.creds.me?.id || jid),
|
|
2729
|
+
...options
|
|
2730
|
+
});
|
|
2731
|
+
await relayRichMessage(jid, fullMsg, options);
|
|
2732
|
+
if (config.emitOwnEvents) {
|
|
2733
|
+
process.nextTick(async () => {
|
|
2734
|
+
await messageMutex.mutex(() => upsertMessage(fullMsg, 'append'));
|
|
2735
|
+
});
|
|
2736
|
+
}
|
|
2737
|
+
return fullMsg;
|
|
2738
|
+
};
|
|
2739
|
+
|
|
2740
|
+
/**
|
|
2741
|
+
* Sends a unified-response video grid. This uses GenAIVideoPrimitive items
|
|
2742
|
+
* inside GenAIGridLayoutViewModel; it is not the image-only GRID_IMAGE type.
|
|
2743
|
+
*/
|
|
2744
|
+
socket.sendVideoGridMessage = async (jid, videos = [], options = {}) => {
|
|
2745
|
+
if (!jid) throw new Boom('sendVideoGridMessage requires a target JID', { statusCode: 400 });
|
|
2746
|
+
if (!Array.isArray(videos) || videos.length < 2) {
|
|
2747
|
+
throw new Boom('sendVideoGridMessage requires at least two videos', { statusCode: 400 });
|
|
2748
|
+
}
|
|
2749
|
+
const wrapped = prepareVideoGridMessage({
|
|
2750
|
+
videos,
|
|
2651
2751
|
verificationMetadata: options.verificationMetadata
|
|
2652
2752
|
});
|
|
2653
2753
|
const fullMsg = generateWAMessageFromContent(jid, wrapped, {
|
|
@@ -472,6 +472,78 @@ export const prepareRichMenuMessage = (content = {}) => {
|
|
|
472
472
|
* @param {'END'|'START'|'CENTER'} [options.alignment='END'] - Button alignment
|
|
473
473
|
* @returns {Object} Wrapped botForwardedMessage ready for relayMessage
|
|
474
474
|
*/
|
|
475
|
+
/** Build the manually serialized AIRich command menu shown in the screenshot. */
|
|
476
|
+
export const prepareAIRichCommandMenu = ({ image, title = 'CODY AI', subtitle = '', code = '', rows = [], buttons = [], footer = 'Developed by - CODY AI', latexImage, latexExpression = '' } = {}) => {
|
|
477
|
+
if (!image) throw new Error('AIRich command menu requires an image URL');
|
|
478
|
+
if (!Array.isArray(rows)) throw new Error('AIRich command-menu rows must be an array');
|
|
479
|
+
if (!Array.isArray(buttons)) throw new Error('AIRich command-menu buttons must be an array');
|
|
480
|
+
const safeRows = rows.map((row, index) => ({ command: String(row?.[0] ?? row?.command ?? `command_${index + 1}`), description: String(row?.[1] ?? row?.description ?? '') }));
|
|
481
|
+
const table = ['Command | Function', '--- | ---', ...safeRows.map((row) => `${row.command} | ${row.description}`)].join('\n');
|
|
482
|
+
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' } }] : [];
|
|
483
|
+
const text = [String(title), subtitle && String(subtitle), code && `Code: ${String(code)}`, '## Command List', table, footer && String(footer)].filter(Boolean).join('\n');
|
|
484
|
+
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) } };
|
|
485
|
+
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' }));
|
|
486
|
+
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' }));
|
|
487
|
+
footerPrimitives.push({ cta_text: '☷ OPEN MENU', cta_type: 'OPEN_URL', cta_url: String(buttons[0]?.url || ''), __typename: 'GenAIFooterActionPrimitive' });
|
|
488
|
+
const sections = [
|
|
489
|
+
{ __typename: 'GenAIUnifiedResponseSection', view_model: { __typename: 'GenAISingleLayoutViewModel', primitive: imagePrimitive } },
|
|
490
|
+
...(ctas.length ? [{ __typename: 'GenAIUnifiedResponseSection', view_model: { primitives: [{ __typename: 'GenAI3PExtWidgetPrimitive', header: { __typename: 'GenAI3PExtWidgetStandardHeader', title: 'Command List' }, body: { __typename: 'GenAIExtCalendarEventList', ctas, sections: [] } }], __typename: 'GenAIActionRowLayoutViewModel' } }] : []),
|
|
491
|
+
{ __typename: 'GenAIUnifiedResponseSection', view_model: { primitive: { text: `{{NGA}}${text}{{/NGA}}`, inline_entities: inlineEntities, __typename: 'GenAIMarkdownTextUXPrimitive' }, __typename: 'GenAISingleLayoutViewModel' } },
|
|
492
|
+
{ __typename: 'GenAIUnifiedResponseSection', view_model: { primitives: footerPrimitives, __typename: 'GenAIActionRowLayoutViewModel' } }
|
|
493
|
+
];
|
|
494
|
+
const richResponseMessage = proto.AIRichResponseMessage.create({
|
|
495
|
+
submessages: [{ messageType: RichSubMessageType.TEXT, messageText: text }],
|
|
496
|
+
messageType: proto.AIRichResponseMessageType.AI_RICH_RESPONSE_TYPE_STANDARD,
|
|
497
|
+
unifiedResponse: { data: Buffer.from(JSON.stringify({ response_id: randomUUID(), sections })) },
|
|
498
|
+
contextInfo: { isForwarded: true, forwardingScore: 1, forwardedAiBotMessageInfo: { botJid: '867051314767696@bot' }, forwardOrigin: 4 }
|
|
499
|
+
});
|
|
500
|
+
return wrapToBotForwardedMessage(richResponseMessage);
|
|
501
|
+
};
|
|
502
|
+
|
|
503
|
+
/** Build the A2UI interactive command menu used by the full-size card layout. */
|
|
504
|
+
export const prepareA2UICommandMenu = ({ imageMessage, title = 'CODY AI', rows = [], buttons = [], fallback = '' } = {}) => {
|
|
505
|
+
if (!Array.isArray(rows)) throw new Error('A2UI command-menu rows must be an array');
|
|
506
|
+
if (!Array.isArray(buttons)) throw new Error('A2UI command-menu buttons must be an array');
|
|
507
|
+
const safeRows = rows.map((row, index) => ({
|
|
508
|
+
title: String(row?.[0] ?? row?.title ?? `Command ${index + 1}`),
|
|
509
|
+
description: String(row?.[1] ?? row?.description ?? ''),
|
|
510
|
+
id: String(row?.[2] ?? row?.id ?? row?.[0] ?? `command_${index + 1}`)
|
|
511
|
+
}));
|
|
512
|
+
const safeButtons = buttons.map((button, index) => ({ id: `a2ui_button_${index + 1}`, label: String(button?.text || button?.label || `Button ${index + 1}`), url: String(button?.url || '') }));
|
|
513
|
+
const componentIds = ['title', 'divider', 'tableCard', 'footer', ...(safeButtons.length ? ['buttonRow'] : [])];
|
|
514
|
+
const components = [
|
|
515
|
+
{ id: 'root', component: 'Column', align: 'center', children: componentIds },
|
|
516
|
+
{ id: 'title', component: 'Text', text: String(title), variant: 'h2' },
|
|
517
|
+
{ id: 'divider', component: 'Divider' },
|
|
518
|
+
{ id: 'tableCard', component: 'Card', child: 'tableColumn' },
|
|
519
|
+
{ id: 'tableColumn', component: 'Column', children: ['headerRow', ...safeRows.flatMap((_, index) => [`divider_${index}`, `row_${index}`])] },
|
|
520
|
+
{ id: 'headerRow', component: 'Row', children: ['headerCommand', 'headerFunction'] },
|
|
521
|
+
{ id: 'headerCommand', component: 'Text', text: 'Command', variant: 'h5' },
|
|
522
|
+
{ id: 'headerFunction', component: 'Text', text: 'Function', variant: 'h5' },
|
|
523
|
+
...safeRows.flatMap((row, index) => [
|
|
524
|
+
{ id: `divider_${index}`, component: 'Divider' },
|
|
525
|
+
{ id: `row_${index}`, component: 'Row', children: [`command_${index}`, `description_${index}`] },
|
|
526
|
+
{ id: `command_${index}`, component: 'Text', text: row.title },
|
|
527
|
+
{ id: `description_${index}`, component: 'Text', text: row.description }
|
|
528
|
+
]),
|
|
529
|
+
{ id: 'footer', component: 'Text', text: 'Developed by - CODY AI', variant: 'caption' }
|
|
530
|
+
];
|
|
531
|
+
if (safeButtons.length) {
|
|
532
|
+
components.push({ id: 'buttonRow', component: 'Row', justify: 'spaceEvenly', children: safeButtons.flatMap((button, index) => [`buttonLabel_${index}`, `button_${index}`]) });
|
|
533
|
+
safeButtons.forEach((button, index) => {
|
|
534
|
+
components.push({ id: `buttonLabel_${index}`, component: 'Text', text: button.label });
|
|
535
|
+
components.push({ id: `button_${index}`, component: 'Button', child: `buttonLabel_${index}`, variant: 'primary', action: { call: 'openUrl', args: { url: button.url } } });
|
|
536
|
+
});
|
|
537
|
+
}
|
|
538
|
+
return {
|
|
539
|
+
interactiveMessage: {
|
|
540
|
+
...(imageMessage ? { header: { imageMessage, hasMediaAttachment: true } } : {}),
|
|
541
|
+
bloksWidget: { type: 'im_a2ui', data: JSON.stringify({ version: 'v0.9', createSurface: { surfaceId: 'plogme-a2ui-command-menu', catalogId: 'https://a2ui.org/specification/v0_9/catalogs/basic/catalog.json', components } }), fallback: String(fallback || title) },
|
|
542
|
+
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 }] }) }] }
|
|
543
|
+
}
|
|
544
|
+
};
|
|
545
|
+
};
|
|
546
|
+
|
|
475
547
|
export const prepareCopyToClipboardMessage = (options = {}) => {
|
|
476
548
|
const { text = '', label, alignment = 'END', verificationMetadata } = options;
|
|
477
549
|
const displayText = label || text;
|
|
@@ -697,9 +769,114 @@ export const prepareGridImageMessage = ({ imageUrls = [], verificationMetadata }
|
|
|
697
769
|
return wrapToBotForwardedMessage(richResponseMessage, verificationMetadata);
|
|
698
770
|
};
|
|
699
771
|
|
|
772
|
+
/**
|
|
773
|
+
* Build a video grid using the client-supported GenAIVideoPrimitive.
|
|
774
|
+
*
|
|
775
|
+
* WhatsApp exposes no AIRich GRID_VIDEO submessage type. Video grids therefore
|
|
776
|
+
* use the generic unified-response Grid layout with one GenAIVideoPrimitive per
|
|
777
|
+
* item. This is deliberately separate from prepareGridImageMessage so callers
|
|
778
|
+
* do not accidentally label video media as GenAIImagePrimitive.
|
|
779
|
+
*/
|
|
780
|
+
/** Build a full-width vertical album with one large media item per row. */
|
|
781
|
+
export const prepareVerticalAlbumMessage = ({ items = [], verificationMetadata } = {}) => {
|
|
782
|
+
if (!Array.isArray(items) || items.length < 2) throw new Error('Vertical album requires at least two media items');
|
|
783
|
+
const expiration = String(Date.now() + 30 * 24 * 60 * 60 * 1000);
|
|
784
|
+
const primitives = items.map((item) => {
|
|
785
|
+
const kind = item?.type || (item?.video ? 'video' : 'image');
|
|
786
|
+
if (kind === 'video') {
|
|
787
|
+
const video = item.video || item.data || item;
|
|
788
|
+
const url = typeof video === 'string' ? video : (video?.url || video?.videoUrl || '');
|
|
789
|
+
if (!url) throw new Error('Each video album item requires a video URL');
|
|
790
|
+
return buildImaginePrimitive({ mediaType: 'video', url, thumbnail: video?.thumbnail || video?.thumbnailUrl, mimeType: video?.mimeType || video?.mime_type || 'video/mp4', fileLength: video?.fileLength || video?.file_length || 0, duration: video?.duration || 0, status: 'READY' });
|
|
791
|
+
}
|
|
792
|
+
const image = item?.image || item?.data || item;
|
|
793
|
+
const sourceUrl = typeof image === 'string' ? image : (image?.sourceUrl || image?.url || '');
|
|
794
|
+
if (!sourceUrl) throw new Error('Each image album item requires an image URL');
|
|
795
|
+
const previewUrl = image?.imagePreviewUrl || image?.previewUrl || sourceUrl;
|
|
796
|
+
const fullUrl = image?.imageHighResUrl || image?.highResUrl || image?.url || sourceUrl;
|
|
797
|
+
const width = image?.width || item?.width || 600;
|
|
798
|
+
const height = image?.height || item?.height || 400;
|
|
799
|
+
const mimeType = image?.mimeType || image?.mime_type || 'image/png';
|
|
800
|
+
return {
|
|
801
|
+
preview_image: { url: previewUrl, mime_type: mimeType, expiration_timestamp_ms: expiration, width, height },
|
|
802
|
+
full_image: { url: fullUrl, mime_type: mimeType, expiration_timestamp_ms: expiration, width, height },
|
|
803
|
+
dark_mode_preview_image: { url: image?.darkModePreviewUrl || previewUrl, mime_type: mimeType, expiration_timestamp_ms: expiration, width, height },
|
|
804
|
+
dark_mode_full_image: { url: image?.darkModeHighResUrl || image?.darkModePreviewUrl || fullUrl, mime_type: mimeType, expiration_timestamp_ms: expiration, width, height },
|
|
805
|
+
asset_query_status: 'FETCHED',
|
|
806
|
+
__typename: 'GenAIImagePrimitive'
|
|
807
|
+
};
|
|
808
|
+
});
|
|
809
|
+
const richResponseMessage = proto.AIRichResponseMessage.create({
|
|
810
|
+
submessages: [],
|
|
811
|
+
messageType: proto.AIRichResponseMessageType.AI_RICH_RESPONSE_TYPE_STANDARD,
|
|
812
|
+
unifiedResponse: { data: Buffer.from(JSON.stringify({ response_id: randomUUID(), sections: [{ view_model: { primitives, __typename: 'GenAIVStackLayoutViewModel' } }] })) },
|
|
813
|
+
contextInfo: { isForwarded: true, forwardingScore: 1, forwardedAiBotMessageInfo: { botJid: '867051314767696@bot' }, forwardOrigin: 4 }
|
|
814
|
+
});
|
|
815
|
+
return wrapToBotForwardedMessage(richResponseMessage, verificationMetadata);
|
|
816
|
+
};
|
|
817
|
+
|
|
818
|
+
export const prepareVideoGridMessage = ({ videos = [], verificationMetadata } = {}) => {
|
|
819
|
+
if (!Array.isArray(videos) || videos.length < 2) {
|
|
820
|
+
throw new Error('Video grid requires at least two videos');
|
|
821
|
+
}
|
|
822
|
+
const primitives = videos.map((video) => {
|
|
823
|
+
const url = typeof video === 'string' ? video : (video?.url || video?.videoUrl || video?.reelsUrl || '');
|
|
824
|
+
if (!url) throw new Error('Each video-grid item requires a video URL');
|
|
825
|
+
const item = typeof video === 'string' ? {} : video;
|
|
826
|
+
return {
|
|
827
|
+
post_id: item.postId == null ? undefined : String(item.postId),
|
|
828
|
+
reels_url: url,
|
|
829
|
+
reels_title: item.title,
|
|
830
|
+
thumbnail_url: item.thumbnailUrl || item.thumbnail,
|
|
831
|
+
creator: item.creator,
|
|
832
|
+
avatar_url: item.avatarUrl,
|
|
833
|
+
content_hash: item.contentHash,
|
|
834
|
+
likes_count: item.likes,
|
|
835
|
+
comments_count: item.comments,
|
|
836
|
+
shares_count: item.shares,
|
|
837
|
+
is_verified: item.isVerified,
|
|
838
|
+
source_app: item.sourceApp,
|
|
839
|
+
video_delivery_response: {
|
|
840
|
+
progressive_urls: (item.progressiveUrls || []).map((value) => ({
|
|
841
|
+
progressive_url: value,
|
|
842
|
+
__typename: 'GenAIProgressiveUrlResponse'
|
|
843
|
+
})),
|
|
844
|
+
dash_manifests: (item.dashManifests || []).map((value) => ({
|
|
845
|
+
manifest_xml: value,
|
|
846
|
+
__typename: 'GenAIDashManifestResponse'
|
|
847
|
+
})),
|
|
848
|
+
__typename: 'GenAIVideoDeliveryResponse'
|
|
849
|
+
},
|
|
850
|
+
__typename: 'GenAIVideoPrimitive'
|
|
851
|
+
};
|
|
852
|
+
});
|
|
853
|
+
const unified = {
|
|
854
|
+
response_id: randomUUID(),
|
|
855
|
+
sections: [{
|
|
856
|
+
view_model: {
|
|
857
|
+
primitives,
|
|
858
|
+
__typename: 'GenAIGridLayoutViewModel'
|
|
859
|
+
}
|
|
860
|
+
}]
|
|
861
|
+
};
|
|
862
|
+
const richResponseMessage = proto.AIRichResponseMessage.create({
|
|
863
|
+
submessages: [],
|
|
864
|
+
messageType: proto.AIRichResponseMessageType.AI_RICH_RESPONSE_TYPE_STANDARD,
|
|
865
|
+
unifiedResponse: { data: Buffer.from(JSON.stringify(unified)) },
|
|
866
|
+
contextInfo: {
|
|
867
|
+
isForwarded: true,
|
|
868
|
+
forwardingScore: 1,
|
|
869
|
+
forwardedAiBotMessageInfo: { botJid: '867051314767696@bot' },
|
|
870
|
+
forwardOrigin: 4
|
|
871
|
+
}
|
|
872
|
+
});
|
|
873
|
+
return wrapToBotForwardedMessage(richResponseMessage, verificationMetadata);
|
|
874
|
+
};
|
|
875
|
+
|
|
700
876
|
export const prepareRichResponseMessage = (content) => {
|
|
701
877
|
const { alignment, code, contentText, disclaimerText, footerText, headerText, imageText, inlineImage, inlineVideo, items, language, latex, links, noDonation, noHeading, posts, products, reels, sources: sourceInputs, suggested, richResponse, table, tapLinkUrl, title, verificationMetadata } = content;
|
|
702
878
|
let submessages = [];
|
|
879
|
+
const extraSections = [];
|
|
703
880
|
if (Array.isArray(richResponse)) {
|
|
704
881
|
submessages = richResponse.map((submessage) => {
|
|
705
882
|
if (submessage.text) {
|
|
@@ -843,9 +1020,21 @@ export const prepareRichResponseMessage = (content) => {
|
|
|
843
1020
|
});
|
|
844
1021
|
}
|
|
845
1022
|
if (inlineVideo) {
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
1023
|
+
const video = typeof inlineVideo === 'string' ? { url: inlineVideo } : inlineVideo;
|
|
1024
|
+
submessages.push({ messageType: RichSubMessageType.TEXT, messageText: '[ CANNOT_LOAD_VIDEO - NIXEL ]' });
|
|
1025
|
+
extraSections.push({
|
|
1026
|
+
view_model: {
|
|
1027
|
+
primitive: buildImaginePrimitive({
|
|
1028
|
+
mediaType: 'video',
|
|
1029
|
+
url: video?.url || video?.videoUrl || '',
|
|
1030
|
+
mimeType: video?.mimeType || video?.mime_type || 'video/mp4',
|
|
1031
|
+
fileLength: video?.fileLength || video?.file_length || 0,
|
|
1032
|
+
duration: video?.duration || 0,
|
|
1033
|
+
thumbnail: video?.thumbnail,
|
|
1034
|
+
status: video?.status || 'READY'
|
|
1035
|
+
}),
|
|
1036
|
+
__typename: 'GenAISingleLayoutViewModel'
|
|
1037
|
+
}
|
|
849
1038
|
});
|
|
850
1039
|
}
|
|
851
1040
|
if (latex) {
|
|
@@ -863,7 +1052,40 @@ export const prepareRichResponseMessage = (content) => {
|
|
|
863
1052
|
title: 'PAIR CRYSN⚉VA AI ⩇⩇:⩇⩇',
|
|
864
1053
|
displayName: 'pair bot'
|
|
865
1054
|
};
|
|
866
|
-
const
|
|
1055
|
+
const rawSources = Array.isArray(sourceInputs) ? sourceInputs : [];
|
|
1056
|
+
// Existing `sources: [{ url, displayName, subtitle }]` callers use
|
|
1057
|
+
// citation entities. The serialized AIRich addSource format is the
|
|
1058
|
+
// distinct tuple form: `[[faviconUrl, sourceUrl, displayText], ...]`.
|
|
1059
|
+
const sourceTuples = rawSources.length > 0 && rawSources.every((source) => Array.isArray(source));
|
|
1060
|
+
if (sourceTuples) {
|
|
1061
|
+
const sourceCards = rawSources.map((source) => {
|
|
1062
|
+
const item = Array.isArray(source)
|
|
1063
|
+
? { icon: source[0], url: source[1], text: source[2] }
|
|
1064
|
+
: source;
|
|
1065
|
+
const sourceUrl = item.url || item.sourceUrl || item.source_url || '';
|
|
1066
|
+
return {
|
|
1067
|
+
source_type: 'THIRD_PARTY',
|
|
1068
|
+
source_display_name: item.text || item.displayName || item.sourceDisplayName || '',
|
|
1069
|
+
source_subtitle: item.subtitle || 'AI',
|
|
1070
|
+
source_url: sourceUrl,
|
|
1071
|
+
favicon: {
|
|
1072
|
+
url: item.icon || item.iconUrl || item.favicon || '',
|
|
1073
|
+
mime_type: item.mimeType || 'image/jpeg',
|
|
1074
|
+
width: item.width || 16,
|
|
1075
|
+
height: item.height || 16
|
|
1076
|
+
}
|
|
1077
|
+
};
|
|
1078
|
+
}).filter((source) => source.source_url || source.source_display_name);
|
|
1079
|
+
if (sourceCards.length) {
|
|
1080
|
+
extraSections.push({
|
|
1081
|
+
view_model: {
|
|
1082
|
+
primitive: { sources: sourceCards, __typename: 'GenAISearchResultPrimitive' },
|
|
1083
|
+
__typename: 'GenAISingleLayoutViewModel'
|
|
1084
|
+
}
|
|
1085
|
+
});
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
const sourceLinks = (sourceTuples ? [] : (sourceInputs || links || [])).map((linkField) => {
|
|
867
1089
|
if (typeof linkField === 'string') return { text: '', url: linkField, displayName: linkField };
|
|
868
1090
|
return { text: linkField?.text || linkField?.displayName || linkField?.title || '', ...linkField };
|
|
869
1091
|
});
|
|
@@ -898,14 +1120,53 @@ export const prepareRichResponseMessage = (content) => {
|
|
|
898
1120
|
if (posts) {
|
|
899
1121
|
submessages.push({
|
|
900
1122
|
messageType: RichSubMessageType.TEXT,
|
|
901
|
-
messageText: '
|
|
1123
|
+
messageText: '[ CANNOT_LOAD_POST - NIXEL ]'
|
|
902
1124
|
});
|
|
1125
|
+
const postItems = (Array.isArray(posts) ? posts : [posts]).map((post) => ({
|
|
1126
|
+
title: post?.title || '',
|
|
1127
|
+
subtitle: post?.subtitle || '',
|
|
1128
|
+
username: post?.username || post?.user || '',
|
|
1129
|
+
profile_picture_url: post?.profile_picture_url || post?.profile || post?.profile_url || '',
|
|
1130
|
+
is_verified: !!(post?.is_verified ?? post?.verified),
|
|
1131
|
+
thumbnail_url: post?.thumbnail_url || post?.thumbnail || '',
|
|
1132
|
+
post_caption: post?.post_caption || post?.caption || '',
|
|
1133
|
+
likes_count: post?.likes_count ?? post?.like ?? 0,
|
|
1134
|
+
comments_count: post?.comments_count ?? post?.comment ?? 0,
|
|
1135
|
+
shares_count: post?.shares_count ?? post?.share ?? 0,
|
|
1136
|
+
post_url: post?.post_url || post?.url || '',
|
|
1137
|
+
post_deeplink: post?.post_deeplink || post?.deeplink || '',
|
|
1138
|
+
source_app: post?.source_app || post?.source || '',
|
|
1139
|
+
footer_label: post?.footer_label || post?.footer || '',
|
|
1140
|
+
footer_icon: post?.footer_icon || post?.icon || '',
|
|
1141
|
+
is_carousel: post?.is_carousel ?? true,
|
|
1142
|
+
orientation: post?.orientation || 'LANDSCAPE',
|
|
1143
|
+
post_type: post?.post_type || 'VIDEO',
|
|
1144
|
+
__typename: 'GenAIPostPrimitive'
|
|
1145
|
+
}));
|
|
1146
|
+
if (postItems.length) extraSections.push({ view_model: { primitives: postItems, __typename: 'GenAIHScrollLayoutViewModel' } });
|
|
903
1147
|
}
|
|
904
1148
|
if (products) {
|
|
905
1149
|
submessages.push({
|
|
906
1150
|
messageType: RichSubMessageType.TEXT,
|
|
907
|
-
messageText: '
|
|
1151
|
+
messageText: '[ CANNOT_LOAD_PRODUCT - NIXEL ]'
|
|
908
1152
|
});
|
|
1153
|
+
const productItems = (Array.isArray(products) ? products : [products]).map((product) => ({
|
|
1154
|
+
title: product?.title || '',
|
|
1155
|
+
brand: product?.brand || '',
|
|
1156
|
+
price: product?.price || '',
|
|
1157
|
+
sale_price: product?.sale_price || product?.salePrice || '',
|
|
1158
|
+
product_url: product?.product_url || product?.url || '',
|
|
1159
|
+
image: { url: product?.image?.url || product?.image || '' },
|
|
1160
|
+
additional_images: [{ url: product?.icon_url || product?.icon || '' }],
|
|
1161
|
+
__typename: 'GenAIProductItemCardPrimitive'
|
|
1162
|
+
}));
|
|
1163
|
+
if (productItems.length) {
|
|
1164
|
+
extraSections.push({
|
|
1165
|
+
view_model: Array.isArray(products)
|
|
1166
|
+
? { primitives: productItems, __typename: 'GenAIHScrollLayoutViewModel' }
|
|
1167
|
+
: { primitive: productItems[0], __typename: 'GenAISingleLayoutViewModel' }
|
|
1168
|
+
});
|
|
1169
|
+
}
|
|
909
1170
|
}
|
|
910
1171
|
if (suggested) {
|
|
911
1172
|
submessages.push({
|
|
@@ -934,6 +1195,7 @@ export const prepareRichResponseMessage = (content) => {
|
|
|
934
1195
|
}
|
|
935
1196
|
const uuid = randomUUID();
|
|
936
1197
|
const unified = toUnified(submessages, uuid);
|
|
1198
|
+
unified.sections = [...unified.sections.filter(Boolean), ...extraSections];
|
|
937
1199
|
const richResponseMessage = proto.AIRichResponseMessage.create({
|
|
938
1200
|
submessages,
|
|
939
1201
|
messageType: proto.AIRichResponseMessageType.AI_RICH_RESPONSE_TYPE_STANDARD,
|
package/lib/index.d.ts
CHANGED
|
@@ -224,7 +224,8 @@ export interface RelayMessageOptions {
|
|
|
224
224
|
|
|
225
225
|
export interface AlbumMediaInput {
|
|
226
226
|
image?: unknown;
|
|
227
|
-
|
|
227
|
+
video?: unknown;
|
|
228
|
+
type?: 'image' | 'video';
|
|
228
229
|
data?: unknown;
|
|
229
230
|
sourceUrl?: string;
|
|
230
231
|
imagePreviewUrl?: string;
|
|
@@ -246,10 +247,50 @@ export interface AnimatedWallpaperInput {
|
|
|
246
247
|
opacity?: number;
|
|
247
248
|
}
|
|
248
249
|
|
|
250
|
+
export interface A2UICommandMenuContent {
|
|
251
|
+
image?: unknown;
|
|
252
|
+
title?: string;
|
|
253
|
+
rows?: Array<[string, string, string?] | { title?: string; description?: string; id?: string }>;
|
|
254
|
+
buttons?: Array<{ text?: string; label?: string; url?: string }>;
|
|
255
|
+
fallback?: string;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
export interface AIRichCommandMenuContent {
|
|
259
|
+
image: string;
|
|
260
|
+
title?: string;
|
|
261
|
+
subtitle?: string;
|
|
262
|
+
code?: string;
|
|
263
|
+
rows?: Array<[string, string] | { command?: string; description?: string }>;
|
|
264
|
+
latexImage?: string;
|
|
265
|
+
latexExpression?: string;
|
|
266
|
+
buttons?: Array<{ id?: string; text?: string; label?: string; url?: string; toast?: string }>;
|
|
267
|
+
footer?: string;
|
|
268
|
+
}
|
|
269
|
+
|
|
249
270
|
export interface WASocket {
|
|
250
271
|
sendMessage(jid: string | string[], content: StatusContent | GroupStatusContent | Record<string, unknown>, options?: Partial<StatusOptions> & Record<string, unknown>): Promise<unknown>;
|
|
251
|
-
/** Send at least two image URL items as
|
|
272
|
+
/** Send at least two image/video URL items as a full-width AIRich vertical album. */
|
|
252
273
|
sendAlbumMessage(jid: string, medias: AlbumMediaInput[], options?: Record<string, unknown>): Promise<unknown>;
|
|
274
|
+
/** Send an AIRich unified-response grid of GenAIVideoPrimitive items. Requires at least two video URLs. */
|
|
275
|
+
sendVideoGridMessage(jid: string, videos: Array<string | {
|
|
276
|
+
url?: string;
|
|
277
|
+
videoUrl?: string;
|
|
278
|
+
reelsUrl?: string;
|
|
279
|
+
postId?: string | number;
|
|
280
|
+
title?: string;
|
|
281
|
+
thumbnailUrl?: string;
|
|
282
|
+
thumbnail?: string;
|
|
283
|
+
creator?: string;
|
|
284
|
+
avatarUrl?: string;
|
|
285
|
+
contentHash?: string;
|
|
286
|
+
likes?: number;
|
|
287
|
+
comments?: number;
|
|
288
|
+
shares?: number;
|
|
289
|
+
isVerified?: boolean;
|
|
290
|
+
sourceApp?: string;
|
|
291
|
+
progressiveUrls?: string[];
|
|
292
|
+
dashManifests?: string[];
|
|
293
|
+
}>, options?: Record<string, unknown>): Promise<unknown>;
|
|
253
294
|
/** Update the connected account's WhatsApp About/profile status. */
|
|
254
295
|
updateProfileStatus(status: string): Promise<void>;
|
|
255
296
|
/** Update the connected account's profile name. */
|
|
@@ -260,6 +301,10 @@ export interface WASocket {
|
|
|
260
301
|
relayMessage(jid: string, message: Record<string, unknown>, options?: RelayMessageOptions): Promise<unknown>;
|
|
261
302
|
/** Send an experimental Meta AI-style rich menu with image, card buttons, and an optional URL footer. */
|
|
262
303
|
richMenu(jid: string, content?: RichMenuContent, options?: Record<string, unknown>): Promise<unknown>;
|
|
304
|
+
/** Send the large A2UI card/menu with an image header, command table, URL buttons, and native-flow selector. */
|
|
305
|
+
sendA2UICommandMenu(jid: string, content?: A2UICommandMenuContent, options?: Record<string, unknown>): Promise<unknown>;
|
|
306
|
+
/** Send the manually serialized AIRich command card with image, Markdown, widget CTAs, and footer actions. */
|
|
307
|
+
sendAIRichCommandMenu(jid: string, content: AIRichCommandMenuContent, options?: Record<string, unknown>): Promise<unknown>;
|
|
263
308
|
/** Send raw HTML through the experimental WhatsApp HTML primitive. */
|
|
264
309
|
sendHtmlMessage(jid: string, content: { html: string; trustedSources?: string[] }, options?: Record<string, unknown>): Promise<unknown>;
|
|
265
310
|
/** Post a personal status using explicit viewers or a configured contact source/provider. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "plogme",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.4",
|
|
4
4
|
"description": "Production-ready Baileys fork ⚉ with AI groups, Meta AI prompting, safe compositing fallbacks, rich responses, interactive messages, albums, newsletters, and communities — without antiban wrappers.",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"type": "module",
|