plogme 2.0.5 → 2.0.6

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
@@ -1194,6 +1291,24 @@ await sock.sendBotToolResult(jid, {
1194
1291
  })
1195
1292
  ```
1196
1293
 
1294
+ ### Planning metadata and tool results
1295
+
1296
+ `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.
1297
+
1298
+ ```js
1299
+ await sock.sendBotPlan(jid, {
1300
+ title: 'Deployment plan',
1301
+ text: 'I will check, deploy, and verify the service.',
1302
+ steps: [
1303
+ { title: 'Health check', description: 'Check the API and database', status: 'planned' },
1304
+ { title: 'Deploy', description: 'Publish the verified build', status: 'executing' },
1305
+ { title: 'Verify', description: 'Confirm the public endpoint', status: 'finished' }
1306
+ ]
1307
+ })
1308
+ ```
1309
+
1310
+ `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.
1311
+
1197
1312
  These methods do not provide Meta's private verification credentials and must not be used to impersonate a first-party Meta AI message.
1198
1313
 
1199
1314
  ## WhatsApp flows and web views
@@ -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, prepareRichResponseMessage, prepareRichGenerationMessage, prepareCopyToClipboardMessage, prepareRichTextMessage, prepareRichQuoteMessage, prepareRichImageMessage, prepareRichLinkMessage, prepareSocialProfileMessage, prepareUnifiedResponseMessage, 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,76 @@ 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
+ };
2373
2443
  socket.richMenu = async (jid, content = {}, options = {}) => {
2374
2444
  if (!jid) throw new Boom('richMenu requires a target JID', { statusCode: 400 });
2375
2445
  const wrapped = prepareRichMenuMessage(content);
@@ -2535,7 +2605,7 @@ export const makeMessagesSocket = (config) => {
2535
2605
  const wrapped = buildBotRichResponse({
2536
2606
  text,
2537
2607
  botJid,
2538
- capabilities: [BotCapabilityType.AGENTIC_PLANNING],
2608
+ capabilities: [BotCapabilityType.AGENTIC_PLANNING, BotCapabilityType.QUERY_PLAN],
2539
2609
  botMetadata: {
2540
2610
  resolvedToolCallMetadata: {
2541
2611
  toolCallId,
@@ -2547,6 +2617,58 @@ export const makeMessagesSocket = (config) => {
2547
2617
  await relayRichMessage(jid, fullMsg, options);
2548
2618
  return fullMsg;
2549
2619
  };
2620
+ /**
2621
+ * Sends a real BotProgressIndicatorMetadata planning envelope. The plan is
2622
+ * application-controlled; this advertises the public schema capability but
2623
+ * does not claim to invoke Meta's private planning backend.
2624
+ */
2625
+ socket.sendBotPlan = async (jid, content = {}, options = {}) => {
2626
+ if (!jid) throw new Boom('sendBotPlan requires a target JID', { statusCode: 400 });
2627
+ const {
2628
+ text = 'Execution plan',
2629
+ title = 'Execution plan',
2630
+ steps = [],
2631
+ estimatedCompletionTime,
2632
+ botJid = '867051314767696@bot'
2633
+ } = content;
2634
+ if (!Array.isArray(steps) || steps.length === 0) {
2635
+ throw new Boom('sendBotPlan requires at least one step', { statusCode: 400 });
2636
+ }
2637
+ const planningStatus = proto.BotProgressIndicatorMetadata.BotPlanningStepMetadata.PlanningStepStatus;
2638
+ const normalizedSteps = steps.map((step, index) => ({
2639
+ statusTitle: String(step.title || step.name || `Step ${index + 1}`),
2640
+ statusBody: String(step.body || step.description || ''),
2641
+ status: step.status === 'executing' ? planningStatus.EXECUTING
2642
+ : step.status === 'finished' || step.status === 'completed' ? planningStatus.FINISHED
2643
+ : planningStatus.PLANNED,
2644
+ isReasoning: step.isReasoning === true,
2645
+ isEnhancedSearch: step.isEnhancedSearch === true,
2646
+ ...(Array.isArray(step.sections) ? { sections: step.sections } : {})
2647
+ }));
2648
+ const wrapped = {
2649
+ messageContextInfo: { botMetadata: {
2650
+ capabilityMetadata: {
2651
+ capabilities: [
2652
+ proto.BotCapabilityMetadata.BotCapabilityType.AGENTIC_PLANNING,
2653
+ proto.BotCapabilityMetadata.BotCapabilityType.QUERY_PLAN
2654
+ ]
2655
+ },
2656
+ progressIndicatorMetadata: {
2657
+ progressDescription: title,
2658
+ stepsMetadata: normalizedSteps,
2659
+ ...(estimatedCompletionTime ? { estimatedCompletionTime } : {})
2660
+ }
2661
+ } },
2662
+ botForwardedMessage: { message: { richResponseMessage: {
2663
+ messageType: 1,
2664
+ submessages: [{ messageType: 2, messageText: text }],
2665
+ contextInfo: { isForwarded: true, forwardingScore: 1, forwardedAiBotMessageInfo: { botJid }, forwardOrigin: 4 }
2666
+ } } }
2667
+ };
2668
+ const fullMsg = await generateWAMessageFromContent(jid, wrapped, { logger, userJid: authState.creds.me?.id, messageId: options.messageId || generateMessageIDV2(authState.creds.me?.id), ...options });
2669
+ await relayMessage(jid, fullMsg.message, { messageId: fullMsg.key.id, useCachedGroupMetadata: options.useCachedGroupMetadata, statusJidList: options.statusJidList });
2670
+ return fullMsg;
2671
+ };
2550
2672
  socket.sendStatus = async (content, options = {}) => {
2551
2673
  const statusJidList = await resolveStatusRecipients(options.statusJidList || content?.statusJidList);
2552
2674
  return socket.sendMessage(STATUS_JID, {
@@ -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: {
@@ -436,6 +452,7 @@ export const prepareSocialEntityMessage = ({
436
452
  entity_id: String(entityId ?? username ?? ''),
437
453
  entity_name: displayName,
438
454
  entity_full_name: fullName,
455
+ platform: String(platform || 'GENERIC').toUpperCase(),
439
456
  entity_picture_url: imageUrl,
440
457
  entity_url: profileUrl,
441
458
  entity_type: resolvedType,
@@ -473,7 +490,7 @@ export const prepareSocialEntityMessage = ({
473
490
  };
474
491
 
475
492
  /** Dedicated Instagram profile card using the universal social-entity wire format. */
476
- export const prepareInstagramProfileMessage = (content = {}) => prepareSocialEntityMessage({
493
+ export const prepareInstagramProfileMessage = (content = {}) => prepareSocialProfileMessage({
477
494
  ...content,
478
495
  platform: 'INSTAGRAM',
479
496
  entityType: 'IG_PROFILE',
@@ -917,23 +934,13 @@ export const prepareVerticalAlbumMessage = ({ items = [], verificationMetadata }
917
934
  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
935
  }
919
936
  const image = item?.image || item?.data || item;
920
- const sourceUrl = typeof image === 'string' ? image : (image?.sourceUrl || image?.url || '');
937
+ const sourceUrl = typeof image === 'string' ? image : firstImageValue(
938
+ image?.imageHighResUrl, image?.highResUrl, image?.fullUrl,
939
+ image?.imagePreviewUrl, image?.previewUrl, image?.sourceUrl, image?.url
940
+ ) || '';
921
941
  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
- };
942
+ const primitive = buildRichImagePrimitive({ ...image, width: image?.width || item?.width, height: image?.height || item?.height }, expiration);
943
+ return { ...primitive, display_mode: 'FULL_WIDTH', full_width: true };
937
944
  });
938
945
  const richResponseMessage = proto.AIRichResponseMessage.create({
939
946
  submessages: [],
@@ -1002,6 +1009,107 @@ export const prepareVideoGridMessage = ({ videos = [], verificationMetadata } =
1002
1009
  return wrapToBotForwardedMessage(richResponseMessage, verificationMetadata);
1003
1010
  };
1004
1011
 
1012
+ /**
1013
+ * Builds a compact social-profile card used by WhatsApp's unified response
1014
+ * renderer. The summary is an inline social entity, not ordinary plain text.
1015
+ */
1016
+ export const prepareSocialProfileMessage = (options = {}) => {
1017
+ const {
1018
+ username,
1019
+ platform = 'instagram',
1020
+ title = 'CODY AI',
1021
+ subtitle = '⚉',
1022
+ imageUrl = '',
1023
+ entityId = username,
1024
+ entityUrl = `https://www.${platform}.com/${username || ''}`,
1025
+ entityType = platform === 'instagram' ? 'IG_PROFILE' : `${String(platform).toUpperCase()}_PROFILE`,
1026
+ fullName = 'NOT found',
1027
+ isVerified = false,
1028
+ resultText = 'See results'
1029
+ } = options;
1030
+ if (!username || typeof username !== 'string') {
1031
+ throw new Boom('sendSocialProfile requires a username', { statusCode: 400 });
1032
+ }
1033
+ const image = imageUrl ? { url: imageUrl, mime_type: 'image/png' } : undefined;
1034
+ const sections = [
1035
+ {
1036
+ view_model: {
1037
+ primitives: [{
1038
+ title,
1039
+ subtitle,
1040
+ ...(image ? { image } : {}),
1041
+ entity_id: String(entityId || username),
1042
+ entity_url: entityUrl,
1043
+ entity_type: 'WEBSITE',
1044
+ action_type: 'OPEN_URL',
1045
+ is_verified: Boolean(isVerified),
1046
+ __typename: 'GenAICompactEntityPrimitive'
1047
+ }],
1048
+ __typename: 'GenAIActionRowLayoutViewModel'
1049
+ }
1050
+ },
1051
+ {
1052
+ view_model: {
1053
+ primitives: [{ type: 'HORIZONTAL_LINE', __typename: 'GenAIDividerPrimitive' }],
1054
+ __typename: 'GenAIVStackLayoutViewModel'
1055
+ }
1056
+ },
1057
+ {
1058
+ view_model: {
1059
+ primitives: [
1060
+ { __typename: 'GenAISpacerPrimitive' },
1061
+ {
1062
+ text: `# {{social_entity_1}}${resultText}\0{{/social_entity_1}} `,
1063
+ inline_entities: [{
1064
+ key: 'social_entity_1',
1065
+ metadata: {
1066
+ __typename: 'GenAISocialEntityItem',
1067
+ entity_id: username,
1068
+ entity_name: username,
1069
+ entity_full_name: fullName,
1070
+ platform: String(platform || 'GENERIC').toUpperCase(),
1071
+ entity_picture_url: imageUrl,
1072
+ entity_url: entityUrl,
1073
+ entity_type: entityType,
1074
+ is_verified: Boolean(isVerified)
1075
+ }
1076
+ }],
1077
+ __typename: 'GenAIMarkdownTextUXPrimitive'
1078
+ },
1079
+ { __typename: 'GenAISpacerPrimitive' }
1080
+ ],
1081
+ __typename: 'GenAIActionRowLayoutViewModel'
1082
+ }
1083
+ }
1084
+ ];
1085
+ const richResponseMessage = proto.AIRichResponseMessage.create({
1086
+ submessages: [],
1087
+ messageType: proto.AIRichResponseMessageType.AI_RICH_RESPONSE_TYPE_STANDARD,
1088
+ unifiedResponse: { data: Buffer.from(JSON.stringify({ sections })) },
1089
+ contextInfo: { isForwarded: true, forwardOrigin: 4 }
1090
+ });
1091
+ return wrapToBotForwardedMessage(richResponseMessage);
1092
+ };
1093
+
1094
+
1095
+ /**
1096
+ * Builds a generic unified-response envelope without reducing its sections to
1097
+ * plain text. This is the extension point for newer WhatsApp primitives that
1098
+ * are present in WAProto but do not yet have a dedicated convenience helper.
1099
+ */
1100
+ export const prepareUnifiedResponseMessage = ({ sections = [], submessages = [], responseId = randomUUID(), contextInfo } = {}) => {
1101
+ if (!Array.isArray(sections) || sections.length === 0) {
1102
+ throw new Boom('sendUnifiedResponse requires at least one section', { statusCode: 400 });
1103
+ }
1104
+ const richResponseMessage = proto.AIRichResponseMessage.create({
1105
+ submessages,
1106
+ messageType: proto.AIRichResponseMessageType.AI_RICH_RESPONSE_TYPE_STANDARD,
1107
+ unifiedResponse: { data: Buffer.from(JSON.stringify({ response_id: responseId, sections })) },
1108
+ contextInfo: contextInfo || { isForwarded: true, forwardingScore: 1, forwardOrigin: 4 }
1109
+ });
1110
+ return wrapToBotForwardedMessage(richResponseMessage);
1111
+ };
1112
+
1005
1113
  export const prepareRichResponseMessage = (content) => {
1006
1114
  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
1115
  let submessages = [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "plogme",
3
- "version": "2.0.5",
3
+ "version": "2.0.6",
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",