plogme 2.0.2 → 2.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -62,6 +62,30 @@ export const toUnified = (submessages, uuid) => ({
62
62
  }
63
63
  };
64
64
  }
65
+ case RichSubMessageType.GRID_IMAGE: {
66
+ const imageUrls = submessage.gridImageMetadata?.imageUrls || [];
67
+ if (!imageUrls.length) return null;
68
+ 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
+ });
87
+ return { view_model: { primitives, __typename: 'GenAIGridLayoutViewModel' } };
88
+ }
65
89
  case RichSubMessageType.CONTENT_ITEMS: {
66
90
  const items = submessage.contentItemsMetadata?.itemsMetadata || [];
67
91
  const primitives = items.map((item) => {
@@ -448,6 +472,78 @@ export const prepareRichMenuMessage = (content = {}) => {
448
472
  * @param {'END'|'START'|'CENTER'} [options.alignment='END'] - Button alignment
449
473
  * @returns {Object} Wrapped botForwardedMessage ready for relayMessage
450
474
  */
475
+ /** Build the manually serialized AIRich command menu shown in the screenshot. */
476
+ export const prepareAIRichCommandMenu = ({ image, title = 'CODY AI', subtitle = '', code = '', rows = [], buttons = [], footer = 'Developed by - CODY AI', latexImage, latexExpression = '' } = {}) => {
477
+ if (!image) throw new Error('AIRich command menu requires an image URL');
478
+ if (!Array.isArray(rows)) throw new Error('AIRich command-menu rows must be an array');
479
+ if (!Array.isArray(buttons)) throw new Error('AIRich command-menu buttons must be an array');
480
+ const safeRows = rows.map((row, index) => ({ command: String(row?.[0] ?? row?.command ?? `command_${index + 1}`), description: String(row?.[1] ?? row?.description ?? '') }));
481
+ const table = ['Command | Function', '--- | ---', ...safeRows.map((row) => `${row.command} | ${row.description}`)].join('\n');
482
+ const inlineEntities = latexImage ? [{ key: 'NGA', metadata: { latex_expression: String(latexExpression), latex_image: { url: String(latexImage), width: 400, height: 400 }, font_height: 83.333, padding: 15, __typename: 'GenAILatexItem' } }] : [];
483
+ const text = [String(title), subtitle && String(subtitle), code && `Code: ${String(code)}`, '## Command List', table, footer && String(footer)].filter(Boolean).join('\n');
484
+ const imagePrimitive = { __typename: 'GenAIImagePrimitive', preview_image: { __typename: 'GenAIMediaItem', mime_type: 'image/png', url: String(image) }, full_image: { __typename: 'GenAIMediaItem', mime_type: 'image/png', url: String(image) } };
485
+ const ctas = buttons.map((button, index) => ({ label: String(button?.text || button?.label || `Button ${index + 1}`), state: 'PENDING', kind: 'OTHER', tool_call_id: String(button?.id || `airich_menu_${index + 1}`), toast: { label: String(button?.toast || ''), __typename: 'GenAIExtWidgetToast' }, __typename: 'GenAI3PExtWidgetCTA' }));
486
+ const footerPrimitives = buttons.map((button, index) => ({ cta_text: String(button?.text || button?.label || `Button ${index + 1}`), cta_type: 'OPEN_URL', cta_url: String(button?.url || ''), __typename: 'GenAIFooterActionPrimitive' }));
487
+ footerPrimitives.push({ cta_text: '☷ OPEN MENU', cta_type: 'OPEN_URL', cta_url: String(buttons[0]?.url || ''), __typename: 'GenAIFooterActionPrimitive' });
488
+ const sections = [
489
+ { __typename: 'GenAIUnifiedResponseSection', view_model: { __typename: 'GenAISingleLayoutViewModel', primitive: imagePrimitive } },
490
+ ...(ctas.length ? [{ __typename: 'GenAIUnifiedResponseSection', view_model: { primitives: [{ __typename: 'GenAI3PExtWidgetPrimitive', header: { __typename: 'GenAI3PExtWidgetStandardHeader', title: 'Command List' }, body: { __typename: 'GenAIExtCalendarEventList', ctas, sections: [] } }], __typename: 'GenAIActionRowLayoutViewModel' } }] : []),
491
+ { __typename: 'GenAIUnifiedResponseSection', view_model: { primitive: { text: `{{NGA}}${text}{{/NGA}}`, inline_entities: inlineEntities, __typename: 'GenAIMarkdownTextUXPrimitive' }, __typename: 'GenAISingleLayoutViewModel' } },
492
+ { __typename: 'GenAIUnifiedResponseSection', view_model: { primitives: footerPrimitives, __typename: 'GenAIActionRowLayoutViewModel' } }
493
+ ];
494
+ const richResponseMessage = proto.AIRichResponseMessage.create({
495
+ submessages: [{ messageType: RichSubMessageType.TEXT, messageText: text }],
496
+ messageType: proto.AIRichResponseMessageType.AI_RICH_RESPONSE_TYPE_STANDARD,
497
+ unifiedResponse: { data: Buffer.from(JSON.stringify({ response_id: randomUUID(), sections })) },
498
+ contextInfo: { isForwarded: true, forwardingScore: 1, forwardedAiBotMessageInfo: { botJid: '867051314767696@bot' }, forwardOrigin: 4 }
499
+ });
500
+ return wrapToBotForwardedMessage(richResponseMessage);
501
+ };
502
+
503
+ /** Build the A2UI interactive command menu used by the full-size card layout. */
504
+ export const prepareA2UICommandMenu = ({ imageMessage, title = 'CODY AI', rows = [], buttons = [], fallback = '' } = {}) => {
505
+ if (!Array.isArray(rows)) throw new Error('A2UI command-menu rows must be an array');
506
+ if (!Array.isArray(buttons)) throw new Error('A2UI command-menu buttons must be an array');
507
+ const safeRows = rows.map((row, index) => ({
508
+ title: String(row?.[0] ?? row?.title ?? `Command ${index + 1}`),
509
+ description: String(row?.[1] ?? row?.description ?? ''),
510
+ id: String(row?.[2] ?? row?.id ?? row?.[0] ?? `command_${index + 1}`)
511
+ }));
512
+ const safeButtons = buttons.map((button, index) => ({ id: `a2ui_button_${index + 1}`, label: String(button?.text || button?.label || `Button ${index + 1}`), url: String(button?.url || '') }));
513
+ const componentIds = ['title', 'divider', 'tableCard', 'footer', ...(safeButtons.length ? ['buttonRow'] : [])];
514
+ const components = [
515
+ { id: 'root', component: 'Column', align: 'center', children: componentIds },
516
+ { id: 'title', component: 'Text', text: String(title), variant: 'h2' },
517
+ { id: 'divider', component: 'Divider' },
518
+ { id: 'tableCard', component: 'Card', child: 'tableColumn' },
519
+ { id: 'tableColumn', component: 'Column', children: ['headerRow', ...safeRows.flatMap((_, index) => [`divider_${index}`, `row_${index}`])] },
520
+ { id: 'headerRow', component: 'Row', children: ['headerCommand', 'headerFunction'] },
521
+ { id: 'headerCommand', component: 'Text', text: 'Command', variant: 'h5' },
522
+ { id: 'headerFunction', component: 'Text', text: 'Function', variant: 'h5' },
523
+ ...safeRows.flatMap((row, index) => [
524
+ { id: `divider_${index}`, component: 'Divider' },
525
+ { id: `row_${index}`, component: 'Row', children: [`command_${index}`, `description_${index}`] },
526
+ { id: `command_${index}`, component: 'Text', text: row.title },
527
+ { id: `description_${index}`, component: 'Text', text: row.description }
528
+ ]),
529
+ { id: 'footer', component: 'Text', text: 'Developed by - CODY AI', variant: 'caption' }
530
+ ];
531
+ if (safeButtons.length) {
532
+ components.push({ id: 'buttonRow', component: 'Row', justify: 'spaceEvenly', children: safeButtons.flatMap((button, index) => [`buttonLabel_${index}`, `button_${index}`]) });
533
+ safeButtons.forEach((button, index) => {
534
+ components.push({ id: `buttonLabel_${index}`, component: 'Text', text: button.label });
535
+ components.push({ id: `button_${index}`, component: 'Button', child: `buttonLabel_${index}`, variant: 'primary', action: { call: 'openUrl', args: { url: button.url } } });
536
+ });
537
+ }
538
+ return {
539
+ interactiveMessage: {
540
+ ...(imageMessage ? { header: { imageMessage, hasMediaAttachment: true } } : {}),
541
+ bloksWidget: { type: 'im_a2ui', data: JSON.stringify({ version: 'v0.9', createSurface: { surfaceId: 'plogme-a2ui-command-menu', catalogId: 'https://a2ui.org/specification/v0_9/catalogs/basic/catalog.json', components } }), fallback: String(fallback || title) },
542
+ nativeFlowMessage: { messageParamsJson: JSON.stringify({ limited_time_offer: { text: String(title), url: safeButtons[0]?.url || '', copy_code: String(title), expiration_time: Date.now() * 1000 } }), buttons: [{ name: 'single_select', buttonParamsJson: JSON.stringify({ title: String(title), sections: [{ title: 'Available Commands', rows: safeRows }] }) }] }
543
+ }
544
+ };
545
+ };
546
+
451
547
  export const prepareCopyToClipboardMessage = (options = {}) => {
452
548
  const { text = '', label, alignment = 'END', verificationMetadata } = options;
453
549
  const displayText = label || text;
@@ -649,9 +745,138 @@ export const prepareRichLinkMessage = (options = {}) => {
649
745
  return wrapToBotForwardedMessage(richResponseMessage, verificationMetadata);
650
746
  };
651
747
 
748
+ /** Build an AIRich GRID_IMAGE album instead of the legacy albumMessage collection. */
749
+ export const prepareGridImageMessage = ({ imageUrls = [], verificationMetadata } = {}) => {
750
+ if (!Array.isArray(imageUrls) || imageUrls.length < 2) {
751
+ throw new Error('GRID_IMAGE album requires at least two images');
752
+ }
753
+ const submessages = [{
754
+ messageType: RichSubMessageType.GRID_IMAGE,
755
+ gridImageMetadata: { imageUrls }
756
+ }];
757
+ const unified = toUnified(submessages, randomUUID());
758
+ const richResponseMessage = proto.AIRichResponseMessage.create({
759
+ submessages,
760
+ messageType: proto.AIRichResponseMessageType.AI_RICH_RESPONSE_TYPE_STANDARD,
761
+ unifiedResponse: { data: Buffer.from(JSON.stringify(unified)) },
762
+ contextInfo: {
763
+ isForwarded: true,
764
+ forwardingScore: 1,
765
+ forwardedAiBotMessageInfo: { botJid: '867051314767696@bot' },
766
+ forwardOrigin: 4
767
+ }
768
+ });
769
+ return wrapToBotForwardedMessage(richResponseMessage, verificationMetadata);
770
+ };
771
+
772
+ /**
773
+ * Build a video grid using the client-supported GenAIVideoPrimitive.
774
+ *
775
+ * WhatsApp exposes no AIRich GRID_VIDEO submessage type. Video grids therefore
776
+ * use the generic unified-response Grid layout with one GenAIVideoPrimitive per
777
+ * item. This is deliberately separate from prepareGridImageMessage so callers
778
+ * do not accidentally label video media as GenAIImagePrimitive.
779
+ */
780
+ /** Build a full-width vertical album with one large media item per row. */
781
+ export const prepareVerticalAlbumMessage = ({ items = [], verificationMetadata } = {}) => {
782
+ if (!Array.isArray(items) || items.length < 2) throw new Error('Vertical album requires at least two media items');
783
+ const expiration = String(Date.now() + 30 * 24 * 60 * 60 * 1000);
784
+ const primitives = items.map((item) => {
785
+ const kind = item?.type || (item?.video ? 'video' : 'image');
786
+ if (kind === 'video') {
787
+ const video = item.video || item.data || item;
788
+ const url = typeof video === 'string' ? video : (video?.url || video?.videoUrl || '');
789
+ if (!url) throw new Error('Each video album item requires a video URL');
790
+ return buildImaginePrimitive({ mediaType: 'video', url, thumbnail: video?.thumbnail || video?.thumbnailUrl, mimeType: video?.mimeType || video?.mime_type || 'video/mp4', fileLength: video?.fileLength || video?.file_length || 0, duration: video?.duration || 0, status: 'READY' });
791
+ }
792
+ const image = item?.image || item?.data || item;
793
+ const sourceUrl = typeof image === 'string' ? image : (image?.sourceUrl || image?.url || '');
794
+ if (!sourceUrl) throw new Error('Each image album item requires an image URL');
795
+ const previewUrl = image?.imagePreviewUrl || image?.previewUrl || sourceUrl;
796
+ const fullUrl = image?.imageHighResUrl || image?.highResUrl || image?.url || sourceUrl;
797
+ const width = image?.width || item?.width || 600;
798
+ const height = image?.height || item?.height || 400;
799
+ const mimeType = image?.mimeType || image?.mime_type || 'image/png';
800
+ return {
801
+ preview_image: { url: previewUrl, mime_type: mimeType, expiration_timestamp_ms: expiration, width, height },
802
+ full_image: { url: fullUrl, mime_type: mimeType, expiration_timestamp_ms: expiration, width, height },
803
+ dark_mode_preview_image: { url: image?.darkModePreviewUrl || previewUrl, mime_type: mimeType, expiration_timestamp_ms: expiration, width, height },
804
+ dark_mode_full_image: { url: image?.darkModeHighResUrl || image?.darkModePreviewUrl || fullUrl, mime_type: mimeType, expiration_timestamp_ms: expiration, width, height },
805
+ asset_query_status: 'FETCHED',
806
+ __typename: 'GenAIImagePrimitive'
807
+ };
808
+ });
809
+ const richResponseMessage = proto.AIRichResponseMessage.create({
810
+ submessages: [],
811
+ messageType: proto.AIRichResponseMessageType.AI_RICH_RESPONSE_TYPE_STANDARD,
812
+ unifiedResponse: { data: Buffer.from(JSON.stringify({ response_id: randomUUID(), sections: [{ view_model: { primitives, __typename: 'GenAIVStackLayoutViewModel' } }] })) },
813
+ contextInfo: { isForwarded: true, forwardingScore: 1, forwardedAiBotMessageInfo: { botJid: '867051314767696@bot' }, forwardOrigin: 4 }
814
+ });
815
+ return wrapToBotForwardedMessage(richResponseMessage, verificationMetadata);
816
+ };
817
+
818
+ export const prepareVideoGridMessage = ({ videos = [], verificationMetadata } = {}) => {
819
+ if (!Array.isArray(videos) || videos.length < 2) {
820
+ throw new Error('Video grid requires at least two videos');
821
+ }
822
+ const primitives = videos.map((video) => {
823
+ const url = typeof video === 'string' ? video : (video?.url || video?.videoUrl || video?.reelsUrl || '');
824
+ if (!url) throw new Error('Each video-grid item requires a video URL');
825
+ const item = typeof video === 'string' ? {} : video;
826
+ return {
827
+ post_id: item.postId == null ? undefined : String(item.postId),
828
+ reels_url: url,
829
+ reels_title: item.title,
830
+ thumbnail_url: item.thumbnailUrl || item.thumbnail,
831
+ creator: item.creator,
832
+ avatar_url: item.avatarUrl,
833
+ content_hash: item.contentHash,
834
+ likes_count: item.likes,
835
+ comments_count: item.comments,
836
+ shares_count: item.shares,
837
+ is_verified: item.isVerified,
838
+ source_app: item.sourceApp,
839
+ video_delivery_response: {
840
+ progressive_urls: (item.progressiveUrls || []).map((value) => ({
841
+ progressive_url: value,
842
+ __typename: 'GenAIProgressiveUrlResponse'
843
+ })),
844
+ dash_manifests: (item.dashManifests || []).map((value) => ({
845
+ manifest_xml: value,
846
+ __typename: 'GenAIDashManifestResponse'
847
+ })),
848
+ __typename: 'GenAIVideoDeliveryResponse'
849
+ },
850
+ __typename: 'GenAIVideoPrimitive'
851
+ };
852
+ });
853
+ const unified = {
854
+ response_id: randomUUID(),
855
+ sections: [{
856
+ view_model: {
857
+ primitives,
858
+ __typename: 'GenAIGridLayoutViewModel'
859
+ }
860
+ }]
861
+ };
862
+ const richResponseMessage = proto.AIRichResponseMessage.create({
863
+ submessages: [],
864
+ messageType: proto.AIRichResponseMessageType.AI_RICH_RESPONSE_TYPE_STANDARD,
865
+ unifiedResponse: { data: Buffer.from(JSON.stringify(unified)) },
866
+ contextInfo: {
867
+ isForwarded: true,
868
+ forwardingScore: 1,
869
+ forwardedAiBotMessageInfo: { botJid: '867051314767696@bot' },
870
+ forwardOrigin: 4
871
+ }
872
+ });
873
+ return wrapToBotForwardedMessage(richResponseMessage, verificationMetadata);
874
+ };
875
+
652
876
  export const prepareRichResponseMessage = (content) => {
653
877
  const { alignment, code, contentText, disclaimerText, footerText, headerText, imageText, inlineImage, inlineVideo, items, language, latex, links, noDonation, noHeading, posts, products, reels, sources: sourceInputs, suggested, richResponse, table, tapLinkUrl, title, verificationMetadata } = content;
654
878
  let submessages = [];
879
+ const extraSections = [];
655
880
  if (Array.isArray(richResponse)) {
656
881
  submessages = richResponse.map((submessage) => {
657
882
  if (submessage.text) {
@@ -770,7 +995,10 @@ export const prepareRichResponseMessage = (content) => {
770
995
  reelSource: reel?.reelSource || reel?.reel_source || 'IG',
771
996
  isVerified: !!(reel?.isVerified ?? reel?.is_verified)
772
997
  } })) : [];
773
- const resolvedItems = items || reelItems;
998
+ const resolvedItems = [
999
+ ...(Array.isArray(items) ? items : []),
1000
+ ...reelItems
1001
+ ];
774
1002
  if (resolvedItems?.length) {
775
1003
  submessages.push({
776
1004
  messageType: RichSubMessageType.CONTENT_ITEMS,
@@ -792,9 +1020,21 @@ export const prepareRichResponseMessage = (content) => {
792
1020
  });
793
1021
  }
794
1022
  if (inlineVideo) {
795
- submessages.push({
796
- messageType: RichSubMessageType.TEXT,
797
- messageText: 'INLINE_VIDEO'
1023
+ const video = typeof inlineVideo === 'string' ? { url: inlineVideo } : inlineVideo;
1024
+ submessages.push({ messageType: RichSubMessageType.TEXT, messageText: '[ CANNOT_LOAD_VIDEO - NIXEL ]' });
1025
+ extraSections.push({
1026
+ view_model: {
1027
+ primitive: buildImaginePrimitive({
1028
+ mediaType: 'video',
1029
+ url: video?.url || video?.videoUrl || '',
1030
+ mimeType: video?.mimeType || video?.mime_type || 'video/mp4',
1031
+ fileLength: video?.fileLength || video?.file_length || 0,
1032
+ duration: video?.duration || 0,
1033
+ thumbnail: video?.thumbnail,
1034
+ status: video?.status || 'READY'
1035
+ }),
1036
+ __typename: 'GenAISingleLayoutViewModel'
1037
+ }
798
1038
  });
799
1039
  }
800
1040
  if (latex) {
@@ -812,7 +1052,40 @@ export const prepareRichResponseMessage = (content) => {
812
1052
  title: 'PAIR CRYSN⚉VA AI ⩇⩇:⩇⩇',
813
1053
  displayName: 'pair bot'
814
1054
  };
815
- const sourceLinks = (sourceInputs || links || []).map((linkField) => {
1055
+ const rawSources = Array.isArray(sourceInputs) ? sourceInputs : [];
1056
+ // Existing `sources: [{ url, displayName, subtitle }]` callers use
1057
+ // citation entities. The serialized AIRich addSource format is the
1058
+ // distinct tuple form: `[[faviconUrl, sourceUrl, displayText], ...]`.
1059
+ const sourceTuples = rawSources.length > 0 && rawSources.every((source) => Array.isArray(source));
1060
+ if (sourceTuples) {
1061
+ const sourceCards = rawSources.map((source) => {
1062
+ const item = Array.isArray(source)
1063
+ ? { icon: source[0], url: source[1], text: source[2] }
1064
+ : source;
1065
+ const sourceUrl = item.url || item.sourceUrl || item.source_url || '';
1066
+ return {
1067
+ source_type: 'THIRD_PARTY',
1068
+ source_display_name: item.text || item.displayName || item.sourceDisplayName || '',
1069
+ source_subtitle: item.subtitle || 'AI',
1070
+ source_url: sourceUrl,
1071
+ favicon: {
1072
+ url: item.icon || item.iconUrl || item.favicon || '',
1073
+ mime_type: item.mimeType || 'image/jpeg',
1074
+ width: item.width || 16,
1075
+ height: item.height || 16
1076
+ }
1077
+ };
1078
+ }).filter((source) => source.source_url || source.source_display_name);
1079
+ if (sourceCards.length) {
1080
+ extraSections.push({
1081
+ view_model: {
1082
+ primitive: { sources: sourceCards, __typename: 'GenAISearchResultPrimitive' },
1083
+ __typename: 'GenAISingleLayoutViewModel'
1084
+ }
1085
+ });
1086
+ }
1087
+ }
1088
+ const sourceLinks = (sourceTuples ? [] : (sourceInputs || links || [])).map((linkField) => {
816
1089
  if (typeof linkField === 'string') return { text: '', url: linkField, displayName: linkField };
817
1090
  return { text: linkField?.text || linkField?.displayName || linkField?.title || '', ...linkField };
818
1091
  });
@@ -847,14 +1120,53 @@ export const prepareRichResponseMessage = (content) => {
847
1120
  if (posts) {
848
1121
  submessages.push({
849
1122
  messageType: RichSubMessageType.TEXT,
850
- messageText: 'POSTS'
1123
+ messageText: '[ CANNOT_LOAD_POST - NIXEL ]'
851
1124
  });
1125
+ const postItems = (Array.isArray(posts) ? posts : [posts]).map((post) => ({
1126
+ title: post?.title || '',
1127
+ subtitle: post?.subtitle || '',
1128
+ username: post?.username || post?.user || '',
1129
+ profile_picture_url: post?.profile_picture_url || post?.profile || post?.profile_url || '',
1130
+ is_verified: !!(post?.is_verified ?? post?.verified),
1131
+ thumbnail_url: post?.thumbnail_url || post?.thumbnail || '',
1132
+ post_caption: post?.post_caption || post?.caption || '',
1133
+ likes_count: post?.likes_count ?? post?.like ?? 0,
1134
+ comments_count: post?.comments_count ?? post?.comment ?? 0,
1135
+ shares_count: post?.shares_count ?? post?.share ?? 0,
1136
+ post_url: post?.post_url || post?.url || '',
1137
+ post_deeplink: post?.post_deeplink || post?.deeplink || '',
1138
+ source_app: post?.source_app || post?.source || '',
1139
+ footer_label: post?.footer_label || post?.footer || '',
1140
+ footer_icon: post?.footer_icon || post?.icon || '',
1141
+ is_carousel: post?.is_carousel ?? true,
1142
+ orientation: post?.orientation || 'LANDSCAPE',
1143
+ post_type: post?.post_type || 'VIDEO',
1144
+ __typename: 'GenAIPostPrimitive'
1145
+ }));
1146
+ if (postItems.length) extraSections.push({ view_model: { primitives: postItems, __typename: 'GenAIHScrollLayoutViewModel' } });
852
1147
  }
853
1148
  if (products) {
854
1149
  submessages.push({
855
1150
  messageType: RichSubMessageType.TEXT,
856
- messageText: 'PRODUCTS'
1151
+ messageText: '[ CANNOT_LOAD_PRODUCT - NIXEL ]'
857
1152
  });
1153
+ const productItems = (Array.isArray(products) ? products : [products]).map((product) => ({
1154
+ title: product?.title || '',
1155
+ brand: product?.brand || '',
1156
+ price: product?.price || '',
1157
+ sale_price: product?.sale_price || product?.salePrice || '',
1158
+ product_url: product?.product_url || product?.url || '',
1159
+ image: { url: product?.image?.url || product?.image || '' },
1160
+ additional_images: [{ url: product?.icon_url || product?.icon || '' }],
1161
+ __typename: 'GenAIProductItemCardPrimitive'
1162
+ }));
1163
+ if (productItems.length) {
1164
+ extraSections.push({
1165
+ view_model: Array.isArray(products)
1166
+ ? { primitives: productItems, __typename: 'GenAIHScrollLayoutViewModel' }
1167
+ : { primitive: productItems[0], __typename: 'GenAISingleLayoutViewModel' }
1168
+ });
1169
+ }
858
1170
  }
859
1171
  if (suggested) {
860
1172
  submessages.push({
@@ -883,6 +1195,7 @@ export const prepareRichResponseMessage = (content) => {
883
1195
  }
884
1196
  const uuid = randomUUID();
885
1197
  const unified = toUnified(submessages, uuid);
1198
+ unified.sections = [...unified.sections.filter(Boolean), ...extraSections];
886
1199
  const richResponseMessage = proto.AIRichResponseMessage.create({
887
1200
  submessages,
888
1201
  messageType: proto.AIRichResponseMessageType.AI_RICH_RESPONSE_TYPE_STANDARD,
package/lib/index.d.ts CHANGED
@@ -225,6 +225,17 @@ export interface RelayMessageOptions {
225
225
  export interface AlbumMediaInput {
226
226
  image?: unknown;
227
227
  video?: unknown;
228
+ type?: 'image' | 'video';
229
+ data?: unknown;
230
+ sourceUrl?: string;
231
+ imagePreviewUrl?: string;
232
+ imageHighResUrl?: string;
233
+ darkModePreviewUrl?: string;
234
+ darkModeHighResUrl?: string;
235
+ width?: number;
236
+ height?: number;
237
+ darkWidth?: number;
238
+ darkHeight?: number;
228
239
  caption?: string;
229
240
  [key: string]: unknown;
230
241
  }
@@ -236,10 +247,50 @@ export interface AnimatedWallpaperInput {
236
247
  opacity?: number;
237
248
  }
238
249
 
250
+ export interface A2UICommandMenuContent {
251
+ image?: unknown;
252
+ title?: string;
253
+ rows?: Array<[string, string, string?] | { title?: string; description?: string; id?: string }>;
254
+ buttons?: Array<{ text?: string; label?: string; url?: string }>;
255
+ fallback?: string;
256
+ }
257
+
258
+ export interface AIRichCommandMenuContent {
259
+ image: string;
260
+ title?: string;
261
+ subtitle?: string;
262
+ code?: string;
263
+ rows?: Array<[string, string] | { command?: string; description?: string }>;
264
+ latexImage?: string;
265
+ latexExpression?: string;
266
+ buttons?: Array<{ id?: string; text?: string; label?: string; url?: string; toast?: string }>;
267
+ footer?: string;
268
+ }
269
+
239
270
  export interface WASocket {
240
271
  sendMessage(jid: string | string[], content: StatusContent | GroupStatusContent | Record<string, unknown>, options?: Partial<StatusOptions> & Record<string, unknown>): Promise<unknown>;
241
- /** Send at least two image/video items as an experimental WhatsApp album. */
272
+ /** Send at least two image/video URL items as a full-width AIRich vertical album. */
242
273
  sendAlbumMessage(jid: string, medias: AlbumMediaInput[], options?: Record<string, unknown>): Promise<unknown>;
274
+ /** Send an AIRich unified-response grid of GenAIVideoPrimitive items. Requires at least two video URLs. */
275
+ sendVideoGridMessage(jid: string, videos: Array<string | {
276
+ url?: string;
277
+ videoUrl?: string;
278
+ reelsUrl?: string;
279
+ postId?: string | number;
280
+ title?: string;
281
+ thumbnailUrl?: string;
282
+ thumbnail?: string;
283
+ creator?: string;
284
+ avatarUrl?: string;
285
+ contentHash?: string;
286
+ likes?: number;
287
+ comments?: number;
288
+ shares?: number;
289
+ isVerified?: boolean;
290
+ sourceApp?: string;
291
+ progressiveUrls?: string[];
292
+ dashManifests?: string[];
293
+ }>, options?: Record<string, unknown>): Promise<unknown>;
243
294
  /** Update the connected account's WhatsApp About/profile status. */
244
295
  updateProfileStatus(status: string): Promise<void>;
245
296
  /** Update the connected account's profile name. */
@@ -250,6 +301,10 @@ export interface WASocket {
250
301
  relayMessage(jid: string, message: Record<string, unknown>, options?: RelayMessageOptions): Promise<unknown>;
251
302
  /** Send an experimental Meta AI-style rich menu with image, card buttons, and an optional URL footer. */
252
303
  richMenu(jid: string, content?: RichMenuContent, options?: Record<string, unknown>): Promise<unknown>;
304
+ /** Send the large A2UI card/menu with an image header, command table, URL buttons, and native-flow selector. */
305
+ sendA2UICommandMenu(jid: string, content?: A2UICommandMenuContent, options?: Record<string, unknown>): Promise<unknown>;
306
+ /** Send the manually serialized AIRich command card with image, Markdown, widget CTAs, and footer actions. */
307
+ sendAIRichCommandMenu(jid: string, content: AIRichCommandMenuContent, options?: Record<string, unknown>): Promise<unknown>;
253
308
  /** Send raw HTML through the experimental WhatsApp HTML primitive. */
254
309
  sendHtmlMessage(jid: string, content: { html: string; trustedSources?: string[] }, options?: Record<string, unknown>): Promise<unknown>;
255
310
  /** Post a personal status using explicit viewers or a configured contact source/provider. */
@@ -270,6 +325,8 @@ export interface WASocket {
270
325
  sendBotPlanning(jid: string, content?: { text?: string; steps?: Array<string | { title?: string; name?: string; body?: string }>; queryPlan?: boolean; botJid?: string }, options?: Record<string, unknown>): Promise<unknown>;
271
326
  /** Send a rich response with experimental resolved tool-call metadata. */
272
327
  sendBotToolResult(jid: string, content?: { text?: string; toolCallId?: string; resolutionData?: unknown; resolutionDataSerialized?: string; botJid?: string }, options?: Record<string, unknown>): Promise<unknown>;
328
+ /** Send one tolerant AIRich response containing every supported section supplied in content. */
329
+ sendPlogmeMessage(jid: string, content?: Record<string, unknown>, quoted?: unknown, options?: Record<string, unknown>): Promise<unknown>;
273
330
  /** Resolve a WhatsApp Channel invite code or invite URL to channel metadata. */
274
331
  newsletterGetInviteInfo(inviteCodeOrUrl: string): Promise<Record<string, unknown> | null>;
275
332
  /** React to a Channel post; pass an empty string, null, or undefined to remove the reaction. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "plogme",
3
- "version": "2.0.2",
3
+ "version": "2.0.4",
4
4
  "description": "Production-ready Baileys fork ⚉ with AI groups, Meta AI prompting, safe compositing fallbacks, rich responses, interactive messages, albums, newsletters, and communities — without antiban wrappers.",
5
5
  "main": "lib/index.js",
6
6
  "type": "module",