@vanzxy/baileys 1.6.4 → 1.6.6

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.

Potentially problematic release.


This version of @vanzxy/baileys might be problematic. Click here for more details.

@@ -0,0 +1,232 @@
1
+ /**
2
+ * Chat Control Utilities
3
+ *
4
+ * Source: @innovatorssoft/baileys (chat-control.js)
5
+ * Rewritten as clean TypeScript with full types and JSDoc.
6
+ *
7
+ * Three components:
8
+ * - TypingIndicator — composing / recording presence helpers
9
+ * - PinnedMessagesManager — client-side pin tracking
10
+ * - ReadReceiptController — configurable automatic read receipts
11
+ */
12
+ // ─── Constants ─────────────────────────────────────────────────────────────────
13
+ /**
14
+ * Standard disappearing-message duration constants (in seconds).
15
+ * Pass to `sock.sendMessage(jid, { disappearingMessagesInChat: DISAPPEARING_DURATIONS.DAYS_7 })`.
16
+ */
17
+ export const DISAPPEARING_DURATIONS = {
18
+ /** Disable disappearing messages */
19
+ OFF: 0,
20
+ /** 24 hours */
21
+ HOURS_24: 86400,
22
+ /** 7 days */
23
+ DAYS_7: 604800,
24
+ /** 90 days */
25
+ DAYS_90: 7776000
26
+ };
27
+ /**
28
+ * Manages composing ("typing...") and recording ("recording...") presence
29
+ * indicators with per-JID timer tracking.
30
+ *
31
+ * @example
32
+ * const typing = createTypingIndicator(
33
+ * (jid, presence) => sock.sendPresenceUpdate(presence, jid)
34
+ * )
35
+ *
36
+ * // Simulate typing then send a message
37
+ * const result = await typing.simulateTyping(jid, 1500, () =>
38
+ * sock.sendMessage(jid, { text: 'Hello!' })
39
+ * )
40
+ */
41
+ export class TypingIndicator {
42
+ constructor(sendPresence) {
43
+ this.sendPresence = sendPresence;
44
+ this.timers = new Map();
45
+ }
46
+ /** Show the "typing..." (composing) indicator for a JID. */
47
+ async startTyping(jid, options = {}) {
48
+ this.clearTimer(jid);
49
+ await this.sendPresence(jid, 'composing');
50
+ if (options.autoPause !== false && options.duration) {
51
+ const t = setTimeout(() => void this.stopTyping(jid), options.duration);
52
+ this.timers.set(jid, t);
53
+ }
54
+ }
55
+ /** Show the "recording..." (audio/video) indicator for a JID. */
56
+ async startRecording(jid, options = {}) {
57
+ this.clearTimer(jid);
58
+ await this.sendPresence(jid, 'recording');
59
+ if (options.autoPause !== false && options.duration) {
60
+ const t = setTimeout(() => void this.stopTyping(jid), options.duration);
61
+ this.timers.set(jid, t);
62
+ }
63
+ }
64
+ /** Stop any active composing/recording indicator for a JID. */
65
+ async stopTyping(jid) {
66
+ this.clearTimer(jid);
67
+ try {
68
+ await this.sendPresence(jid, 'paused');
69
+ }
70
+ catch {
71
+ // Ignore errors when stopping presence (connection may be closed)
72
+ }
73
+ }
74
+ /** Stop all active indicators. */
75
+ async stopAll() {
76
+ const jids = Array.from(this.timers.keys());
77
+ await Promise.all(jids.map(jid => this.stopTyping(jid)));
78
+ }
79
+ /**
80
+ * Show typing for `durationMs`, run `callback`, then stop the indicator.
81
+ *
82
+ * @template T
83
+ * @returns The return value of `callback`
84
+ *
85
+ * @example
86
+ * await typing.simulateTyping(jid, 2000, async () => {
87
+ * await sock.sendMessage(jid, { text: 'Here is your answer' })
88
+ * })
89
+ */
90
+ async simulateTyping(jid, durationMs, callback) {
91
+ await this.startTyping(jid);
92
+ await new Promise(resolve => setTimeout(resolve, durationMs));
93
+ await this.stopTyping(jid);
94
+ return callback();
95
+ }
96
+ clearTimer(jid) {
97
+ const existing = this.timers.get(jid);
98
+ if (existing) {
99
+ clearTimeout(existing);
100
+ this.timers.delete(jid);
101
+ }
102
+ }
103
+ }
104
+ /** Factory — create a TypingIndicator. */
105
+ export const createTypingIndicator = (sendPresence) => new TypingIndicator(sendPresence);
106
+ /**
107
+ * Client-side tracker for pinned messages.
108
+ * Listen to `messages.update` for `pinInChatMessage` protocol messages and call
109
+ * `manager.pin(jid, msgId, pinnedBy)` / `manager.unpin(jid, msgId)` accordingly.
110
+ */
111
+ export class PinnedMessagesManager {
112
+ constructor() {
113
+ this.store = new Map();
114
+ }
115
+ /**
116
+ * Record a newly pinned message.
117
+ * @returns The created pin entry
118
+ */
119
+ pin(jid, messageId, pinnedBy, expiresAt) {
120
+ const entry = { messageId, jid, pinnedAt: new Date(), pinnedBy, expiresAt };
121
+ const existing = this.store.get(jid) ?? [];
122
+ // Remove any previous pin with the same message ID before re-adding
123
+ const filtered = existing.filter(p => p.messageId !== messageId);
124
+ filtered.push(entry);
125
+ this.store.set(jid, filtered);
126
+ return entry;
127
+ }
128
+ /**
129
+ * Remove a pinned message.
130
+ * @returns `true` if the pin was found and removed, `false` otherwise
131
+ */
132
+ unpin(jid, messageId) {
133
+ const existing = this.store.get(jid);
134
+ if (!existing)
135
+ return false;
136
+ const filtered = existing.filter(p => p.messageId !== messageId);
137
+ if (filtered.length === existing.length)
138
+ return false;
139
+ this.store.set(jid, filtered);
140
+ return true;
141
+ }
142
+ /** Get all pinned messages for a chat. */
143
+ getPinned(jid) {
144
+ return this.store.get(jid) ?? [];
145
+ }
146
+ /** Check if a message is pinned in a chat. */
147
+ isPinned(jid, messageId) {
148
+ return (this.store.get(jid) ?? []).some(p => p.messageId === messageId);
149
+ }
150
+ /** Remove all pins for a chat. */
151
+ clearPins(jid) {
152
+ this.store.delete(jid);
153
+ }
154
+ /**
155
+ * Evict pins whose `expiresAt` is in the past.
156
+ * @returns Number of expired pins removed
157
+ */
158
+ clearExpired() {
159
+ let cleared = 0;
160
+ const now = Date.now();
161
+ for (const [jid, pins] of this.store) {
162
+ const valid = pins.filter(p => !p.expiresAt || p.expiresAt.getTime() > now);
163
+ cleared += pins.length - valid.length;
164
+ this.store.set(jid, valid);
165
+ }
166
+ return cleared;
167
+ }
168
+ /** Total pin count across all chats. */
169
+ get totalPins() {
170
+ let total = 0;
171
+ for (const pins of this.store.values())
172
+ total += pins.length;
173
+ return total;
174
+ }
175
+ }
176
+ /** Factory — create a PinnedMessagesManager. */
177
+ export const createPinnedMessagesManager = () => new PinnedMessagesManager();
178
+ /**
179
+ * Create a read-receipt controller with optional auto-delay and per-JID exclusions.
180
+ *
181
+ * @example
182
+ * const readCtrl = createReadReceiptController(
183
+ * (jid, participant, ids) => sock.readMessages(ids.map(id => ({ remoteJid: jid, id, participant }))),
184
+ * { enabled: true, readDelay: 500, excludeJids: [spamJid] }
185
+ * )
186
+ *
187
+ * sock.ev.on('messages.upsert', ({ messages }) => {
188
+ * for (const msg of messages) {
189
+ * const { key } = msg
190
+ * if (!key.fromMe)
191
+ * readCtrl.markRead(key.remoteJid!, key.participant, [key.id!])
192
+ * }
193
+ * })
194
+ */
195
+ export const createReadReceiptController = (sendReadReceipt, config = {}) => {
196
+ let currentConfig = {
197
+ enabled: config.enabled ?? true,
198
+ excludeJids: config.excludeJids ?? [],
199
+ readDelay: config.readDelay ?? 0
200
+ };
201
+ return {
202
+ setConfig(newConfig) {
203
+ currentConfig = { ...currentConfig, ...newConfig };
204
+ },
205
+ getConfig() {
206
+ return { ...currentConfig };
207
+ },
208
+ enable() {
209
+ currentConfig.enabled = true;
210
+ },
211
+ disable() {
212
+ currentConfig.enabled = false;
213
+ },
214
+ isEnabled() {
215
+ return currentConfig.enabled;
216
+ },
217
+ async markRead(jid, participant, messageIds) {
218
+ if (!currentConfig.enabled)
219
+ return;
220
+ if (currentConfig.excludeJids.includes(jid))
221
+ return;
222
+ if (currentConfig.readDelay > 0) {
223
+ await new Promise(r => setTimeout(r, currentConfig.readDelay));
224
+ }
225
+ await sendReadReceipt(jid, participant, messageIds);
226
+ },
227
+ async forceMarkRead(jid, participant, messageIds) {
228
+ await sendReadReceipt(jid, participant, messageIds);
229
+ }
230
+ };
231
+ };
232
+ //# sourceMappingURL=chat-control.js.map
@@ -49,6 +49,12 @@ export function fetchLatestWaWebVersion(options?: {}): Promise<{
49
49
  isLatest: boolean;
50
50
  error: unknown;
51
51
  }>;
52
+ export function fetchBestWaVersion(options?: {}): Promise<{
53
+ version: number[];
54
+ isLatest: boolean;
55
+ source: 'wa-web' | 'baileys-fork' | 'hardcoded-fallback';
56
+ error?: unknown;
57
+ }>;
52
58
  export function generateMdTagPrefix(): string;
53
59
  export function getStatusFromReceiptType(type: any): any;
54
60
  export function getErrorCodeFromStreamError(node: any): {
@@ -261,6 +261,30 @@ export const fetchLatestWaWebVersion = async (options = {}) => {
261
261
  };
262
262
  }
263
263
  };
264
+ /**
265
+ * Vanz@Fix (bug 64): the comment on `version` in Defaults/index.js has long
266
+ * recommended calling `fetchLatestWaWebVersion()` first (parses WA's own
267
+ * sw.js — most accurate, and sidesteps the upstream `isLatest:true`-while-stale
268
+ * bug, WhiskeySockets#2679), falling back to `fetchLatestBaileysVersion()`,
269
+ * and only then the hardcoded constant — but nothing actually implemented
270
+ * that chain, so it was on every consumer to hand-roll it (or, more likely,
271
+ * skip it, which is how a bot ends up pinned to a stale `version` and starts
272
+ * getting rejected with 405 during pairing once WA bumps its minimum — see
273
+ * WhiskeySockets#2370 / #2485). This runs the documented chain for real.
274
+ * Never throws: worst case it resolves to the hardcoded fallback with
275
+ * isLatest:false, same as calling either function alone would.
276
+ */
277
+ export const fetchBestWaVersion = async (options = {}) => {
278
+ const webResult = await fetchLatestWaWebVersion(options);
279
+ if (webResult.isLatest) {
280
+ return { ...webResult, source: 'wa-web' };
281
+ }
282
+ const baileysResult = await fetchLatestBaileysVersion(options);
283
+ if (baileysResult.isLatest) {
284
+ return { ...baileysResult, source: 'baileys-fork' };
285
+ }
286
+ return { ...baileysResult, source: 'hardcoded-fallback' };
287
+ };
264
288
  /** unique message tag prefix for MD clients */
265
289
  export const generateMdTagPrefix = () => {
266
290
  const bytes = randomBytes(4);
@@ -33,3 +33,7 @@ export * from "./media-messages.js";
33
33
  export * from "./stickerpack.js";
34
34
  export * from "./media-set.js";
35
35
  export * from "./status.js";
36
+ export * from "./use-cache-manager-auth-state.js";
37
+ export * from "./baileys-event-stream.js";
38
+ export * from "./past-participants.js";
39
+ export * from "./chat-control.js";
@@ -39,4 +39,8 @@ export * from './media-messages.js';
39
39
  export * from './stickerpack.js';
40
40
  export * from './media-set.js';
41
41
  export * from './status.js';
42
+ export * from './use-cache-manager-auth-state.js';
43
+ export * from './baileys-event-stream.js';
44
+ export * from './past-participants.js';
45
+ export * from './chat-control.js';
42
46
  //# sourceMappingURL=index.js.map
@@ -12,7 +12,10 @@
12
12
  * buffer from `generatePP` (normalized preview). Both are kept.
13
13
  */
14
14
  import { randomBytes } from 'node:crypto';
15
- import { generateWAMessageContent, generateWAMessageFromContent, unixTimestampSeconds } from './messages.js';
15
+ // Vanz@Fix: unixTimestampSeconds is not re-exported by messages.js (it only imports
16
+ // it internally from generics.js for its own use) — pull it from generics.js directly.
17
+ import { generateWAMessageContent, generateWAMessageFromContent } from './messages.js';
18
+ import { unixTimestampSeconds } from './generics.js';
16
19
  import { S_WHATSAPP_NET } from '../WABinary/index.js';
17
20
  import { generatePP, generateProfilePictureFP } from './media-messages.js';
18
21
  /** Update the profile picture for yourself or a group — sends the main (scaled-to-fit) image. */
@@ -1,7 +1,20 @@
1
1
  // Vanz@Add --- ported from Bail-master addons/message-search.ts (type-only
2
2
  // annotations dropped; behavior unchanged).
3
+ // Vanz@Fix (bug 58): peel off ephemeral / view-once wrappers so search still
4
+ // indexes text from disappearing-message chats instead of always seeing '{}'.
5
+ const unwrapMessage = (content) => {
6
+ let c = content;
7
+ while (c && (c.ephemeralMessage || c.viewOnceMessage || c.viewOnceMessageV2 || c.viewOnceMessageV2Extension || c.documentWithCaptionMessage)) {
8
+ c = c.ephemeralMessage?.message ||
9
+ c.viewOnceMessage?.message ||
10
+ c.viewOnceMessageV2?.message ||
11
+ c.viewOnceMessageV2Extension?.message ||
12
+ c.documentWithCaptionMessage?.message;
13
+ }
14
+ return c;
15
+ };
3
16
  export const extractMessageText = (message) => {
4
- const c = message.message;
17
+ const c = unwrapMessage(message.message);
5
18
  if (!c)
6
19
  return '';
7
20
  if (c.conversation)
@@ -27,7 +40,7 @@ export const extractMessageText = (message) => {
27
40
  return '';
28
41
  };
29
42
  const getMessageType = (message) => {
30
- const c = message.message;
43
+ const c = unwrapMessage(message.message);
31
44
  if (!c)
32
45
  return 'other';
33
46
  if (c.conversation || c.extendedTextMessage)
@@ -95,10 +108,12 @@ export const searchMessages = (messages, query, options = {}) => {
95
108
  relevanceScore: calculateRelevance(query, text, pos)
96
109
  });
97
110
  }
98
- if (options.limit && results.length >= options.limit)
99
- break;
111
+ // Vanz@Fix (bug 59): limit used to break the scan loop *before* sorting,
112
+ // so it kept the first N matches in document order and could drop
113
+ // higher-relevance matches found later. Sort first, then slice.
100
114
  }
101
- return results.sort((a, b) => b.relevanceScore - a.relevanceScore);
115
+ results.sort((a, b) => b.relevanceScore - a.relevanceScore);
116
+ return options.limit ? results.slice(0, options.limit) : results;
102
117
  };
103
118
  /** Regex-based search (e.g. for commands/patterns), unordered by relevance. */
104
119
  export const searchMessagesRegex = (messages, pattern, options = {}) => {
@@ -1815,10 +1815,14 @@ export const normalizeMessageContent = (content) => {
1815
1815
  }
1816
1816
  return content;
1817
1817
  // Lia@Changes 03-02-26 --- Add all futureProofMessage into getFutureProofMessage()
1818
+ // Vanz@Fix 29-08-26 --- whitelist was missing 2 FutureProofMessage-typed fields added to
1819
+ // WAProto this session: botPlatformRegistrationSuccessMessage (131) and
1820
+ // newsletterScheduledMessage (132) — both would've silently failed to unwrap.
1818
1821
  function getFutureProofMessage(message) {
1819
1822
  return (message?.associatedChildMessage ||
1820
1823
  message?.botForwardedMessage ||
1821
1824
  message?.botInvokeMessage ||
1825
+ message?.botPlatformRegistrationSuccessMessage ||
1822
1826
  message?.botTaskMessage ||
1823
1827
  message?.documentWithCaptionMessage ||
1824
1828
  message?.editedMessage ||
@@ -1833,6 +1837,7 @@ export const normalizeMessageContent = (content) => {
1833
1837
  message?.newsletterAdminProfileMessage ||
1834
1838
  message?.newsletterAdminProfileMessageV2 ||
1835
1839
  message?.newsletterAdminProfileStatusMessage ||
1840
+ message?.newsletterScheduledMessage ||
1836
1841
  message?.pollCreationMessageV4 ||
1837
1842
  message?.pollCreationOptionImageMessage ||
1838
1843
  message?.questionMessage ||
@@ -0,0 +1,14 @@
1
+ import type { proto } from '../../WAProto/index.js';
2
+ export interface ProcessedPastParticipant {
3
+ jid: string;
4
+ leaveTs?: number;
5
+ leaveReason?: 'left' | 'removed';
6
+ }
7
+ export interface ProcessedPastParticipants {
8
+ groupJid: string;
9
+ participants: ProcessedPastParticipant[];
10
+ }
11
+ /** Process proto.IPastParticipants[] from a history sync payload into a structured list per group. */
12
+ export declare const processPastParticipants: (pastParticipantsList: proto.IPastParticipants[]) => ProcessedPastParticipants[];
13
+ /** Check if a history sync event contains past participants data. */
14
+ export declare const hasPastParticipants: (event: { pastParticipants?: unknown[] }) => boolean;
@@ -0,0 +1,40 @@
1
+ // Ported from @queenanya/baileys `addons/past-participants.ts`.
2
+ // No logic changes — @vanzxy/baileys's own WAProto already has the
3
+ // `proto.PastParticipant.LeaveReason` enum and `HistorySync.pastParticipants`
4
+ // field this depends on (confirmed present), so this ports as-is.
5
+ //
6
+ // This reads WhatsApp's own server-provided participant-leave history from
7
+ // a history-sync payload (`proto.IPastParticipants[]`) — it's real protocol
8
+ // data, not something reconstructed by listening to live events, so it
9
+ // includes participants who left/were removed *before* this session ever
10
+ // connected (which a live `group-participants.update` listener can never
11
+ // see).
12
+ import { proto } from '../../WAProto/index.js';
13
+
14
+ /**
15
+ * Process proto.IPastParticipants[] from a history sync payload.
16
+ * Returns a structured list per group.
17
+ */
18
+ export const processPastParticipants = (pastParticipantsList) => {
19
+ return pastParticipantsList.map((pp) => {
20
+ const groupJid = pp.groupJid ?? '';
21
+ const participants = (pp.pastParticipants ?? []).map((p) => ({
22
+ jid: p.userJid ?? '',
23
+ leaveTs: p.leaveTs ? Number(p.leaveTs) : undefined,
24
+ leaveReason: p.leaveReason === proto.PastParticipant.LeaveReason.LEFT
25
+ ? 'left'
26
+ : p.leaveReason === proto.PastParticipant.LeaveReason.REMOVED
27
+ ? 'removed'
28
+ : undefined
29
+ }));
30
+ return { groupJid, participants };
31
+ });
32
+ };
33
+
34
+ /**
35
+ * Check if a history sync event contains past participants data.
36
+ * (pastParticipants field replaces chunkOrder in this patch)
37
+ */
38
+ export const hasPastParticipants = (event) => {
39
+ return Array.isArray(event.pastParticipants) && event.pastParticipants.length > 0;
40
+ };
@@ -21,8 +21,12 @@ const REAL_MSG_REQ_ME_STUB_TYPES = new Set([WAMessageStubType.GROUP_PARTICIPANT_
21
21
  // `LIDMigrationMappingSyncMessage { encodedMappingPayload: bytes }` is generated now.
22
22
  // Nothing suggests the *inner* wire layout actually changed (WA's extractor just stopped
23
23
  // walking this nested message), so we decode it by hand instead of guessing a new shape.
24
- // TODO: verify against live traffic on the next audit pass — if Meta did change the inner
25
- // layout this will start throwing and LID/PN pairs will silently stop syncing.
24
+ // Vanz@Verify 29-08-26 --- cross-checked this hand decode against Baileys-5's WAProto.proto
25
+ // (an independent, still-intact older schema dump that predates the removal) and it's an exact
26
+ // field-for-field match: LIDMigrationMappingSyncPayload{ repeated pnToLidMappings=1,
27
+ // optional chatDbMigrationTimestamp=2(uint64) }, LIDMigrationMapping{ pn=1, assignedLid=2,
28
+ // latestLid=3, all uint64 }. Not literal live-traffic verification, but a real independent
29
+ // source confirms the guess rather than just "seems plausible" — safe to leave as-is.
26
30
  function decodeLidMigrationMappingSyncPayload(buf) {
27
31
  const r = Reader.create(buf);
28
32
  const out = { pnToLidMappings: [], chatDbMigrationTimestamp: undefined };
@@ -91,7 +91,9 @@ const ITSL_CONCURRENCY_LIMIT = 15;
91
91
  * cover→trayIcon-in-ZIP, and a separate 252×252 JPEG thumbnail upload.
92
92
  */
93
93
  export const prepareStickerPackMessageItsliaaa = async (message, options) => {
94
- const { cover, stickers = [], name = '📦 Sticker Pack', publisher = 'GitHub: itsliaaa', description = '🏷️ itsliaaa/baileys' } = message;
94
+ // Vanz@Fix (bug 62): packId was never destructured here, so a caller-supplied
95
+ // pack ID was silently dropped and a fresh one always generated below.
96
+ const { cover, stickers = [], name = '📦 Sticker Pack', publisher = 'GitHub: itsliaaa', description = '🏷️ itsliaaa/baileys', packId } = message;
95
97
  if (stickers.length > 60) {
96
98
  throw new Boom('Sticker pack exceeds the maximum limit of 60 stickers', { statusCode: 400 });
97
99
  }
@@ -124,11 +126,10 @@ export const prepareStickerPackMessageItsliaaa = async (message, options) => {
124
126
  const lib = await getImageProcessingLibrary();
125
127
  const hasSharp = 'sharp' in lib && !!lib.sharp?.default;
126
128
  const hasImage = 'image' in lib && !!lib.image?.Transformer;
127
- const hasJimp = 'jimp' in lib && !!lib.jimp?.Jimp;
128
129
  if (!hasSharp && !hasImage) {
129
130
  throw new Boom('No image processing library (sharp or @napi-rs/image) available for converting sticker to WebP.');
130
131
  }
131
- const stickerPackIdValue = generateMessageIDV2();
132
+ const stickerPackIdValue = packId ?? generateMessageIDV2();
132
133
  const stickerData = {};
133
134
  const stickerMetadata = new Array(stickers.length);
134
135
  for (let i = 0; i < stickers.length; i += ITSL_CONCURRENCY_LIMIT) {
@@ -226,15 +227,13 @@ export const prepareStickerPackMessageItsliaaa = async (message, options) => {
226
227
  if (hasSharp) {
227
228
  thumbnailBuffer = await lib.sharp.default(coverBuffer).resize(252, 252).jpeg().toBuffer();
228
229
  }
229
- else if (hasImage) {
230
- thumbnailBuffer = await new lib.image.Transformer(coverBuffer).resize(252, 252).jpeg();
231
- }
232
- else if (hasJimp) {
233
- const jimpImage = await lib.jimp.Jimp.read(coverBuffer);
234
- thumbnailBuffer = await jimpImage.resize({ w: 252, h: 252 }).getBuffer('image/jpeg');
235
- }
236
230
  else {
237
- throw new Error('No image processing library available for thumbnail generation');
231
+ // hasImage is guaranteed here since hasSharp||hasImage is enforced above.
232
+ // Vanz@Fix (bug 63): removed a jimp fallback branch that could never be
233
+ // reached (the earlier guard already requires sharp or image), and which
234
+ // main sticker/cover conversion above doesn't support anyway — it was
235
+ // dead code presenting a false sense of a jimp-only code path.
236
+ thumbnailBuffer = await new lib.image.Transformer(coverBuffer).resize(252, 252).jpeg();
238
237
  }
239
238
  if (!thumbnailBuffer || thumbnailBuffer.length === 0) {
240
239
  throw new Error('Failed to generate thumbnail buffer');
@@ -0,0 +1,13 @@
1
+ import type { AuthenticationState } from '../Types/index.js';
2
+ /** Minimal interface compatible with any cache-manager v5 store (Redis, Memcached, keyv, @cacheable/node-cache, etc). */
3
+ export type CacheManagerStore = {
4
+ set(key: string, value: string, ttl?: number): Promise<void>;
5
+ get(key: string): Promise<string | undefined | null>;
6
+ del(key: string): Promise<void>;
7
+ keys(pattern?: string): Promise<string[]>;
8
+ };
9
+ export declare const useCacheManagerAuthState: (store: CacheManagerStore, sessionKey: string) => Promise<{
10
+ state: AuthenticationState;
11
+ saveCreds: () => Promise<void>;
12
+ clearState: () => Promise<void>;
13
+ }>;
@@ -0,0 +1,82 @@
1
+ // Ported from @queenanya/baileys `addons/use-cache-manager-auth-state.ts`
2
+ // (that fork credits it to `@innovatorssoft/baileys` make-cache-manager-store.js).
3
+ // Adjustments for @vanzxy/baileys: import paths rewired to this fork's
4
+ // Utils layout (auth-utils.js / generics.js), otherwise logic-for-logic
5
+ // identical to the original — including the 2-year TTL on `creds` and the
6
+ // `sessionKey*` wildcard sweep in `clearState()`.
7
+ //
8
+ // Store interface required (matches any cache-manager v5-compatible store,
9
+ // e.g. Redis/Memcached/keyv, or this fork's own @cacheable/node-cache dep):
10
+ // store.get(key) -> string | undefined | null
11
+ // store.set(key, value, ttl?) -> void
12
+ // store.del(key) -> void
13
+ // store.keys(pattern?) -> string[] (used by clearState() for the wildcard sweep)
14
+ import { proto } from '../../WAProto/index.js';
15
+ import { initAuthCreds } from './auth-utils.js';
16
+ import { BufferJSON } from './generics.js';
17
+
18
+ export const useCacheManagerAuthState = async (store, sessionKey) => {
19
+ const defaultKey = (file) => `${sessionKey}:${file}`;
20
+ const writeData = async (file, data) => {
21
+ const ttl = file === 'creds' ? 63115200 : undefined; // 2 years for creds
22
+ await store.set(defaultKey(file), JSON.stringify(data, BufferJSON.replacer), ttl);
23
+ };
24
+ const readData = async (file) => {
25
+ try {
26
+ const data = await store.get(defaultKey(file));
27
+ return data !== null && data !== undefined ? JSON.parse(data, BufferJSON.reviver) : null;
28
+ }
29
+ catch {
30
+ return null;
31
+ }
32
+ };
33
+ const removeData = async (file) => {
34
+ try {
35
+ await store.del(defaultKey(file));
36
+ }
37
+ catch {
38
+ console.error(`[useCacheManagerAuthState] Error removing ${file} from session ${sessionKey}`);
39
+ }
40
+ };
41
+ const clearState = async () => {
42
+ try {
43
+ const keys = await store.keys(`${sessionKey}*`);
44
+ await Promise.all(keys.map((key) => store.del(key)));
45
+ }
46
+ catch {
47
+ // best-effort — not every store backend supports pattern listing
48
+ }
49
+ };
50
+ const creds = (await readData('creds')) || initAuthCreds();
51
+ return {
52
+ clearState,
53
+ state: {
54
+ creds,
55
+ keys: {
56
+ get: async (type, ids) => {
57
+ const data = {};
58
+ await Promise.all(ids.map(async (id) => {
59
+ let value = await readData(`${type}-${id}`);
60
+ if (type === 'app-state-sync-key' && value) {
61
+ value = proto.Message.AppStateSyncKeyData.fromObject(value);
62
+ }
63
+ data[id] = value;
64
+ }));
65
+ return data;
66
+ },
67
+ set: async (data) => {
68
+ const tasks = [];
69
+ for (const category in data) {
70
+ for (const id in data[category]) {
71
+ const value = data[category][id];
72
+ const key = `${category}-${id}`;
73
+ tasks.push(value ? writeData(key, value) : removeData(key));
74
+ }
75
+ }
76
+ await Promise.all(tasks);
77
+ }
78
+ }
79
+ },
80
+ saveCreds: () => writeData('creds', creds)
81
+ };
82
+ };
@@ -1,7 +1,7 @@
1
1
  import { Boom } from '@hapi/boom';
2
2
  import { createHash } from 'crypto';
3
3
  import { proto } from '../../WAProto/index.js';
4
- import { KEY_BUNDLE_TYPE, WA_ADV_ACCOUNT_SIG_PREFIX, WA_ADV_DEVICE_SIG_PREFIX, WA_ADV_HOSTED_ACCOUNT_SIG_PREFIX } from '../Defaults/index.js';
4
+ import { KEY_BUNDLE_TYPE, WA_ADV_ACCOUNT_SIG_PREFIX, WA_ADV_DEVICE_SIG_PREFIX, WA_ADV_HOSTED_ACCOUNT_SIG_PREFIX, COMPANION_DEVICE_VERSION } from '../Defaults/index.js';
5
5
  import { getBinaryNodeChild, jidDecode, S_WHATSAPP_NET } from '../WABinary/index.js';
6
6
  import { Curve, hmacSign } from './crypto.js';
7
7
  import { encodeBigEndian } from './generics.js';
@@ -94,11 +94,7 @@ export const generateRegistrationNode = ({ registrationId, signedPreKey, signedI
94
94
  onDemandReady: undefined,
95
95
  supportGuestChat: undefined
96
96
  },
97
- version: {
98
- primary: 10,
99
- secondary: 15,
100
- tertiary: 7
101
- }
97
+ version: COMPANION_DEVICE_VERSION
102
98
  };
103
99
  const companionProto = proto.DeviceProps.encode(companion).finish();
104
100
  const registerPayload = {
@@ -1,6 +1,17 @@
1
1
  // Vanz@Add --- ported from Bail-master addons/vcard.ts (type-only
2
2
  // annotations dropped; behavior unchanged).
3
3
  export const escapeVCard = (s) => s.replace(/\\/g, '\\\\').replace(/;/g, '\\;').replace(/,/g, '\\,').replace(/\n/g, '\\n');
4
+ // Vanz@Fix 30-08-26 --- the old inline unescape used for FN/ORG/TITLE,
5
+ // `value.replace(/\\([;,n\\])/g, '$1')`, mishandled the `\n` case: it matched
6
+ // the backslash + literal "n" produced by escapeVCard's `\n` escape, then
7
+ // substituted back just the captured "n" character instead of a real newline
8
+ // (`$1` is literally the char "n", not the escape sequence). So a name/org/
9
+ // title containing a real newline round-tripped as "n" inserted in place of
10
+ // the line break instead of the original newline. NOTE already unescaped
11
+ // `\n` correctly on its own, separately. This single helper now reverses
12
+ // every escape escapeVCard() produces (backslash, semicolon, comma, newline)
13
+ // the same correct way, and is reused for all four fields below.
14
+ export const unescapeVCard = (s) => s.replace(/\\(.)/g, (_, ch) => (ch === 'n' ? '\n' : ch));
4
15
  export const formatPhone = (p) => p.replace(/[^\d+]/g, '');
5
16
  /** Build a VCARD 3.0 string from structured contact data. */
6
17
  export const generateVCard = (c) => {
@@ -49,11 +60,11 @@ export const parseVCard = (vcard) => {
49
60
  continue;
50
61
  const value = vp.join(':');
51
62
  if (key.startsWith('FN'))
52
- contact.fullName = value.replace(/\\([;,n\\])/g, '$1');
63
+ contact.fullName = unescapeVCard(value);
53
64
  else if (key.startsWith('ORG'))
54
- contact.organization = value.replace(/\\([;,n\\])/g, '$1');
65
+ contact.organization = unescapeVCard(value);
55
66
  else if (key.startsWith('TITLE'))
56
- contact.title = value.replace(/\\([;,n\\])/g, '$1');
67
+ contact.title = unescapeVCard(value);
57
68
  else if (key.startsWith('TEL')) {
58
69
  contact.phones = contact.phones || [];
59
70
  const tm = key.match(/type=(\w+)/i);
@@ -67,7 +78,7 @@ export const parseVCard = (vcard) => {
67
78
  else if (key.startsWith('BDAY'))
68
79
  contact.birthday = value;
69
80
  else if (key.startsWith('NOTE'))
70
- contact.note = value.replace(/\\n/g, '\n');
81
+ contact.note = unescapeVCard(value);
71
82
  }
72
83
  return contact;
73
84
  };