plogme 2.0.0 → 2.0.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.
@@ -5,7 +5,7 @@
5
5
  * If you use or copy this code, please credit my name or project. AND DO NOT CHANGE THIS NOTE
6
6
  */
7
7
  import { getRandomValues, randomUUID, randomBytes } from 'crypto';
8
- import { DONATE_URL, LEXER_REGEX } from '../Defaults/index.js';
8
+ import { BOT_RENDERING_CONFIG_METADATA, DONATE_URL, LEXER_REGEX } from '../Defaults/index.js';
9
9
  import { LANGUAGE_KEYWORDS } from '../WABinary/constants.js';
10
10
  import { CodeHighlightType, RichSubMessageType } from '../Types/RichType.js';
11
11
  import { proto } from '../../WAProto/index.js';
@@ -196,7 +196,8 @@ export const prepareRichGenerationMessage = ({
196
196
  estimatedMs,
197
197
  itemId = `imagine_${randomUUID()}`,
198
198
  responseId = randomUUID(),
199
- forwarded = true
199
+ forwarded = true,
200
+ verificationMetadata
200
201
  } = {}) => {
201
202
  const sections = [];
202
203
  if (text) {
@@ -210,7 +211,7 @@ export const prepareRichGenerationMessage = ({
210
211
  unifiedResponse: { data: Buffer.from(JSON.stringify({ response_id: responseId, sections })) },
211
212
  contextInfo: forwarded ? { isForwarded: true, forwardingScore: 1, forwardedAiBotMessageInfo: { botJid: '867051314767696@bot' }, forwardOrigin: 4 } : {}
212
213
  });
213
- const message = wrapToBotForwardedMessage(richResponseMessage);
214
+ const message = wrapToBotForwardedMessage(richResponseMessage, verificationMetadata);
214
215
  message.messageContextInfo = message.messageContextInfo || {};
215
216
  message.messageContextInfo.botMetadata = {
216
217
  ...(message.messageContextInfo.botMetadata || {}),
@@ -220,56 +221,72 @@ export const prepareRichGenerationMessage = ({
220
221
  };
221
222
 
222
223
  // Lia@Note 17-04-26 --- WIP
224
+ /**
225
+ * A preview-media descriptor is only meaningful when the client can locate and
226
+ * decrypt the media. A descriptor without a key, hash and direct path describes
227
+ * media that cannot be fetched, so it is dropped rather than shipped
228
+ * present-but-empty (which is what previously produced `''` crypto fields and a
229
+ * hard-coded `image/jpeg` mimetype on every reel).
230
+ *
231
+ * Callers that upload real media supply it as `previewMedia` on the reel item or
232
+ * LaTeX expression: `{ fileSha256, mediaKey, fileEncSha256, directPath, mimetype?, mediaKeyTimestamp?, mediaId? }`.
233
+ */
234
+ const normalizePreviewMedia = (media) => {
235
+ if (!media || typeof media !== 'object') return null;
236
+ const { fileSha256, mediaKey, fileEncSha256, directPath, mimetype, mediaKeyTimestamp } = media;
237
+ if (!fileSha256 || !mediaKey || !fileEncSha256 || !directPath) return null;
238
+ return {
239
+ fileSha256,
240
+ mediaKey,
241
+ fileEncSha256,
242
+ directPath,
243
+ mediaKeyTimestamp: mediaKeyTimestamp ?? unixTimestampSeconds(),
244
+ mimetype: mimetype || 'image/jpeg'
245
+ };
246
+ };
247
+
223
248
  export const buildAdditionalBotMetadataContext = (submessages) => {
224
249
  const sources = [];
225
250
  const mediaDetailsMetadataList = [];
226
- for (let i = 0; i < submessages.length; i++) {
227
- const submessage = submessages[i];
251
+ for (const submessage of submessages) {
228
252
  switch (submessage.messageType) {
229
253
  case RichSubMessageType.CONTENT_ITEMS: {
230
254
  const itemsMetadata = submessage.contentItemsMetadata?.itemsMetadata || [];
231
- for (let n = 0; n < itemsMetadata.length; n++) {
232
- const reelItem = itemsMetadata[n]?.reelItem || itemsMetadata[n] || {};
255
+ for (const item of itemsMetadata) {
256
+ const reelItem = item?.reelItem || item || {};
233
257
  sources.push({
234
258
  provider: 0,
235
259
  thumbnailCdnUrl: reelItem.thumbnailUrl || reelItem.thumbnail_url || '',
236
260
  sourceProviderUrl: reelItem.videoUrl || reelItem.reels_url || reelItem.url || '',
237
261
  sourceQuery: '',
238
262
  faviconCdnUrl: reelItem.profileIconUrl || reelItem.avatar_url || '',
239
- citationNumber: i + 1,
263
+ // 1-based source ordinal, not the submessage index.
264
+ citationNumber: sources.length + 1,
240
265
  sourceTitle: reelItem.title || reelItem.reels_title || reelItem.creator || ''
241
266
  });
242
- mediaDetailsMetadataList.push({
243
- id: randomBytes(32).toString('hex'),
244
- previewMedia: {
245
- fileSha256: '',
246
- mediaKey: '',
247
- fileEncSha256: '',
248
- directPath: '',
249
- mediaKeyTimestamp: unixTimestampSeconds(),
250
- mimetype: 'image/jpeg'
251
- }
252
- });
267
+ const previewMedia = normalizePreviewMedia(reelItem.previewMedia);
268
+ if (previewMedia) {
269
+ mediaDetailsMetadataList.push({
270
+ id: reelItem.mediaId || reelItem.previewMedia?.mediaId || randomBytes(32).toString('hex'),
271
+ previewMedia
272
+ });
273
+ }
253
274
  }
254
275
  break;
255
276
  }
256
- case RichSubMessageType.LATEX:
257
- const expressions = submessage.latexMetadata.expressions;
258
- for (let n = 0; n < expressions.length; n++) {
259
- const expression = expressions[n];
260
- mediaDetailsMetadataList.push({
261
- id: randomBytes(32).toString('hex'),
262
- previewMedia: {
263
- fileSha256: '',
264
- mediaKey: '',
265
- fileEncSha256: '',
266
- directPath: '',
267
- mediaKeyTimestamp: unixTimestampSeconds(),
268
- mimetype: 'image/jpeg'
269
- }
270
- });
277
+ case RichSubMessageType.LATEX: {
278
+ const expressions = submessage.latexMetadata?.expressions || [];
279
+ for (const expression of expressions) {
280
+ const previewMedia = normalizePreviewMedia(expression?.previewMedia);
281
+ if (previewMedia) {
282
+ mediaDetailsMetadataList.push({
283
+ id: expression.mediaId || expression.previewMedia?.mediaId || randomBytes(32).toString('hex'),
284
+ previewMedia
285
+ });
286
+ }
271
287
  }
272
288
  break;
289
+ }
273
290
  }
274
291
  }
275
292
  return { sources, mediaDetailsMetadataList };
@@ -336,7 +353,7 @@ const richMenuButton = (button, toast = '') => {
336
353
  * buttonsMessage/nativeFlowMessage payload.
337
354
  */
338
355
  export const prepareRichMenuMessage = (content = {}) => {
339
- const { header = {}, body = {}, footer = {} } = content || {};
356
+ const { header = {}, body = {}, footer = {}, verificationMetadata } = content || {};
340
357
  const sections = [];
341
358
  const title = header?.title || '';
342
359
  if (title) {
@@ -418,7 +435,7 @@ export const prepareRichMenuMessage = (content = {}) => {
418
435
  unifiedResponse: { data: Buffer.from(JSON.stringify(unified)) },
419
436
  contextInfo: { isForwarded: true, forwardingScore: 1, forwardOrigin: 4 },
420
437
  });
421
- return wrapToBotForwardedMessage(richResponseMessage);
438
+ return wrapToBotForwardedMessage(richResponseMessage, verificationMetadata);
422
439
  };
423
440
 
424
441
  /**
@@ -432,7 +449,7 @@ export const prepareRichMenuMessage = (content = {}) => {
432
449
  * @returns {Object} Wrapped botForwardedMessage ready for relayMessage
433
450
  */
434
451
  export const prepareCopyToClipboardMessage = (options = {}) => {
435
- const { text = '', label, alignment = 'END' } = options;
452
+ const { text = '', label, alignment = 'END', verificationMetadata } = options;
436
453
  const displayText = label || text;
437
454
  const sections = [
438
455
  {
@@ -458,7 +475,7 @@ export const prepareCopyToClipboardMessage = (options = {}) => {
458
475
  unifiedResponse: { data: Buffer.from(JSON.stringify(unified)) },
459
476
  contextInfo: { isForwarded: true, forwardingScore: 1, forwardOrigin: 4 },
460
477
  });
461
- return wrapToBotForwardedMessage(richResponseMessage);
478
+ return wrapToBotForwardedMessage(richResponseMessage, verificationMetadata);
462
479
  };
463
480
 
464
481
  /**
@@ -470,7 +487,7 @@ export const prepareCopyToClipboardMessage = (options = {}) => {
470
487
  * @returns {Object} Wrapped botForwardedMessage ready for relayMessage
471
488
  */
472
489
  export const prepareRichTextMessage = (options = {}) => {
473
- const { text = '' } = options;
490
+ const { text = '', verificationMetadata } = options;
474
491
  const sections = [
475
492
  {
476
493
  __typename: 'GenAIUnifiedResponseSection',
@@ -491,12 +508,12 @@ export const prepareRichTextMessage = (options = {}) => {
491
508
  unifiedResponse: { data: Buffer.from(JSON.stringify(unified)) },
492
509
  contextInfo: { isForwarded: true, forwardingScore: 1, forwardOrigin: 4 },
493
510
  });
494
- return wrapToBotForwardedMessage(richResponseMessage);
511
+ return wrapToBotForwardedMessage(richResponseMessage, verificationMetadata);
495
512
  };
496
513
 
497
514
  /** Builds Markdown text with an optional GenAI COPY_TO_CLIPBOARD action section. */
498
515
  export const prepareRichQuoteMessage = (options = {}) => {
499
- const { text = '', copyText, copyLabel = copyText, alignment = 'END' } = options;
516
+ const { text = '', copyText, copyLabel = copyText, alignment = 'END', verificationMetadata } = options;
500
517
  const sections = [{
501
518
  __typename: 'GenAIUnifiedResponseSection',
502
519
  view_model: {
@@ -522,7 +539,7 @@ export const prepareRichQuoteMessage = (options = {}) => {
522
539
  unifiedResponse: { data: Buffer.from(JSON.stringify(unified)) },
523
540
  contextInfo: { isForwarded: true, forwardingScore: 1, forwardOrigin: 4 },
524
541
  });
525
- return wrapToBotForwardedMessage(richResponseMessage);
542
+ return wrapToBotForwardedMessage(richResponseMessage, verificationMetadata);
526
543
  };
527
544
 
528
545
  /**
@@ -535,7 +552,7 @@ export const prepareRichQuoteMessage = (options = {}) => {
535
552
  * @returns {Object} Wrapped botForwardedMessage ready for relayMessage
536
553
  */
537
554
  export const prepareRichImageMessage = (options = {}) => {
538
- const { url = '', mimeType = 'image/png' } = options;
555
+ const { url = '', mimeType = 'image/png', verificationMetadata } = options;
539
556
  const media = {
540
557
  __typename: 'GenAIMediaItem',
541
558
  mime_type: mimeType,
@@ -562,7 +579,7 @@ export const prepareRichImageMessage = (options = {}) => {
562
579
  unifiedResponse: { data: Buffer.from(JSON.stringify(unified)) },
563
580
  contextInfo: { isForwarded: true, forwardingScore: 1, forwardOrigin: 4 },
564
581
  });
565
- return wrapToBotForwardedMessage(richResponseMessage);
582
+ return wrapToBotForwardedMessage(richResponseMessage, verificationMetadata);
566
583
  };
567
584
 
568
585
 
@@ -575,7 +592,7 @@ export const prepareRichImageMessage = (options = {}) => {
575
592
  * @returns {Object} Wrapped botForwardedMessage ready for relayMessage
576
593
  */
577
594
  export const prepareRichLinkMessage = (options = {}) => {
578
- const { links = [], text: fallbackText } = options;
595
+ const { links = [], text: fallbackText, verificationMetadata } = options;
579
596
  const sections = [];
580
597
  const allInlineEntities = [];
581
598
  let combinedText = '';
@@ -629,11 +646,11 @@ export const prepareRichLinkMessage = (options = {}) => {
629
646
  unifiedResponse: { data: Buffer.from(JSON.stringify(unified)) },
630
647
  contextInfo: { isForwarded: true, forwardingScore: 1, forwardOrigin: 4 },
631
648
  });
632
- return wrapToBotForwardedMessage(richResponseMessage);
649
+ return wrapToBotForwardedMessage(richResponseMessage, verificationMetadata);
633
650
  };
634
651
 
635
652
  export const prepareRichResponseMessage = (content) => {
636
- 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 } = content;
653
+ 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;
637
654
  let submessages = [];
638
655
  if (Array.isArray(richResponse)) {
639
656
  submessages = richResponse.map((submessage) => {
@@ -879,7 +896,7 @@ export const prepareRichResponseMessage = (content) => {
879
896
  forwardOrigin: 4
880
897
  }
881
898
  });
882
- const message = wrapToBotForwardedMessage(richResponseMessage);
899
+ const message = wrapToBotForwardedMessage(richResponseMessage, verificationMetadata);
883
900
  const botMetadata = message.messageContextInfo.botMetadata;
884
901
  // Lia@Note 13-05-26 --- Add disclaimer text on richResponseMessage
885
902
  if (disclaimerText) {
@@ -898,6 +915,11 @@ export const prepareRichResponseMessage = (content) => {
898
915
  return message;
899
916
  };
900
917
  // Lia@Note 17-04-26 --- signature and certificateChain for proofs[] field
918
+ /**
919
+ * @deprecated Generates random bytes that verify against nothing. Kept only for
920
+ * experimental tooling; the library no longer attaches generated proofs to
921
+ * messages. Pass real proofs through the `verificationMetadata` option instead.
922
+ */
901
923
  export const botMetadataSignature = () => {
902
924
  const signature = new Uint8Array(64);
903
925
  getRandomValues(signature);
@@ -923,7 +945,7 @@ export const botMetadataCertificate = (length = 685) => {
923
945
  * @returns {Object} Wrapped botForwardedMessage ready for relayMessage
924
946
  */
925
947
  export const prepareHtmlMessage = (options = {}) => {
926
- const { html = '', trustedSources = [] } = options;
948
+ const { html = '', trustedSources = [], verificationMetadata } = options;
927
949
  const sections = [
928
950
  {
929
951
  __typename: 'GenAIUnifiedResponseSection',
@@ -944,7 +966,7 @@ export const prepareHtmlMessage = (options = {}) => {
944
966
  unifiedResponse: { data: Buffer.from(JSON.stringify(unified)) },
945
967
  contextInfo: { isForwarded: true, forwardingScore: 1, forwardOrigin: 4 },
946
968
  });
947
- return wrapToBotForwardedMessage(richResponseMessage);
969
+ return wrapToBotForwardedMessage(richResponseMessage, verificationMetadata);
948
970
  };
949
971
 
950
972
  /**
@@ -959,7 +981,8 @@ export const prepareRichHtmlMessage = (options = {}) => {
959
981
  url = '',
960
982
  title = 'Preview',
961
983
  tabHeader = 'HTML',
962
- tabs
984
+ tabs,
985
+ verificationMetadata
963
986
  } = options;
964
987
  const tabItems = Array.isArray(tabs) && tabs.length
965
988
  ? tabs
@@ -1003,26 +1026,55 @@ export const prepareRichHtmlMessage = (options = {}) => {
1003
1026
  unifiedResponse: { data: Buffer.from(JSON.stringify(unified)) },
1004
1027
  contextInfo: { isForwarded: true, forwardingScore: 1, forwardOrigin: 4 }
1005
1028
  });
1006
- return wrapToBotForwardedMessage(richResponseMessage);
1029
+ return wrapToBotForwardedMessage(richResponseMessage, verificationMetadata);
1007
1030
  };
1008
1031
 
1009
- export const wrapToBotForwardedMessage = (richResponseMessage) => ({
1032
+ /**
1033
+ * Proofs are forwarded only when the caller supplies real, verifiable proofs.
1034
+ * The library never fabricates a certificate chain or signature: an unverifiable
1035
+ * proof is worse than none, because it asserts provenance the caller cannot back.
1036
+ */
1037
+ export const hasVerifiableProofs = (verificationMetadata) =>
1038
+ Array.isArray(verificationMetadata?.proofs) && verificationMetadata.proofs.length > 0;
1039
+
1040
+ /**
1041
+ * Attach trusted verification proofs to an already-built rich envelope.
1042
+ *
1043
+ * Pure, null-safe, and idempotent: returns the envelope untouched when no
1044
+ * verifiable proofs are supplied or when the envelope carries no botMetadata.
1045
+ * Used by the send pipeline (`relayMessage`) to auto-inject proofs configured
1046
+ * once on the socket as `config.metaAiRendering.verificationMetadata`, so every
1047
+ * rich message (menu, reels, rich text/image/link, HTML) is covered without each
1048
+ * send method needing to thread the config through.
1049
+ */
1050
+ export const attachBotVerificationProofs = (envelope, verificationMetadata) => {
1051
+ if (!envelope || !hasVerifiableProofs(verificationMetadata))
1052
+ return envelope;
1053
+ const botMetadata = envelope?.messageContextInfo?.botMetadata;
1054
+ if (!botMetadata || botMetadata.verificationMetadata)
1055
+ return envelope;
1056
+ botMetadata.verificationMetadata = verificationMetadata;
1057
+ return envelope;
1058
+ };
1059
+
1060
+ /**
1061
+ * Wrap a rich response in the bot-forwarded envelope.
1062
+ *
1063
+ * @param {Object} richResponseMessage - proto.AIRichResponseMessage
1064
+ * @param {Object} [verificationMetadata] - Trusted proofs supplied by the caller
1065
+ * (typically `config.metaAiRendering.verificationMetadata`). Omitted when absent
1066
+ * or empty, so no fabricated provenance is ever sent.
1067
+ */
1068
+ export const wrapToBotForwardedMessage = (richResponseMessage, verificationMetadata) => ({
1010
1069
  messageContextInfo: {
1011
1070
  botMetadata: {
1012
- // Lia@Note 09-04-26 --- TODO: Fill verificationMetadata field
1013
- verificationMetadata: {
1014
- proofs: [
1015
- {
1016
- certificateChain: [
1017
- botMetadataCertificate(),
1018
- botMetadataCertificate(892)
1019
- ],
1020
- version: 1,
1021
- useCase: 1,
1022
- signature: botMetadataSignature()
1023
- }
1024
- ]
1025
- }
1071
+ pluginMetadata: {},
1072
+ ...(hasVerifiableProofs(verificationMetadata) ? { verificationMetadata } : {}),
1073
+ // Lia@Fix --- The Bloks rendering config is required for the client to resolve
1074
+ // and verify rich media. Without it, rich messages attach media the client
1075
+ // treats as unverified: "WhatsApp can't verify the security of this media",
1076
+ // tapping Download crashes the app, and reel previews stop rendering.
1077
+ botRenderingConfigMetadata: BOT_RENDERING_CONFIG_METADATA
1026
1078
  }
1027
1079
  },
1028
1080
  botForwardedMessage: {
package/lib/index.d.ts CHANGED
@@ -266,8 +266,14 @@ export interface WASocket {
266
266
  sendBotDiagnostics(jid: string, content?: { text?: string; botBackend?: number; toolsUsed?: string[]; isThinking?: boolean; botJid?: string }, options?: Record<string, unknown>): Promise<unknown>;
267
267
  /** Send a rich response with experimental bot command metadata. */
268
268
  sendBotCommand(jid: string, content?: { text?: string; commandName?: string; commandDescription?: string; commandPrompt?: string; botJid?: string }, options?: Record<string, unknown>): Promise<unknown>;
269
+ /** Send an agentic execution plan, advertising AGENTIC_PLANNING and QUERY_PLAN. */
270
+ 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>;
269
271
  /** Send a rich response with experimental resolved tool-call metadata. */
270
272
  sendBotToolResult(jid: string, content?: { text?: string; toolCallId?: string; resolutionData?: unknown; resolutionDataSerialized?: string; botJid?: string }, options?: Record<string, unknown>): Promise<unknown>;
273
+ /** Resolve a WhatsApp Channel invite code or invite URL to channel metadata. */
274
+ newsletterGetInviteInfo(inviteCodeOrUrl: string): Promise<Record<string, unknown> | null>;
275
+ /** React to a Channel post; pass an empty string, null, or undefined to remove the reaction. */
276
+ newsletterReactMessage(jid: string, serverId: string | number, reaction?: string | null): Promise<void>;
271
277
  /** Send Markdown text containing blockquotes in a forwarded GenAI rich response. */
272
278
  sendMarkdownQuote(jid: string, content?: { text?: string; copyText?: string; copyLabel?: string; alignment?: 'END' | 'START' | 'CENTER' }, options?: Record<string, unknown>): Promise<unknown>;
273
279
  /** Send Markdown blockquotes followed by a GenAI COPY_TO_CLIPBOARD action. */
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "plogme",
3
- "version": "2.0.0",
4
- "description": "Production-ready Baileys fork \u2689 with AI groups, Meta AI prompting, safe compositing fallbacks, rich responses, interactive messages, albums, newsletters, and communities \u2014 without antiban wrappers.",
3
+ "version": "2.0.2",
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",
7
7
  "scripts": {