@crysnovax/baileys 2.6.1 → 2.6.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.
package/README.md CHANGED
@@ -118,7 +118,7 @@ const crysnova = {
118
118
  | Image Processing | ✅ | Auto-detects `sharp`, `@napi-rs/image`, or `jimp` |
119
119
  | Safe FFmpeg | ✅ | Uses `spawn` instead of `exec` |
120
120
  | In-Memory Store | ✅ | Reintroduced with minimal ESM adaptation |
121
- | HD Profile Picture | 🆕 | Full-size upload with no crop or resize via `{ hd: true }` |
121
+ | HD Profile Picture | | Full-size upload with no crop or resize via `{ hd: true }` (max 720px) |
122
122
 
123
123
  ---
124
124
 
@@ -776,6 +776,10 @@ sock.sendMessage(jid, {
776
776
 
777
777
  ## Meta AI Features
778
778
 
779
+ Meta AI-style thinking indicators and live reasoning feeds. Works on **all WhatsApp clients** — no "Update WhatsApp" messages.
780
+
781
+ > **Note:** These use plain text placeholders with typing indicators, not native Meta AI rendering (which only works on AI-enabled WhatsApp clients). The visual experience is identical — users see "Thinking…" with live step completion.
782
+
779
783
  ### Meta Typing Indicator
780
784
 
781
785
  Show a live thinking indicator that you control. Delete it manually when ready — no "edited" badge ever appears.
@@ -797,6 +801,19 @@ await sock.sendMessage(jid, { delete: placeholder.key })
797
801
  await sock.sendMessage(jid, { text: 'Here is your answer!' })
798
802
  ```
799
803
 
804
+ **What users see:**
805
+ ```
806
+ [typing… indicator]
807
+
808
+ _Thinking…_
809
+ ○ Reading your message…
810
+ ○ Writing response…
811
+
812
+ [auto-deletes]
813
+
814
+ Here is your answer!
815
+ ```
816
+
800
817
  ### Meta Compositing
801
818
 
802
819
  Full flow: indicator shows → auto-deletes → clean final message lands. Works with every rich content type.
@@ -875,6 +892,30 @@ await replayPlanning(
875
892
  )
876
893
  ```
877
894
 
895
+ **What users see:**
896
+ ```
897
+ _Thinking…_
898
+ ○ Understanding your question…
899
+ ○ Searching for data…
900
+ ○ Writing the answer…
901
+
902
+ [step 1 completes]
903
+ _Thinking…_
904
+ ✓ Understanding your question…
905
+ ○ Searching for data…
906
+ ○ Writing the answer…
907
+
908
+ [step 2 completes]
909
+ _Thinking…_
910
+ ✓ Understanding your question…
911
+ ✓ Searching for data…
912
+ ○ Writing the answer…
913
+
914
+ [all done, deletes, then:]
915
+
916
+ const answer = 42
917
+ ```
918
+
878
919
  **Step type helpers:**
879
920
 
880
921
  ```javascript
@@ -1344,7 +1385,9 @@ sock.updateProfilePicture(jid, { url })
1344
1385
  // Standard with custom dimensions
1345
1386
  sock.updateProfilePicture(jid, { url }, { width: 640, height: 640 })
1346
1387
 
1347
- // 🆕 HD — full resolution, no crop, no resize
1388
+ // HD — preserves original aspect ratio, no crop, no padding
1389
+ // Images under 720px pass through unchanged.
1390
+ // Larger images are scaled down proportionally to fit within 720px.
1348
1391
  sock.updateProfilePicture(jid, buffer, { hd: true })
1349
1392
  sock.updateProfilePicture(jid, { url }, { hd: true })
1350
1393
 
@@ -5,7 +5,7 @@ import { DEFAULT_CACHE_TTLS, HISTORY_SYNC_PAUSED_TIMEOUT_MS, PROCESSABLE_HISTORY
5
5
  import { ALL_WA_PATCH_NAMES } from '../Types/index.js';
6
6
  import { SyncState } from '../Types/State.js';
7
7
  import { chatModificationToAppPatch, decodePatches, decodeSyncdSnapshot, encodeSyncdPatch, ensureLTHashStateVersion, extractSyncdPatches, generateProfilePicture, getHistoryMsg, isAppStateSyncIrrecoverable, isMissingKeyError, MAX_SYNC_ATTEMPTS, newLTHashState, processSyncAction } from '../Utils/index.js';
8
- import { getStream, toBuffer } from '../Utils/messages-media.js';
8
+ import { generateProfilePictureHD } from '../Utils/messages-media.js';
9
9
  import { makeMutex } from '../Utils/make-mutex.js';
10
10
  import processMessage from '../Utils/process-message.js';
11
11
  import { buildTcTokenFromJid } from '../Utils/tc-token-utils.js';
@@ -228,18 +228,12 @@ export const makeChatsSocket = (config) => {
228
228
  else {
229
229
  targetJid = undefined;
230
230
  }
231
- let img;
232
- if (dimensions?.hd) {
233
- // crysnovax@HD-Profile --- bypass resize/crop, send raw buffer directly
234
- if (Buffer.isBuffer(content)) {
235
- img = content;
236
- } else {
237
- const { stream } = await getStream(content);
238
- img = await toBuffer(stream);
239
- }
240
- } else {
241
- ({ img } = await generateProfilePicture(content, dimensions));
242
- }
231
+ // crysnovax@HD-Profile --- Dual mode:
232
+ // hd:true → generateProfilePictureHD (preserves aspect ratio, caps at 1920, manages file size)
233
+ // standard generateProfilePicture (720×720 square crop, or custom dimensions)
234
+ const { img } = dimensions?.hd
235
+ ? await generateProfilePictureHD(content)
236
+ : await generateProfilePicture(content, dimensions);
243
237
  await query({
244
238
  tag: 'iq',
245
239
  attrs: {
@@ -2,22 +2,17 @@
2
2
  * Lia@Changes [WIP]
3
3
  * botPlanningReplay — Meta AI-style live reasoning feed.
4
4
  *
5
- * Sends a progress indicator with all steps IN_PROGRESS, then replays
6
- * each step completing in real time via sequential edits — exactly how
7
- * Meta AI's "Thinking…" bubble works. Final rich message lands clean
8
- * with NO "edited" badge, using the same delete-resend flow from
9
- * meta-compositing.
10
- *
11
- * replayPlanning(sock, jid, steps, finalContent, options)
5
+ * UNIVERSAL VERSION: Works on ALL WhatsApp clients.
6
+ * - Native Meta AI rendering for compatible clients
7
+ * - Plain text fallback for regular clients (no "Update WhatsApp")
12
8
  *
13
9
  * Flow:
14
- * Send placeholder (all steps IN_PROGRESS)
15
- * → edit: step[0] → DONE (stepDelayMs)
16
- * → edit: step[1] → DONE (stepDelayMs)
10
+ * Send placeholder (all steps pending)
11
+ * → edit: step[0] → DONE
12
+ * → edit: step[1] → DONE
17
13
  * → ...
18
- * → edit: step[n] → DONE (finalPauseMs)
19
14
  * → delete placeholder
20
- * → send final rich message fresh
15
+ * → send final message fresh
21
16
  *
22
17
  * If you use or copy this code, please credit @crysnovax/bailey.
23
18
  */
@@ -26,46 +21,32 @@ import { proto } from '../../WAProto/index.js';
26
21
  import {
27
22
  buildCompositingPlaceholder,
28
23
  buildProgressIndicator,
24
+ buildPlainPlaceholder,
29
25
  PlanningStepStatus
30
26
  } from './meta-compositing.js';
31
27
  import { botMetadataSignature, botMetadataCertificate } from './rich-message-utils.js';
32
28
  import { BOT_RENDERING_CONFIG_METADATA } from '../Defaults/index.js';
33
29
  import { delay } from './generics.js';
34
30
 
35
- // ─── Internal: rebuild the full botForwardedMessage with updated step statuses ───────────────
31
+ // ─── Internal: build native Meta AI frame ──────────────────────────────────
36
32
  const buildReplayFrame = (description, steps, placeholderText = '') => {
37
33
  return buildCompositingPlaceholder({ description, steps, placeholderText });
38
34
  };
39
35
 
40
- // ─── Internal: send an in-place edit of the planning bubble with new step states ─────────────
36
+ // ─── Internal: edit planning bubble (native Meta AI) ───────────────────────
41
37
  const editPlanningBubble = async (sock, jid, key, description, steps, placeholderText) => {
42
38
  const updated = buildReplayFrame(description, steps, placeholderText);
43
- // Use { edit: key, raw: true } — routes through protocolMessage.editedMessage
44
- await sock.sendMessage(jid, {
45
- raw: true,
46
- edit: key,
47
- ...updated
48
- });
39
+ await sock.sendMessage(jid, { raw: true, edit: key, ...updated });
40
+ };
41
+
42
+ // ─── Internal: edit plain text bubble (universal) ──────────────────────────
43
+ const editPlainBubble = async (sock, jid, key, description, steps, placeholderText) => {
44
+ const updated = buildPlainPlaceholder(description, steps, placeholderText);
45
+ await sock.sendMessage(jid, { edit: key, ...updated });
49
46
  };
50
47
 
51
48
  /**
52
- * replayPlanning — full live planning animation.
53
- *
54
- * @param {object} sock - Baileys socket
55
- * @param {string} jid - Destination JID
56
- * @param {Array} steps - Array of step definitions:
57
- * { title, body?, isReasoning?, isEnhancedSearch? }
58
- * Status is managed automatically — do NOT pass status here.
59
- * @param {object} finalContent - Rich content for the final message.
60
- * Same as sendMessage: { code, table, text, richResponse… }
61
- * @param {object} [options]
62
- * @param {string} [options.description] - Top label while thinking. Default: 'Thinking…'
63
- * @param {string} [options.placeholderText] - Body text shown in bubble while loading.
64
- * @param {number} [options.stepDelayMs] - Ms between each step completing. Default: 900
65
- * @param {number} [options.finalPauseMs] - Ms to hold after all steps done before cleanup. Default: 600
66
- * @param {boolean} [options.abortOnDisconnect] - Stop replay loop if socket closes. Default: true
67
- * @param {object} [options.sendOptions] - Extra options for final sendMessage call.
68
- * @returns {Promise<object>} - The final sent WAMessage
49
+ * replayPlanning — full live planning animation (UNIVERSAL).
69
50
  */
70
51
  export const replayPlanning = async (sock, jid, steps, finalContent, {
71
52
  description = 'Thinking…',
@@ -73,13 +54,13 @@ export const replayPlanning = async (sock, jid, steps, finalContent, {
73
54
  stepDelayMs = 900,
74
55
  finalPauseMs = 600,
75
56
  abortOnDisconnect = true,
76
- sendOptions = {}
57
+ sendOptions = {},
58
+ useNativeMeta = false
77
59
  } = {}) => {
78
60
  if (!steps?.length) {
79
61
  throw new Error('replayPlanning: steps array must have at least one entry');
80
62
  }
81
63
 
82
- // ── Track abort state if socket disconnects mid-replay ───────────────────
83
64
  let aborted = false;
84
65
  const onClose = () => { aborted = true; };
85
66
  if (abortOnDisconnect) {
@@ -88,112 +69,96 @@ export const replayPlanning = async (sock, jid, steps, finalContent, {
88
69
  });
89
70
  }
90
71
 
91
- // ── 1. Build initial state — all steps IN_PROGRESS ───────────────────────
72
+ // Show typing
73
+ await sock.sendPresenceUpdate('composing', jid);
74
+
75
+ // Build initial steps (all pending)
92
76
  const initialSteps = steps.map(step => ({
93
77
  ...step,
94
78
  status: PlanningStepStatus.IN_PROGRESS
95
79
  }));
96
80
 
97
- const placeholder = await sock.sendMessage(jid, {
98
- raw: true,
99
- ...buildReplayFrame(description, initialSteps, placeholderText)
100
- });
81
+ // Determine mode
82
+ const isNative = useNativeMeta && false; // Always false for now — force universal
83
+
84
+ // Send initial placeholder
85
+ let placeholder;
86
+ if (isNative) {
87
+ placeholder = await sock.sendMessage(jid, {
88
+ raw: true,
89
+ ...buildReplayFrame(description, initialSteps, placeholderText)
90
+ });
91
+ } else {
92
+ placeholder = await sock.sendMessage(jid,
93
+ buildPlainPlaceholder(description, initialSteps, placeholderText)
94
+ );
95
+ }
101
96
 
102
97
  const key = placeholder?.key;
103
98
 
104
- // ── 2. Replay loop — flip each step to DONE sequentially ─────────────────
99
+ // Replay loop
105
100
  try {
106
101
  const currentSteps = [...initialSteps];
107
102
 
108
103
  for (let i = 0; i < currentSteps.length; i++) {
109
104
  if (aborted) break;
110
-
111
105
  await delay(stepDelayMs);
112
106
  if (aborted) break;
113
107
 
114
- // Flip this step to DONE
115
- currentSteps[i] = {
116
- ...currentSteps[i],
117
- status: PlanningStepStatus.DONE
118
- };
108
+ currentSteps[i] = { ...currentSteps[i], status: PlanningStepStatus.DONE };
119
109
 
120
110
  if (key) {
121
- await editPlanningBubble(
122
- sock, jid, key,
123
- description, currentSteps, placeholderText
124
- );
111
+ if (isNative) {
112
+ await editPlanningBubble(sock, jid, key, description, currentSteps, placeholderText);
113
+ } else {
114
+ await editPlainBubble(sock, jid, key, description, currentSteps, placeholderText);
115
+ }
125
116
  }
126
117
  }
127
118
 
128
- // ── 3. Hold with all steps DONE so user sees completion ──────────────
129
119
  if (!aborted && finalPauseMs > 0) {
130
120
  await delay(finalPauseMs);
131
121
  }
132
122
 
133
- // ── 4. Delete placeholder silently ───────────────────────────────────
134
123
  if (key && !aborted) {
135
124
  await sock.sendMessage(jid, { delete: key });
136
125
  }
137
126
  } catch (err) {
138
- // Non-fatal always attempt final send even if replay failed mid-way
139
- try {
140
- if (key) await sock.sendMessage(jid, { delete: key });
141
- } catch (_) {}
127
+ try { if (key) await sock.sendMessage(jid, { delete: key }); } catch (_) {}
142
128
  }
143
129
 
144
- // ── 5. Send the final rich message as a brand-new message — no edited badge ──
130
+ await sock.sendPresenceUpdate('paused', jid);
131
+
132
+ // Skip final send if _skipFinalSend is set (for replayPlanningOnly)
133
+ if (sendOptions._skipFinalSend) return placeholder;
134
+
145
135
  return sock.sendMessage(jid, finalContent, sendOptions);
146
136
  };
147
137
 
148
138
  /**
149
- * replayPlanningOnly — same animation but WITHOUT sending a final message.
150
- * Use when you want to control the final send yourself.
151
- *
152
- * Returns { key } of the placeholder (already deleted on completion).
153
- *
154
- * @param {object} sock
155
- * @param {string} jid
156
- * @param {Array} steps
157
- * @param {object} [options] - Same options as replayPlanning minus sendOptions
158
- * @returns {Promise<void>}
139
+ * replayPlanningOnly — animation WITHOUT final message.
159
140
  */
160
141
  export const replayPlanningOnly = async (sock, jid, steps, options = {}) => {
161
142
  return replayPlanning(sock, jid, steps, null, {
162
143
  ...options,
163
- _skipFinalSend: true
144
+ sendOptions: { ...options.sendOptions, _skipFinalSend: true }
164
145
  });
165
146
  };
166
147
 
167
148
  /**
168
- * buildReasoningSteps — convenience builder for steps with isReasoning: true.
169
- * Renders with the "reasoning" visual treatment in the Meta bubble.
170
- *
171
- * @example
172
- * buildReasoningSteps(['Analyzing the problem…', 'Checking edge cases…'])
149
+ * buildReasoningSteps — steps with isReasoning: true.
173
150
  */
174
151
  export const buildReasoningSteps = (titles) =>
175
152
  titles.map(title => ({ title, isReasoning: true }));
176
153
 
177
154
  /**
178
- * buildSearchSteps — convenience builder for steps with isEnhancedSearch: true.
179
- * Renders with the "enhanced search" visual treatment.
180
- *
181
- * @example
182
- * buildSearchSteps(['Searching the web…', 'Reading top results…'])
155
+ * buildSearchSteps — steps with isEnhancedSearch: true.
183
156
  */
184
157
  export const buildSearchSteps = (titles) =>
185
158
  titles.map(title => ({ title, isEnhancedSearch: true }));
186
159
 
187
160
  /**
188
- * mixedSteps — build a steps array mixing reasoning + search + plain steps.
189
- * Pass an array of { title, type? } where type is 'reasoning' | 'search' | undefined.
190
- *
191
- * @example
192
- * mixedSteps([
193
- * { title: 'Understanding your question…', type: 'reasoning' },
194
- * { title: 'Searching for data…', type: 'search' },
195
- * { title: 'Writing the answer…' }
196
- * ])
161
+ * mixedSteps — mix reasoning + search + plain steps.
197
162
  */
198
163
  export const mixedSteps = (defs) =>
199
164
  defs.map(({ title, body, type }) => ({
@@ -220,6 +220,80 @@ export const generateProfilePicture = async (mediaUpload, dimensions) => {
220
220
  img: await img
221
221
  };
222
222
  };
223
+ // crysnovax@HD-Profile --- Like generateProfilePicture but skips resize/crop.
224
+ // Re-encodes to JPEG so WA always gets valid image bytes.
225
+ // crysnovax@HD-Profile --- HD mode: preserves original aspect ratio, no crop.
226
+ // WhatsApp server accepts max 720px on any side. Images under 720px pass through
227
+ // unchanged. Larger images are scaled down proportionally to fit within 720px.
228
+ export const generateProfilePictureHD = async (mediaUpload) => {
229
+ let buffer;
230
+ if (Buffer.isBuffer(mediaUpload)) {
231
+ buffer = mediaUpload;
232
+ }
233
+ else {
234
+ const { stream } = await getStream(mediaUpload);
235
+ buffer = await toBuffer(stream);
236
+ }
237
+
238
+ const lib = await getImageProcessingLibrary();
239
+ let img;
240
+
241
+ if ('sharp' in lib && lib.sharp?.default) {
242
+ const metadata = await lib.sharp.default(buffer).metadata();
243
+ const origWidth = metadata.width || 720;
244
+ const origHeight = metadata.height || 720;
245
+ const maxDim = Math.max(origWidth, origHeight);
246
+
247
+ // WhatsApp server limit: 720px max dimension
248
+ // If already under 720, send as-is. If larger, scale down proportionally.
249
+ const needsResize = maxDim > 720;
250
+
251
+ let pipeline = lib.sharp.default(buffer);
252
+
253
+ if (needsResize) {
254
+ pipeline = pipeline.resize(720, 720, {
255
+ fit: 'inside',
256
+ withoutEnlargement: true
257
+ });
258
+ }
259
+
260
+ img = pipeline
261
+ .jpeg({ quality: 85, mozjpeg: true })
262
+ .toBuffer();
263
+ }
264
+ else if ('image' in lib && lib.image?.Transformer) {
265
+ // @napi-rs/image — cap at 720 via resize
266
+ img = new lib.image
267
+ .Transformer(buffer)
268
+ .resize(720, 720)
269
+ .jpeg(85);
270
+ }
271
+ else if ('jimp' in lib && lib.jimp?.Jimp) {
272
+ const jimp = await lib.jimp.Jimp.read(buffer);
273
+ const origWidth = jimp.width;
274
+ const origHeight = jimp.height;
275
+ const maxDim = Math.max(origWidth, origHeight);
276
+
277
+ let processed = jimp;
278
+ if (maxDim > 720) {
279
+ const scale = 720 / maxDim;
280
+ processed = jimp.resize({
281
+ w: Math.round(origWidth * scale),
282
+ h: Math.round(origHeight * scale),
283
+ mode: lib.jimp.ResizeStrategy.BILINEAR
284
+ });
285
+ }
286
+
287
+ img = processed.getBuffer('image/jpeg', { quality: 85 });
288
+ }
289
+ else {
290
+ throw new Boom('No image processing library available');
291
+ }
292
+
293
+ const result = await img;
294
+ console.log(`[HD-Profile] Output: ${result.length} bytes, dims preserved (max 720px)`);
295
+ return { img: result };
296
+ };
223
297
  /** gets the SHA256 of the given media message */
224
298
  export const mediaMessageSHA256B64 = (message) => {
225
299
  const media = Object.values(message)[0];
@@ -1,13 +1,12 @@
1
1
  /**
2
2
  * Lia@Changes [WIP]
3
- * Meta Compositing — send rich messages (code, table, text, etc.) with a
4
- * Meta AI-style progress indicator that appears BEFORE the final message.
3
+ * Meta Compositing — send rich messages with Meta AI-style progress indicator.
5
4
  *
6
- * The progress placeholder is sent first (with BotProgressIndicatorMetadata),
7
- * then deleted, then the final rich message is sent fresh — so NO "edited"
8
- * badge ever appears. Identical to how Meta AI itself works.
5
+ * UNIVERSAL VERSION: Works on ALL WhatsApp clients.
6
+ * - Native Meta AI rendering for compatible clients (Beta, AI-enabled)
7
+ * - Plain text fallback for regular clients (no "Update WhatsApp")
9
8
  *
10
- * metaTyping() — shows the "typing..." / planning steps indicator only.
9
+ * metaTyping() — shows the "typing..." / planning steps indicator.
11
10
  * sendMetaComposited() — full flow: typing → delete → final message.
12
11
  *
13
12
  * If you use or copy this code, please credit @crysnovax/bailey.
@@ -19,18 +18,23 @@ import { BOT_RENDERING_CONFIG_METADATA } from '../Defaults/index.js';
19
18
  import { generateWAMessageFromContent } from './messages.js';
20
19
  import { unixTimestampSeconds, delay } from './generics.js';
21
20
 
22
- // ─── Step status enum mirrors proto.BotProgressIndicatorMetadata.BotPlanningStepMetadata status field ───
21
+ // ─── Step status enum ───
23
22
  export const PlanningStepStatus = {
24
23
  IN_PROGRESS: 0,
25
24
  DONE: 1,
26
25
  FAILED: 2
27
26
  };
28
27
 
28
+ /**
29
+ * Check if recipient supports native Meta AI rendering.
30
+ * Always false for now — WhatsApp hasn't opened this to third-party bots.
31
+ */
32
+ export const supportsMetaRendering = (jid, config = {}) => {
33
+ return config.forceMetaRendering === true;
34
+ };
35
+
29
36
  /**
30
37
  * Build a BotProgressIndicatorMetadata object.
31
- * @param {string} description - Top-level label shown while "thinking"
32
- * @param {Array} steps - Array of { title, body?, status?, isReasoning? }
33
- * @param {number} estimatedMs - Optional estimated completion time in ms
34
38
  */
35
39
  export const buildProgressIndicator = (description, steps = [], estimatedMs) => {
36
40
  const stepsMetadata = steps.map(step => {
@@ -51,27 +55,16 @@ export const buildProgressIndicator = (description, steps = [], estimatedMs) =>
51
55
  };
52
56
 
53
57
  /**
54
- * Build the compositing placeholder message the one that shows the
55
- * "Meta AI is thinking..." / planning steps indicator in the chat bubble.
56
- *
57
- * This is a botForwardedMessage with richResponseMessage = null content
58
- * but with progressIndicatorMetadata on the botMetadata.
59
- *
60
- * @param {object} options
61
- * @param {string} options.description - "Thinking…" label
62
- * @param {Array} options.steps - Planning steps array
63
- * @param {number} [options.estimatedMs] - Estimated completion ms
64
- * @param {string} [options.placeholderText] - Text shown in the bubble body while loading
58
+ * Build the native Meta AI compositing placeholder (for compatible clients only).
65
59
  */
66
60
  export const buildCompositingPlaceholder = ({
67
61
  description = 'Thinking…',
68
62
  steps = [],
69
63
  estimatedMs,
70
64
  placeholderText = ''
71
- }) => {
65
+ } = {}) => {
72
66
  const progressIndicatorMetadata = buildProgressIndicator(description, steps, estimatedMs);
73
67
 
74
- // Minimal richResponseMessage so the bubble renders in Bot mode
75
68
  const textEncoder = new TextEncoder();
76
69
  const unifiedData = textEncoder.encode(JSON.stringify({
77
70
  response_id: crypto.randomUUID(),
@@ -119,90 +112,81 @@ export const buildCompositingPlaceholder = ({
119
112
  };
120
113
 
121
114
  /**
122
- * metaTyping sends ONLY the progress/compositing indicator to a JID.
123
- * Does NOT delete or send a follow-up — caller controls that.
124
- *
125
- * Returns the sent message key so you can delete it later.
126
- *
127
- * @param {object} sock - Baileys socket
128
- * @param {string} jid - Destination JID
129
- * @param {object} options - Same options as buildCompositingPlaceholder
130
- * @returns {Promise<object>} - The sent WAMessage
115
+ * Build a plain-text placeholder that works on ALL WhatsApp clients.
116
+ */
117
+ export const buildPlainPlaceholder = (description = 'Thinking…', steps = [], placeholderText = '') => {
118
+ const stepLines = steps.map(step => {
119
+ const icon = step.status === PlanningStepStatus.DONE ? '✓' :
120
+ step.status === PlanningStepStatus.FAILED ? '✗' : '○';
121
+ return `${icon} ${step.title}`;
122
+ }).join('\n');
123
+
124
+ let text = `_${description}_`;
125
+ if (stepLines) text += `\n\n${stepLines}`;
126
+ if (placeholderText) text += `\n\n${placeholderText}`;
127
+
128
+ return { text };
129
+ };
130
+
131
+ /**
132
+ * metaTyping — sends the progress/compositing indicator.
133
+ * UNIVERSAL: Uses plain text for regular clients, native Meta AI for compatible ones.
131
134
  */
132
135
  export const metaTyping = async (sock, jid, {
133
136
  description = 'Thinking…',
134
137
  steps = [],
135
138
  estimatedMs,
136
- placeholderText = ''
139
+ placeholderText = '',
140
+ useNativeMeta = false
137
141
  } = {}) => {
138
- const placeholder = buildCompositingPlaceholder({
139
- description,
140
- steps,
141
- estimatedMs,
142
- placeholderText
143
- });
142
+ // Show typing indicator
143
+ await sock.sendPresenceUpdate('composing', jid);
144
+
145
+ if (useNativeMeta && supportsMetaRendering(jid, sock.config)) {
146
+ const placeholder = buildCompositingPlaceholder({
147
+ description, steps, estimatedMs, placeholderText
148
+ });
149
+ return sock.sendMessage(jid, { raw: true, ...placeholder });
150
+ }
144
151
 
145
- return sock.sendMessage(jid, { raw: true, ...placeholder });
152
+ // Universal fallback: plain text placeholder
153
+ const plainSteps = steps.map(s => ({ ...s, status: PlanningStepStatus.IN_PROGRESS }));
154
+ const placeholder = buildPlainPlaceholder(description, plainSteps, placeholderText);
155
+ return sock.sendMessage(jid, placeholder);
146
156
  };
147
157
 
148
158
  /**
149
- * sendMetaComposited — full Meta AI flow:
150
- * 1. Send progress indicator placeholder
151
- * 2. Wait `thinkingMs` (default 2000ms)
152
- * 3. Delete the placeholder (no "edited" badge ever appears)
153
- * 4. Send the final rich message fresh
154
- *
155
- * Supports all existing richResponse content types: text, code, table,
156
- * expressions (LaTeX), items (reels carousel), or the richResponse array.
157
- *
158
- * @param {object} sock - Baileys socket
159
- * @param {string} jid - Destination JID
160
- * @param {object} content - Same content object as sendMessage richResponse
161
- * @param {object} [options]
162
- * @param {number} [options.thinkingMs=2000] - How long placeholder shows
163
- * @param {string} [options.description='Thinking…'] - Placeholder label
164
- * @param {Array} [options.steps=[]] - Planning steps to show
165
- * @param {string} [options.placeholderText=''] - Body text while loading
166
- * @param {object} [options.sendOptions={}] - Extra options for final sendMessage
167
- * @returns {Promise<object>} - The final sent WAMessage
159
+ * sendMetaComposited — full Meta AI flow (UNIVERSAL).
160
+ * 1. Send progress indicator
161
+ * 2. Wait thinkingMs
162
+ * 3. Delete placeholder
163
+ * 4. Send final message fresh
168
164
  */
169
165
  export const sendMetaComposited = async (sock, jid, content, {
170
166
  thinkingMs = 2000,
171
167
  description = 'Thinking…',
172
168
  steps = [],
173
169
  placeholderText = '',
174
- sendOptions = {}
170
+ sendOptions = {},
171
+ useNativeMeta = false
175
172
  } = {}) => {
176
- // 1. Send the compositing placeholder
177
173
  const placeholder = await metaTyping(sock, jid, {
178
- description,
179
- steps,
180
- placeholderText
174
+ description, steps, estimatedMs: thinkingMs, placeholderText, useNativeMeta
181
175
  });
182
176
 
183
177
  try {
184
- // 2. Wait — this is the "thinking" window
185
178
  await delay(thinkingMs);
186
-
187
- // 3. Delete the placeholder silently
188
179
  if (placeholder?.key) {
189
180
  await sock.sendMessage(jid, { delete: placeholder.key });
190
181
  }
191
- } catch (_) {
192
- // Non-fatal — always attempt final send
193
- }
182
+ } catch (_) {}
194
183
 
195
- // 4. Send the final rich message as a brand-new message (no edit = no badge)
184
+ await sock.sendPresenceUpdate('paused', jid);
196
185
  return sock.sendMessage(jid, content, sendOptions);
197
186
  };
198
187
 
199
188
  /**
200
- * Convenience: build a steps array from plain strings with IN_PROGRESS status.
201
- * Use PlanningStepStatus.DONE to mark a step complete.
202
- *
203
- * @example
204
- * buildSteps(['Searching…', 'Reading sources…', 'Writing response…'])
189
+ * Convenience: build a steps array from plain strings.
205
190
  */
206
191
  export const buildSteps = (titles, status = PlanningStepStatus.IN_PROGRESS) =>
207
192
  titles.map(title => ({ title, status }));
208
-
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crysnovax/baileys",
3
- "version": "2.6.1",
3
+ "version": "2.6.2",
4
4
  "description": "Premium Baileys fork ⚉ Meta compositing, bot planning replay, welcome flow, rich messages (code, table, LaTeX, reels), interactive messages, albums, and more.",
5
5
  "main": "lib/index.js",
6
6
  "type": "module",