plogme 2.1.0 → 2.1.2

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.
Files changed (147) hide show
  1. package/README.md +226 -2
  2. package/WAProto/WAProto.proto +12 -0
  3. package/WAProto/index.d.ts +12 -0
  4. package/WAProto/index.js +12 -0
  5. package/engine-requirements.js +12 -0
  6. package/lib/Defaults/ban-checker.js +12 -0
  7. package/lib/Defaults/index.js +12 -0
  8. package/lib/Signal/Group/ciphertext-message.js +12 -0
  9. package/lib/Signal/Group/group-session-builder.js +12 -0
  10. package/lib/Signal/Group/group_cipher.js +12 -0
  11. package/lib/Signal/Group/index.js +12 -0
  12. package/lib/Signal/Group/keyhelper.js +12 -0
  13. package/lib/Signal/Group/sender-chain-key.js +12 -0
  14. package/lib/Signal/Group/sender-key-distribution-message.js +12 -0
  15. package/lib/Signal/Group/sender-key-message.js +12 -0
  16. package/lib/Signal/Group/sender-key-name.js +12 -0
  17. package/lib/Signal/Group/sender-key-record.js +12 -0
  18. package/lib/Signal/Group/sender-key-state.js +12 -0
  19. package/lib/Signal/Group/sender-message-key.js +12 -0
  20. package/lib/Signal/libsignal.js +12 -0
  21. package/lib/Signal/lid-mapping.js +12 -0
  22. package/lib/Socket/Client/index.js +12 -0
  23. package/lib/Socket/Client/types.js +12 -0
  24. package/lib/Socket/Client/websocket.js +12 -0
  25. package/lib/Socket/aigroups.js +12 -0
  26. package/lib/Socket/ban-checker.js +12 -0
  27. package/lib/Socket/business.js +12 -0
  28. package/lib/Socket/chats.js +12 -0
  29. package/lib/Socket/communities.js +12 -0
  30. package/lib/Socket/graphql.js +12 -0
  31. package/lib/Socket/groups.js +12 -0
  32. package/lib/Socket/index.js +12 -0
  33. package/lib/Socket/interactive-handler.js +12 -0
  34. package/lib/Socket/interop.js +12 -0
  35. package/lib/Socket/managed-account.js +12 -0
  36. package/lib/Socket/messages-recv.js +12 -0
  37. package/lib/Socket/messages-send.js +358 -7
  38. package/lib/Socket/mex.js +12 -0
  39. package/lib/Socket/newsletter.js +12 -0
  40. package/lib/Socket/privacy.js +12 -0
  41. package/lib/Socket/registration.js +12 -0
  42. package/lib/Socket/socket.js +12 -0
  43. package/lib/Socket/username.js +12 -0
  44. package/lib/Store/index.js +12 -0
  45. package/lib/Store/make-cache-manager-store.js +12 -0
  46. package/lib/Store/make-in-memory-store.js +12 -0
  47. package/lib/Store/make-ordered-dictionary.js +12 -0
  48. package/lib/Store/object-repository.js +12 -0
  49. package/lib/Types/Auth.js +12 -0
  50. package/lib/Types/Bussines.js +12 -0
  51. package/lib/Types/Call.js +12 -0
  52. package/lib/Types/Chat.js +12 -0
  53. package/lib/Types/Contact.js +12 -0
  54. package/lib/Types/Events.js +12 -0
  55. package/lib/Types/GroupMetadata.js +12 -0
  56. package/lib/Types/Label.js +12 -0
  57. package/lib/Types/LabelAssociation.js +12 -0
  58. package/lib/Types/Message.js +12 -0
  59. package/lib/Types/Mex.js +12 -0
  60. package/lib/Types/Product.js +12 -0
  61. package/lib/Types/RichType.js +12 -0
  62. package/lib/Types/Signal.js +12 -0
  63. package/lib/Types/Socket.js +12 -0
  64. package/lib/Types/State.js +12 -0
  65. package/lib/Types/USync.js +12 -0
  66. package/lib/Types/index.js +12 -0
  67. package/lib/Utils/auth-utils.js +12 -0
  68. package/lib/Utils/bot-capabilities.js +12 -0
  69. package/lib/Utils/bot-planning-replay.js +12 -0
  70. package/lib/Utils/browser-utils.js +12 -0
  71. package/lib/Utils/business.js +12 -0
  72. package/lib/Utils/channel-policy.js +12 -0
  73. package/lib/Utils/chat-utils.js +12 -0
  74. package/lib/Utils/companion-reg-client-utils.js +12 -0
  75. package/lib/Utils/consumer-application.js +12 -0
  76. package/lib/Utils/crypto.js +12 -0
  77. package/lib/Utils/decode-wa-message.js +12 -0
  78. package/lib/Utils/event-buffer.js +12 -0
  79. package/lib/Utils/games/miniapp.js +12 -0
  80. package/lib/Utils/games/slot-machine.js +12 -0
  81. package/lib/Utils/games/website-preview.js +12 -0
  82. package/lib/Utils/generics.js +12 -0
  83. package/lib/Utils/group-history.js +12 -0
  84. package/lib/Utils/history.js +12 -0
  85. package/lib/Utils/identity-change-handler.js +12 -0
  86. package/lib/Utils/index.js +12 -0
  87. package/lib/Utils/jid-display-normalization.js +12 -0
  88. package/lib/Utils/link-preview.js +12 -0
  89. package/lib/Utils/logger.js +12 -0
  90. package/lib/Utils/lt-hash.js +12 -0
  91. package/lib/Utils/make-mutex.js +12 -0
  92. package/lib/Utils/message-composer.js +18 -1
  93. package/lib/Utils/message-inspect.js +12 -0
  94. package/lib/Utils/message-retry-manager.js +12 -0
  95. package/lib/Utils/messages-media.js +12 -0
  96. package/lib/Utils/messages.js +232 -4
  97. package/lib/Utils/meta-ai-msmsg.js +12 -0
  98. package/lib/Utils/meta-compositing.js +12 -0
  99. package/lib/Utils/native-flow.js +12 -0
  100. package/lib/Utils/noise-handler.js +12 -0
  101. package/lib/Utils/offline-node-processor.js +12 -0
  102. package/lib/Utils/pre-key-manager.js +12 -0
  103. package/lib/Utils/process-message.js +12 -0
  104. package/lib/Utils/reporting-utils.js +12 -0
  105. package/lib/Utils/rich-message-utils.js +484 -91
  106. package/lib/Utils/signal.js +12 -0
  107. package/lib/Utils/source-grep.js +12 -0
  108. package/lib/Utils/stanza-ack.js +12 -0
  109. package/lib/Utils/sync-action-utils.js +12 -0
  110. package/lib/Utils/tc-token-utils.js +12 -0
  111. package/lib/Utils/use-multi-file-auth-state.js +12 -0
  112. package/lib/Utils/use-single-file-auth-state.js +12 -0
  113. package/lib/Utils/use-sqlite-auth-state.js +12 -0
  114. package/lib/Utils/validate-connection.js +12 -0
  115. package/lib/Utils/view-once-utils.js +12 -0
  116. package/lib/Utils/voip-rekey.js +12 -0
  117. package/lib/Utils/welcome-flow.js +12 -0
  118. package/lib/WABinary/constants.js +12 -0
  119. package/lib/WABinary/decode.js +12 -0
  120. package/lib/WABinary/encode.js +12 -0
  121. package/lib/WABinary/generic-utils.js +12 -0
  122. package/lib/WABinary/index.js +12 -0
  123. package/lib/WABinary/jid-utils.js +12 -0
  124. package/lib/WABinary/types.js +12 -0
  125. package/lib/WAM/BinaryInfo.js +12 -0
  126. package/lib/WAM/constants.js +12 -0
  127. package/lib/WAM/encode.js +12 -0
  128. package/lib/WAM/index.js +12 -0
  129. package/lib/WAUSync/Protocols/USyncBusinessProtocol.js +12 -0
  130. package/lib/WAUSync/Protocols/USyncContactProtocol.js +12 -0
  131. package/lib/WAUSync/Protocols/USyncDeviceProtocol.js +12 -0
  132. package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.js +12 -0
  133. package/lib/WAUSync/Protocols/USyncFeatureProtocol.js +12 -0
  134. package/lib/WAUSync/Protocols/USyncPictureProtocol.js +12 -0
  135. package/lib/WAUSync/Protocols/USyncSidelistProtocol.js +12 -0
  136. package/lib/WAUSync/Protocols/USyncStatusProtocol.js +12 -0
  137. package/lib/WAUSync/Protocols/USyncTextStatusProtocol.js +12 -0
  138. package/lib/WAUSync/Protocols/USyncUsernameProtocol.js +12 -0
  139. package/lib/WAUSync/Protocols/UsyncBotProfileProtocol.js +12 -0
  140. package/lib/WAUSync/Protocols/UsyncLIDProtocol.js +12 -0
  141. package/lib/WAUSync/Protocols/index.js +12 -0
  142. package/lib/WAUSync/USyncQuery.js +12 -0
  143. package/lib/WAUSync/USyncUser.js +12 -0
  144. package/lib/WAUSync/index.js +12 -0
  145. package/lib/index.d.ts +50 -2
  146. package/lib/index.js +12 -0
  147. package/package.json +9 -2
@@ -1,3 +1,15 @@
1
+ /**
2
+ * @crysnpvax
3
+ *
4
+ * File: lib/Utils/rich-message-utils.js
5
+ * About: Rich message utils module for the Plogme WhatsApp protocol client.
6
+ * Important exports: tokenizeCode, normalizeNewReel, normalizeNewRichImage, prepareNewRichMediaCompatibility, toUnified, buildImaginePrimitive, prepareRichGenerationMessage, buildAdditionalBotMetadataContext; …
7
+ * Directions: Import the listed exports and preserve protocol, validation, and compatibility invariants when modifying.
8
+ *
9
+ * Copyright and attribution notice.
10
+ * Do not copy, reproduce, redistribute, modify, or create derivative works
11
+ * from this source without explicit written permission.
12
+ */
1
13
  /**
2
14
  * Lia@Changes 09-04-26 [WIP]
3
15
  * Adds support for tables and code blocks with richResponseMessage (wrapped inside botForwardedMessage).
@@ -11,6 +23,7 @@ import { LANGUAGE_KEYWORDS } from '../WABinary/constants.js';
11
23
  import { CodeHighlightType, RichSubMessageType } from '../Types/RichType.js';
12
24
  import { proto } from '../../WAProto/index.js';
13
25
  import { unixTimestampSeconds } from './generics.js';
26
+ import { generateTableContent } from './message-composer.js';
14
27
  const NOOP = new Set([]);
15
28
  export const tokenizeCode = (code, language = 'javascript') => {
16
29
  const keywords = LANGUAGE_KEYWORDS[language] || NOOP;
@@ -47,6 +60,14 @@ export const tokenizeCode = (code, language = 'javascript') => {
47
60
  };
48
61
  const firstImageValue = (...values) => values.find(value => value !== undefined && value !== null && String(value).length > 0);
49
62
 
63
+ const inferImageMimeType = (url = '') => {
64
+ const path = String(url).split('?')[0].toLowerCase();
65
+ if (path.endsWith('.jpg') || path.endsWith('.jpeg')) return 'image/jpeg';
66
+ if (path.endsWith('.webp')) return 'image/webp';
67
+ if (path.endsWith('.gif')) return 'image/gif';
68
+ return 'image/png';
69
+ };
70
+
50
71
  /** Normalize album image variants without downgrading full-size URLs to previews. */
51
72
  const buildRichImagePrimitive = (image = {}, expiration = String(Date.now() + 30 * 24 * 60 * 60 * 1000)) => {
52
73
  const previewUrl = firstImageValue(image.imagePreviewUrl, image.previewUrl, image.preview_url, image.sourceUrl, image.source_url, image.url) || '';
@@ -59,13 +80,7 @@ const buildRichImagePrimitive = (image = {}, expiration = String(Date.now() + 30
59
80
  const fullHeight = Number(image.fullHeight || image.full_height || image.highResHeight || image.high_res_height || image.height || height);
60
81
  const darkWidth = Number(image.darkWidth || image.dark_width || fullWidth);
61
82
  const darkHeight = Number(image.darkHeight || image.dark_height || fullHeight);
62
- const mimeType = image.mimeType || image.mime_type || (() => {
63
- const value = String(fullUrl || previewUrl).split('?')[0].toLowerCase();
64
- if (value.endsWith('.jpg') || value.endsWith('.jpeg')) return 'image/jpeg';
65
- if (value.endsWith('.webp')) return 'image/webp';
66
- if (value.endsWith('.gif')) return 'image/gif';
67
- return 'image/png';
68
- })();
83
+ const mimeType = image.mimeType || image.mime_type || inferImageMimeType(fullUrl || previewUrl);
69
84
  const media = (url, mediaWidth, mediaHeight) => ({
70
85
  url,
71
86
  url_fallback: url,
@@ -86,6 +101,54 @@ const buildRichImagePrimitive = (image = {}, expiration = String(Date.now() + 30
86
101
  primitive.dark_mode_full_image = media(darkFullUrl, darkWidth, darkHeight);
87
102
  return primitive;
88
103
  };
104
+
105
+ /** Normalize the field names used by New compatibility's Meta/reels helpers. */
106
+ export const normalizeNewReel = (reel = {}) => ({
107
+ reels_url: reel.videoUrl || reel.video_url || reel.reels_url || reel.reelsUrl || reel.url || '',
108
+ thumbnail_url: reel.thumbnailUrl || reel.thumbnail_url || reel.thumbnail || '',
109
+ creator: reel.creator || reel.username || reel.title || '',
110
+ avatar_url: reel.profileIconUrl || reel.avatar_url || reel.avatar || reel.icon || '',
111
+ reels_title: reel.title || reel.reels_title || reel.reelsTitle || '',
112
+ likes_count: reel.likesCount ?? reel.likes_count ?? reel.likes ?? 0,
113
+ comments_count: reel.commentsCount ?? reel.comments_count ?? reel.comments ?? 0,
114
+ shares_count: reel.sharesCount ?? reel.shares_count ?? reel.shares ?? 0,
115
+ view_count: reel.viewCount ?? reel.view_count ?? reel.views ?? 0,
116
+ reel_source: reel.reelSource || reel.reel_source || reel.source || 'IG',
117
+ is_verified: !!(reel.isVerified ?? reel.is_verified ?? reel.verified),
118
+ orientation: reel.orientation || reel.ratio || 'LANDSCAPE',
119
+ post_type: reel.postType || reel.post_type || 'VIDEO',
120
+ is_carousel: reel.isCarousel ?? reel.is_carousel ?? false
121
+ });
122
+
123
+ /** Build the New-compatible image/post shape without replacing native image data. */
124
+ export const normalizeNewRichImage = (image = {}) => ({
125
+ thumbnail_url: image.thumbnailUrl || image.thumbnail_url || image.thumbnail || image.previewUrl || image.preview_url || '',
126
+ image_url: image.imageUrl || image.image_url || image.fullUrl || image.full_url || image.url || '',
127
+ source_url: image.sourceUrl || image.source_url || image.url || '',
128
+ title: image.title || image.caption || '',
129
+ creator: image.creator || image.username || '',
130
+ avatar_url: image.profileIconUrl || image.avatar_url || image.avatar || '',
131
+ is_verified: !!(image.isVerified ?? image.is_verified ?? image.verified),
132
+ post_caption: image.postCaption || image.post_caption || image.caption || '',
133
+ likes_count: image.likesCount ?? image.likes_count ?? image.likes ?? 0,
134
+ comments_count: image.commentsCount ?? image.comments_count ?? image.comments ?? 0,
135
+ shares_count: image.sharesCount ?? image.shares_count ?? image.shares ?? 0,
136
+ orientation: image.orientation || image.ratio || 'LANDSCAPE',
137
+ post_type: image.postType || image.post_type || 'IMAGE',
138
+ is_carousel: image.isCarousel ?? image.is_carousel ?? false
139
+ });
140
+
141
+ /** Create the second New-shaped representation beside native AIRich sections. */
142
+ export const prepareNewRichMediaCompatibility = (content = {}) => {
143
+ const reels = (Array.isArray(content.reels) ? content.reels : []).map(normalizeNewReel);
144
+ const images = (Array.isArray(content.images) ? content.images : content.image ? [content.image] : []).map(normalizeNewRichImage);
145
+ const posts = (Array.isArray(content.posts) ? content.posts : content.post ? [content.post] : []).map(normalizeNewRichImage);
146
+ const sections = [];
147
+ if (reels.length) sections.push({ view_model: { primitives: reels.map(item => ({ ...item, __typename: 'GenAIReelPrimitive' })), __typename: 'GenAIHScrollLayoutViewModel' } });
148
+ if (images.length) sections.push({ view_model: { primitives: images.map(item => ({ ...item, __typename: 'GenAIImagePrimitive' })), __typename: 'GenAIHScrollLayoutViewModel' } });
149
+ if (posts.length) sections.push({ view_model: { primitives: posts.map(item => ({ ...item, __typename: 'GenAIPostPrimitive' })), __typename: 'GenAIHScrollLayoutViewModel' } });
150
+ return { compatibility: 'new-rich-media', reels, images, posts, sections };
151
+ };
89
152
  // Lia@Changes 09-04-26 --- Inject buffer into unifiedResponse.data to support proper rendering of rich messages (ex: tables and code blocks)
90
153
  export const toUnified = (submessages, uuid) => ({
91
154
  response_id: uuid || randomUUID(),
@@ -138,8 +201,24 @@ export const toUnified = (submessages, uuid) => ({
138
201
  }
139
202
  case RichSubMessageType.INLINE_IMAGE: {
140
203
  const image = submessage.imageMetadata || {};
141
- const media = mediaPrimitive({ url: image.imageUrl || image.url, mime_type: image.mimeType || image.mime_type, width: image.width, height: image.height });
142
- return media ? { view_model: { primitive: media, __typename: 'GenAISingleLayoutViewModel' } } : {};
204
+ const rawImage = image.imageUrl || image.url || image.image;
205
+ const imageData = typeof rawImage === 'string'
206
+ ? { ...image, url: rawImage }
207
+ : { ...image, ...(rawImage || {}) };
208
+ const primitive = buildRichImagePrimitive({
209
+ ...imageData,
210
+ mimeType: imageData.mimeType || imageData.mime_type,
211
+ });
212
+ return primitive.full_image.url
213
+ ? {
214
+ view_model: {
215
+ primitives: [{ ...primitive, display_mode: 'FULL_WIDTH', full_width: true }],
216
+ display_mode: 'FULL_WIDTH',
217
+ full_width: true,
218
+ __typename: 'GenAIVStackLayoutViewModel'
219
+ }
220
+ }
221
+ : {};
143
222
  }
144
223
  case RichSubMessageType.LATEX: {
145
224
  const metadata = submessage.latexMetadata || {};
@@ -842,23 +921,20 @@ export const prepareRichQuoteMessage = (options = {}) => {
842
921
  * @returns {Object} Wrapped botForwardedMessage ready for relayMessage
843
922
  */
844
923
  export const prepareRichImageMessage = (options = {}) => {
845
- const { url = '', mimeType = 'image/png', verificationMetadata } = options;
846
- const media = {
847
- __typename: 'GenAIMediaItem',
848
- mime_type: mimeType,
849
- url,
850
- url_fallback: url,
924
+ const { url = '', mimeType, verificationMetadata } = options;
925
+ const primitive = {
926
+ ...buildRichImagePrimitive({ url, mimeType }),
927
+ display_mode: 'FULL_WIDTH',
928
+ full_width: true,
851
929
  };
852
930
  const sections = [
853
931
  {
854
932
  __typename: 'GenAIUnifiedResponseSection',
855
933
  view_model: {
856
- __typename: 'GenAISingleLayoutViewModel',
857
- primitive: {
858
- __typename: 'GenAIImagePrimitive',
859
- preview_image: media,
860
- full_image: media,
861
- },
934
+ __typename: 'GenAIVStackLayoutViewModel',
935
+ primitives: [primitive],
936
+ display_mode: 'FULL_WIDTH',
937
+ full_width: true,
862
938
  },
863
939
  },
864
940
  ];
@@ -1320,8 +1396,61 @@ export const prepareBotPttTranscriptMessage = ({ text = '', transcript = '', ver
1320
1396
  return envelope;
1321
1397
  };
1322
1398
 
1323
- export const prepareRichResponseMessage = (content) => {
1324
- 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;
1399
+ const buildFoaHtmlSection = (html, trustedSources = []) => ({
1400
+ __typename: 'GenAIUnifiedResponseSection',
1401
+ view_model: {
1402
+ __typename: 'GenAISingleLayoutViewModel',
1403
+ primitive: {
1404
+ __typename: 'FOAHtmlPrimitiveDemoDONOTUSE',
1405
+ trusted_sources: Array.isArray(trustedSources) ? trustedSources : [],
1406
+ payload: String(html ?? '').trim()
1407
+ }
1408
+ }
1409
+ });
1410
+
1411
+ const buildRichHtmlScreen = (options = {}) => {
1412
+ const {
1413
+ html = '',
1414
+ trustedSources = [],
1415
+ url = '',
1416
+ title = 'Preview',
1417
+ tabHeader = 'HTML',
1418
+ tabs
1419
+ } = options;
1420
+ const tabItems = Array.isArray(tabs) && tabs.length
1421
+ ? tabs
1422
+ : [{ id: 'tab_0', tab_header: tabHeader, html }];
1423
+ const embeddedTabs = tabItems.map((tab, index) => ({
1424
+ id: tab.id || `tab_${index}`,
1425
+ tab_header: tab.tab_header || tab.title || `HTML ${index + 1}`,
1426
+ sections: [{
1427
+ __typename: 'GenAIUnifiedResponseSection',
1428
+ view_model: {
1429
+ __typename: 'GenAISingleLayoutViewModel',
1430
+ primitive: {
1431
+ __typename: 'GenAIaeacdsnwHtmlPrimitive',
1432
+ payload: String(tab.html ?? tab.payload ?? html).trim(),
1433
+ url: tab.url || url,
1434
+ trusted_sources: tab.trustedSources || trustedSources
1435
+ }
1436
+ }
1437
+ }],
1438
+ step_entries: []
1439
+ }));
1440
+ return {
1441
+ title,
1442
+ embeddedScreen: {
1443
+ title,
1444
+ content: [{
1445
+ __typename: 'FOAIDNixelButtonSheets',
1446
+ tabs: embeddedTabs
1447
+ }]
1448
+ }
1449
+ };
1450
+ };
1451
+
1452
+ const prepareRichResponseMessageFixedOrder = (content) => {
1453
+ const { alignment, code, compatibility, contentText, disclaimerText, footerText, headerText, html, richHtml, trustedSources, imageText, image, images, inlineImage, inlineVideo, items, language, latex, links, noDonation, noHeading, posts, products, reels, sources: sourceInputs, suggested, richResponse, table, tapLinkUrl, title, verificationMetadata, sections: rawSections, richSections, copyButton, copyToClipboard, copyText, copyLabel, richText, quote, richImage, richLinks, richMap, map, dynamicMedia, richDynamicMedia, socialEntity, socialProfile, richMenu, airichCommandMenu, unifiedResponse, gridImages, verticalAlbum, videoGrid, richGeneration, richLabel, suggestedPrompt, botSources, botPttTranscript } = content;
1325
1454
  let submessages = [];
1326
1455
  const extraSections = [];
1327
1456
  if (Array.isArray(richResponse)) {
@@ -1456,28 +1585,34 @@ export const prepareRichResponseMessage = (content) => {
1456
1585
  });
1457
1586
  }
1458
1587
  if (inlineImage) {
1588
+ const image = typeof inlineImage === 'string' ? { url: inlineImage } : (inlineImage || {});
1459
1589
  submessages.push({
1460
1590
  messageType: RichSubMessageType.INLINE_IMAGE,
1461
1591
  imageMetadata: {
1462
- imageUrl: inlineImage,
1592
+ ...image,
1593
+ imageUrl: image.url || image.imageUrl || image.image || '',
1463
1594
  imageText,
1464
1595
  alignment,
1465
- tapLinkUrl
1596
+ tapLinkUrl,
1597
+ mimeType: image.mimeType || image.mime_type,
1598
+ width: image.width,
1599
+ height: image.height,
1600
+ fullWidth: image.fullWidth || image.full_width,
1601
+ fullHeight: image.fullHeight || image.full_height
1466
1602
  }
1467
1603
  });
1468
1604
  }
1469
1605
  if (inlineVideo) {
1470
1606
  const video = typeof inlineVideo === 'string' ? { url: inlineVideo } : inlineVideo;
1471
- submessages.push({ messageType: RichSubMessageType.TEXT, messageText: '[ CANNOT_LOAD_VIDEO - NIXEL ]' });
1472
1607
  extraSections.push({
1473
1608
  view_model: {
1474
1609
  primitive: buildImaginePrimitive({
1475
1610
  mediaType: 'video',
1476
- url: video?.url || video?.videoUrl || '',
1477
- mimeType: video?.mimeType || video?.mime_type || 'video/mp4',
1478
- fileLength: video?.fileLength || video?.file_length || 0,
1479
- duration: video?.duration || 0,
1480
- thumbnail: video?.thumbnail,
1611
+ url: video?.url || video?.videoUrl || video?.video_url || '',
1612
+ mimeType: video?.mimeType || video?.mime_type || video?.mimetype || 'video/mp4',
1613
+ fileLength: video?.fileLength ?? video?.file_length ?? video?.filesize ?? 0,
1614
+ duration: video?.duration ?? 0,
1615
+ thumbnail: video?.thumbnail || video?.thumbnailUrl || video?.thumbnail_url,
1481
1616
  status: video?.status || 'READY'
1482
1617
  }),
1483
1618
  __typename: 'GenAISingleLayoutViewModel'
@@ -1565,10 +1700,6 @@ export const prepareRichResponseMessage = (content) => {
1565
1700
  });
1566
1701
  }
1567
1702
  if (posts) {
1568
- submessages.push({
1569
- messageType: RichSubMessageType.TEXT,
1570
- messageText: '[ CANNOT_LOAD_POST - NIXEL ]'
1571
- });
1572
1703
  const postItems = (Array.isArray(posts) ? posts : [posts]).map((post) => ({
1573
1704
  title: post?.title || '',
1574
1705
  subtitle: post?.subtitle || '',
@@ -1593,10 +1724,6 @@ export const prepareRichResponseMessage = (content) => {
1593
1724
  if (postItems.length) extraSections.push({ view_model: { primitives: postItems, __typename: 'GenAIHScrollLayoutViewModel' } });
1594
1725
  }
1595
1726
  if (products) {
1596
- submessages.push({
1597
- messageType: RichSubMessageType.TEXT,
1598
- messageText: '[ CANNOT_LOAD_PRODUCT - NIXEL ]'
1599
- });
1600
1727
  const productItems = (Array.isArray(products) ? products : [products]).map((product) => ({
1601
1728
  title: product?.title || '',
1602
1729
  brand: product?.brand || '',
@@ -1621,14 +1748,14 @@ export const prepareRichResponseMessage = (content) => {
1621
1748
  messageText: 'SUGGESTED_PROMPT'
1622
1749
  });
1623
1750
  }
1624
- if (table) {
1751
+ if (Array.isArray(table)) {
1625
1752
  submessages.push({
1626
1753
  messageType: RichSubMessageType.TABLE,
1627
1754
  tableMetadata: {
1628
1755
  title,
1629
1756
  rows: table.map((items, index) => ({
1630
1757
  isHeading: !noHeading && index == 0,
1631
- items
1758
+ items: Array.isArray(items) ? items.map(String) : [String(items)]
1632
1759
  }))
1633
1760
  }
1634
1761
  });
@@ -1640,11 +1767,82 @@ export const prepareRichResponseMessage = (content) => {
1640
1767
  });
1641
1768
  }
1642
1769
  }
1770
+ const htmlIsTabbedConfig = html && typeof html === 'object' && Array.isArray(html.tabs);
1771
+ const simpleHtmlPayload = htmlIsTabbedConfig ? '' : (typeof html === 'string' ? html : (html?.html ?? html?.payload ?? ''));
1772
+ const htmlPayload = simpleHtmlPayload;
1773
+ const htmlTrustedSources = Array.isArray(html?.trustedSources) ? html.trustedSources : trustedSources;
1774
+ if (String(htmlPayload).trim()) {
1775
+ extraSections.push(buildFoaHtmlSection(htmlPayload, htmlTrustedSources));
1776
+ }
1777
+ const richHtmlOptions = richHtml ?? (htmlIsTabbedConfig ? html : null);
1778
+ const richHtmlConfig = typeof richHtmlOptions === 'string'
1779
+ ? { html: richHtmlOptions, trustedSources }
1780
+ : (richHtmlOptions && typeof richHtmlOptions === 'object'
1781
+ ? { ...richHtmlOptions, trustedSources: richHtmlOptions.trustedSources ?? trustedSources }
1782
+ : null);
1783
+ const hasRichHtmlContent = !!(richHtmlConfig && (
1784
+ String(richHtmlConfig.html || '').trim() ||
1785
+ (Array.isArray(richHtmlConfig.tabs) && richHtmlConfig.tabs.some(tab => String(tab?.html ?? tab?.payload ?? '').trim()))
1786
+ ));
1787
+ let richHtmlScreen;
1788
+ if (hasRichHtmlContent) {
1789
+ richHtmlScreen = buildRichHtmlScreen(richHtmlConfig);
1790
+ submessages.unshift({
1791
+ messageType: RichSubMessageType.TEXT,
1792
+ messageText: `> ${richHtmlScreen.title}`
1793
+ });
1794
+ }
1643
1795
  const uuid = randomUUID();
1644
1796
  const unified = toUnified(submessages, uuid);
1645
1797
  unified.sections = [...unified.sections.filter(Boolean), ...extraSections];
1798
+ if (compatibility === 'new-rich-media' || compatibility === 'both') {
1799
+ const compatible = prepareNewRichMediaCompatibility({ reels, images: images || (inlineImage ? [inlineImage] : image ? [image] : []), posts });
1800
+ unified.sections.push(...compatible.sections);
1801
+ unified.new_rich_media = {
1802
+ compatibility: compatible.compatibility,
1803
+ reels: compatible.reels,
1804
+ images: compatible.images,
1805
+ posts: compatible.posts
1806
+ };
1807
+ }
1808
+ if (richHtmlScreen) unified.embedded_screens = [richHtmlScreen.embeddedScreen];
1809
+
1810
+ // Compose the remaining AIRich convenience formats as sections. These
1811
+ // aliases intentionally mirror the standalone send* helpers, allowing a
1812
+ // caller to mix them with text, HTML, tables, media, and citations in one
1813
+ // PLOGME message. Raw sections remain available as the forward-compatible
1814
+ // escape hatch for new WhatsApp primitives.
1815
+ const composed = { sections: unified.sections, embedded_screens: unified.embedded_screens, submessages, botMetadata: {} };
1816
+ const append = (prepared) => appendPreparedRichContent(composed, prepared);
1817
+ if (copyButton || copyToClipboard || copyText) append(prepareCopyToClipboardMessage(copyButton || copyToClipboard || { text: copyText, label: copyLabel }));
1818
+ if (richText) append(prepareRichTextMessage(typeof richText === 'string' ? { text: richText } : richText));
1819
+ if (quote) append(prepareRichQuoteMessage(typeof quote === 'string' ? { text: quote } : quote));
1820
+ if (richImage) append(prepareRichImageMessage(typeof richImage === 'string' ? { url: richImage } : richImage));
1821
+ if (richLinks) append(prepareRichLinkMessage(Array.isArray(richLinks) ? { links: richLinks } : richLinks));
1822
+ if (richMap || map) append(prepareRichMapMessage(richMap || map));
1823
+ if (dynamicMedia || richDynamicMedia) append(prepareRichDynamicMediaMessage(dynamicMedia || richDynamicMedia));
1824
+ if (socialEntity) append(prepareSocialEntityMessage(socialEntity));
1825
+ if (socialProfile) append(prepareSocialProfileMessage(socialProfile));
1826
+ if (richMenu) append(prepareRichMenuMessage(richMenu));
1827
+ if (airichCommandMenu) append(prepareAIRichCommandMenu(airichCommandMenu));
1828
+ if (gridImages) append(prepareGridImageMessage({ imageUrls: gridImages, verificationMetadata }));
1829
+ if (verticalAlbum) append(prepareVerticalAlbumMessage({ items: verticalAlbum, verificationMetadata }));
1830
+ if (videoGrid) append(prepareVideoGridMessage({ videos: videoGrid, verificationMetadata }));
1831
+ if (richGeneration) append(prepareRichGenerationMessage({ ...richGeneration, verificationMetadata }));
1832
+ if (richLabel) append(buildRichLabelMessage(richLabel));
1833
+ if (suggestedPrompt) append(prepareBotPromptSuggestionsMessage({ ...suggestedPrompt, verificationMetadata }));
1834
+ if (botSources) append(prepareBotSourcesMessage({ ...botSources, verificationMetadata }));
1835
+ if (botPttTranscript) append(prepareBotPttTranscriptMessage({ ...botPttTranscript, verificationMetadata }));
1836
+ if (unifiedResponse?.sections) {
1837
+ unified.sections.push(...unifiedResponse.sections);
1838
+ if (Array.isArray(unifiedResponse.embedded_screens)) unified.embedded_screens = [...(unified.embedded_screens || []), ...unifiedResponse.embedded_screens];
1839
+ }
1840
+ for (const section of [...(Array.isArray(rawSections) ? rawSections : []), ...(Array.isArray(richSections) ? richSections : [])]) {
1841
+ if (section && typeof section === 'object') unified.sections.push(section);
1842
+ }
1843
+ if (composed.embedded_screens?.length) unified.embedded_screens = composed.embedded_screens;
1646
1844
  const richResponseMessage = proto.AIRichResponseMessage.create({
1647
- submessages,
1845
+ submessages: composed.submessages,
1648
1846
  messageType: proto.AIRichResponseMessageType.AI_RICH_RESPONSE_TYPE_STANDARD,
1649
1847
  unifiedResponse: {
1650
1848
  data: Buffer.from(JSON.stringify(unified)) // Lia@Note 25-04-26 --- Expects "ArrayBufferLike"
@@ -1658,6 +1856,7 @@ export const prepareRichResponseMessage = (content) => {
1658
1856
  });
1659
1857
  const message = wrapToBotForwardedMessage(richResponseMessage, verificationMetadata);
1660
1858
  const botMetadata = message.messageContextInfo.botMetadata;
1859
+ Object.assign(botMetadata, composed.botMetadata);
1661
1860
  // Lia@Note 13-05-26 --- Add disclaimer text on richResponseMessage
1662
1861
  if (disclaimerText) {
1663
1862
  botMetadata.messageDisclaimerText = disclaimerText;
@@ -1706,19 +1905,7 @@ export const botMetadataCertificate = (length = 685) => {
1706
1905
  */
1707
1906
  export const prepareHtmlMessage = (options = {}) => {
1708
1907
  const { html = '', trustedSources = [], verificationMetadata } = options;
1709
- const sections = [
1710
- {
1711
- __typename: 'GenAIUnifiedResponseSection',
1712
- view_model: {
1713
- __typename: 'GenAISingleLayoutViewModel',
1714
- primitive: {
1715
- __typename: 'FOAHtmlPrimitiveDemoDONOTUSE',
1716
- trusted_sources: trustedSources,
1717
- payload: String(html).trim()
1718
- }
1719
- }
1720
- }
1721
- ];
1908
+ const sections = [buildFoaHtmlSection(html, trustedSources)];
1722
1909
  const unified = { response_id: randomUUID(), sections };
1723
1910
  const richResponseMessage = proto.AIRichResponseMessage.create({
1724
1911
  submessages: [],
@@ -1735,35 +1922,8 @@ export const prepareHtmlMessage = (options = {}) => {
1735
1922
  * aeacdsnw HTML primitive used by the attached game message.
1736
1923
  */
1737
1924
  export const prepareRichHtmlMessage = (options = {}) => {
1738
- const {
1739
- html = '',
1740
- trustedSources = [],
1741
- url = '',
1742
- title = 'Preview',
1743
- tabHeader = 'HTML',
1744
- tabs,
1745
- verificationMetadata
1746
- } = options;
1747
- const tabItems = Array.isArray(tabs) && tabs.length
1748
- ? tabs
1749
- : [{ id: 'tab_0', tab_header: tabHeader, html }];
1750
- const embeddedTabs = tabItems.map((tab, index) => ({
1751
- id: tab.id || `tab_${index}`,
1752
- tab_header: tab.tab_header || tab.title || `HTML ${index + 1}`,
1753
- sections: [{
1754
- __typename: 'GenAIUnifiedResponseSection',
1755
- view_model: {
1756
- __typename: 'GenAISingleLayoutViewModel',
1757
- primitive: {
1758
- __typename: 'GenAIaeacdsnwHtmlPrimitive',
1759
- payload: String(tab.html ?? tab.payload ?? html).trim(),
1760
- url: tab.url || url,
1761
- trusted_sources: tab.trustedSources || trustedSources
1762
- }
1763
- }
1764
- }],
1765
- step_entries: []
1766
- }));
1925
+ const { verificationMetadata } = options;
1926
+ const { title, embeddedScreen } = buildRichHtmlScreen(options);
1767
1927
  const unified = {
1768
1928
  response_id: randomUUID(),
1769
1929
  sections: [{
@@ -1772,13 +1932,7 @@ export const prepareRichHtmlMessage = (options = {}) => {
1772
1932
  __typename: 'GenAISingleLayoutViewModel'
1773
1933
  }
1774
1934
  }],
1775
- embedded_screens: [{
1776
- title,
1777
- content: [{
1778
- __typename: 'FOAIDNixelButtonSheets',
1779
- tabs: embeddedTabs
1780
- }]
1781
- }]
1935
+ embedded_screens: [embeddedScreen]
1782
1936
  };
1783
1937
  const richResponseMessage = proto.AIRichResponseMessage.create({
1784
1938
  messageType: proto.AIRichResponseMessageType.AI_RICH_RESPONSE_TYPE_STANDARD,
@@ -1789,6 +1943,245 @@ export const prepareRichHtmlMessage = (options = {}) => {
1789
1943
  return wrapToBotForwardedMessage(richResponseMessage, verificationMetadata);
1790
1944
  };
1791
1945
 
1946
+ const decodePreparedUnified = (prepared) => {
1947
+ const richResponse = prepared?.botForwardedMessage?.message?.richResponseMessage;
1948
+ const rawData = richResponse?.unifiedResponse?.data;
1949
+ if (!rawData) return null;
1950
+ try {
1951
+ const text = Buffer.isBuffer(rawData)
1952
+ ? rawData.toString()
1953
+ : (rawData instanceof Uint8Array ? Buffer.from(rawData).toString() : String(rawData));
1954
+ const decoded = Buffer.from(text, /^[A-Za-z0-9+/]+=*$/.test(text) ? 'base64' : 'utf8').toString();
1955
+ return JSON.parse(decoded);
1956
+ }
1957
+ catch {
1958
+ return null;
1959
+ }
1960
+ };
1961
+
1962
+ /**
1963
+ * Compose each rich fragment in the order its key appears in the caller's
1964
+ * object. The fixed-order renderer remains the single source of wire formats;
1965
+ * this wrapper only orders independently rendered sections before combining
1966
+ * them into one AIRich response.
1967
+ */
1968
+ export const prepareRichResponseMessage = (content = {}) => {
1969
+ if (!content || typeof content !== 'object' || Array.isArray(content)) {
1970
+ throw new Boom('rich response content must be an object', { statusCode: 400 });
1971
+ }
1972
+ const submessages = [];
1973
+ const sections = [];
1974
+ const embeddedScreens = [];
1975
+ const mergedBotMetadata = {};
1976
+ const compatibility = content.compatibility === 'new-rich-media' || content.compatibility === 'both';
1977
+ let compatibilityAdded = false;
1978
+ let newRichMedia;
1979
+
1980
+ const appendPrepared = (prepared) => {
1981
+ const unified = decodePreparedUnified(prepared);
1982
+ if (Array.isArray(unified?.sections)) sections.push(...unified.sections.filter(Boolean));
1983
+ if (Array.isArray(unified?.embedded_screens)) embeddedScreens.push(...unified.embedded_screens);
1984
+ const richResponse = prepared?.botForwardedMessage?.message?.richResponseMessage;
1985
+ if (Array.isArray(richResponse?.submessages)) submessages.push(...richResponse.submessages);
1986
+ const botMetadata = prepared?.messageContextInfo?.botMetadata;
1987
+ if (botMetadata) Object.assign(mergedBotMetadata, botMetadata);
1988
+ };
1989
+
1990
+ const appendField = (key, value) => {
1991
+ const fragment = { noDonation: true, verificationMetadata: content.verificationMetadata };
1992
+ switch (key) {
1993
+ case 'text':
1994
+ fragment.contentText = value;
1995
+ break;
1996
+ case 'table':
1997
+ appendTableValue(value);
1998
+ return;
1999
+ case 'code':
2000
+ fragment.code = value;
2001
+ fragment.language = content.language;
2002
+ break;
2003
+ case 'inlineImage':
2004
+ fragment.inlineImage = value;
2005
+ fragment.imageText = content.imageText;
2006
+ fragment.alignment = content.alignment;
2007
+ fragment.tapLinkUrl = content.tapLinkUrl;
2008
+ break;
2009
+ case 'html':
2010
+ fragment.html = value;
2011
+ fragment.trustedSources = content.trustedSources;
2012
+ break;
2013
+ case 'richHtml':
2014
+ fragment.richHtml = value;
2015
+ fragment.trustedSources = content.trustedSources;
2016
+ break;
2017
+ case 'richTable':
2018
+ appendTableValue(value);
2019
+ return;
2020
+ case 'tables':
2021
+ if (Array.isArray(value)) value.forEach(appendTableValue);
2022
+ return;
2023
+ case 'richResponse': {
2024
+ if (!Array.isArray(value)) break;
2025
+ for (const item of value) {
2026
+ if (item?.inlineVideo) appendField('inlineVideo', item.inlineVideo);
2027
+ else if (Object.hasOwn(item || {}, 'table')) appendField('table', item.table);
2028
+ else if (item?.posts) appendField('posts', item.posts);
2029
+ else if (item?.products) appendField('products', item.products);
2030
+ else if (item?.suggested) appendField('suggested', item.suggested);
2031
+ else appendPrepared(prepareRichResponseMessageFixedOrder({
2032
+ richResponse: [item],
2033
+ noDonation: true,
2034
+ verificationMetadata: content.verificationMetadata
2035
+ }));
2036
+ }
2037
+ return;
2038
+ }
2039
+ case 'contentItemsGroup':
2040
+ Object.assign(fragment, value);
2041
+ break;
2042
+ default:
2043
+ fragment[key] = value;
2044
+ }
2045
+ appendPrepared(prepareRichResponseMessageFixedOrder(fragment));
2046
+ };
2047
+
2048
+ const appendSimpleTable = (tableRows) => {
2049
+ const normalizedRows = tableRows.map(row => Array.isArray(row) ? row : [row]);
2050
+ appendPrepared(prepareRichResponseMessageFixedOrder({
2051
+ table: normalizedRows,
2052
+ title: content.title,
2053
+ noHeading: content.noHeading,
2054
+ noDonation: true,
2055
+ verificationMetadata: content.verificationMetadata
2056
+ }));
2057
+ };
2058
+
2059
+ const appendRichTable = (table) => {
2060
+ if (!table || typeof table !== 'object' || Array.isArray(table)) return;
2061
+ const richTable = generateTableContent(
2062
+ String(table.title ?? ''),
2063
+ Array.isArray(table.headers) ? table.headers : [],
2064
+ Array.isArray(table.rows) ? table.rows : [],
2065
+ undefined,
2066
+ { headerText: table.headerText, footer: table.footer }
2067
+ );
2068
+ const richResponse = richTable?.message?.botForwardedMessage?.message?.richResponseMessage;
2069
+ if (Array.isArray(richResponse?.submessages)) submessages.push(...richResponse.submessages);
2070
+ };
2071
+
2072
+ const appendTableValue = (table) => {
2073
+ if (Array.isArray(table)) {
2074
+ appendSimpleTable(table);
2075
+ return;
2076
+ }
2077
+ if (!table || typeof table !== 'object') return;
2078
+ if (Array.isArray(table.headers) && Array.isArray(table.rows)) {
2079
+ appendRichTable(table);
2080
+ }
2081
+ else if (Array.isArray(table.rows)) {
2082
+ appendSimpleTable([...(Array.isArray(table.headers) ? [table.headers] : []), ...table.rows]);
2083
+ }
2084
+ };
2085
+
2086
+ const appendCompatibility = () => {
2087
+ if (compatibilityAdded || !compatibility) return;
2088
+ compatibilityAdded = true;
2089
+ const compatible = prepareNewRichMediaCompatibility({
2090
+ reels: content.reels,
2091
+ images: content.images || (content.image ? [content.image] : []),
2092
+ posts: content.posts
2093
+ });
2094
+ if (!compatible.sections.length) return;
2095
+ sections.push(...compatible.sections);
2096
+ newRichMedia = {
2097
+ compatibility: compatible.compatibility,
2098
+ reels: compatible.reels,
2099
+ images: compatible.images,
2100
+ posts: compatible.posts
2101
+ };
2102
+ };
2103
+
2104
+ const nonRenderableKeys = new Set([
2105
+ 'alignment', 'compatibility', 'copyLabel', 'disclaimerText',
2106
+ 'image', 'images', 'language', 'noDonation', 'noHeading', 'title',
2107
+ 'tapLinkUrl', 'trustedSources', 'verificationMetadata'
2108
+ ]);
2109
+ const renderableKeys = new Set([
2110
+ 'headerText', 'contentText', 'text', 'footerText', 'code', 'table', 'richTable', 'tables', 'items', 'reels',
2111
+ 'inlineImage', 'inlineVideo', 'latex', 'links', 'sources', 'posts', 'products', 'suggested',
2112
+ 'html', 'richHtml', 'richResponse', 'copyButton', 'copyToClipboard', 'copyText',
2113
+ 'richText', 'quote', 'richImage', 'richLinks', 'richMap', 'map', 'dynamicMedia',
2114
+ 'richDynamicMedia', 'socialEntity', 'socialProfile', 'richMenu', 'airichCommandMenu',
2115
+ 'unifiedResponse', 'gridImages', 'verticalAlbum', 'videoGrid', 'richGeneration',
2116
+ 'richLabel', 'suggestedPrompt', 'botSources', 'botPttTranscript', 'sections', 'richSections'
2117
+ ]);
2118
+ const inputEntries = Object.entries(content);
2119
+ const entries = [
2120
+ ...inputEntries.filter(([key]) => key === 'headerText'),
2121
+ ...inputEntries.filter(([key]) => key !== 'headerText' && key !== 'footerText'),
2122
+ ...inputEntries.filter(([key]) => key === 'footerText')
2123
+ ];
2124
+ for (let index = 0; index < entries.length; index++) {
2125
+ const [key, value] = entries[index];
2126
+ if (['reels', 'images', 'image', 'posts'].includes(key)) appendCompatibility();
2127
+ if (key === 'items' || key === 'reels') {
2128
+ const grouped = {};
2129
+ let nextIndex = index;
2130
+ while (nextIndex < entries.length && ['items', 'reels'].includes(entries[nextIndex][0])) {
2131
+ const [groupKey, groupValue] = entries[nextIndex];
2132
+ grouped[groupKey] = groupValue;
2133
+ nextIndex++;
2134
+ }
2135
+ if (Object.hasOwn(grouped, 'reels')) appendCompatibility();
2136
+ appendField('contentItemsGroup', grouped);
2137
+ index = nextIndex - 1;
2138
+ continue;
2139
+ }
2140
+ if (!renderableKeys.has(key) || nonRenderableKeys.has(key)) continue;
2141
+ appendField(key, value);
2142
+ }
2143
+
2144
+ if (content.noDonation !== true) {
2145
+ appendPrepared(prepareRichResponseMessageFixedOrder({ links: [], noDonation: false }));
2146
+ }
2147
+
2148
+ const responseId = randomUUID();
2149
+ const unified = {
2150
+ response_id: responseId,
2151
+ sections,
2152
+ ...(embeddedScreens.length ? { embedded_screens: embeddedScreens } : {}),
2153
+ ...(newRichMedia ? { new_rich_media: newRichMedia } : {})
2154
+ };
2155
+ const richResponseMessage = proto.AIRichResponseMessage.create({
2156
+ submessages,
2157
+ messageType: proto.AIRichResponseMessageType.AI_RICH_RESPONSE_TYPE_STANDARD,
2158
+ unifiedResponse: { data: Buffer.from(JSON.stringify(unified)) },
2159
+ contextInfo: { isForwarded: true, forwardingScore: 1, forwardedAiBotMessageInfo: { botJid: '867051314767696@bot' }, forwardOrigin: 4 }
2160
+ });
2161
+ const message = wrapToBotForwardedMessage(richResponseMessage, content.verificationMetadata);
2162
+ const botMetadata = message.messageContextInfo.botMetadata;
2163
+ Object.assign(botMetadata, mergedBotMetadata);
2164
+ if (content.disclaimerText) botMetadata.messageDisclaimerText = content.disclaimerText;
2165
+ botMetadata.botResponseId = responseId;
2166
+ const { sources, mediaDetailsMetadataList } = buildAdditionalBotMetadataContext(submessages);
2167
+ if (sources.length > 0) botMetadata.richResponseSourcesMetadata = { sources };
2168
+ if (mediaDetailsMetadataList.length > 0) botMetadata.unifiedResponseMutation = { mediaDetailsMetadataList };
2169
+ return message;
2170
+ };
2171
+
2172
+ /** Merge any prepared AIRich helper into a single PLOGME response. */
2173
+ const appendPreparedRichContent = (target, prepared) => {
2174
+ const unified = decodePreparedUnified(prepared);
2175
+ if (unified?.sections?.length) target.sections.push(...unified.sections);
2176
+ if (Array.isArray(unified?.embedded_screens)) {
2177
+ target.embedded_screens = [...(target.embedded_screens || []), ...unified.embedded_screens];
2178
+ }
2179
+ const richResponse = prepared?.botForwardedMessage?.message?.richResponseMessage;
2180
+ if (Array.isArray(richResponse?.submessages)) target.submessages.push(...richResponse.submessages);
2181
+ const botMetadata = prepared?.messageContextInfo?.botMetadata;
2182
+ if (botMetadata) Object.assign(target.botMetadata, botMetadata);
2183
+ };
2184
+
1792
2185
  /**
1793
2186
  * Proofs are forwarded only when the caller supplies real, verifiable proofs.
1794
2187
  * The library never fabricates a certificate chain or signature: an unverifiable