plogme 2.0.3 → 2.0.5

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.
@@ -376,6 +376,110 @@ const richMenuButton = (button, toast = '') => {
376
376
  * This is an experimental WhatsApp rich-response format, not a standard
377
377
  * buttonsMessage/nativeFlowMessage payload.
378
378
  */
379
+ /**
380
+ * Build a universal AIRich social/entity result card.
381
+ * The wire format is provider-neutral; Instagram is exposed as a dedicated
382
+ * convenience wrapper below because it is the primary supported profile view.
383
+ */
384
+ export const prepareSocialEntityMessage = ({
385
+ username = '',
386
+ platform = 'GENERIC',
387
+ title = 'CODY AI',
388
+ subtitle = '⚉',
389
+ imageUrl = '',
390
+ entityId,
391
+ entityUrl = '',
392
+ entityType,
393
+ fullName = 'NOT found',
394
+ isVerified = false,
395
+ resultText = 'See results',
396
+ botJid = '867051314767696@bot',
397
+ responseId
398
+ } = {}) => {
399
+ const normalizedPlatform = String(platform || 'GENERIC').toUpperCase();
400
+ const profileUrl = entityUrl || (username ? `https://www.${normalizedPlatform.toLowerCase()}.com/${encodeURIComponent(username)}` : '');
401
+ const resolvedType = entityType || (normalizedPlatform === 'INSTAGRAM' ? 'IG_PROFILE' : 'SOCIAL_PROFILE');
402
+ const displayName = username || fullName || 'Profile';
403
+ const sections = [
404
+ {
405
+ view_model: {
406
+ primitives: [{
407
+ title,
408
+ subtitle,
409
+ ...(imageUrl ? { image: { url: imageUrl, mime_type: 'image/png' } } : {}),
410
+ entity_id: String(entityId ?? username ?? ''),
411
+ entity_url: profileUrl,
412
+ entity_type: 'WEBSITE',
413
+ action_type: 'OPEN_URL',
414
+ is_verified: !!isVerified,
415
+ __typename: 'GenAICompactEntityPrimitive'
416
+ }],
417
+ __typename: 'GenAIActionRowLayoutViewModel'
418
+ }
419
+ },
420
+ {
421
+ view_model: {
422
+ primitives: [{ type: 'HORIZONTAL_LINE', __typename: 'GenAIDividerPrimitive' }],
423
+ __typename: 'GenAIVStackLayoutViewModel'
424
+ }
425
+ },
426
+ {
427
+ view_model: {
428
+ primitives: [
429
+ { __typename: 'GenAISpacerPrimitive' },
430
+ {
431
+ text: `# {{social_entity_1}}${resultText}\0{{/social_entity_1}} `,
432
+ inline_entities: [{
433
+ key: 'social_entity_1',
434
+ metadata: {
435
+ __typename: 'GenAISocialEntityItem',
436
+ entity_id: String(entityId ?? username ?? ''),
437
+ entity_name: displayName,
438
+ entity_full_name: fullName,
439
+ entity_picture_url: imageUrl,
440
+ entity_url: profileUrl,
441
+ entity_type: resolvedType,
442
+ platform: normalizedPlatform,
443
+ is_verified: !!isVerified
444
+ }
445
+ }],
446
+ __typename: 'GenAIMarkdownTextUXPrimitive'
447
+ },
448
+ { __typename: 'GenAISpacerPrimitive' }
449
+ ],
450
+ __typename: 'GenAIActionRowLayoutViewModel'
451
+ }
452
+ }
453
+ ];
454
+ return {
455
+ botForwardedMessage: {
456
+ message: {
457
+ richResponseMessage: {
458
+ messageType: 1,
459
+ submessages: [],
460
+ unifiedResponse: {
461
+ response_id: responseId || randomUUID(),
462
+ data: Buffer.from(JSON.stringify({ sections })).toString('base64')
463
+ },
464
+ contextInfo: {
465
+ isForwarded: true,
466
+ forwardOrigin: 4,
467
+ forwardedAiBotMessageInfo: { botJid }
468
+ }
469
+ }
470
+ }
471
+ }
472
+ };
473
+ };
474
+
475
+ /** Dedicated Instagram profile card using the universal social-entity wire format. */
476
+ export const prepareInstagramProfileMessage = (content = {}) => prepareSocialEntityMessage({
477
+ ...content,
478
+ platform: 'INSTAGRAM',
479
+ entityType: 'IG_PROFILE',
480
+ entityUrl: content.entityUrl || (content.username ? `https://www.instagram.com/${encodeURIComponent(content.username)}` : undefined)
481
+ });
482
+
379
483
  export const prepareRichMenuMessage = (content = {}) => {
380
484
  const { header = {}, body = {}, footer = {}, verificationMetadata } = content || {};
381
485
  const sections = [];
@@ -472,6 +576,101 @@ export const prepareRichMenuMessage = (content = {}) => {
472
576
  * @param {'END'|'START'|'CENTER'} [options.alignment='END'] - Button alignment
473
577
  * @returns {Object} Wrapped botForwardedMessage ready for relayMessage
474
578
  */
579
+ /** Build the manually serialized AIRich command menu shown in the screenshot. */
580
+ export const prepareAIRichCommandMenu = ({ image, title = 'CODY AI', subtitle = '', code = '', rows = [], buttons = [], footer = 'Developed by - CODY AI', latexImage, latexExpression = '' } = {}) => {
581
+ if (!image) throw new Error('AIRich command menu requires an image URL');
582
+ if (!Array.isArray(rows)) throw new Error('AIRich command-menu rows must be an array');
583
+ if (!Array.isArray(buttons)) throw new Error('AIRich command-menu buttons must be an array');
584
+ const safeRows = rows.map((row, index) => ({ command: String(row?.[0] ?? row?.command ?? `command_${index + 1}`), description: String(row?.[1] ?? row?.description ?? '') }));
585
+ const table = ['Command | Function', '--- | ---', ...safeRows.map((row) => `${row.command} | ${row.description}`)].join('\n');
586
+ 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' } }] : [];
587
+ const text = [String(title), subtitle && String(subtitle), code && `Code: ${String(code)}`, '## Command List', table, footer && String(footer)].filter(Boolean).join('\n');
588
+ 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) } };
589
+ 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' }));
590
+ 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' }));
591
+ footerPrimitives.push({ cta_text: '☷ OPEN MENU', cta_type: 'OPEN_URL', cta_url: String(buttons[0]?.url || ''), __typename: 'GenAIFooterActionPrimitive' });
592
+ const sections = [
593
+ { __typename: 'GenAIUnifiedResponseSection', view_model: { __typename: 'GenAISingleLayoutViewModel', primitive: imagePrimitive } },
594
+ ...(ctas.length ? [{ __typename: 'GenAIUnifiedResponseSection', view_model: { primitives: [{ __typename: 'GenAI3PExtWidgetPrimitive', header: { __typename: 'GenAI3PExtWidgetStandardHeader', title: 'Command List' }, body: { __typename: 'GenAIExtCalendarEventList', ctas, sections: [] } }], __typename: 'GenAIActionRowLayoutViewModel' } }] : []),
595
+ { __typename: 'GenAIUnifiedResponseSection', view_model: { primitive: { text: `{{NGA}}${text}{{/NGA}}`, inline_entities: inlineEntities, __typename: 'GenAIMarkdownTextUXPrimitive' }, __typename: 'GenAISingleLayoutViewModel' } },
596
+ { __typename: 'GenAIUnifiedResponseSection', view_model: { primitives: footerPrimitives, __typename: 'GenAIActionRowLayoutViewModel' } }
597
+ ];
598
+ const richResponseMessage = proto.AIRichResponseMessage.create({
599
+ submessages: [{ messageType: RichSubMessageType.TEXT, messageText: text }],
600
+ messageType: proto.AIRichResponseMessageType.AI_RICH_RESPONSE_TYPE_STANDARD,
601
+ unifiedResponse: { data: Buffer.from(JSON.stringify({ response_id: randomUUID(), sections })) },
602
+ contextInfo: { isForwarded: true, forwardingScore: 1, forwardedAiBotMessageInfo: { botJid: '867051314767696@bot' }, forwardOrigin: 4 }
603
+ });
604
+ return wrapToBotForwardedMessage(richResponseMessage);
605
+ };
606
+
607
+ /**
608
+ * Build an arbitrary A2UI interactive message. The command-menu helper below
609
+ * is only a convenience preset; callers may provide any supported component,
610
+ * widget, or native-flow payload through this universal builder.
611
+ */
612
+ export const prepareA2UIMessage = ({ imageMessage, bloksWidget = {}, nativeFlowMessage, fallback = '', ...interactiveFields } = {}) => ({
613
+ interactiveMessage: {
614
+ ...interactiveFields,
615
+ ...(imageMessage ? { header: { imageMessage, hasMediaAttachment: true } } : {}),
616
+ bloksWidget: {
617
+ type: bloksWidget.type || 'im_a2ui',
618
+ data: typeof bloksWidget.data === 'string' ? bloksWidget.data : JSON.stringify(bloksWidget.data || {}),
619
+ ...(bloksWidget.fallback || fallback ? { fallback: String(bloksWidget.fallback || fallback) } : {})
620
+ },
621
+ ...(nativeFlowMessage ? {
622
+ nativeFlowMessage: {
623
+ ...nativeFlowMessage,
624
+ ...(nativeFlowMessage.buttons ? { buttons: nativeFlowMessage.buttons } : {})
625
+ }
626
+ } : {})
627
+ }
628
+ });
629
+
630
+ /** Build the A2UI interactive command menu used by the full-size card layout. */
631
+ export const prepareA2UICommandMenu = ({ imageMessage, title = 'CODY AI', rows = [], buttons = [], fallback = '' } = {}) => {
632
+ if (!Array.isArray(rows)) throw new Error('A2UI command-menu rows must be an array');
633
+ if (!Array.isArray(buttons)) throw new Error('A2UI command-menu buttons must be an array');
634
+ const safeRows = rows.map((row, index) => ({
635
+ title: String(row?.[0] ?? row?.title ?? `Command ${index + 1}`),
636
+ description: String(row?.[1] ?? row?.description ?? ''),
637
+ id: String(row?.[2] ?? row?.id ?? row?.[0] ?? `command_${index + 1}`)
638
+ }));
639
+ const safeButtons = buttons.map((button, index) => ({ id: `a2ui_button_${index + 1}`, label: String(button?.text || button?.label || `Button ${index + 1}`), url: String(button?.url || '') }));
640
+ const componentIds = ['title', 'divider', 'tableCard', 'footer', ...(safeButtons.length ? ['buttonRow'] : [])];
641
+ const components = [
642
+ { id: 'root', component: 'Column', align: 'center', children: componentIds },
643
+ { id: 'title', component: 'Text', text: String(title), variant: 'h2' },
644
+ { id: 'divider', component: 'Divider' },
645
+ { id: 'tableCard', component: 'Card', child: 'tableColumn' },
646
+ { id: 'tableColumn', component: 'Column', children: ['headerRow', ...safeRows.flatMap((_, index) => [`divider_${index}`, `row_${index}`])] },
647
+ { id: 'headerRow', component: 'Row', children: ['headerCommand', 'headerFunction'] },
648
+ { id: 'headerCommand', component: 'Text', text: 'Command', variant: 'h5' },
649
+ { id: 'headerFunction', component: 'Text', text: 'Function', variant: 'h5' },
650
+ ...safeRows.flatMap((row, index) => [
651
+ { id: `divider_${index}`, component: 'Divider' },
652
+ { id: `row_${index}`, component: 'Row', children: [`command_${index}`, `description_${index}`] },
653
+ { id: `command_${index}`, component: 'Text', text: row.title },
654
+ { id: `description_${index}`, component: 'Text', text: row.description }
655
+ ]),
656
+ { id: 'footer', component: 'Text', text: 'Developed by - CODY AI', variant: 'caption' }
657
+ ];
658
+ if (safeButtons.length) {
659
+ components.push({ id: 'buttonRow', component: 'Row', justify: 'spaceEvenly', children: safeButtons.flatMap((button, index) => [`buttonLabel_${index}`, `button_${index}`]) });
660
+ safeButtons.forEach((button, index) => {
661
+ components.push({ id: `buttonLabel_${index}`, component: 'Text', text: button.label });
662
+ components.push({ id: `button_${index}`, component: 'Button', child: `buttonLabel_${index}`, variant: 'primary', action: { call: 'openUrl', args: { url: button.url } } });
663
+ });
664
+ }
665
+ return {
666
+ interactiveMessage: {
667
+ ...(imageMessage ? { header: { imageMessage, hasMediaAttachment: true } } : {}),
668
+ 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) },
669
+ 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 }] }) }] }
670
+ }
671
+ };
672
+ };
673
+
475
674
  export const prepareCopyToClipboardMessage = (options = {}) => {
476
675
  const { text = '', label, alignment = 'END', verificationMetadata } = options;
477
676
  const displayText = label || text;
@@ -697,9 +896,116 @@ export const prepareGridImageMessage = ({ imageUrls = [], verificationMetadata }
697
896
  return wrapToBotForwardedMessage(richResponseMessage, verificationMetadata);
698
897
  };
699
898
 
899
+ /**
900
+ * Build a video grid using the client-supported GenAIVideoPrimitive.
901
+ *
902
+ * WhatsApp exposes no AIRich GRID_VIDEO submessage type. Video grids therefore
903
+ * use the generic unified-response Grid layout with one GenAIVideoPrimitive per
904
+ * item. This is deliberately separate from prepareGridImageMessage so callers
905
+ * do not accidentally label video media as GenAIImagePrimitive.
906
+ */
907
+ /** Build a full-width vertical album with one large media item per row. */
908
+ export const prepareVerticalAlbumMessage = ({ items = [], verificationMetadata } = {}) => {
909
+ if (!Array.isArray(items) || items.length < 2) throw new Error('Vertical album requires at least two media items');
910
+ const expiration = String(Date.now() + 30 * 24 * 60 * 60 * 1000);
911
+ const primitives = items.map((item) => {
912
+ const kind = item?.type || (item?.video ? 'video' : 'image');
913
+ if (kind === 'video') {
914
+ const video = item.video || item.data || item;
915
+ const url = typeof video === 'string' ? video : (video?.url || video?.videoUrl || '');
916
+ if (!url) throw new Error('Each video album item requires a video URL');
917
+ 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
+ }
919
+ const image = item?.image || item?.data || item;
920
+ const sourceUrl = typeof image === 'string' ? image : (image?.sourceUrl || image?.url || '');
921
+ 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
+ };
937
+ });
938
+ const richResponseMessage = proto.AIRichResponseMessage.create({
939
+ submessages: [],
940
+ messageType: proto.AIRichResponseMessageType.AI_RICH_RESPONSE_TYPE_STANDARD,
941
+ unifiedResponse: { data: Buffer.from(JSON.stringify({ response_id: randomUUID(), sections: [{ view_model: { primitives, display_mode: 'FULL_WIDTH', full_width: true, __typename: 'GenAIVStackLayoutViewModel' } }] })) },
942
+ contextInfo: { isForwarded: true, forwardingScore: 1, forwardedAiBotMessageInfo: { botJid: '867051314767696@bot' }, forwardOrigin: 4 }
943
+ });
944
+ return wrapToBotForwardedMessage(richResponseMessage, verificationMetadata);
945
+ };
946
+
947
+ export const prepareVideoGridMessage = ({ videos = [], verificationMetadata } = {}) => {
948
+ if (!Array.isArray(videos) || videos.length < 2) {
949
+ throw new Error('Video grid requires at least two videos');
950
+ }
951
+ const primitives = videos.map((video) => {
952
+ const url = typeof video === 'string' ? video : (video?.url || video?.videoUrl || video?.reelsUrl || '');
953
+ if (!url) throw new Error('Each video-grid item requires a video URL');
954
+ const item = typeof video === 'string' ? {} : video;
955
+ return {
956
+ post_id: item.postId == null ? undefined : String(item.postId),
957
+ reels_url: url,
958
+ reels_title: item.title,
959
+ thumbnail_url: item.thumbnailUrl || item.thumbnail,
960
+ creator: item.creator,
961
+ avatar_url: item.avatarUrl,
962
+ content_hash: item.contentHash,
963
+ likes_count: item.likes,
964
+ comments_count: item.comments,
965
+ shares_count: item.shares,
966
+ is_verified: item.isVerified,
967
+ source_app: item.sourceApp,
968
+ video_delivery_response: {
969
+ progressive_urls: (item.progressiveUrls || []).map((value) => ({
970
+ progressive_url: value,
971
+ __typename: 'GenAIProgressiveUrlResponse'
972
+ })),
973
+ dash_manifests: (item.dashManifests || []).map((value) => ({
974
+ manifest_xml: value,
975
+ __typename: 'GenAIDashManifestResponse'
976
+ })),
977
+ __typename: 'GenAIVideoDeliveryResponse'
978
+ },
979
+ __typename: 'GenAIVideoPrimitive'
980
+ };
981
+ });
982
+ const unified = {
983
+ response_id: randomUUID(),
984
+ sections: [{
985
+ view_model: {
986
+ primitives,
987
+ __typename: 'GenAIGridLayoutViewModel'
988
+ }
989
+ }]
990
+ };
991
+ const richResponseMessage = proto.AIRichResponseMessage.create({
992
+ submessages: [],
993
+ messageType: proto.AIRichResponseMessageType.AI_RICH_RESPONSE_TYPE_STANDARD,
994
+ unifiedResponse: { data: Buffer.from(JSON.stringify(unified)) },
995
+ contextInfo: {
996
+ isForwarded: true,
997
+ forwardingScore: 1,
998
+ forwardedAiBotMessageInfo: { botJid: '867051314767696@bot' },
999
+ forwardOrigin: 4
1000
+ }
1001
+ });
1002
+ return wrapToBotForwardedMessage(richResponseMessage, verificationMetadata);
1003
+ };
1004
+
700
1005
  export const prepareRichResponseMessage = (content) => {
701
1006
  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
1007
  let submessages = [];
1008
+ const extraSections = [];
703
1009
  if (Array.isArray(richResponse)) {
704
1010
  submessages = richResponse.map((submessage) => {
705
1011
  if (submessage.text) {
@@ -843,9 +1149,21 @@ export const prepareRichResponseMessage = (content) => {
843
1149
  });
844
1150
  }
845
1151
  if (inlineVideo) {
846
- submessages.push({
847
- messageType: RichSubMessageType.TEXT,
848
- messageText: 'INLINE_VIDEO'
1152
+ const video = typeof inlineVideo === 'string' ? { url: inlineVideo } : inlineVideo;
1153
+ submessages.push({ messageType: RichSubMessageType.TEXT, messageText: '[ CANNOT_LOAD_VIDEO - NIXEL ]' });
1154
+ extraSections.push({
1155
+ view_model: {
1156
+ primitive: buildImaginePrimitive({
1157
+ mediaType: 'video',
1158
+ url: video?.url || video?.videoUrl || '',
1159
+ mimeType: video?.mimeType || video?.mime_type || 'video/mp4',
1160
+ fileLength: video?.fileLength || video?.file_length || 0,
1161
+ duration: video?.duration || 0,
1162
+ thumbnail: video?.thumbnail,
1163
+ status: video?.status || 'READY'
1164
+ }),
1165
+ __typename: 'GenAISingleLayoutViewModel'
1166
+ }
849
1167
  });
850
1168
  }
851
1169
  if (latex) {
@@ -863,7 +1181,40 @@ export const prepareRichResponseMessage = (content) => {
863
1181
  title: 'PAIR CRYSN⚉VA AI ⩇⩇:⩇⩇',
864
1182
  displayName: 'pair bot'
865
1183
  };
866
- const sourceLinks = (sourceInputs || links || []).map((linkField) => {
1184
+ const rawSources = Array.isArray(sourceInputs) ? sourceInputs : [];
1185
+ // Existing `sources: [{ url, displayName, subtitle }]` callers use
1186
+ // citation entities. The serialized AIRich addSource format is the
1187
+ // distinct tuple form: `[[faviconUrl, sourceUrl, displayText], ...]`.
1188
+ const sourceTuples = rawSources.length > 0 && rawSources.every((source) => Array.isArray(source));
1189
+ if (sourceTuples) {
1190
+ const sourceCards = rawSources.map((source) => {
1191
+ const item = Array.isArray(source)
1192
+ ? { icon: source[0], url: source[1], text: source[2] }
1193
+ : source;
1194
+ const sourceUrl = item.url || item.sourceUrl || item.source_url || '';
1195
+ return {
1196
+ source_type: 'THIRD_PARTY',
1197
+ source_display_name: item.text || item.displayName || item.sourceDisplayName || '',
1198
+ source_subtitle: item.subtitle || 'AI',
1199
+ source_url: sourceUrl,
1200
+ favicon: {
1201
+ url: item.icon || item.iconUrl || item.favicon || '',
1202
+ mime_type: item.mimeType || 'image/jpeg',
1203
+ width: item.width || 16,
1204
+ height: item.height || 16
1205
+ }
1206
+ };
1207
+ }).filter((source) => source.source_url || source.source_display_name);
1208
+ if (sourceCards.length) {
1209
+ extraSections.push({
1210
+ view_model: {
1211
+ primitive: { sources: sourceCards, __typename: 'GenAISearchResultPrimitive' },
1212
+ __typename: 'GenAISingleLayoutViewModel'
1213
+ }
1214
+ });
1215
+ }
1216
+ }
1217
+ const sourceLinks = (sourceTuples ? [] : (sourceInputs || links || [])).map((linkField) => {
867
1218
  if (typeof linkField === 'string') return { text: '', url: linkField, displayName: linkField };
868
1219
  return { text: linkField?.text || linkField?.displayName || linkField?.title || '', ...linkField };
869
1220
  });
@@ -898,14 +1249,53 @@ export const prepareRichResponseMessage = (content) => {
898
1249
  if (posts) {
899
1250
  submessages.push({
900
1251
  messageType: RichSubMessageType.TEXT,
901
- messageText: 'POSTS'
1252
+ messageText: '[ CANNOT_LOAD_POST - NIXEL ]'
902
1253
  });
1254
+ const postItems = (Array.isArray(posts) ? posts : [posts]).map((post) => ({
1255
+ title: post?.title || '',
1256
+ subtitle: post?.subtitle || '',
1257
+ username: post?.username || post?.user || '',
1258
+ profile_picture_url: post?.profile_picture_url || post?.profile || post?.profile_url || '',
1259
+ is_verified: !!(post?.is_verified ?? post?.verified),
1260
+ thumbnail_url: post?.thumbnail_url || post?.thumbnail || '',
1261
+ post_caption: post?.post_caption || post?.caption || '',
1262
+ likes_count: post?.likes_count ?? post?.like ?? 0,
1263
+ comments_count: post?.comments_count ?? post?.comment ?? 0,
1264
+ shares_count: post?.shares_count ?? post?.share ?? 0,
1265
+ post_url: post?.post_url || post?.url || '',
1266
+ post_deeplink: post?.post_deeplink || post?.deeplink || '',
1267
+ source_app: post?.source_app || post?.source || '',
1268
+ footer_label: post?.footer_label || post?.footer || '',
1269
+ footer_icon: post?.footer_icon || post?.icon || '',
1270
+ is_carousel: post?.is_carousel ?? true,
1271
+ orientation: post?.orientation || 'LANDSCAPE',
1272
+ post_type: post?.post_type || 'VIDEO',
1273
+ __typename: 'GenAIPostPrimitive'
1274
+ }));
1275
+ if (postItems.length) extraSections.push({ view_model: { primitives: postItems, __typename: 'GenAIHScrollLayoutViewModel' } });
903
1276
  }
904
1277
  if (products) {
905
1278
  submessages.push({
906
1279
  messageType: RichSubMessageType.TEXT,
907
- messageText: 'PRODUCTS'
1280
+ messageText: '[ CANNOT_LOAD_PRODUCT - NIXEL ]'
908
1281
  });
1282
+ const productItems = (Array.isArray(products) ? products : [products]).map((product) => ({
1283
+ title: product?.title || '',
1284
+ brand: product?.brand || '',
1285
+ price: product?.price || '',
1286
+ sale_price: product?.sale_price || product?.salePrice || '',
1287
+ product_url: product?.product_url || product?.url || '',
1288
+ image: { url: product?.image?.url || product?.image || '' },
1289
+ additional_images: [{ url: product?.icon_url || product?.icon || '' }],
1290
+ __typename: 'GenAIProductItemCardPrimitive'
1291
+ }));
1292
+ if (productItems.length) {
1293
+ extraSections.push({
1294
+ view_model: Array.isArray(products)
1295
+ ? { primitives: productItems, __typename: 'GenAIHScrollLayoutViewModel' }
1296
+ : { primitive: productItems[0], __typename: 'GenAISingleLayoutViewModel' }
1297
+ });
1298
+ }
909
1299
  }
910
1300
  if (suggested) {
911
1301
  submessages.push({
@@ -934,6 +1324,7 @@ export const prepareRichResponseMessage = (content) => {
934
1324
  }
935
1325
  const uuid = randomUUID();
936
1326
  const unified = toUnified(submessages, uuid);
1327
+ unified.sections = [...unified.sections.filter(Boolean), ...extraSections];
937
1328
  const richResponseMessage = proto.AIRichResponseMessage.create({
938
1329
  submessages,
939
1330
  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
- type?: 'image';
227
+ video?: unknown;
228
+ type?: 'image' | 'video';
228
229
  data?: unknown;
229
230
  sourceUrl?: string;
230
231
  imagePreviewUrl?: string;
@@ -246,10 +247,73 @@ export interface AnimatedWallpaperInput {
246
247
  opacity?: number;
247
248
  }
248
249
 
250
+ export interface A2UIMessageContent {
251
+ image?: unknown;
252
+ bloksWidget?: { type?: string; data?: unknown; fallback?: string };
253
+ nativeFlowMessage?: { messageParamsJson?: string; buttons?: unknown[]; [key: string]: unknown };
254
+ [key: string]: unknown;
255
+ }
256
+
257
+ export interface A2UICommandMenuContent {
258
+ image?: unknown;
259
+ title?: string;
260
+ rows?: Array<[string, string, string?] | { title?: string; description?: string; id?: string }>;
261
+ buttons?: Array<{ text?: string; label?: string; url?: string }>;
262
+ fallback?: string;
263
+ }
264
+
265
+ export interface SocialEntityContent {
266
+ username?: string;
267
+ platform?: string;
268
+ title?: string;
269
+ subtitle?: string;
270
+ imageUrl?: string;
271
+ entityId?: string | number;
272
+ entityUrl?: string;
273
+ entityType?: string;
274
+ fullName?: string;
275
+ isVerified?: boolean;
276
+ resultText?: string;
277
+ botJid?: string;
278
+ responseId?: string;
279
+ }
280
+
281
+ export interface AIRichCommandMenuContent {
282
+ image: string;
283
+ title?: string;
284
+ subtitle?: string;
285
+ code?: string;
286
+ rows?: Array<[string, string] | { command?: string; description?: string }>;
287
+ latexImage?: string;
288
+ latexExpression?: string;
289
+ buttons?: Array<{ id?: string; text?: string; label?: string; url?: string; toast?: string }>;
290
+ footer?: string;
291
+ }
292
+
249
293
  export interface WASocket {
250
294
  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 an AIRich GRID_IMAGE album. */
295
+ /** Send at least two image/video URL items as a full-width AIRich vertical album. */
252
296
  sendAlbumMessage(jid: string, medias: AlbumMediaInput[], options?: Record<string, unknown>): Promise<unknown>;
297
+ /** Send an AIRich unified-response grid of GenAIVideoPrimitive items. Requires at least two video URLs. */
298
+ sendVideoGridMessage(jid: string, videos: Array<string | {
299
+ url?: string;
300
+ videoUrl?: string;
301
+ reelsUrl?: string;
302
+ postId?: string | number;
303
+ title?: string;
304
+ thumbnailUrl?: string;
305
+ thumbnail?: string;
306
+ creator?: string;
307
+ avatarUrl?: string;
308
+ contentHash?: string;
309
+ likes?: number;
310
+ comments?: number;
311
+ shares?: number;
312
+ isVerified?: boolean;
313
+ sourceApp?: string;
314
+ progressiveUrls?: string[];
315
+ dashManifests?: string[];
316
+ }>, options?: Record<string, unknown>): Promise<unknown>;
253
317
  /** Update the connected account's WhatsApp About/profile status. */
254
318
  updateProfileStatus(status: string): Promise<void>;
255
319
  /** Update the connected account's profile name. */
@@ -260,6 +324,16 @@ export interface WASocket {
260
324
  relayMessage(jid: string, message: Record<string, unknown>, options?: RelayMessageOptions): Promise<unknown>;
261
325
  /** Send an experimental Meta AI-style rich menu with image, card buttons, and an optional URL footer. */
262
326
  richMenu(jid: string, content?: RichMenuContent, options?: Record<string, unknown>): Promise<unknown>;
327
+ /** Send a provider-neutral AIRich social/entity result card. */
328
+ sendSocialEntity(jid: string, content?: SocialEntityContent, options?: Record<string, unknown>): Promise<unknown>;
329
+ /** Send the primary Instagram profile result using the universal social/entity format. */
330
+ sendInstagramProfile(jid: string, content: SocialEntityContent, options?: Record<string, unknown>): Promise<unknown>;
331
+ /** Send arbitrary A2UI/interactive payloads with immediate relay/edit rendering. */
332
+ sendA2UIMessage(jid: string, content?: A2UIMessageContent, options?: Record<string, unknown>): Promise<unknown>;
333
+ /** Send the large A2UI card/menu with an image header, command table, URL buttons, and native-flow selector. */
334
+ sendA2UICommandMenu(jid: string, content?: A2UICommandMenuContent, options?: Record<string, unknown>): Promise<unknown>;
335
+ /** Send the manually serialized AIRich command card with image, Markdown, widget CTAs, and footer actions. */
336
+ sendAIRichCommandMenu(jid: string, content: AIRichCommandMenuContent, options?: Record<string, unknown>): Promise<unknown>;
263
337
  /** Send raw HTML through the experimental WhatsApp HTML primitive. */
264
338
  sendHtmlMessage(jid: string, content: { html: string; trustedSources?: string[] }, options?: Record<string, unknown>): Promise<unknown>;
265
339
  /** 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",
3
+ "version": "2.0.5",
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",