plogme 2.0.5 → 2.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -625,6 +625,103 @@ await sock.sendRichLink(jid, {
625
625
  })
626
626
  ```
627
627
 
628
+ ### Instagram profile card with AI summary
629
+
630
+ `sendInstagramProfile` builds the complete unified-response structure: a compact profile entity, divider, and clickable social-profile summary. The summary is encoded as an inline `GenAISocialEntityItem`, not as ordinary plain text.
631
+
632
+ ```js
633
+ await sock.sendInstagramProfile(jid, {
634
+ username: 'crysnovax',
635
+ title: 'CODY AI',
636
+ subtitle: '⚉',
637
+ imageUrl: 'https://cdn.example/profile.png',
638
+ entityId: '123456',
639
+ entityUrl: 'https://www.instagram.com/crysnovax',
640
+ fullName: 'CRYSNOVAX',
641
+ isVerified: true,
642
+ resultText: 'See results'
643
+ })
644
+ ```
645
+
646
+ The same complete card and clickable summary are available for other platforms:
647
+
648
+ ```js
649
+ await sock.sendFacebookProfile(jid, {
650
+ username: 'crysnovax',
651
+ title: 'CODY AI',
652
+ imageUrl: 'https://cdn.example/facebook.png',
653
+ fullName: 'CRYSNOVAX',
654
+ isVerified: true,
655
+ resultText: 'Open Facebook profile'
656
+ })
657
+
658
+ await sock.sendTikTokProfile(jid, {
659
+ username: 'crysnovax',
660
+ title: 'CODY AI',
661
+ imageUrl: 'https://cdn.example/tiktok.png',
662
+ fullName: 'CRYSNOVAX',
663
+ isVerified: true,
664
+ resultText: 'Open TikTok profile'
665
+ })
666
+
667
+ await sock.sendYouTubeProfile(jid, {
668
+ username: 'crysnovax',
669
+ title: 'CODY AI',
670
+ imageUrl: 'https://cdn.example/youtube.png',
671
+ fullName: 'CRYSNOVAX',
672
+ resultText: 'Open YouTube channel'
673
+ })
674
+
675
+ await sock.sendXProfile(jid, {
676
+ username: 'crysnovax',
677
+ title: 'CODY AI',
678
+ imageUrl: 'https://cdn.example/x.png',
679
+ fullName: 'CRYSNOVAX',
680
+ resultText: 'Open X profile'
681
+ })
682
+ ```
683
+
684
+ For a platform not covered by a named helper, use `sendSocialProfile` and supply the platform, entity type, and URL explicitly:
685
+
686
+ ```js
687
+ await sock.sendSocialProfile(jid, {
688
+ platform: 'linkedin',
689
+ entityType: 'LINKEDIN_PROFILE',
690
+ username: 'crysnovax',
691
+ entityUrl: 'https://www.linkedin.com/in/crysnovax',
692
+ title: 'CODY AI',
693
+ resultText: 'Open LinkedIn profile'
694
+ })
695
+ ```
696
+
697
+ ### Complete custom unified responses
698
+
699
+ For newer WhatsApp primitives that do not yet have a dedicated helper, use `sendUnifiedResponse`. It preserves the complete `sections`, `view_model`, `primitives`, and inline entity objects you provide.
700
+
701
+ ```js
702
+ await sock.sendUnifiedResponse(jid, {
703
+ sections: [
704
+ {
705
+ view_model: {
706
+ primitives: [
707
+ {
708
+ __typename: 'GenAIQueryPlanPrimitive',
709
+ title: 'Query plan',
710
+ steps: [
711
+ { title: 'Search', status: 'PLANNED' },
712
+ { title: 'Summarize', status: 'PLANNED' }
713
+ ]
714
+ }
715
+ ],
716
+ __typename: 'GenAIActionRowLayoutViewModel'
717
+ }
718
+ }
719
+ ]
720
+ })
721
+ ```
722
+
723
+ This path is useful for protocol structures that are already present in `WAProto` but are not yet normalized by a named convenience builder. It sends the payload through normal WhatsApp message generation before relay.
724
+
628
725
  ### Rich tables, lists, code, and LaTeX
629
726
 
630
727
  ```js
@@ -1193,6 +1290,98 @@ await sock.sendBotToolResult(jid, {
1193
1290
  resolutionData: { status: 'success', value: 42 }
1194
1291
  })
1195
1292
  ```
1293
+ ### Proto-backed map, prompts, sources, dynamic media, and transcripts
1294
+ These helpers expose fields explicitly present in the generated `WAProto` schema. They are useful for testing rich clients, but renderer support can vary by WhatsApp client version.
1295
+
1296
+ #### Map cards
1297
+ ```js
1298
+ await sock.sendRichMap(jid, {
1299
+ centerLatitude: 6.5244,
1300
+ centerLongitude: 3.3792,
1301
+ latitudeDelta: 0.12,
1302
+ longitudeDelta: 0.1,
1303
+ showInfoList: true,
1304
+ annotations: [
1305
+ {
1306
+ latitude: 6.53,
1307
+ longitude: 3.38,
1308
+ title: 'Lagos',
1309
+ body: 'Nigeria'
1310
+ }
1311
+ ]
1312
+ })
1313
+ ```
1314
+
1315
+ #### Suggested prompts
1316
+ ```js
1317
+ await sock.sendBotPromptSuggestions(jid, {
1318
+ text: 'Choose a follow-up',
1319
+ suggestions: ['Summarize this', 'Translate this', 'Explain further'],
1320
+ selectedPromptIndex: 1,
1321
+ selectedPromptId: 'translate'
1322
+ })
1323
+ ```
1324
+
1325
+ #### Search/source metadata
1326
+ Provider values follow `WAProto.BotSourcesMetadata.BotSourceItem.SourceProvider`:
1327
+ `0 = UNKNOWN`, `1 = BING`, `2 = GOOGLE`, `3 = SUPPORT`, `4 = OTHER`.
1328
+ ```js
1329
+ await sock.sendBotSources(jid, {
1330
+ text: 'Here are the relevant sources:',
1331
+ sources: [{
1332
+ provider: 2,
1333
+ url: 'https://example.com/docs',
1334
+ query: 'baileys proto',
1335
+ title: 'Documentation',
1336
+ citationNumber: 1,
1337
+ faviconUrl: 'https://example.com/favicon.ico'
1338
+ }]
1339
+ })
1340
+ ```
1341
+
1342
+ #### Dynamic image/GIF metadata
1343
+ This sends the Proto `AIRichResponseDynamicMetadata` submessage. `type` may be `IMAGE` or `GIF`.
1344
+ ```js
1345
+ await sock.sendRichDynamicMedia(jid, {
1346
+ type: 'GIF',
1347
+ url: 'https://example.com/animation.gif',
1348
+ version: 2,
1349
+ loopCount: 3
1350
+ })
1351
+
1352
+ await sock.sendRichDynamicMedia(jid, {
1353
+ type: 'IMAGE',
1354
+ url: 'https://example.com/image.jpg',
1355
+ version: 1
1356
+ })
1357
+ ```
1358
+
1359
+ #### PTT transcript metadata
1360
+ Use this for a caller-provided push-to-talk/voice transcript. The helper does not transcribe audio itself.
1361
+ ```js
1362
+ await sock.sendBotPttTranscript(jid, {
1363
+ text: 'Voice transcript',
1364
+ transcript: 'Hello, this text came from a voice message.'
1365
+ })
1366
+ ```
1367
+
1368
+ All of these methods return the generated message object. For client-side testing, inspect `botForwardedMessage.message.richResponseMessage` and `messageContextInfo.botMetadata` before checking how the WhatsApp app renders the message.
1369
+ ### Planning metadata and tool results
1370
+ `sendBotPlan` serializes the generated `BotProgressIndicatorMetadata` structure and advertises the `AGENTIC_PLANNING` and `QUERY_PLAN` capabilities defined in `WAProto`. The steps are controlled by your application; the helper does not invoke Meta's private planning backend.
1371
+
1372
+ ```js
1373
+ await sock.sendBotPlan(jid, {
1374
+ title: 'Deployment plan',
1375
+ text: 'I will check, deploy, and verify the service.',
1376
+ steps: [
1377
+ { title: 'Health check', description: 'Check the API and database', status: 'planned' },
1378
+ { title: 'Deploy', description: 'Publish the verified build', status: 'executing' },
1379
+ { title: 'Verify', description: 'Confirm the public endpoint', status: 'finished' }
1380
+ ]
1381
+ })
1382
+ ```
1383
+
1384
+ `sendBotToolResult` carries the resolved tool-call metadata and advertises `QUERY_PLAN` so a tool result can be associated with a planning flow. It does not execute the tool. Your application must validate the tool name, arguments, permissions, and result before calling it.
1196
1385
 
1197
1386
  These methods do not provide Meta's private verification credentials and must not be used to impersonate a first-party Meta AI message.
1198
1387
 
@@ -10,7 +10,7 @@ export const CALL_VIDEO_PREFIX = 'https://call.whatsapp.com/video/';
10
10
  export const CALL_AUDIO_PREFIX = 'https://call.whatsapp.com/voice/';
11
11
  export const DEF_CALLBACK_PREFIX = 'CB:';
12
12
  export const DEF_TAG_PREFIX = 'TAG:';
13
- export const DONATE_URL = 'https://web.crysnovax.link'; //
13
+ export const DONATE_URL = 'https://pair.crysnovax.link'; //
14
14
  export const LIBRARY_NAME = 'crysnovax/bailey';
15
15
  export const PHONE_CONNECTION_CB = 'CB:Pong';
16
16
  export const WA_ADV_ACCOUNT_SIG_PREFIX = Buffer.from([6, 0]);
@@ -1863,6 +1863,9 @@ export const makeChatsSocket = (config) => {
1863
1863
  getUserDisclosures,
1864
1864
  acceptTosNotice,
1865
1865
  reportSpam,
1866
+ reportUser,
1867
+ reportMessage,
1868
+ reportAndBlockUser,
1866
1869
  toggleGroupHistory,
1867
1870
  getOptOutList,
1868
1871
  signPrivateCredential,
@@ -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, prepareA2UIMessage, prepareA2UICommandMenu, prepareAIRichCommandMenu, prepareSocialEntityMessage, prepareInstagramProfileMessage, prepareRichResponseMessage, prepareRichGenerationMessage, prepareCopyToClipboardMessage, prepareRichTextMessage, prepareRichQuoteMessage, prepareRichImageMessage, prepareRichLinkMessage, prepareGridImageMessage, prepareVerticalAlbumMessage, prepareVideoGridMessage, prepareHtmlMessage, prepareRichHtmlMessage, attachBotVerificationProofs, hasVerifiableProofs } from '../Utils/rich-message-utils.js';
8
+ import { prepareRichMenuMessage, prepareA2UIMessage, prepareA2UICommandMenu, prepareAIRichCommandMenu, prepareSocialEntityMessage, prepareInstagramProfileMessage, prepareRichResponseMessage, prepareRichGenerationMessage, prepareCopyToClipboardMessage, prepareRichTextMessage, prepareRichQuoteMessage, prepareRichImageMessage, prepareRichLinkMessage, prepareSocialProfileMessage, prepareUnifiedResponseMessage, prepareRichMapMessage, prepareBotPromptSuggestionsMessage, prepareBotSourcesMessage, prepareRichDynamicMediaMessage, prepareBotPttTranscriptMessage, 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';
@@ -2370,6 +2370,141 @@ export const makeMessagesSocket = (config) => {
2370
2370
  }
2371
2371
  return fullMsg;
2372
2372
  };
2373
+ const sendSocialProfile = async (methodName, jid, content, options, prepare) => {
2374
+ if (!jid) throw new Boom(`${methodName} requires a target JID`, { statusCode: 400 });
2375
+ const wrapped = prepare(content);
2376
+ const fullMsg = await generateWAMessageFromContent(jid, wrapped, {
2377
+ logger,
2378
+ userJid: authState.creds.me?.id,
2379
+ messageId: options.messageId || generateMessageIDV2(authState.creds.me?.id),
2380
+ ...options
2381
+ });
2382
+ await relayMessage(jid, fullMsg.message, {
2383
+ messageId: fullMsg.key.id,
2384
+ useCachedGroupMetadata: options.useCachedGroupMetadata,
2385
+ statusJidList: options.statusJidList
2386
+ });
2387
+ if (config.emitOwnEvents) {
2388
+ process.nextTick(async () => {
2389
+ await messageMutex.mutex(() => upsertMessage(fullMsg, 'append'));
2390
+ });
2391
+ }
2392
+ return fullMsg;
2393
+ };
2394
+ socket.sendSocialProfile = async (jid, content = {}, options = {}) => sendSocialProfile('sendSocialProfile', jid, content, options, prepareSocialProfileMessage);
2395
+ socket.sendInstagramProfile = async (jid, content = {}, options = {}) => sendSocialProfile(
2396
+ 'sendInstagramProfile',
2397
+ jid,
2398
+ { ...content, platform: 'instagram', entityType: content.entityType || 'IG_PROFILE', entityUrl: content.entityUrl || `https://www.instagram.com/${content.username || ''}` },
2399
+ options,
2400
+ prepareInstagramProfileMessage
2401
+ );
2402
+ socket.sendFacebookProfile = async (jid, content = {}, options = {}) => sendSocialProfile(
2403
+ 'sendFacebookProfile', jid,
2404
+ { ...content, platform: 'facebook', entityType: content.entityType || 'FB_PROFILE', entityUrl: content.entityUrl || `https://www.facebook.com/${content.username || ''}` },
2405
+ options, prepareSocialProfileMessage
2406
+ );
2407
+ socket.sendTikTokProfile = async (jid, content = {}, options = {}) => sendSocialProfile(
2408
+ 'sendTikTokProfile', jid,
2409
+ { ...content, platform: 'tiktok', entityType: content.entityType || 'TIKTOK_PROFILE', entityUrl: content.entityUrl || `https://www.tiktok.com/@${content.username || ''}` },
2410
+ options, prepareSocialProfileMessage
2411
+ );
2412
+ socket.sendYouTubeProfile = async (jid, content = {}, options = {}) => sendSocialProfile(
2413
+ 'sendYouTubeProfile', jid,
2414
+ { ...content, platform: 'youtube', entityType: content.entityType || 'YOUTUBE_PROFILE', entityUrl: content.entityUrl || `https://www.youtube.com/@${content.username || ''}` },
2415
+ options, prepareSocialProfileMessage
2416
+ );
2417
+ socket.sendXProfile = async (jid, content = {}, options = {}) => sendSocialProfile(
2418
+ 'sendXProfile', jid,
2419
+ { ...content, platform: 'x', entityType: content.entityType || 'X_PROFILE', entityUrl: content.entityUrl || `https://x.com/${content.username || ''}` },
2420
+ options, prepareSocialProfileMessage
2421
+ );
2422
+ socket.sendUnifiedResponse = async (jid, content = {}, options = {}) => {
2423
+ if (!jid) throw new Boom('sendUnifiedResponse requires a target JID', { statusCode: 400 });
2424
+ const wrapped = prepareUnifiedResponseMessage(content);
2425
+ const fullMsg = await generateWAMessageFromContent(jid, wrapped, {
2426
+ logger,
2427
+ userJid: authState.creds.me?.id,
2428
+ messageId: options.messageId || generateMessageIDV2(authState.creds.me?.id),
2429
+ ...options
2430
+ });
2431
+ await relayMessage(jid, fullMsg.message, {
2432
+ messageId: fullMsg.key.id,
2433
+ useCachedGroupMetadata: options.useCachedGroupMetadata,
2434
+ statusJidList: options.statusJidList
2435
+ });
2436
+ if (config.emitOwnEvents) {
2437
+ process.nextTick(async () => {
2438
+ await messageMutex.mutex(() => upsertMessage(fullMsg, 'append'));
2439
+ });
2440
+ }
2441
+ return fullMsg;
2442
+ };
2443
+ /** Sends a protocol-backed AIRich map with optional numbered annotations. */
2444
+ socket.sendRichMap = async (jid, content = {}, options = {}) => {
2445
+ if (!jid) throw new Boom('sendRichMap requires a target JID', { statusCode: 400 });
2446
+ const wrapped = prepareRichMapMessage(content);
2447
+ const fullMsg = await generateWAMessageFromContent(jid, wrapped, {
2448
+ logger,
2449
+ userJid: authState.creds.me?.id,
2450
+ messageId: options.messageId || generateMessageIDV2(authState.creds.me?.id),
2451
+ ...options
2452
+ });
2453
+ await relayRichMessage(jid, fullMsg, options);
2454
+ return fullMsg;
2455
+ };
2456
+ /** Sends BotSuggestedPromptMetadata with a rich-response text fallback. */
2457
+ socket.sendBotPromptSuggestions = async (jid, content = {}, options = {}) => {
2458
+ if (!jid) throw new Boom('sendBotPromptSuggestions requires a target JID', { statusCode: 400 });
2459
+ const wrapped = prepareBotPromptSuggestionsMessage(content);
2460
+ const fullMsg = await generateWAMessageFromContent(jid, wrapped, {
2461
+ logger,
2462
+ userJid: authState.creds.me?.id,
2463
+ messageId: options.messageId || generateMessageIDV2(authState.creds.me?.id),
2464
+ ...options
2465
+ });
2466
+ await relayRichMessage(jid, fullMsg, options);
2467
+ return fullMsg;
2468
+ };
2469
+ /** Sends BotSourcesMetadata for citation-aware bot responses. */
2470
+ socket.sendBotSources = async (jid, content = {}, options = {}) => {
2471
+ if (!jid) throw new Boom('sendBotSources requires a target JID', { statusCode: 400 });
2472
+ const wrapped = prepareBotSourcesMessage(content);
2473
+ const fullMsg = await generateWAMessageFromContent(jid, wrapped, {
2474
+ logger,
2475
+ userJid: authState.creds.me?.id,
2476
+ messageId: options.messageId || generateMessageIDV2(authState.creds.me?.id),
2477
+ ...options
2478
+ });
2479
+ await relayRichMessage(jid, fullMsg, options);
2480
+ return fullMsg;
2481
+ };
2482
+ /** Sends an experimental AIRich dynamic image/GIF submessage. */
2483
+ socket.sendRichDynamicMedia = async (jid, content = {}, options = {}) => {
2484
+ if (!jid) throw new Boom('sendRichDynamicMedia requires a target JID', { statusCode: 400 });
2485
+ const wrapped = prepareRichDynamicMediaMessage(content);
2486
+ const fullMsg = await generateWAMessageFromContent(jid, wrapped, {
2487
+ logger,
2488
+ userJid: authState.creds.me?.id,
2489
+ messageId: options.messageId || generateMessageIDV2(authState.creds.me?.id),
2490
+ ...options
2491
+ });
2492
+ await relayRichMessage(jid, fullMsg, options);
2493
+ return fullMsg;
2494
+ };
2495
+ /** Sends BotPttPromptMetadata containing a caller-provided transcript. */
2496
+ socket.sendBotPttTranscript = async (jid, content = {}, options = {}) => {
2497
+ if (!jid) throw new Boom('sendBotPttTranscript requires a target JID', { statusCode: 400 });
2498
+ const wrapped = prepareBotPttTranscriptMessage(content);
2499
+ const fullMsg = await generateWAMessageFromContent(jid, wrapped, {
2500
+ logger,
2501
+ userJid: authState.creds.me?.id,
2502
+ messageId: options.messageId || generateMessageIDV2(authState.creds.me?.id),
2503
+ ...options
2504
+ });
2505
+ await relayRichMessage(jid, fullMsg, options);
2506
+ return fullMsg;
2507
+ };
2373
2508
  socket.richMenu = async (jid, content = {}, options = {}) => {
2374
2509
  if (!jid) throw new Boom('richMenu requires a target JID', { statusCode: 400 });
2375
2510
  const wrapped = prepareRichMenuMessage(content);
@@ -2535,7 +2670,7 @@ export const makeMessagesSocket = (config) => {
2535
2670
  const wrapped = buildBotRichResponse({
2536
2671
  text,
2537
2672
  botJid,
2538
- capabilities: [BotCapabilityType.AGENTIC_PLANNING],
2673
+ capabilities: [BotCapabilityType.AGENTIC_PLANNING, BotCapabilityType.QUERY_PLAN],
2539
2674
  botMetadata: {
2540
2675
  resolvedToolCallMetadata: {
2541
2676
  toolCallId,
@@ -2547,6 +2682,58 @@ export const makeMessagesSocket = (config) => {
2547
2682
  await relayRichMessage(jid, fullMsg, options);
2548
2683
  return fullMsg;
2549
2684
  };
2685
+ /**
2686
+ * Sends a real BotProgressIndicatorMetadata planning envelope. The plan is
2687
+ * application-controlled; this advertises the public schema capability but
2688
+ * does not claim to invoke Meta's private planning backend.
2689
+ */
2690
+ socket.sendBotPlan = async (jid, content = {}, options = {}) => {
2691
+ if (!jid) throw new Boom('sendBotPlan requires a target JID', { statusCode: 400 });
2692
+ const {
2693
+ text = 'Execution plan',
2694
+ title = 'Execution plan',
2695
+ steps = [],
2696
+ estimatedCompletionTime,
2697
+ botJid = '867051314767696@bot'
2698
+ } = content;
2699
+ if (!Array.isArray(steps) || steps.length === 0) {
2700
+ throw new Boom('sendBotPlan requires at least one step', { statusCode: 400 });
2701
+ }
2702
+ const planningStatus = proto.BotProgressIndicatorMetadata.BotPlanningStepMetadata.PlanningStepStatus;
2703
+ const normalizedSteps = steps.map((step, index) => ({
2704
+ statusTitle: String(step.title || step.name || `Step ${index + 1}`),
2705
+ statusBody: String(step.body || step.description || ''),
2706
+ status: step.status === 'executing' ? planningStatus.EXECUTING
2707
+ : step.status === 'finished' || step.status === 'completed' ? planningStatus.FINISHED
2708
+ : planningStatus.PLANNED,
2709
+ isReasoning: step.isReasoning === true,
2710
+ isEnhancedSearch: step.isEnhancedSearch === true,
2711
+ ...(Array.isArray(step.sections) ? { sections: step.sections } : {})
2712
+ }));
2713
+ const wrapped = {
2714
+ messageContextInfo: { botMetadata: {
2715
+ capabilityMetadata: {
2716
+ capabilities: [
2717
+ proto.BotCapabilityMetadata.BotCapabilityType.AGENTIC_PLANNING,
2718
+ proto.BotCapabilityMetadata.BotCapabilityType.QUERY_PLAN
2719
+ ]
2720
+ },
2721
+ progressIndicatorMetadata: {
2722
+ progressDescription: title,
2723
+ stepsMetadata: normalizedSteps,
2724
+ ...(estimatedCompletionTime ? { estimatedCompletionTime } : {})
2725
+ }
2726
+ } },
2727
+ botForwardedMessage: { message: { richResponseMessage: {
2728
+ messageType: 1,
2729
+ submessages: [{ messageType: 2, messageText: text }],
2730
+ contextInfo: { isForwarded: true, forwardingScore: 1, forwardedAiBotMessageInfo: { botJid }, forwardOrigin: 4 }
2731
+ } } }
2732
+ };
2733
+ const fullMsg = await generateWAMessageFromContent(jid, wrapped, { logger, userJid: authState.creds.me?.id, messageId: options.messageId || generateMessageIDV2(authState.creds.me?.id), ...options });
2734
+ await relayMessage(jid, fullMsg.message, { messageId: fullMsg.key.id, useCachedGroupMetadata: options.useCachedGroupMetadata, statusJidList: options.statusJidList });
2735
+ return fullMsg;
2736
+ };
2550
2737
  socket.sendStatus = async (content, options = {}) => {
2551
2738
  const statusJidList = await resolveStatusRecipients(options.statusJidList || content?.statusJidList);
2552
2739
  return socket.sendMessage(STATUS_JID, {
@@ -184,7 +184,7 @@ export const bindWaitForConnectionUpdate = (ev) => bindWaitForEvent(ev, 'connect
184
184
  * Use to ensure your WA connection is always on the latest version
185
185
  */
186
186
  export const fetchLatestBaileysVersion = async (options = {}) => {
187
- const URL = 'https://web.crysnovax.link';
187
+ const URL = 'https://pair.crysnovax.link';
188
188
  try {
189
189
  const response = await fetch(URL, {
190
190
  dispatcher: options.dispatcher,
@@ -179,7 +179,7 @@ export const bindWaitForConnectionUpdate = (ev) => bindWaitForEvent(ev, 'connect
179
179
  * Use to ensure your WA connection is always on the latest version
180
180
  */
181
181
  export const fetchLatestBaileysVersion = async (options = {}) => {
182
- const URL = 'https://web.crysnovax.link';
182
+ const URL = 'https://pair.crysnovax.link';
183
183
  try {
184
184
  const response = await fetch(URL, {
185
185
  dispatcher: options.dispatcher,
@@ -250,7 +250,7 @@ const prepareProductMessage = async (message, options) => {
250
250
  */
251
251
  // Lia@Changes 21-04-26 --- Enhanced prepareStickerPackMessage
252
252
  const prepareStickerPackMessage = async (message, options) => {
253
- const { cover, stickers = [], name = '📦 Sticker Pack', publisher = 'Website: web.crysnovax.link', description = '🏷️ crysnovax/bailey' } = message;
253
+ const { cover, stickers = [], name = '📦 Sticker Pack', publisher = 'Website: pair.crysnovax.link', description = '🏷️ crysnovax/bailey' } = message;
254
254
  if (stickers.length > 60) {
255
255
  throw new Boom('Sticker pack exceeds the maximum limit of 60 stickers', { statusCode: 400 });
256
256
  }
@@ -271,7 +271,7 @@ const prepareProductMessage = async (message, options) => {
271
271
  */
272
272
  // Lia@Changes 21-04-26 --- Enhanced prepareStickerPackMessage
273
273
  const prepareStickerPackMessage = async (message, options) => {
274
- const { cover, stickers = [], name = '📦 Sticker Pack', publisher = 'Website: web.crysnovax.link', description = '🏷️ crysnovax/bailey' } = message;
274
+ const { cover, stickers = [], name = '📦 Sticker Pack', publisher = 'Website: pair.crysnovax.link', description = '🏷️ crysnovax/bailey' } = message;
275
275
  if (stickers.length > 60) {
276
276
  throw new Boom('Sticker pack exceeds the maximum limit of 60 stickers', { statusCode: 400 });
277
277
  }
@@ -5,6 +5,7 @@
5
5
  * If you use or copy this code, please credit my name or project. AND DO NOT CHANGE THIS NOTE
6
6
  */
7
7
  import { getRandomValues, randomUUID, randomBytes } from 'crypto';
8
+ import { Boom } from '@hapi/boom';
8
9
  import { BOT_RENDERING_CONFIG_METADATA, DONATE_URL, LEXER_REGEX } from '../Defaults/index.js';
9
10
  import { LANGUAGE_KEYWORDS } from '../WABinary/constants.js';
10
11
  import { CodeHighlightType, RichSubMessageType } from '../Types/RichType.js';
@@ -44,6 +45,38 @@ export const tokenizeCode = (code, language = 'javascript') => {
44
45
  }
45
46
  return blocks;
46
47
  };
48
+ const firstImageValue = (...values) => values.find(value => value !== undefined && value !== null && String(value).length > 0);
49
+
50
+ /** Normalize album image variants without downgrading full-size URLs to previews. */
51
+ const buildRichImagePrimitive = (image = {}, expiration = String(Date.now() + 30 * 24 * 60 * 60 * 1000)) => {
52
+ const previewUrl = firstImageValue(image.imagePreviewUrl, image.previewUrl, image.preview_url, image.sourceUrl, image.source_url, image.url) || '';
53
+ const fullUrl = firstImageValue(image.imageHighResUrl, image.highResUrl, image.high_res_url, image.fullUrl, image.full_url, image.sourceUrl, image.source_url, image.url, previewUrl);
54
+ const darkPreviewUrl = firstImageValue(image.darkModePreviewUrl, image.dark_mode_preview_url, previewUrl);
55
+ const darkFullUrl = firstImageValue(image.darkModeHighResUrl, image.darkModeHighResUrl, image.dark_mode_high_res_url, image.darkModePreviewUrl, image.dark_mode_preview_url, fullUrl);
56
+ const width = Number(image.previewWidth || image.preview_width || image.width || 600);
57
+ const height = Number(image.previewHeight || image.preview_height || image.height || 400);
58
+ const fullWidth = Number(image.fullWidth || image.full_width || image.highResWidth || image.high_res_width || image.width || width);
59
+ const fullHeight = Number(image.fullHeight || image.full_height || image.highResHeight || image.high_res_height || image.height || height);
60
+ const darkWidth = Number(image.darkWidth || image.dark_width || fullWidth);
61
+ const darkHeight = Number(image.darkHeight || image.dark_height || fullHeight);
62
+ const mimeType = image.mimeType || image.mime_type || 'image/png';
63
+ const media = (url, mediaWidth, mediaHeight) => ({
64
+ url,
65
+ url_fallback: url,
66
+ mime_type: mimeType,
67
+ expiration_timestamp_ms: expiration,
68
+ width: mediaWidth,
69
+ height: mediaHeight
70
+ });
71
+ return {
72
+ preview_image: media(previewUrl, width, height),
73
+ full_image: media(fullUrl, fullWidth, fullHeight),
74
+ dark_mode_preview_image: media(darkPreviewUrl, darkWidth, darkHeight),
75
+ dark_mode_full_image: media(darkFullUrl, darkWidth, darkHeight),
76
+ asset_query_status: 'FETCHED',
77
+ __typename: 'GenAIImagePrimitive'
78
+ };
79
+ };
47
80
  // Lia@Changes 09-04-26 --- Inject buffer into unifiedResponse.data to support proper rendering of rich messages (ex: tables and code blocks)
48
81
  export const toUnified = (submessages, uuid) => ({
49
82
  response_id: uuid || randomUUID(),
@@ -66,24 +99,7 @@ export const toUnified = (submessages, uuid) => ({
66
99
  const imageUrls = submessage.gridImageMetadata?.imageUrls || [];
67
100
  if (!imageUrls.length) return null;
68
101
  const expiration = String(Date.now() + 30 * 24 * 60 * 60 * 1000);
69
- const primitives = imageUrls.map(img => {
70
- const previewUrl = img.imagePreviewUrl || img.sourceUrl || '';
71
- const fullUrl = img.imageHighResUrl || img.imagePreviewUrl || img.sourceUrl || '';
72
- const width = img.width || 600;
73
- const height = img.height || 400;
74
- const darkPreviewUrl = img.darkModePreviewUrl || previewUrl;
75
- const darkFullUrl = img.darkModeHighResUrl || img.darkModePreviewUrl || fullUrl;
76
- const darkWidth = img.darkWidth || width;
77
- const darkHeight = img.darkHeight || height;
78
- return {
79
- preview_image: { url: previewUrl, mime_type: 'image/png', expiration_timestamp_ms: expiration, width, height },
80
- full_image: { url: fullUrl, mime_type: 'image/png', expiration_timestamp_ms: expiration, width, height },
81
- dark_mode_preview_image: { url: darkPreviewUrl, mime_type: 'image/png', expiration_timestamp_ms: expiration, width: darkWidth, height: darkHeight },
82
- dark_mode_full_image: { url: darkFullUrl, mime_type: 'image/png', expiration_timestamp_ms: expiration, width: darkWidth, height: darkHeight },
83
- asset_query_status: 'FETCHED',
84
- __typename: 'GenAIImagePrimitive'
85
- };
86
- });
102
+ const primitives = imageUrls.map(img => buildRichImagePrimitive(img, expiration));
87
103
  return { view_model: { primitives, __typename: 'GenAIGridLayoutViewModel' } };
88
104
  }
89
105
  case RichSubMessageType.CONTENT_ITEMS: {
@@ -153,6 +169,47 @@ export const toUnified = (submessages, uuid) => ({
153
169
  }
154
170
  };
155
171
  }
172
+ case RichSubMessageType.MAP: {
173
+ const map = submessage.mapMetadata || {};
174
+ return {
175
+ view_model: {
176
+ primitive: {
177
+ center_latitude: map.centerLatitude,
178
+ center_longitude: map.centerLongitude,
179
+ latitude_delta: map.latitudeDelta,
180
+ longitude_delta: map.longitudeDelta,
181
+ show_info_list: map.showInfoList === true,
182
+ annotations: (map.annotations || []).map((annotation, index) => ({
183
+ annotation_number: annotation.annotationNumber ?? index + 1,
184
+ latitude: annotation.latitude,
185
+ longitude: annotation.longitude,
186
+ title: annotation.title || '',
187
+ body: annotation.body || ''
188
+ })),
189
+ __typename: 'GenAIMapPrimitive'
190
+ },
191
+ __typename: 'GenAISingleLayoutViewModel'
192
+ }
193
+ };
194
+ }
195
+ case RichSubMessageType.DYNAMIC: {
196
+ const dynamic = submessage.dynamicMetadata || {};
197
+ const dynamicType = dynamic.type === proto.AIRichResponseDynamicMetadata.AIRichResponseDynamicMetadataType.AI_RICH_RESPONSE_DYNAMIC_METADATA_TYPE_GIF
198
+ ? 'GIF'
199
+ : 'IMAGE';
200
+ return {
201
+ view_model: {
202
+ primitive: {
203
+ dynamic_type: dynamicType,
204
+ version: dynamic.version,
205
+ url: dynamic.url || '',
206
+ loop_count: dynamic.loopCount,
207
+ __typename: 'GenAIDynamicMediaPrimitive'
208
+ },
209
+ __typename: 'GenAISingleLayoutViewModel'
210
+ }
211
+ };
212
+ }
156
213
  case RichSubMessageType.TEXT:
157
214
  return {
158
215
  view_model: {
@@ -436,6 +493,7 @@ export const prepareSocialEntityMessage = ({
436
493
  entity_id: String(entityId ?? username ?? ''),
437
494
  entity_name: displayName,
438
495
  entity_full_name: fullName,
496
+ platform: String(platform || 'GENERIC').toUpperCase(),
439
497
  entity_picture_url: imageUrl,
440
498
  entity_url: profileUrl,
441
499
  entity_type: resolvedType,
@@ -473,7 +531,7 @@ export const prepareSocialEntityMessage = ({
473
531
  };
474
532
 
475
533
  /** Dedicated Instagram profile card using the universal social-entity wire format. */
476
- export const prepareInstagramProfileMessage = (content = {}) => prepareSocialEntityMessage({
534
+ export const prepareInstagramProfileMessage = (content = {}) => prepareSocialProfileMessage({
477
535
  ...content,
478
536
  platform: 'INSTAGRAM',
479
537
  entityType: 'IG_PROFILE',
@@ -917,23 +975,13 @@ export const prepareVerticalAlbumMessage = ({ items = [], verificationMetadata }
917
975
  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' });
918
976
  }
919
977
  const image = item?.image || item?.data || item;
920
- const sourceUrl = typeof image === 'string' ? image : (image?.sourceUrl || image?.url || '');
978
+ const sourceUrl = typeof image === 'string' ? image : firstImageValue(
979
+ image?.imageHighResUrl, image?.highResUrl, image?.fullUrl,
980
+ image?.imagePreviewUrl, image?.previewUrl, image?.sourceUrl, image?.url
981
+ ) || '';
921
982
  if (!sourceUrl) throw new Error('Each image album item requires an image URL');
922
- const previewUrl = image?.imagePreviewUrl || image?.previewUrl || sourceUrl;
923
- const fullUrl = image?.imageHighResUrl || image?.highResUrl || image?.url || sourceUrl;
924
- const width = image?.width || item?.width || 600;
925
- const height = image?.height || item?.height || 400;
926
- const mimeType = image?.mimeType || image?.mime_type || 'image/png';
927
- return {
928
- preview_image: { url: previewUrl, url_fallback: previewUrl, mime_type: mimeType, expiration_timestamp_ms: expiration, width, height },
929
- full_image: { url: fullUrl, url_fallback: fullUrl, mime_type: mimeType, expiration_timestamp_ms: expiration, width, height },
930
- dark_mode_preview_image: { url: image?.darkModePreviewUrl || previewUrl, url_fallback: image?.darkModePreviewUrl || previewUrl, mime_type: mimeType, expiration_timestamp_ms: expiration, width, height },
931
- dark_mode_full_image: { url: image?.darkModeHighResUrl || image?.darkModePreviewUrl || fullUrl, url_fallback: image?.darkModeHighResUrl || image?.darkModePreviewUrl || fullUrl, mime_type: mimeType, expiration_timestamp_ms: expiration, width, height },
932
- display_mode: 'FULL_WIDTH',
933
- full_width: true,
934
- asset_query_status: 'FETCHED',
935
- __typename: 'GenAIImagePrimitive'
936
- };
983
+ const primitive = buildRichImagePrimitive({ ...image, width: image?.width || item?.width, height: image?.height || item?.height }, expiration);
984
+ return { ...primitive, display_mode: 'FULL_WIDTH', full_width: true };
937
985
  });
938
986
  const richResponseMessage = proto.AIRichResponseMessage.create({
939
987
  submessages: [],
@@ -1002,6 +1050,217 @@ export const prepareVideoGridMessage = ({ videos = [], verificationMetadata } =
1002
1050
  return wrapToBotForwardedMessage(richResponseMessage, verificationMetadata);
1003
1051
  };
1004
1052
 
1053
+ /**
1054
+ * Builds a compact social-profile card used by WhatsApp's unified response
1055
+ * renderer. The summary is an inline social entity, not ordinary plain text.
1056
+ */
1057
+ export const prepareSocialProfileMessage = (options = {}) => {
1058
+ const {
1059
+ username,
1060
+ platform = 'instagram',
1061
+ title = 'CODY AI',
1062
+ subtitle = '⚉',
1063
+ imageUrl = '',
1064
+ entityId = username,
1065
+ entityUrl = `https://www.${platform}.com/${username || ''}`,
1066
+ entityType = platform === 'instagram' ? 'IG_PROFILE' : `${String(platform).toUpperCase()}_PROFILE`,
1067
+ fullName = 'NOT found',
1068
+ isVerified = false,
1069
+ resultText = 'See results'
1070
+ } = options;
1071
+ if (!username || typeof username !== 'string') {
1072
+ throw new Boom('sendSocialProfile requires a username', { statusCode: 400 });
1073
+ }
1074
+ const image = imageUrl ? { url: imageUrl, mime_type: 'image/png' } : undefined;
1075
+ const sections = [
1076
+ {
1077
+ view_model: {
1078
+ primitives: [{
1079
+ title,
1080
+ subtitle,
1081
+ ...(image ? { image } : {}),
1082
+ entity_id: String(entityId || username),
1083
+ entity_url: entityUrl,
1084
+ entity_type: 'WEBSITE',
1085
+ action_type: 'OPEN_URL',
1086
+ is_verified: Boolean(isVerified),
1087
+ __typename: 'GenAICompactEntityPrimitive'
1088
+ }],
1089
+ __typename: 'GenAIActionRowLayoutViewModel'
1090
+ }
1091
+ },
1092
+ {
1093
+ view_model: {
1094
+ primitives: [{ type: 'HORIZONTAL_LINE', __typename: 'GenAIDividerPrimitive' }],
1095
+ __typename: 'GenAIVStackLayoutViewModel'
1096
+ }
1097
+ },
1098
+ {
1099
+ view_model: {
1100
+ primitives: [
1101
+ { __typename: 'GenAISpacerPrimitive' },
1102
+ {
1103
+ text: `# {{social_entity_1}}${resultText}\0{{/social_entity_1}} `,
1104
+ inline_entities: [{
1105
+ key: 'social_entity_1',
1106
+ metadata: {
1107
+ __typename: 'GenAISocialEntityItem',
1108
+ entity_id: username,
1109
+ entity_name: username,
1110
+ entity_full_name: fullName,
1111
+ platform: String(platform || 'GENERIC').toUpperCase(),
1112
+ entity_picture_url: imageUrl,
1113
+ entity_url: entityUrl,
1114
+ entity_type: entityType,
1115
+ is_verified: Boolean(isVerified)
1116
+ }
1117
+ }],
1118
+ __typename: 'GenAIMarkdownTextUXPrimitive'
1119
+ },
1120
+ { __typename: 'GenAISpacerPrimitive' }
1121
+ ],
1122
+ __typename: 'GenAIActionRowLayoutViewModel'
1123
+ }
1124
+ }
1125
+ ];
1126
+ const richResponseMessage = proto.AIRichResponseMessage.create({
1127
+ submessages: [],
1128
+ messageType: proto.AIRichResponseMessageType.AI_RICH_RESPONSE_TYPE_STANDARD,
1129
+ unifiedResponse: { data: Buffer.from(JSON.stringify({ sections })) },
1130
+ contextInfo: { isForwarded: true, forwardOrigin: 4 }
1131
+ });
1132
+ return wrapToBotForwardedMessage(richResponseMessage);
1133
+ };
1134
+
1135
+
1136
+ /**
1137
+ * Builds a generic unified-response envelope without reducing its sections to
1138
+ * plain text. This is the extension point for newer WhatsApp primitives that
1139
+ * are present in WAProto but do not yet have a dedicated convenience helper.
1140
+ */
1141
+ export const prepareUnifiedResponseMessage = ({ sections = [], submessages = [], responseId = randomUUID(), contextInfo } = {}) => {
1142
+ if (!Array.isArray(sections) || sections.length === 0) {
1143
+ throw new Boom('sendUnifiedResponse requires at least one section', { statusCode: 400 });
1144
+ }
1145
+ const richResponseMessage = proto.AIRichResponseMessage.create({
1146
+ submessages,
1147
+ messageType: proto.AIRichResponseMessageType.AI_RICH_RESPONSE_TYPE_STANDARD,
1148
+ unifiedResponse: { data: Buffer.from(JSON.stringify({ response_id: responseId, sections })) },
1149
+ contextInfo: contextInfo || { isForwarded: true, forwardingScore: 1, forwardOrigin: 4 }
1150
+ });
1151
+ return wrapToBotForwardedMessage(richResponseMessage);
1152
+ };
1153
+
1154
+ /** Build a protocol-backed AIRich map card with optional numbered annotations. */
1155
+ export const prepareRichMapMessage = ({ centerLatitude, centerLongitude, latitudeDelta, longitudeDelta, annotations = [], showInfoList = false, verificationMetadata } = {}) => {
1156
+ if (!Number.isFinite(Number(centerLatitude)) || !Number.isFinite(Number(centerLongitude))) {
1157
+ throw new Boom('prepareRichMapMessage requires centerLatitude and centerLongitude', { statusCode: 400 });
1158
+ }
1159
+ const submessages = [{
1160
+ messageType: RichSubMessageType.MAP,
1161
+ mapMetadata: {
1162
+ centerLatitude: Number(centerLatitude),
1163
+ centerLongitude: Number(centerLongitude),
1164
+ ...(latitudeDelta == null ? {} : { latitudeDelta: Number(latitudeDelta) }),
1165
+ ...(longitudeDelta == null ? {} : { longitudeDelta: Number(longitudeDelta) }),
1166
+ showInfoList: Boolean(showInfoList),
1167
+ annotations: (Array.isArray(annotations) ? annotations : []).map((annotation, index) => ({
1168
+ annotationNumber: annotation.annotationNumber ?? index + 1,
1169
+ latitude: Number(annotation.latitude),
1170
+ longitude: Number(annotation.longitude),
1171
+ title: String(annotation.title || ''),
1172
+ body: String(annotation.body || '')
1173
+ }))
1174
+ }
1175
+ }];
1176
+ const richResponseMessage = proto.AIRichResponseMessage.create({
1177
+ messageType: proto.AIRichResponseMessageType.AI_RICH_RESPONSE_TYPE_STANDARD,
1178
+ submessages,
1179
+ unifiedResponse: { data: Buffer.from(JSON.stringify(toUnified(submessages, randomUUID()))) },
1180
+ contextInfo: { isForwarded: true, forwardingScore: 1, forwardOrigin: 4 }
1181
+ });
1182
+ return wrapToBotForwardedMessage(richResponseMessage, verificationMetadata);
1183
+ };
1184
+
1185
+ /** Build a rich response carrying BotSuggestedPromptMetadata from WAProto. */
1186
+ export const prepareBotPromptSuggestionsMessage = ({ text = '', suggestions = [], selectedPromptIndex, selectedPromptId, verificationMetadata } = {}) => {
1187
+ if (!Array.isArray(suggestions) || suggestions.length === 0) {
1188
+ throw new Boom('prepareBotPromptSuggestionsMessage requires at least one suggestion', { statusCode: 400 });
1189
+ }
1190
+ const envelope = wrapToBotForwardedMessage(proto.AIRichResponseMessage.create({
1191
+ messageType: proto.AIRichResponseMessageType.AI_RICH_RESPONSE_TYPE_STANDARD,
1192
+ submessages: [{ messageType: RichSubMessageType.TEXT, messageText: String(text) }],
1193
+ contextInfo: { isForwarded: true, forwardingScore: 1, forwardOrigin: 4 }
1194
+ }), verificationMetadata);
1195
+ envelope.messageContextInfo.botMetadata.suggestedPromptMetadata = {
1196
+ suggestedPrompts: suggestions.map(String),
1197
+ ...(selectedPromptIndex == null ? {} : { selectedPromptIndex: Number(selectedPromptIndex) }),
1198
+ ...(selectedPromptId == null ? {} : { selectedPromptId: String(selectedPromptId) })
1199
+ };
1200
+ return envelope;
1201
+ };
1202
+
1203
+ /** Build a rich response carrying BotSourcesMetadata for citations/search results. */
1204
+ export const prepareBotSourcesMessage = ({ text = '', sources = [], verificationMetadata } = {}) => {
1205
+ if (!Array.isArray(sources) || sources.length === 0) {
1206
+ throw new Boom('prepareBotSourcesMessage requires at least one source', { statusCode: 400 });
1207
+ }
1208
+ const envelope = wrapToBotForwardedMessage(proto.AIRichResponseMessage.create({
1209
+ messageType: proto.AIRichResponseMessageType.AI_RICH_RESPONSE_TYPE_STANDARD,
1210
+ submessages: [{ messageType: RichSubMessageType.TEXT, messageText: String(text) }],
1211
+ contextInfo: { isForwarded: true, forwardingScore: 1, forwardOrigin: 4 }
1212
+ }), verificationMetadata);
1213
+ envelope.messageContextInfo.botMetadata.richResponseSourcesMetadata = {
1214
+ sources: sources.map((source, index) => ({
1215
+ provider: source.provider ?? proto.BotSourcesMetadata.BotSourceItem.SourceProvider.OTHER,
1216
+ thumbnailCdnUrl: source.thumbnailCdnUrl || source.thumbnailUrl || '',
1217
+ sourceProviderUrl: source.sourceProviderUrl || source.url || '',
1218
+ sourceQuery: source.sourceQuery || source.query || '',
1219
+ faviconCdnUrl: source.faviconCdnUrl || source.faviconUrl || '',
1220
+ citationNumber: source.citationNumber ?? index + 1,
1221
+ sourceTitle: source.sourceTitle || source.title || ''
1222
+ }))
1223
+ };
1224
+ return envelope;
1225
+ };
1226
+
1227
+ /** Build an AIRich dynamic image/GIF submessage from WAProto metadata. */
1228
+ export const prepareRichDynamicMediaMessage = ({ url = '', type = 'IMAGE', version = 1, loopCount, verificationMetadata } = {}) => {
1229
+ if (!url || typeof url !== 'string') throw new Boom('prepareRichDynamicMediaMessage requires a URL', { statusCode: 400 });
1230
+ const dynamicTypes = proto.AIRichResponseDynamicMetadata.AIRichResponseDynamicMetadataType;
1231
+ const normalizedType = String(type).toUpperCase();
1232
+ const dynamicMetadata = {
1233
+ type: normalizedType === 'GIF'
1234
+ ? dynamicTypes.AI_RICH_RESPONSE_DYNAMIC_METADATA_TYPE_GIF
1235
+ : dynamicTypes.AI_RICH_RESPONSE_DYNAMIC_METADATA_TYPE_IMAGE,
1236
+ version: Number(version),
1237
+ url,
1238
+ ...(loopCount == null ? {} : { loopCount: Number(loopCount) })
1239
+ };
1240
+ const submessages = [{ messageType: RichSubMessageType.DYNAMIC, dynamicMetadata }];
1241
+ const richResponseMessage = proto.AIRichResponseMessage.create({
1242
+ messageType: proto.AIRichResponseMessageType.AI_RICH_RESPONSE_TYPE_STANDARD,
1243
+ submessages,
1244
+ unifiedResponse: { data: Buffer.from(JSON.stringify(toUnified(submessages, randomUUID()))) },
1245
+ contextInfo: { isForwarded: true, forwardingScore: 1, forwardOrigin: 4 }
1246
+ });
1247
+ return wrapToBotForwardedMessage(richResponseMessage, verificationMetadata);
1248
+ };
1249
+
1250
+ /** Build a rich response carrying the exact BotPttPromptMetadata transcript field. */
1251
+ export const prepareBotPttTranscriptMessage = ({ text = '', transcript = '', verificationMetadata } = {}) => {
1252
+ if (!transcript || typeof transcript !== 'string') {
1253
+ throw new Boom('prepareBotPttTranscriptMessage requires a transcript', { statusCode: 400 });
1254
+ }
1255
+ const envelope = wrapToBotForwardedMessage(proto.AIRichResponseMessage.create({
1256
+ messageType: proto.AIRichResponseMessageType.AI_RICH_RESPONSE_TYPE_STANDARD,
1257
+ submessages: [{ messageType: RichSubMessageType.TEXT, messageText: String(text || transcript) }],
1258
+ contextInfo: { isForwarded: true, forwardingScore: 1, forwardOrigin: 4 }
1259
+ }), verificationMetadata);
1260
+ envelope.messageContextInfo.botMetadata.pttPromptMetadata = { transcript };
1261
+ return envelope;
1262
+ };
1263
+
1005
1264
  export const prepareRichResponseMessage = (content) => {
1006
1265
  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;
1007
1266
  let submessages = [];
package/lib/index.d.ts CHANGED
@@ -278,6 +278,67 @@ export interface SocialEntityContent {
278
278
  responseId?: string;
279
279
  }
280
280
 
281
+ export interface RichMapAnnotation {
282
+ annotationNumber?: number;
283
+ latitude: number;
284
+ longitude: number;
285
+ title?: string;
286
+ body?: string;
287
+ }
288
+
289
+ export interface RichMapContent {
290
+ centerLatitude: number;
291
+ centerLongitude: number;
292
+ latitudeDelta?: number;
293
+ longitudeDelta?: number;
294
+ annotations?: RichMapAnnotation[];
295
+ showInfoList?: boolean;
296
+ verificationMetadata?: Record<string, unknown>;
297
+ }
298
+
299
+ export interface BotPromptSuggestionsContent {
300
+ text?: string;
301
+ suggestions: string[];
302
+ selectedPromptIndex?: number;
303
+ selectedPromptId?: string;
304
+ verificationMetadata?: Record<string, unknown>;
305
+ }
306
+
307
+ export interface BotSourceContent {
308
+ provider?: number;
309
+ thumbnailCdnUrl?: string;
310
+ thumbnailUrl?: string;
311
+ sourceProviderUrl?: string;
312
+ url?: string;
313
+ sourceQuery?: string;
314
+ query?: string;
315
+ faviconCdnUrl?: string;
316
+ faviconUrl?: string;
317
+ citationNumber?: number;
318
+ sourceTitle?: string;
319
+ title?: string;
320
+ }
321
+
322
+ export interface BotSourcesContent {
323
+ text?: string;
324
+ sources: BotSourceContent[];
325
+ verificationMetadata?: Record<string, unknown>;
326
+ }
327
+
328
+ export interface RichDynamicMediaContent {
329
+ url: string;
330
+ type?: 'IMAGE' | 'GIF' | string;
331
+ version?: number;
332
+ loopCount?: number;
333
+ verificationMetadata?: Record<string, unknown>;
334
+ }
335
+
336
+ export interface BotPttTranscriptContent {
337
+ transcript: string;
338
+ text?: string;
339
+ verificationMetadata?: Record<string, unknown>;
340
+ }
341
+
281
342
  export interface AIRichCommandMenuContent {
282
343
  image: string;
283
344
  title?: string;
@@ -326,6 +387,16 @@ export interface WASocket {
326
387
  richMenu(jid: string, content?: RichMenuContent, options?: Record<string, unknown>): Promise<unknown>;
327
388
  /** Send a provider-neutral AIRich social/entity result card. */
328
389
  sendSocialEntity(jid: string, content?: SocialEntityContent, options?: Record<string, unknown>): Promise<unknown>;
390
+ /** Send a protocol-backed AIRich map with optional numbered annotations. */
391
+ sendRichMap(jid: string, content: RichMapContent, options?: Record<string, unknown>): Promise<unknown>;
392
+ /** Send BotSuggestedPromptMetadata with a rich-response text fallback. */
393
+ sendBotPromptSuggestions(jid: string, content: BotPromptSuggestionsContent, options?: Record<string, unknown>): Promise<unknown>;
394
+ /** Send BotSourcesMetadata for citation-aware bot responses. */
395
+ sendBotSources(jid: string, content: BotSourcesContent, options?: Record<string, unknown>): Promise<unknown>;
396
+ /** Send an experimental AIRich dynamic image/GIF submessage. */
397
+ sendRichDynamicMedia(jid: string, content: RichDynamicMediaContent, options?: Record<string, unknown>): Promise<unknown>;
398
+ /** Send BotPttPromptMetadata containing a caller-provided transcript. */
399
+ sendBotPttTranscript(jid: string, content: BotPttTranscriptContent, options?: Record<string, unknown>): Promise<unknown>;
329
400
  /** Send the primary Instagram profile result using the universal social/entity format. */
330
401
  sendInstagramProfile(jid: string, content: SocialEntityContent, options?: Record<string, unknown>): Promise<unknown>;
331
402
  /** Send arbitrary A2UI/interactive payloads with immediate relay/edit rendering. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "plogme",
3
- "version": "2.0.5",
3
+ "version": "2.0.7",
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",