plogme 2.0.8 → 2.1.0

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.
@@ -7,7 +7,7 @@
7
7
  * - Plain text fallback is used unless the native payload can be authenticated
8
8
  *
9
9
  * metaTyping() — shows the "typing..." / planning steps indicator.
10
- * sendMetaComposited() — full flow: typing → delete → final message.
10
+ * sendMetaComposited() — full flow: initial message → streaming MESSAGE_EDIT frames → final edit.
11
11
  *
12
12
  * If you use or copy this code, please credit @crysnovax/bailey.
13
13
  */
@@ -161,12 +161,21 @@ export const metaTyping = async (sock, jid, {
161
161
  return sock.sendMessage(jid, placeholder);
162
162
  };
163
163
 
164
+ /** Send a streaming MESSAGE_EDIT frame without changing the original key. */
165
+ export const editMetaMessage = async (sock, jid, key, content, sendOptions = {}, streamContext) => {
166
+ if (!key)
167
+ return undefined;
168
+ const frame = streamContext
169
+ ? { ...content, messageContextInfo: { ...streamContext, ...content.messageContextInfo } }
170
+ : content;
171
+ return sock.sendMessage(jid, { ...frame, edit: key }, sendOptions);
172
+ };
173
+
164
174
  /**
165
- * sendMetaComposited — full Meta AI flow (UNIVERSAL).
166
- * 1. Send progress indicator
167
- * 2. Wait thinkingMs
168
- * 3. Delete placeholder
169
- * 4. Send final message fresh
175
+ * sendMetaComposited — full Meta AI-style flow (UNIVERSAL).
176
+ * 1. Send initial progress indicator
177
+ * 2. Optionally emit streamUpdates as MESSAGE_EDIT frames
178
+ * 3. Edit the same message into the final response
170
179
  */
171
180
  export const sendMetaComposited = async (sock, jid, content, {
172
181
  thinkingMs = 2000,
@@ -174,7 +183,10 @@ export const sendMetaComposited = async (sock, jid, content, {
174
183
  steps = [],
175
184
  placeholderText = '',
176
185
  sendOptions = {},
177
- useNativeMeta = false
186
+ useNativeMeta = false,
187
+ streamUpdates = [],
188
+ streamUpdateDelayMs = 0,
189
+ streamContext
178
190
  } = {}) => {
179
191
  const placeholder = await metaTyping(sock, jid, {
180
192
  description, steps, estimatedMs: thinkingMs, placeholderText, useNativeMeta
@@ -183,12 +195,29 @@ export const sendMetaComposited = async (sock, jid, content, {
183
195
  try {
184
196
  await delay(thinkingMs);
185
197
  if (placeholder?.key) {
186
- await sock.sendMessage(jid, { delete: placeholder.key });
198
+ for (const update of streamUpdates) {
199
+ const frame = typeof update === 'string' ? { text: update } : update;
200
+ try {
201
+ await editMetaMessage(sock, jid, placeholder.key, frame, sendOptions, streamContext);
202
+ }
203
+ catch (_) {}
204
+ if (streamUpdateDelayMs > 0)
205
+ await delay(streamUpdateDelayMs);
206
+ }
207
+ try {
208
+ await editMetaMessage(sock, jid, placeholder.key, content, sendOptions, streamContext);
209
+ }
210
+ catch (_) {
211
+ await sock.sendMessage(jid, content, sendOptions);
212
+ }
213
+ }
214
+ else {
215
+ await sock.sendMessage(jid, content, sendOptions);
187
216
  }
188
217
  } catch (_) {}
189
218
 
190
219
  await sock.sendPresenceUpdate('paused', jid);
191
- return sock.sendMessage(jid, content, sendOptions);
220
+ return placeholder;
192
221
  };
193
222
 
194
223
  /**
@@ -59,7 +59,13 @@ const buildRichImagePrimitive = (image = {}, expiration = String(Date.now() + 30
59
59
  const fullHeight = Number(image.fullHeight || image.full_height || image.highResHeight || image.high_res_height || image.height || height);
60
60
  const darkWidth = Number(image.darkWidth || image.dark_width || fullWidth);
61
61
  const darkHeight = Number(image.darkHeight || image.dark_height || fullHeight);
62
- const mimeType = image.mimeType || image.mime_type || 'image/png';
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
+ })();
63
69
  const media = (url, mediaWidth, mediaHeight) => ({
64
70
  url,
65
71
  url_fallback: url,
@@ -68,14 +74,17 @@ const buildRichImagePrimitive = (image = {}, expiration = String(Date.now() + 30
68
74
  width: mediaWidth,
69
75
  height: mediaHeight
70
76
  });
71
- return {
72
- preview_image: media(previewUrl, width, height),
77
+ const primitive = {
73
78
  full_image: media(fullUrl, fullWidth, fullHeight),
74
- dark_mode_preview_image: media(darkPreviewUrl, darkWidth, darkHeight),
75
- dark_mode_full_image: media(darkFullUrl, darkWidth, darkHeight),
76
79
  asset_query_status: 'FETCHED',
77
80
  __typename: 'GenAIImagePrimitive'
78
81
  };
82
+ // WhatsApp requires the preview slots to recognize an image primitive.
83
+ // Omitting them makes mixed image/video albums silently drop the image.
84
+ primitive.preview_image = media(previewUrl, width, height);
85
+ primitive.dark_mode_preview_image = media(darkPreviewUrl, darkWidth, darkHeight);
86
+ primitive.dark_mode_full_image = media(darkFullUrl, darkWidth, darkHeight);
87
+ return primitive;
79
88
  };
80
89
  // Lia@Changes 09-04-26 --- Inject buffer into unifiedResponse.data to support proper rendering of rich messages (ex: tables and code blocks)
81
90
  export const toUnified = (submessages, uuid) => ({
@@ -974,13 +983,33 @@ export const prepareVerticalAlbumMessage = ({ items = [], verificationMetadata }
974
983
  if (!url) throw new Error('Each video album item requires a video URL');
975
984
  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' });
976
985
  }
977
- const image = item?.image || item?.data || item;
978
- const sourceUrl = typeof image === 'string' ? image : firstImageValue(
986
+ const rawImage = item?.image || item?.data || item;
987
+ const image = typeof rawImage === 'string' ? { url: rawImage } : (rawImage || {});
988
+ const sourceUrl = firstImageValue(
979
989
  image?.imageHighResUrl, image?.highResUrl, image?.fullUrl,
980
990
  image?.imagePreviewUrl, image?.previewUrl, image?.sourceUrl, image?.url
981
991
  ) || '';
982
992
  if (!sourceUrl) throw new Error('Each image album item requires an image URL');
983
- const primitive = buildRichImagePrimitive({ ...image, width: image?.width || item?.width, height: image?.height || item?.height }, expiration);
993
+ // Unlike a compact image grid, vertical albums are intended to render
994
+ // the actual media at full size. Some clients choose preview_image and
995
+ // never follow full_image, so expose the selected full URL/dimensions
996
+ // in both slots instead of allowing a thumbnail URL to win.
997
+ const fullUrl = firstImageValue(
998
+ image?.imageHighResUrl, image?.highResUrl, image?.high_res_url,
999
+ image?.fullUrl, image?.full_url, image?.sourceUrl, image?.source_url, image?.url
1000
+ ) || sourceUrl;
1001
+ const fullWidth = Number(image?.fullWidth || image?.full_width || image?.highResWidth || image?.high_res_width || image?.width || item?.width || 0);
1002
+ const fullHeight = Number(image?.fullHeight || image?.full_height || image?.highResHeight || image?.high_res_height || image?.height || item?.height || 0);
1003
+ const primitive = buildRichImagePrimitive({
1004
+ ...image,
1005
+ imagePreviewUrl: fullUrl,
1006
+ previewUrl: fullUrl,
1007
+ sourceUrl: fullUrl,
1008
+ imageHighResUrl: fullUrl,
1009
+ fullUrl,
1010
+ ...(fullWidth > 0 ? { width: fullWidth, fullWidth } : {}),
1011
+ ...(fullHeight > 0 ? { height: fullHeight, fullHeight } : {})
1012
+ }, expiration);
984
1013
  return { ...primitive, display_mode: 'FULL_WIDTH', full_width: true };
985
1014
  });
986
1015
  const richResponseMessage = proto.AIRichResponseMessage.create({
@@ -1788,6 +1817,57 @@ export const attachBotVerificationProofs = (envelope, verificationMetadata) => {
1788
1817
  return envelope;
1789
1818
  };
1790
1819
 
1820
+ /**
1821
+ * Build a forwarded AIRich label message.
1822
+ *
1823
+ * This is the small single-label surface used by WhatsApp clients for a
1824
+ * colored forwarded bubble. The unified response is intentionally serialized
1825
+ * as base64 text because that is the wire shape accepted by the raw
1826
+ * `relayMessage` API.
1827
+ */
1828
+ export const buildRichLabelMessage = ({
1829
+ text = '',
1830
+ variant = 'POSITIVE',
1831
+ backgroundColor = '#FF0000FF',
1832
+ textColor = '#000000FF',
1833
+ responseId = randomUUID(),
1834
+ } = {}) => {
1835
+ const unified = {
1836
+ __typename: 'GenAIUnifiedResponse',
1837
+ response_id: responseId,
1838
+ sections: [{
1839
+ __typename: 'GenAIUnifiedResponseSection',
1840
+ view_model: {
1841
+ __typename: 'GenAISingleLayoutViewModel',
1842
+ primitive: {
1843
+ __typename: 'GenAILabelPrimitive',
1844
+ text: String(text),
1845
+ variant,
1846
+ background_color_override: backgroundColor,
1847
+ text_color_override: textColor,
1848
+ },
1849
+ },
1850
+ }],
1851
+ };
1852
+
1853
+ return {
1854
+ botForwardedMessage: {
1855
+ message: {
1856
+ richResponseMessage: {
1857
+ messageType: 1,
1858
+ unifiedResponse: {
1859
+ data: Buffer.from(JSON.stringify(unified)).toString('base64'),
1860
+ },
1861
+ contextInfo: {
1862
+ isForwarded: true,
1863
+ forwardOrigin: 4,
1864
+ },
1865
+ },
1866
+ },
1867
+ },
1868
+ };
1869
+ };
1870
+
1791
1871
  /**
1792
1872
  * Wrap a rich response in the bot-forwarded envelope.
1793
1873
  *
@@ -96,7 +96,9 @@ export const generateRegistrationNode = ({ registrationId, signedPreKey, signedI
96
96
  supportMessageAssociation: true,
97
97
  supportGroupHistory: false,
98
98
  onDemandReady: undefined,
99
- supportGuestChat: undefined
99
+ supportGuestChat: undefined,
100
+ supportOmittedConversationIndex: true,
101
+ supportUniversalReachChat: true
100
102
  },
101
103
  version: {
102
104
  primary: 10,
@@ -203,4 +205,4 @@ export const encodeSignedDeviceIdentity = (account, includeSignatureKey) => {
203
205
  account.accountSignatureKey = null;
204
206
  }
205
207
  return proto.ADVSignedDeviceIdentity.encode(account).finish();
206
- };
208
+ };
package/lib/index.d.ts CHANGED
@@ -132,6 +132,25 @@ export interface AIPromptOptions {
132
132
  /** Extra options forwarded to `sendMessage` (quoted, linkPreview, ...). */
133
133
  [key: string]: unknown;
134
134
  }
135
+ /** Options for the verified Meta AI (Hatch) send path. */
136
+ export interface MetaAISendOptions {
137
+ /** Destination JID; defaults to `867051314767696@bot` for a direct prompt. */
138
+ jid?: string;
139
+ invokerJid?: string;
140
+ sessionId?: string;
141
+ messageSecret?: Buffer;
142
+ capabilities?: number[];
143
+ /** F-BID used when invoking Meta AI in a group. */
144
+ botFbid?: string;
145
+ /** Add a visible Meta AI mention to a group prompt. */
146
+ mention?: boolean;
147
+ personaType?: string;
148
+ agentEngagementType?: string;
149
+ modeSelected?: string;
150
+ conversationContext?: Uint8Array | Buffer;
151
+ quoted?: unknown;
152
+ links?: unknown;
153
+ }
135
154
 
136
155
  export interface RichMenuButton {
137
156
  id?: string;
@@ -351,6 +370,26 @@ export interface AIRichCommandMenuContent {
351
370
  footer?: string;
352
371
  }
353
372
 
373
+ export declare function parseUnifiedResponseData(data: unknown): Record<string, unknown> | null;
374
+ export declare function textFromUnifiedResponse(data: unknown): string | null;
375
+ export declare function textFromRichResponse(message: unknown): string | null;
376
+ export declare function extractMetaAiText(message: unknown): string | null;
377
+ export declare function decodeUnifiedResponseInPlace<T extends Record<string, unknown>>(message: T): T;
378
+ export interface MetaCompositedOptions {
379
+ thinkingMs?: number;
380
+ description?: string;
381
+ steps?: Array<Record<string, unknown>>;
382
+ estimatedMs?: number;
383
+ placeholderText?: string;
384
+ sendOptions?: Record<string, unknown>;
385
+ useNativeMeta?: boolean;
386
+ streamUpdates?: Array<string | Record<string, unknown>>;
387
+ streamUpdateDelayMs?: number;
388
+ streamContext?: Record<string, unknown>;
389
+ }
390
+ export declare function editMetaMessage(sock: WASocket, jid: string, key: WAMessageKey, content: Record<string, unknown>, sendOptions?: Record<string, unknown>, streamContext?: Record<string, unknown>): Promise<unknown>;
391
+ export declare function sendMetaComposited(sock: WASocket, jid: string, content: Record<string, unknown>, options?: MetaCompositedOptions): Promise<unknown>;
392
+
354
393
  export interface WASocket {
355
394
  sendMessage(jid: string | string[], content: StatusContent | GroupStatusContent | Record<string, unknown>, options?: Partial<StatusOptions> & Record<string, unknown>): Promise<unknown>;
356
395
  /** Send at least two image/video URL items as a full-width AIRich vertical album. */
@@ -427,6 +466,12 @@ export interface WASocket {
427
466
  sendBotToolResult(jid: string, content?: { text?: string; toolCallId?: string; resolutionData?: unknown; resolutionDataSerialized?: string; botJid?: string }, options?: Record<string, unknown>): Promise<unknown>;
428
467
  /** Send one tolerant AIRich response containing every supported section supplied in content. */
429
468
  sendPlogmeMessage(jid: string, content?: Record<string, unknown>, quoted?: unknown, options?: Record<string, unknown>): Promise<unknown>;
469
+ /** Send a compact forwarded AIRich label bubble with optional colors and variant. */
470
+ sendRichLabelMessage(jid: string, content?: { text?: string; variant?: string; backgroundColor?: string; textColor?: string; responseId?: string }, options?: Record<string, unknown>): Promise<Record<string, unknown>>;
471
+ /** Send a prompt through the verified Meta AI/Hatch route and return its message ID. */
472
+ sendMetaAI(text: string, options?: MetaAISendOptions): Promise<string>;
473
+ /** Backward-compatible destination-first signature. */
474
+ sendMetaAI(jid: string, text: string, options?: Omit<MetaAISendOptions, 'jid'>): Promise<string>;
430
475
  /** Resolve a WhatsApp Channel invite code or invite URL to channel metadata. */
431
476
  newsletterGetInviteInfo(inviteCodeOrUrl: string): Promise<Record<string, unknown> | null>;
432
477
  /** 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.8",
3
+ "version": "2.1.0",
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",