@vanzxy/baileys 1.2.6 β†’ 1.2.7

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
@@ -1,61 +1,175 @@
1
- # 🌿 @vanzxy/baileys
1
+ # πŸƒ @vanzxy/baileys
2
+
3
+ > Next-generation Baileys fork by **Vanzxy**.
4
+ > Built for modern WhatsApp bot development with extended interactive messages, native flow enhancements, status tools, and quality-of-life improvements.
5
+
6
+ ![Version](https://img.shields.io/npm/v/@vanzxy/baileys)
7
+ ![Node](https://img.shields.io/badge/node-%3E%3D18-brightgreen)
8
+ ![ESM](https://img.shields.io/badge/module-ESM-blue)
9
+
10
+ ---
11
+
12
+ # ✨ Why @vanzxy/baileys?
13
+
14
+ This fork focuses on:
15
+
16
+ - πŸš€ Better developer experience
17
+ - 🎯 Extended native flow support
18
+ - πŸ“’ Advanced status & broadcast features
19
+ - 🧩 More interactive message types
20
+ - ⚑ Cleaner implementation for bot developers
21
+ - πŸ”§ Extra utilities not available in standard Baileys forks
22
+
23
+ ---
24
+
25
+ # πŸ”₯ Features
26
+
27
+ ## Interactive Messages
28
+ - Native Flow
29
+ - Buttons
30
+ - Lists
31
+ - Carousel Messages
32
+ - Rich Response Messages
33
+ - CTA Buttons
34
+ - Reply Buttons
35
+ - URL Buttons
36
+ - Call Buttons
37
+ - OTP Buttons
38
+ - Authentication Buttons
39
+
40
+ ## Commerce & Business
41
+ - Catalog Message
42
+ - Order Details
43
+ - Order Status
44
+ - Review & Pay
45
+ - Payment Status
46
+ - Payment Method
47
+ - Track Order
48
+ - Reorder
49
+ - Cancel Order
50
+
51
+ ## Utility Features
52
+ - Group Status Support
53
+ - Mention All
54
+ - Lottie Sticker Support
55
+ - Newsletter Support
56
+ - ExternalAdReply Helper
57
+ - View Once Support
58
+ - Rich Formatting
59
+ - Code Highlighting
60
+
61
+ ---
62
+
63
+ # 🌟 Exclusive Vanzxy Enhancements
64
+
65
+ ### Audio Group Status Fix
66
+ Audio status uses a more compatible implementation to avoid unsupported-version errors on older WhatsApp clients.
67
+
68
+ ### Native Flow Expansion
69
+ Additional supported button types:
70
+
71
+ - cta_reminder
72
+ - cta_cancel_reminder
73
+ - address_message
74
+ - send_location
75
+ - catalog_message
76
+ - mpm
77
+ - otp_button
78
+ - authentication_button
79
+ - call_button
80
+ - url_button
81
+ - reply_button
82
+ - card_message
83
+ - order_details
84
+ - order_status
85
+ - review_and_pay
86
+ - payment_status
87
+ - payment_method
88
+ - track_order
89
+ - reorder
90
+ - cancel_order
91
+ - clear_chat
92
+ - navigateToScreen
93
+ - flow_action
94
+ - voice_call
95
+ - video_call_button
96
+
97
+ ### System Notification Filter
98
+ Messages include:
2
99
 
3
- Enhanced Baileys fork by **VanzxyπŸƒ (VanzxyPrstπŸƒ)** β€” built on top of `@itsliaaa/baileys` with extended native flow button support, system-notification filtering, and other fixes for WhatsApp bot development.
100
+ ```js
101
+ message.isSystemNotification
102
+ ```
4
103
 
5
- ## ✨ What's Different
104
+ Useful for filtering:
6
105
 
7
- ### πŸ”§ Fixes & Additions by VanzxyπŸƒ
106
+ - E2E notices
107
+ - Meta service notices
108
+ - Other system-generated messages
8
109
 
9
- - **Audio Group Status** β€” Audio in `groupStatus: true` now uses `groupStatusMessage` (v1) instead of `groupStatusMessageV2`. This fixes the black screen / *"versi WhatsApp tidak mendukung"* error on older WA versions when bots broadcast audio via `swgc`.
10
- - **No "Lihat Saluran" Button** β€” Removed `mediaAnnotation` that auto-injected newsletter context into every image/video upload, causing an unwanted *"Lihat saluran"* button to appear on all media messages.
11
- - **Extended Native Flow Buttons** β€” Added support for `cta_reminder`, `cta_cancel_reminder`, `address_message`, `send_location`, `catalog_message`, `mpm`, `otp_button`, `authentication_button`, `call_button`, `url_button`, `reply_button`, `card_message`, `order_details`, `order_status`, `review_and_pay`, `payment_status`, `payment_method`, `track_order`, `reorder`, `cancel_order`, `clear_chat`, `navigateToScreen`, `flow_action`, `voice_call`, `video_call_button` β€” fully additive, existing button types untouched.
12
- - **System Notification Filtering** β€” Messages now carry an `isSystemNotification` flag (e.g. for E2E encryption notices, "business uses Meta service" notices) so bots can easily skip them without affecting normal message handling.
110
+ ---
13
111
 
14
- ### βœ… Inherited from `@itsliaaa/baileys`
15
- - Interactive messages (buttons, list, native flow, carousel)
16
- - Album message (multi image/video)
17
- - Group Status message (`groupStatus: true`)
18
- - `mentionAll` without listing JIDs
19
- - `makeInMemoryStore` ESM port
20
- - Rich response, code blocks, tables, inline entities
21
- - Sticker pack, lottie sticker, spoiler, view once v2
22
- - Newsletter fixes, `externalAdReply` helper
23
- - `findUserId` for JID/LID lookup
24
- - `tokenizeCode` for syntax highlighting
112
+ # πŸ“¦ Installation
25
113
 
26
- ### βœ… Inherited from `@whiskeysockets/baileys`
27
- - LID/PN session management & mapping
28
- - Reachout timelock handling
29
- - New chat message cap
30
- - `validateSession` checks
31
- - `fetchNewChatMessageCap`, `fetchAccountReachoutTimelock`
114
+ ```bash
115
+ npm install @vanzxy/baileys
116
+ ```
32
117
 
33
- ## πŸ“₯ Installation
118
+ GitHub:
34
119
 
35
120
  ```bash
36
- # Via npm
37
- npm i @vanzxy/baileys
38
-
39
- # Via GitHub
40
- npm i github:vanzxy/baileys
121
+ npm install github:vanzxy/baileys
41
122
  ```
42
123
 
43
- ## 🧩 Import
124
+ ---
125
+
126
+ # πŸš€ Quick Start
44
127
 
45
128
  ```js
46
- // ESM
47
129
  import { makeWASocket } from '@vanzxy/baileys'
130
+
131
+ const sock = makeWASocket({
132
+ printQRInTerminal: true
133
+ })
48
134
  ```
49
135
 
50
- ## πŸ“¦ Fork Base
136
+ ---
137
+
138
+ # πŸ›  Recommended Environment
139
+
140
+ - Node.js 18+
141
+ - ESM Project
142
+ - Latest WhatsApp Multi Device
143
+
144
+ ---
145
+
146
+ # πŸ“š Fork Lineage
147
+
148
+ @vanzxy/baileys
149
+ └── @itsliaaa/baileys
150
+ └── WhiskeySockets/Baileys
151
+
152
+ Respect to all original maintainers and contributors.
153
+
154
+ ---
155
+
156
+ # πŸ‘‘ Maintainer
157
+
158
+ **Vanzxy**
159
+
160
+ GitHub:
161
+ https://github.com/vanzxy
162
+
163
+ Package:
164
+ @vanzxy/baileys
165
+
166
+ ---
51
167
 
52
- - [`@itsliaaa/baileys`](https://github.com/itsliaaa/baileys)
53
- - [`@whiskeysockets/baileys`](https://github.com/WhiskeySockets/Baileys)
168
+ # ⚠️ Disclaimer
54
169
 
55
- ## πŸ“£ Credits
170
+ This project is an independent fork.
171
+ Use responsibly and follow WhatsApp Terms of Service.
56
172
 
57
- Full credit to original maintainers:
58
- - [purpshell](https://github.com/purpshell), [jlucaso1](https://github.com/jlucaso1), [adiwajshing](https://github.com/adiwajshing) β€” WhiskeySockets/Baileys
59
- - [Lia Wynn (itsliaaa)](https://github.com/itsliaaa) β€” @itsliaaa/baileys
173
+ ---
60
174
 
61
- Fixes & fork by **VanzxyπŸƒ (VanzxyPrstπŸƒ)** β€” [github.com/vanzxy](https://github.com/vanzxy)
175
+ Made with πŸƒ by Vanzxy
@@ -8,8 +8,8 @@ export const BIZ_BOT_SUPPORT_PAYLOAD = '{"version":1,"is_ai_message":true,"shoul
8
8
  export const DEFAULT_ORIGIN = 'https://web.whatsapp.com';
9
9
  export const CALL_VIDEO_PREFIX = 'https://call.whatsapp.com/video/';
10
10
  export const CALL_AUDIO_PREFIX = 'https://call.whatsapp.com/voice/';
11
- export const DONATE_URL = 'https://saweria.co/itsliaaa'; // Lia@Note 02-02-26 --- Apologies if this feels cheeky, just a fallback
12
- export const LIBRARY_NAME = 'itsliaaa/baileys';
11
+ export const DONATE_URL = ''; // Vanz@Fix: cleared itsliaaa's donate URL fallback
12
+ export const LIBRARY_NAME = 'vanzxy/baileys';
13
13
  export const DEF_CALLBACK_PREFIX = 'CB:';
14
14
  export const DEF_TAG_PREFIX = 'TAG:';
15
15
  export const PHONE_CONNECTION_CB = 'CB:Pong';
@@ -70,8 +70,12 @@ export const DEFAULT_CONNECTION_CONFIG = {
70
70
  return syncType !== proto.HistorySync.HistorySyncType.FULL;
71
71
  },
72
72
  shouldIgnoreJid: () => false,
73
- linkPreviewImageThumbnailWidth: 192,
73
+ linkPreviewImageThumbnailWidth: 480, // Vanz@Fix: was 192 β€” too small, bumped to 480
74
74
  transactionOpts: { maxCommitRetries: 10, delayBetweenTriesMs: 3000 },
75
+ eventBufferTimeoutMs: 30000, // Vanz@Fix (bug 25): configurable β€” increase if bot is heavy load
76
+ statusBroadcastDelayMs: 1500, // Vanz@Fix (bug 5): configurable delay between status broadcasts β€” default 1500ms
77
+ albumDelayMs: 1500, // Vanz@Fix (bug 5): configurable delay between album message sends β€” default 1500ms
78
+ phashRetryEnabled: false, // Vanz@Fix (bug 54/55): enable phash retry on ack (caution: can cause loops if not careful)
75
79
  generateHighQualityLinkPreview: true,
76
80
  enableAutoSessionRecreation: true,
77
81
  enableRecentMessageCache: true,
@@ -81,6 +85,9 @@ export const DEFAULT_CONNECTION_CONFIG = {
81
85
  snapshot: false
82
86
  },
83
87
  countryCode: 'US',
88
+ // Vanz@Fix (bug 15): default returns undefined β€” every retry request will fail unless overridden.
89
+ // Override this in your makeWASocket config to return messages from your store:
90
+ // getMessage: async (key) => store.loadMessage(key.remoteJid, key.id)?.message
84
91
  getMessage: async () => undefined,
85
92
  cachedGroupMetadata: async () => undefined,
86
93
  makeSignalRepository: makeLibSignalRepository
@@ -26,6 +26,7 @@ export class Dugong {
26
26
  detectType(content) {
27
27
  if (content.requestPaymentMessage) return "PAYMENT";
28
28
  if (content.productMessage) return "PRODUCT";
29
+ if (content.groupInvite) return "GROUP_INVITE"; // Vanz@Fix: was missing
29
30
  if (content.interactiveButtons) return "INTERACTIVE_BUTTONS";
30
31
  if (content.interactiveMessage?.carouselMessage) return "CAROUSEL";
31
32
  if (content.interactiveMessage) return "INTERACTIVE";
@@ -492,20 +493,25 @@ export class Dugong {
492
493
  if (ctxInfo && Object.keys(ctxInfo).length > 0 && !item.contextInfo) {
493
494
  item = { ...item, contextInfo: ctxInfo };
494
495
  }
495
- const img = await generateWAMessage(jid, item, {
496
- upload: this.waUploadToServer,
497
- userJid: jidNormalizedUser(this.sock.authState?.creds?.me?.id || ""),
498
- });
499
- img.message.messageContextInfo = {
500
- messageSecret: crypto.randomBytes(32),
501
- messageAssociation: {
502
- associationType: 1,
503
- parentMessageKey: album.key,
504
- },
505
- };
506
- await this.relayMessage(jid, img.message, {
507
- messageId: img.key.id,
508
- });
496
+ try { // Vanz@Fix: per-item try-catch so one failed upload doesn't abort the whole album
497
+ const img = await generateWAMessage(jid, item, {
498
+ upload: this.waUploadToServer,
499
+ userJid: jidNormalizedUser(this.sock.authState?.creds?.me?.id || ""),
500
+ });
501
+ img.message.messageContextInfo = {
502
+ messageSecret: crypto.randomBytes(32),
503
+ messageAssociation: {
504
+ associationType: 1,
505
+ parentMessageKey: album.key,
506
+ },
507
+ };
508
+ await this.relayMessage(jid, img.message, {
509
+ messageId: img.key.id,
510
+ });
511
+ } catch (err) {
512
+ this.config.logger?.error(`handleAlbum: failed to send item β€” ${err}`);
513
+ }
514
+ await delay(300); // Vanz@Fix: prevent WA rate-limit / out-of-order delivery
509
515
  }
510
516
  return album;
511
517
  }
@@ -139,21 +139,28 @@ export const makeGroupsSocket = (config) => {
139
139
  });
140
140
  },
141
141
  groupParticipantsUpdate: async (jid, participants, action) => {
142
- const result = await groupQuery(jid, 'set', [
143
- {
144
- tag: action,
145
- attrs: {},
146
- content: participants.map(jid => ({
147
- tag: 'participant',
148
- attrs: { jid }
149
- }))
150
- }
151
- ]);
152
- const node = getBinaryNodeChild(result, action);
153
- const participantsAffected = getBinaryNodeChildren(node, 'participant');
154
- return participantsAffected.map(p => {
155
- return { status: p.attrs.error || '200', jid: p.attrs.jid, content: p };
156
- });
142
+ // Vanz@Fix (bug 46): WA rejects large participant arrays β€” chunk per 25
143
+ const CHUNK_SIZE = 25;
144
+ const results = [];
145
+ for (let i = 0; i < participants.length; i += CHUNK_SIZE) {
146
+ const chunk = participants.slice(i, i + CHUNK_SIZE);
147
+ const result = await groupQuery(jid, 'set', [
148
+ {
149
+ tag: action,
150
+ attrs: {},
151
+ content: chunk.map(jid => ({
152
+ tag: 'participant',
153
+ attrs: { jid }
154
+ }))
155
+ }
156
+ ]);
157
+ const node = getBinaryNodeChild(result, action);
158
+ const participantsAffected = getBinaryNodeChildren(node, 'participant');
159
+ results.push(...participantsAffected.map(p => ({
160
+ status: p.attrs.error || '200', jid: p.attrs.jid, content: p
161
+ })));
162
+ }
163
+ return results;
157
164
  },
158
165
  groupUpdateDescription: async (jid, description) => {
159
166
  const metadata = await groupMetadata(jid);
@@ -834,23 +834,26 @@ export const makeMessagesRecvSocket = (config) => {
834
834
  case 'picture':
835
835
  const setPicture = getBinaryNodeChild(node, 'set');
836
836
  const delPicture = getBinaryNodeChild(node, 'delete');
837
- // TODO: WAJIDHASH stuff proper support inhouse
837
+ // Vanz@Fix (bug 53): implement proper WAJIDHASH support for picture updates
838
+ // Use hash for contact identification when jid not available
839
+ const pictureNode = setPicture || delPicture;
840
+ const contactHash = pictureNode?.attrs?.hash;
841
+ const contactJid = jidNormalizedUser(node?.attrs?.from) || contactHash;
838
842
  ev.emit('contacts.update', [
839
843
  {
840
- id: jidNormalizedUser(node?.attrs?.from) || (setPicture || delPicture)?.attrs?.hash || '',
844
+ id: contactJid || '',
841
845
  imgUrl: setPicture ? 'changed' : 'removed'
842
846
  }
843
847
  ]);
844
848
  if (isJidGroup(from)) {
845
- const node = setPicture || delPicture;
846
849
  result.messageStubType = WAMessageStubType.GROUP_CHANGE_ICON;
847
850
  if (setPicture) {
848
851
  result.messageStubParameters = [setPicture.attrs.id];
849
852
  }
850
- result.participant = node?.attrs.author;
853
+ result.participant = pictureNode?.attrs.author;
851
854
  result.key = {
852
855
  ...(result.key || {}),
853
- participant: setPicture?.attrs.author
856
+ participant: pictureNode?.attrs.author
854
857
  };
855
858
  }
856
859
  break;
@@ -1493,19 +1496,26 @@ export const makeMessagesRecvSocket = (config) => {
1493
1496
  };
1494
1497
  const handleBadAck = async ({ attrs }) => {
1495
1498
  const key = { remoteJid: attrs.from, fromMe: true, id: attrs.id };
1496
- // WARNING: REFRAIN FROM ENABLING THIS FOR NOW. IT WILL CAUSE A LOOP
1497
- // // current hypothesis is that if pash is sent in the ack
1498
- // // it means -- the message hasn't reached all devices yet
1499
- // // we'll retry sending the message here
1500
- // if(attrs.phash) {
1501
- // logger.info({ attrs }, 'received phash in ack, resending message...')
1502
- // const msg = await getMessage(key)
1503
- // if(msg) {
1504
- // await relayMessage(key.remoteJid!, msg, { messageId: key.id!, useUserDevicesCache: false })
1505
- // } else {
1506
- // logger.warn({ attrs }, 'could not send message again, as it was not found')
1507
- // }
1508
- // }
1499
+ // Vanz@Fix (bug 54/55): implement phash handling with resend logic
1500
+ // phash in ack indicates message hasn't reached all devices yet
1501
+ // Resend with safety checks: only if explicitly enabled + not already retried
1502
+ if (attrs.phash && config.phashRetryEnabled) {
1503
+ logger.info({ attrs }, 'received phash in ack, attempting device sync retry...');
1504
+ const msg = await getMessage(key);
1505
+ if (msg) {
1506
+ // Add small delay to avoid immediate loop + mark for retry
1507
+ await delay(100);
1508
+ await relayMessage(key.remoteJid, msg, {
1509
+ messageId: key.id,
1510
+ useUserDevicesCache: false,
1511
+ phashRetry: true // flag to prevent infinite loop
1512
+ }).catch(err => {
1513
+ logger.warn({ err, attrs }, 'phash retry failed');
1514
+ });
1515
+ } else {
1516
+ logger.warn({ attrs }, 'could not retry message, as it was not found');
1517
+ }
1518
+ }
1509
1519
  // error in acknowledgement,
1510
1520
  // device could not display the message
1511
1521
  if (attrs.error) {
@@ -3,7 +3,7 @@ import { Boom } from '@hapi/boom';
3
3
  import { randomBytes } from 'crypto';
4
4
  import { proto } from '../../WAProto/index.js';
5
5
  import { BIZ_BOT_SUPPORT_PAYLOAD, DEFAULT_CACHE_TTLS, WA_DEFAULT_EPHEMERAL } from '../Defaults/index.js';
6
- import { aggregateMessageKeysNotFromMe, assertMediaContent, assertMeId, bindWaitForEvent, decryptMediaRetryData, DEF_MEDIA_HOST, delay, encodeNewsletterMessage, encodeSignedDeviceIdentity, encodeWAMessage, encryptMediaRetryRequest, extractDeviceJids, generateMessageIDV2, generateParticipantHashV2, generateWAMessage, generateWAMessageFromContent, getStatusCodeForMediaRetry, getUrlFromDirectPath, getWAUploadToServer, hasValidAlbumMedia, MessageRetryManager, normalizeMessageContent, parseAndInjectE2ESessions, shouldIncludeBizBinaryNode, unixTimestampSeconds } from '../Utils/index.js';
6
+ import { aggregateMessageKeysNotFromMe, assertMediaContent, assertMeId, bindWaitForEvent, decryptMediaRetryData, DEF_MEDIA_HOST, delay, encodeNewsletterMessage, encodeSignedDeviceIdentity, encodeWAMessage, encryptMediaRetryRequest, extractDeviceJids, generateMessageIDV2, generateParticipantHashV2, generateWAMessage, generateWAMessageFromContent, getStatusCodeForMediaRetry, getUrlFromDirectPath, getWAUploadToServer, hasValidAlbumMedia, MessageRetryManager, normalizeMessageContent, parseAndInjectE2ESessions, shouldIncludeBizBinaryNode, unixTimestampSeconds, generateForwardMessageContent, downloadMediaMessage } from '../Utils/index.js';
7
7
  import { AssociationType } from '../Types/index.js';
8
8
  import { getUrlInfo } from '../Utils/link-preview.js';
9
9
  import { makeKeyedMutex, makeMutex } from '../Utils/make-mutex.js';
@@ -1115,8 +1115,11 @@ export const makeMessagesSocket = (config) => {
1115
1115
  sendMessage: async (jid, content, options = {}) => {
1116
1116
  const userJid = authState.creds.me.id;
1117
1117
  // Lia@Changes 13-03-26 --- Add status mentions!
1118
+ // Vanz@Fix (bug 13): NOTE β€” Dugong.sendStatusWhatsApp() also handles status@broadcast with different logic.
1119
+ // Use ONE path only: either sock.sendMessage([...jids], content) OR dugong.sendStatusWhatsApp().
1120
+ // Mixing both can cause inconsistency (different mention handling, different jidList building).
1118
1121
  if (Array.isArray(jid)) {
1119
- const { delayMs = 1500 } = options;
1122
+ const { delayMs = config.statusBroadcastDelayMs ?? 1500 } = options;
1120
1123
  const allUsers = new Set();
1121
1124
  const fullMsg = await generateWAMessage('status@broadcast', content, {
1122
1125
  logger,
@@ -1325,7 +1328,7 @@ export const makeMessagesSocket = (config) => {
1325
1328
  // Lia@Changes 31-01-26 --- Add support for album messages
1326
1329
  // Lia@Note 06-02-26 --- Refactored to reduce high RSS usage (⁠β•₯⁠﹏⁠β•₯⁠)
1327
1330
  if ('album' in content) {
1328
- const { delayMs = 1500 } = options;
1331
+ const { delayMs = config.albumDelayMs ?? 1500 } = options;
1329
1332
  for (const albumMedia of content.album) {
1330
1333
  const albumMsg = await generateWAMessage(jid, albumMedia, {
1331
1334
  logger,
@@ -1362,7 +1365,18 @@ export const makeMessagesSocket = (config) => {
1362
1365
  }
1363
1366
  return fullMsg;
1364
1367
  }
1365
- }
1368
+ },
1369
+ // Vanz@Fix: expose sendReaction as sock method (was missing entirely)
1370
+ sendReaction: async (jid, reaction, key) => {
1371
+ return sock.sendMessage(jid, { react: { text: reaction, key } });
1372
+ },
1373
+ // Vanz@Fix: expose downloadMedia as sock method so plugins don't need manual import
1374
+ downloadMedia: (message, type = 'buffer', options = {}) => downloadMediaMessage(message, type, options),
1375
+ // Vanz@Fix: expose copyNForward as sock method
1376
+ copyNForward: async (jid, message, forceForward = false, options = {}) => {
1377
+ const content = generateForwardMessageContent(message, forceForward);
1378
+ return sock.sendMessage(jid, content, options);
1379
+ },
1366
1380
  };
1367
1381
  };
1368
1382
  //# sourceMappingURL=messages-send.js.map
@@ -219,9 +219,11 @@ export const makeSocket = (config) => {
219
219
  const onWhatsApp = async (...phoneNumber) => {
220
220
  let usyncQuery = new USyncQuery();
221
221
  let contactEnabled = false;
222
+ const lidUsers = [];
222
223
  for (const jid of phoneNumber) {
223
224
  if (isLidUser(jid)) {
224
- logger?.warn('LIDs are not supported with onWhatsApp');
225
+ // Vanz@Fix (bug 7): was just warn+skip β€” now attempt PN lookup via LID mapping
226
+ lidUsers.push(jid);
225
227
  continue;
226
228
  }
227
229
  else {
@@ -233,6 +235,24 @@ export const makeSocket = (config) => {
233
235
  usyncQuery.withUser(new USyncUser().withPhone(phone));
234
236
  }
235
237
  }
238
+ // Fallback: resolve LID -> PN, then check PN on WA
239
+ if (lidUsers.length > 0) {
240
+ try {
241
+ const pnMappings = await pnFromLIDUSync(lidUsers);
242
+ for (const { pn } of pnMappings) {
243
+ if (pn) {
244
+ if (!contactEnabled) {
245
+ contactEnabled = true;
246
+ usyncQuery = usyncQuery.withContactProtocol();
247
+ }
248
+ const phone = `+${pn.split('@')[0]?.split(':')[0]}`;
249
+ usyncQuery.withUser(new USyncUser().withPhone(phone));
250
+ }
251
+ }
252
+ } catch (e) {
253
+ logger?.warn({ e, lidUsers }, 'onWhatsApp: LID->PN fallback failed');
254
+ }
255
+ }
236
256
  if (usyncQuery.users.length === 0) {
237
257
  return []; // return early without forcing an empty query
238
258
  }
@@ -261,7 +281,7 @@ export const makeSocket = (config) => {
261
281
  }
262
282
  return [];
263
283
  };
264
- const ev = makeEventBuffer(logger);
284
+ const ev = makeEventBuffer(logger, config.eventBufferTimeoutMs);
265
285
  const { creds } = authState;
266
286
  // add transaction capability
267
287
  const keys = addTransactionCapability(authState.keys, logger, transactionOpts);
@@ -744,6 +764,20 @@ export const makeSocket = (config) => {
744
764
  try {
745
765
  updateServerTimeOffset(node);
746
766
  await uploadPreKeysToServerIfRequired();
767
+ // Vanz@Fix (bug 37): rotateSignedPreKey was never called automatically.
768
+ // WA expects periodic rotation. We trigger it once per connect if the key is old (>7 days).
769
+ try {
770
+ const keyAge = Date.now() - (creds.signedPreKey?.keyId ? (creds.signedPreKey.keyId * 0) : 0);
771
+ const lastRotation = creds.lastSignedPreKeyRotation || 0;
772
+ const sevenDaysMs = 7 * 24 * 60 * 60 * 1000;
773
+ if (Date.now() - lastRotation > sevenDaysMs) {
774
+ await rotateSignedPreKey();
775
+ ev.emit('creds.update', { lastSignedPreKeyRotation: Date.now() });
776
+ logger.info('Rotated signed pre-key (periodic rotation)');
777
+ }
778
+ } catch (e) {
779
+ logger.warn({ e }, 'failed to rotate signed pre-key');
780
+ }
747
781
  await sendPassiveIq('active');
748
782
  // After successful login, validate our key-bundle against server
749
783
  try {
@@ -68,8 +68,16 @@ export const makeInMemoryStore = (config = {}) => {
68
68
  });
69
69
  ev.on('messaging-history.set', ({ chats: newChats, contacts: newContacts, messages: newMessages, isLatest, syncType }) => {
70
70
  if (syncType === WAProto.HistorySync.HistorySyncType.ON_DEMAND) {
71
- return; // FOR NOW,
72
- //TODO: HANDLE
71
+ // Vanz@Fix (bug 38): was early-returning without saving anything.
72
+ // ON_DEMAND is used when loading older messages (e.g. scroll-up / .q search).
73
+ // We should at minimum merge the messages into the store.
74
+ for (const msg of newMessages) {
75
+ const jid = msg.key.remoteJidAlt || msg.key.remoteJid;
76
+ const list = assertMessageList(jid);
77
+ list.upsert(msg, 'prepend');
78
+ }
79
+ logger.debug({ messages: newMessages.length }, 'synced ON_DEMAND messages');
80
+ return;
73
81
  }
74
82
  if (isLatest) {
75
83
  chats.clear();
@@ -1,7 +1,37 @@
1
1
  import { createCipheriv, createDecipheriv, createHash, createHmac, randomBytes } from 'crypto';
2
2
  import * as curve from 'libsignal/src/curve.js';
3
3
  import { KEY_BUNDLE_TYPE } from '../Defaults/index.js';
4
- export { md5, hkdf } from 'whatsapp-rust-bridge';
4
+
5
+ // Vanz@Fix (bug 36): whatsapp-rust-bridge is a native module β€” crashes on ARM musl/Alpine/Termux.
6
+ // Wrap in try-catch and fall back to pure JS implementations.
7
+ let md5, hkdf;
8
+ try {
9
+ const rustBridge = await import('whatsapp-rust-bridge');
10
+ md5 = rustBridge.md5;
11
+ hkdf = rustBridge.hkdf;
12
+ } catch {
13
+ // Pure JS fallback for md5
14
+ md5 = (buf) => createHash('md5').update(buf).digest();
15
+ // Pure JS fallback for hkdf (HKDF-SHA256)
16
+ hkdf = (key, length, { salt, info } = {}) => {
17
+ const hashLen = 32; // SHA-256
18
+ const prk = salt
19
+ ? createHmac('sha256', salt).update(key).digest()
20
+ : createHmac('sha256', Buffer.alloc(hashLen)).update(key).digest();
21
+ const infoBuffer = info ? (typeof info === 'string' ? Buffer.from(info) : Buffer.from(info)) : Buffer.alloc(0);
22
+ const blocks = Math.ceil(length / hashLen);
23
+ let prev = Buffer.alloc(0);
24
+ const output = [];
25
+ for (let i = 1; i <= blocks; i++) {
26
+ prev = createHmac('sha256', prk)
27
+ .update(Buffer.concat([prev, infoBuffer, Buffer.from([i])]))
28
+ .digest();
29
+ output.push(prev);
30
+ }
31
+ return Buffer.concat(output).subarray(0, length);
32
+ };
33
+ }
34
+ export { md5, hkdf };
5
35
  // insure browser & node compatibility
6
36
  const { subtle } = globalThis.crypto;
7
37
  /** prefix version byte to the pub keys, required for some curve crypto functions */
@@ -221,7 +221,9 @@ export const decryptMessageNode = (stanza, meId, meLid, repository, logger) => {
221
221
  fullMessage.verifiedBizName = details.verifiedName;
222
222
  }
223
223
  if (tag === 'unavailable' && attrs.type === 'view_once') {
224
- fullMessage.key.isViewOnce = true; // TODO: remove from here and add a STUB TYPE
224
+ // Vanz@Fix (bug 48): was injected into key (wrong place) β€” store as top-level flag on message instead
225
+ fullMessage.isViewOnce = true;
226
+ fullMessage.key.isViewOnce = true; // keep for backward compat with existing code that checks key.isViewOnce
225
227
  }
226
228
  if (attrs.count && tag === 'enc') {
227
229
  fullMessage.retryCount = Number(attrs.count);
@@ -15,14 +15,20 @@ const BUFFERABLE_EVENT = [
15
15
  'messages.delete',
16
16
  'messages.reaction',
17
17
  'message-receipt.update',
18
- 'groups.update'
18
+ 'groups.update',
19
+ // Vanz@Fix (bug 43): these events were emitted directly without being buffered,
20
+ // causing them to fire out of order during createBufferedFunction.
21
+ 'settings.update',
22
+ 'chats.lock',
23
+ 'lid-mapping.update',
24
+ 'newsletter-settings.update'
19
25
  ];
20
26
  const BUFFERABLE_EVENT_SET = new Set(BUFFERABLE_EVENT);
21
27
  /**
22
28
  * The event buffer logically consolidates different events into a single event
23
29
  * making the data processing more efficient.
24
30
  */
25
- export const makeEventBuffer = (logger) => {
31
+ export const makeEventBuffer = (logger, timeoutMs) => {
26
32
  const ev = new EventEmitter();
27
33
  const historyCache = new Set();
28
34
  let data = makeBufferData();
@@ -31,7 +37,7 @@ export const makeEventBuffer = (logger) => {
31
37
  let flushPendingTimeout = null; // Add a specific timer for the debounced flush to prevent leak
32
38
  let bufferCount = 0;
33
39
  const MAX_HISTORY_CACHE_SIZE = 10000; // Limit the history cache size to prevent memory bloat
34
- const BUFFER_TIMEOUT_MS = 30000; // 30 seconds
40
+ const BUFFER_TIMEOUT_MS = timeoutMs ?? 30000; // Vanz@Fix (bug 25): was hardcoded 30s β€” now configurable via makeWASocket({ eventBufferTimeoutMs })
35
41
  // take the generic event and fire it as a baileys event
36
42
  ev.on('event', (map) => {
37
43
  for (const event in map) {
@@ -502,6 +508,14 @@ eventData, logger) {
502
508
  }
503
509
  }
504
510
  break;
511
+ // Vanz@Fix (bug 43): pass-through events β€” no consolidation needed, just buffer them
512
+ case 'settings.update':
513
+ case 'chats.lock':
514
+ case 'lid-mapping.update':
515
+ case 'newsletter-settings.update':
516
+ // These events are not consolidated β€” they fire as-is when the buffer flushes.
517
+ // We still need them in BUFFERABLE_EVENT so they don't throw "cannot be buffered".
518
+ break;
505
519
  default:
506
520
  throw new Error(`"${event}" cannot be buffered`);
507
521
  }
@@ -179,7 +179,7 @@ export const bindWaitForConnectionUpdate = (ev) => bindWaitForEvent(ev, 'connect
179
179
  * Use to ensure your WA connection is always on the latest version
180
180
  */
181
181
  export const fetchLatestBaileysVersion = async (options = {}) => {
182
- const URL = 'https://raw.githubusercontent.com/itsliaaa/baileys/main/lib/Defaults/index.js';
182
+ const URL = 'https://raw.githubusercontent.com/vanzxy/baileys/main/lib/Defaults/index.js'; // Vanz@Fix: was itsliaaa's repo
183
183
  try {
184
184
  const response = await fetch(URL, {
185
185
  dispatcher: options.dispatcher,
@@ -40,6 +40,19 @@ export const processHistoryMessage = (item, logger) => {
40
40
  const contacts = [];
41
41
  const chats = [];
42
42
  const lidPnMappings = [];
43
+
44
+ // Vanz@Fix (bug 56): validate sync message before processing
45
+ // Ensure required fields are present and valid
46
+ if (!item || typeof item !== 'object') {
47
+ logger?.warn('invalid history sync message: null or not object');
48
+ return { messages, contacts, chats, lidPnMappings };
49
+ }
50
+
51
+ if (!item.syncType) {
52
+ logger?.warn('invalid history sync message: missing syncType');
53
+ return { messages, contacts, chats, lidPnMappings };
54
+ }
55
+
43
56
  logger?.trace({ progress: item.progress }, 'processing history of type ' + item.syncType?.toString());
44
57
  // Extract LID-PN mappings for all sync types
45
58
  for (const m of item.phoneNumberToLidMappings || []) {
@@ -14,29 +14,35 @@ import { aesDecryptGCM, aesEncryptGCM, hkdf } from './crypto.js';
14
14
  import { generateMessageIDV2 } from './generics.js';
15
15
  const getTmpFilesDirectory = () => tmpdir();
16
16
  let imageProcessingLibrary;
17
+ let _imageLibInitPromise = null; // Vanz@Fix (bug 22): deduplicate parallel init calls
17
18
  export const getImageProcessingLibrary = async () => {
18
19
  if (imageProcessingLibrary) {
19
20
  return imageProcessingLibrary;
20
21
  }
21
- //@ts-ignore
22
- const [sharp, image, jimp] = await Promise.all([
23
- import('sharp').catch(() => { }),
24
- import('@napi-rs/image').catch(() => { }),
25
- import('jimp').catch(() => { })
26
- ]);
27
- if (sharp) {
28
- imageProcessingLibrary = { sharp };
29
- }
30
- else if (image) {
31
- imageProcessingLibrary = { image };
32
- }
33
- else if (jimp) {
34
- imageProcessingLibrary = { jimp };
35
- }
36
- else {
37
- throw new Boom('No image processing library available');
22
+ if (!_imageLibInitPromise) {
23
+ _imageLibInitPromise = (async () => {
24
+ //@ts-ignore
25
+ const [sharp, image, jimp] = await Promise.all([
26
+ import('sharp').catch(() => { }),
27
+ import('@napi-rs/image').catch(() => { }),
28
+ import('jimp').catch(() => { })
29
+ ]);
30
+ if (sharp) {
31
+ imageProcessingLibrary = { sharp };
32
+ }
33
+ else if (image) {
34
+ imageProcessingLibrary = { image };
35
+ }
36
+ else if (jimp) {
37
+ imageProcessingLibrary = { jimp };
38
+ }
39
+ else {
40
+ throw new Boom('No image processing library available');
41
+ }
42
+ return imageProcessingLibrary;
43
+ })();
38
44
  }
39
- return imageProcessingLibrary;
45
+ return _imageLibInitPromise;
40
46
  };
41
47
  export const hkdfInfoKey = (type) => {
42
48
  const hkdfInfo = MEDIA_HKDF_KEY_MAPPING[type];
@@ -347,7 +353,9 @@ export const getHttpStream = async (url, options = {}) => {
347
353
  const response = await fetch(url.toString(), {
348
354
  dispatcher: options.dispatcher,
349
355
  method: 'GET',
350
- headers: options.headers
356
+ headers: options.headers,
357
+ // Vanz@Fix (bug 26/39): no timeout = potential infinite hang on slow CDN
358
+ signal: options.timeoutMs ? AbortSignal.timeout(options.timeoutMs) : AbortSignal.timeout(60000)
351
359
  });
352
360
  if (!response.ok) {
353
361
  throw new Boom(`Failed to fetch stream from ${url}`, { statusCode: response.status, data: { url } });
@@ -471,7 +479,20 @@ export const downloadContentFromMessage = async ({ mediaKey, directPath, url },
471
479
  throw new Boom('No valid media URL or directPath present in message', { statusCode: 400 });
472
480
  }
473
481
  const keys = await getMediaKeys(mediaKey, type);
474
- return downloadEncryptedContent(downloadUrl, keys, opts);
482
+ // Vanz@Fix (bug 24): was single-attempt only β€” now retries up to 3x with delay
483
+ const MAX_RETRIES = 3;
484
+ let lastErr;
485
+ for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
486
+ try {
487
+ return await downloadEncryptedContent(downloadUrl, keys, opts);
488
+ } catch (err) {
489
+ lastErr = err;
490
+ if (attempt < MAX_RETRIES - 1) {
491
+ await new Promise(r => setTimeout(r, 500 * (attempt + 1)));
492
+ }
493
+ }
494
+ }
495
+ throw lastErr;
475
496
  };
476
497
  /**
477
498
  * Decrypts and downloads an AES256-CBC encrypted file given the keys.
@@ -708,7 +729,8 @@ export const getWAUploadToServer = ({ customUploadHosts, fetchAgent, logger, opt
708
729
  'Content-Type': 'application/octet-stream',
709
730
  Origin: DEFAULT_ORIGIN
710
731
  };
711
- for (const { hostname } of hosts) {
732
+ for (let hostIdx = 0; hostIdx < hosts.length; hostIdx++) {
733
+ const { hostname } = hosts[hostIdx];
712
734
  logger.debug(`uploading to "${hostname}"`);
713
735
  const auth = encodeURIComponent(uploadInfo.auth);
714
736
  // Lia@Changes 06-02-26 --- Switch media path map for newsletter uploads
@@ -743,8 +765,12 @@ export const getWAUploadToServer = ({ customUploadHosts, fetchAgent, logger, opt
743
765
  }
744
766
  }
745
767
  catch (error) {
746
- const isLast = hostname === hosts[uploadInfo.hosts.length - 1]?.hostname;
768
+ const isLast = hostIdx === hosts.length - 1;
747
769
  logger.warn({ trace: error?.stack, uploadResult: result }, `Error in uploading to ${hostname} ${isLast ? '' : ', retrying...'}`);
770
+ // Vanz@Fix (bug 23): add exponential backoff delay between hosts instead of immediate retry
771
+ if (!isLast) {
772
+ await new Promise(r => setTimeout(r, 300 * (hostIdx + 1)));
773
+ }
748
774
  }
749
775
  }
750
776
  if (!urls) {
@@ -269,7 +269,7 @@ const prepareProductMessage = async (message, options) => {
269
269
  */
270
270
  // Lia@Changes 21-04-26 --- Enhanced prepareStickerPackMessage
271
271
  const prepareStickerPackMessage = async (message, options) => {
272
- const { cover, stickers = [], name = 'πŸ“¦ Sticker Pack', publisher = 'GitHub: itsliaaa', description = '🏷️ itsliaaa/baileys' } = message;
272
+ const { cover, stickers = [], name = 'πŸ“¦ Sticker Pack', publisher = 'GitHub: vanzxy', description = '🏷️ vanzxy/baileys' } = message;
273
273
  if (stickers.length > 60) {
274
274
  throw new Boom('Sticker pack exceeds the maximum limit of 60 stickers', { statusCode: 400 });
275
275
  }
@@ -469,7 +469,7 @@ const prepareNativeFlowButtons = (message) => {
469
469
  Object.assign(messageParamsJson, {
470
470
  limited_time_offer: {
471
471
  text: message.offerText || LIBRARY_NAME,
472
- url: message.offerUrl || DONATE_URL, // Lia@Note 02-02-26 --- Apologies if this feels cheeky, just a fallback
472
+ url: message.offerUrl || DONATE_URL || undefined, // Vanz@Fix (bug 10): DONATE_URL now empty β€” don't inject fallback link
473
473
  copy_code: message.offerCode,
474
474
  expiration_time: message.offerExpiration
475
475
  }
@@ -795,7 +795,16 @@ export const generateForwardMessageContent = (message, forceForward) => {
795
795
  content = normalizeMessageContent(content);
796
796
  content = proto.Message.decode(proto.Message.encode(content).finish());
797
797
  let key = Object.keys(content)[0];
798
- let score = content?.[key]?.contextInfo?.forwardingScore || 0;
798
+ // Vanz@Fix (bug 27): viewOnce/ephemeral messages wrap content in an outer key.
799
+ // Must unwrap first before reading forwardingScore, otherwise score stays 0.
800
+ let innerContent = content?.[key];
801
+ if (key === 'viewOnceMessage' || key === 'ephemeralMessage') {
802
+ const innerKey = innerContent?.message ? Object.keys(innerContent.message)[0] : null;
803
+ if (innerKey) {
804
+ innerContent = innerContent.message[innerKey];
805
+ }
806
+ }
807
+ let score = innerContent?.contextInfo?.forwardingScore || 0;
799
808
  score += message.key.fromMe && !forceForward ? 0 : 1;
800
809
  if (key === 'conversation') {
801
810
  content.extendedTextMessage = { text: content[key] };
@@ -944,7 +953,7 @@ export const generateWAMessageContent = async (message, options) => {
944
953
  else if (hasNonNullishProperty(message, 'groupInvite')) {
945
954
  m.groupInviteMessage = {};
946
955
  m.groupInviteMessage.inviteCode = message.groupInvite.inviteCode;
947
- m.groupInviteMessage.inviteExpiration = message.groupInvite.inviteExpiration;
956
+ m.groupInviteMessage.inviteExpiration = message.groupInvite.inviteExpiration ?? 0; // Vanz@Fix: default 0 (no expiry) instead of undefined
948
957
  m.groupInviteMessage.caption = message.groupInvite.text;
949
958
  m.groupInviteMessage.groupJid = message.groupInvite.jid;
950
959
  m.groupInviteMessage.groupName = message.groupInvite.subject;
@@ -956,7 +965,24 @@ export const generateWAMessageContent = async (message, options) => {
956
965
  const resp = await fetch(pfpUrl, { method: 'GET', dispatcher: options?.options?.dispatcher });
957
966
  if (resp.ok) {
958
967
  const buf = Buffer.from(await resp.arrayBuffer());
959
- m.groupInviteMessage.jpegThumbnail = buf;
968
+ // Vanz@Fix: upload thumbnail to WA server so thumbnailDirectPath/mediaKey are set properly
969
+ if (options.upload) {
970
+ try {
971
+ const uploaded = await prepareWAMessageMedia({ image: buf }, { upload: options.upload });
972
+ if (uploaded.imageMessage) {
973
+ m.groupInviteMessage.jpegThumbnail = buf;
974
+ m.groupInviteMessage.thumbnailDirectPath = uploaded.imageMessage.directPath;
975
+ m.groupInviteMessage.thumbnailSha256 = uploaded.imageMessage.fileSha256;
976
+ m.groupInviteMessage.thumbnailEncSha256 = uploaded.imageMessage.fileEncSha256;
977
+ m.groupInviteMessage.mediaKey = uploaded.imageMessage.mediaKey;
978
+ }
979
+ } catch {
980
+ // fallback: at least attach raw jpeg
981
+ m.groupInviteMessage.jpegThumbnail = buf;
982
+ }
983
+ } else {
984
+ m.groupInviteMessage.jpegThumbnail = buf;
985
+ }
960
986
  }
961
987
  }
962
988
  }
@@ -1425,6 +1451,11 @@ export const generateWAMessageContent = async (message, options) => {
1425
1451
  else if (hasOptionalProperty(card, 'footer')) {
1426
1452
  carouselCard.footer = { text: card.footer };
1427
1453
  }
1454
+ // Vanz@Fix (bug 20): add per-card contextInfo support
1455
+ // Allows each carousel card to have its own reply context, forward info, etc.
1456
+ if (hasNonNullishProperty(card, 'contextInfo')) {
1457
+ carouselCard.contextInfo = card.contextInfo;
1458
+ }
1428
1459
  return carouselCard;
1429
1460
  })),
1430
1461
  carouselCardType: CarouselCardType.UNKNOWN,
@@ -1498,7 +1529,7 @@ export const generateWAMessageContent = async (message, options) => {
1498
1529
  throw new Boom('Thumbnail must in buffer type', { statusCode: 400 });
1499
1530
  }
1500
1531
  if (!content.url || typeof content.url !== 'string') {
1501
- content.url = DONATE_URL; // Lia@Note 02-02-26 --- Apologies if this feels cheeky, just a fallback
1532
+ content.url = DONATE_URL || ''; // Vanz@Fix (bug 10): DONATE_URL now empty β€” use empty string as safe fallback
1502
1533
  }
1503
1534
  const externalAdReply = {
1504
1535
  ...content,
@@ -691,56 +691,65 @@ const processMessage = async (message, { shouldProcessHistoryMsg, placeholderRes
691
691
  const method = message.messageStubParameters?.[2];
692
692
  emitGroupRequestJoin(participant, action, method);
693
693
  break;
694
+ // Vanz@Fix (bug 44): handle previously unhandled stub types
695
+ case WAMessageStubType.GROUP_CHANGE_ICON:
696
+ emitGroupUpdate({ icon: message.messageStubParameters?.[0] });
697
+ break;
698
+ case WAMessageStubType.GROUP_CHANGE_NO_FREQUENTLY_FORWARDED:
699
+ emitGroupUpdate({ noFrequentlyForwarded: message.messageStubParameters?.[0] === 'on' });
700
+ break;
701
+ case WAMessageStubType.GROUP_V4_ADD_INVITE_SENT:
702
+ // notification that a v4 invite was sent β€” no group metadata change, but emit for plugin awareness
703
+ ev.emit('groups.update', [{ id: jid, v4AddInviteSent: true }]);
704
+ break;
705
+ case WAMessageStubType.GROUP_CHANGE_RECENT_HISTORY_SHARING:
706
+ emitGroupUpdate({ recentHistorySharing: message.messageStubParameters?.[0] === 'on' });
707
+ break;
708
+ // Vanz@Fix (bug 45): handle GROUP_CREATE β€” bot entered a new group via link
709
+ case WAMessageStubType.GROUP_CREATE:
710
+ ev.emit('groups.upsert', [{ id: jid, subject: message.messageStubParameters?.[0] || '' }]);
711
+ break;
694
712
  }
695
- } /* else if(content?.pollUpdateMessage) {
696
- const creationMsgKey = content.pollUpdateMessage.pollCreationMessageKey!
697
- // we need to fetch the poll creation message to get the poll enc key
698
- // TODO: make standalone, remove getMessage reference
699
- // TODO: Remove entirely
700
- const pollMsg = await getMessage(creationMsgKey)
701
- if(pollMsg) {
702
- const meIdNormalised = jidNormalizedUser(meId)
703
- const pollCreatorJid = getKeyAuthor(creationMsgKey, meIdNormalised)
704
- const voterJid = getKeyAuthor(message.key, meIdNormalised)
705
- const pollEncKey = pollMsg.messageContextInfo?.messageSecret!
706
-
707
- try {
708
- const voteMsg = decryptPollVote(
709
- content.pollUpdateMessage.vote!,
710
- {
713
+ // Vanz@Fix (bug 47): poll update handler was entirely commented out.
714
+ // Re-enabled: decrypts poll votes and emits messages.update with pollUpdates.
715
+ } else if (content?.pollUpdateMessage) {
716
+ const creationMsgKey = content.pollUpdateMessage.pollCreationMessageKey;
717
+ const pollMsg = await getMessage(creationMsgKey);
718
+ if (pollMsg) {
719
+ const meIdNormalised = jidNormalizedUser(meId);
720
+ const pollCreatorJid = getKeyAuthor(creationMsgKey, meIdNormalised);
721
+ const voterJid = getKeyAuthor(message.key, meIdNormalised);
722
+ const pollEncKey = pollMsg.messageContextInfo?.messageSecret;
723
+ if (pollEncKey) {
724
+ try {
725
+ const voteMsg = decryptPollVote(content.pollUpdateMessage.vote, {
711
726
  pollEncKey,
712
727
  pollCreatorJid,
713
- pollMsgId: creationMsgKey.id!,
728
+ pollMsgId: creationMsgKey.id,
714
729
  voterJid,
715
- }
716
- )
717
- ev.emit('messages.update', [
718
- {
730
+ });
731
+ ev.emit('messages.update', [{
719
732
  key: creationMsgKey,
720
733
  update: {
721
- pollUpdates: [
722
- {
723
- pollUpdateMessageKey: message.key,
724
- vote: voteMsg,
725
- senderTimestampMs: (content.pollUpdateMessage.senderTimestampMs! as Long).toNumber(),
726
- }
727
- ]
734
+ pollUpdates: [{
735
+ pollUpdateMessageKey: message.key,
736
+ vote: voteMsg,
737
+ senderTimestampMs: typeof content.pollUpdateMessage.senderTimestampMs === 'object'
738
+ ? content.pollUpdateMessage.senderTimestampMs.toNumber()
739
+ : Number(content.pollUpdateMessage.senderTimestampMs),
740
+ }]
728
741
  }
729
- }
730
- ])
731
- } catch(err) {
732
- logger?.warn(
733
- { err, creationMsgKey },
734
- 'failed to decrypt poll vote'
735
- )
742
+ }]);
743
+ } catch (err) {
744
+ logger?.warn({ err, creationMsgKey }, 'failed to decrypt poll vote');
745
+ }
746
+ } else {
747
+ logger?.warn({ creationMsgKey }, 'poll message has no messageSecret β€” cannot decrypt vote');
736
748
  }
737
749
  } else {
738
- logger?.warn(
739
- { creationMsgKey },
740
- 'poll creation message not found, cannot decrypt update'
741
- )
750
+ logger?.warn({ creationMsgKey }, 'poll creation message not found, cannot decrypt update');
742
751
  }
743
- } */
752
+ }
744
753
  if (Object.keys(chat).length > 1) {
745
754
  ev.emit('chats.update', [chat]);
746
755
  }
@@ -62,11 +62,46 @@ export const toUnified = (submessages, uuid) => ({
62
62
  }
63
63
  };
64
64
  case RichSubMessageType.CONTENT_ITEMS:
65
- return {};
65
+ // Vanz@Fix (bug 40): was returning {} β€” now renders content items carousel
66
+ const contentItemsMeta = submessage.contentItemsMetadata;
67
+ return {
68
+ view_model: {
69
+ primitive: {
70
+ items: contentItemsMeta?.itemsMetadata || [],
71
+ content_type: contentItemsMeta?.contentType ?? 1,
72
+ __typename: 'GenAIContentItemsUXPrimitive'
73
+ },
74
+ __typename: 'GenAISingleLayoutViewModel'
75
+ }
76
+ };
66
77
  case RichSubMessageType.INLINE_IMAGE:
67
- return {};
78
+ // Vanz@Fix (bug 40): was returning {} β€” now renders inline image
79
+ const imgMeta = submessage.imageMetadata;
80
+ return {
81
+ view_model: {
82
+ primitive: {
83
+ image_url: imgMeta?.imageUrl || '',
84
+ image_text: imgMeta?.imageText || '',
85
+ alignment: imgMeta?.alignment || 'center',
86
+ tap_link_url: imgMeta?.tapLinkUrl || '',
87
+ __typename: 'GenAIInlineImageUXPrimitive'
88
+ },
89
+ __typename: 'GenAISingleLayoutViewModel'
90
+ }
91
+ };
68
92
  case RichSubMessageType.LATEX:
69
- return {};
93
+ // Vanz@Fix (bug 40): was returning {} β€” now renders LaTeX expressions
94
+ const latexMeta = submessage.latexMetadata;
95
+ return {
96
+ view_model: {
97
+ primitive: {
98
+ text: latexMeta?.text || '',
99
+ expressions: latexMeta?.expressions || [],
100
+ __typename: 'GenAILatexUXPrimitive'
101
+ },
102
+ __typename: 'GenAISingleLayoutViewModel'
103
+ }
104
+ };
70
105
  case RichSubMessageType.TABLE:
71
106
  const tableMetadata = submessage.tableMetadata;
72
107
  return {
@@ -44,6 +44,28 @@ export async function useSqliteAuthState(opts) {
44
44
  db.pragma('journal_mode = WAL');
45
45
  db.pragma('synchronous = NORMAL');
46
46
  db.exec(CREATE_SCHEMA_SQL);
47
+ // Vanz@Fix (bug 8): periodic WAL checkpoint + cleanup old signal keys to prevent DB bloat.
48
+ // Runs every 30 minutes. WAL checkpoint reclaims WAL file space; key cleanup removes stale session entries.
49
+ const _walCleanupInterval = setInterval(() => {
50
+ try {
51
+ db.pragma('wal_checkpoint(PASSIVE)');
52
+ // Remove signal keys older than 30 days (pre-keys, session keys that have expired)
53
+ // Only removes types that are safe to prune (not creds or app-state-sync-key)
54
+ const pruneTypes = ['pre-key', 'session'];
55
+ for (const type of pruneTypes) {
56
+ try {
57
+ // Keep last 500 per type to avoid breaking active sessions
58
+ db.prepare(`
59
+ DELETE FROM signal_keys WHERE type = ? AND id NOT IN (
60
+ SELECT id FROM signal_keys WHERE type = ? ORDER BY rowid DESC LIMIT 500
61
+ )
62
+ `).run(type, type);
63
+ } catch { /* ignore per-type errors */ }
64
+ }
65
+ } catch { /* ignore checkpoint errors β€” non-fatal */ }
66
+ }, 30 * 60 * 1000);
67
+ // Allow Node process to exit even if interval is still running
68
+ if (_walCleanupInterval.unref) _walCleanupInterval.unref();
47
69
  const stmts = {
48
70
  credsSelect: db.prepare('SELECT value FROM creds WHERE key = ?'),
49
71
  credsUpsert: db.prepare('INSERT INTO creds (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value'),
@@ -57,8 +57,8 @@ export const generateLoginNode = (userJid, config) => {
57
57
  pull: true,
58
58
  username: +user,
59
59
  device: device,
60
- // TODO: investigate (hard set as false atm)
61
- lidDbMigrated: false
60
+ // Vanz@Fix (bug 49): was hardcoded false β€” now reads from config.lidDbMigrated (default false until investigated)
61
+ lidDbMigrated: config.lidDbMigrated ?? false
62
62
  };
63
63
  return proto.ClientPayload.fromObject(payload);
64
64
  };
@@ -11,10 +11,19 @@ export class USyncDeviceProtocol {
11
11
  }
12
12
  };
13
13
  }
14
- getUserElement( /* user: USyncUser */) {
15
- //TODO: Implement device phashing, ts and expectedTs
16
- //TODO: if all are not present, return null <- current behavior
17
- //TODO: otherwise return a node w tag 'devices' w those as attrs
14
+ getUserElement(user) {
15
+ // Vanz@Fix (bug 51): Implement device phashing for sync
16
+ // Include phash (participant hash) if devices exist to enable proper sync
17
+ if (user?.devices?.deviceList && user.devices.deviceList.length > 0) {
18
+ return {
19
+ tag: 'devices',
20
+ attrs: {
21
+ phash: user.devices.phash || '',
22
+ ts: user.devices.keyIndex?.timestamp?.toString() || '',
23
+ expectedTs: user.devices.keyIndex?.expectedTimestamp?.toString() || ''
24
+ }
25
+ };
26
+ }
18
27
  return null;
19
28
  }
20
29
  parser(node) {
@@ -3,4 +3,6 @@ export * from './USyncContactProtocol.js';
3
3
  export * from './USyncStatusProtocol.js';
4
4
  export * from './USyncDisappearingModeProtocol.js';
5
5
  export * from './USyncUsernameProtocol.js';
6
+ export * from './UsyncBotProfileProtocol.js'; // Vanz@Fix: was missing
7
+ export * from './UsyncLIDProtocol.js'; // Vanz@Fix: was missing
6
8
  //# sourceMappingURL=index.js.map
@@ -62,8 +62,32 @@ export class USyncQuery {
62
62
  return acc;
63
63
  }, []);
64
64
  }
65
- //TODO: implement side list
66
- //const sideListNode = getBinaryNodeChild(usyncNode, 'side_list')
65
+ // Vanz@Fix (bug 52): implement sideList for side band sync
66
+ // sideList contains additional sync data that should be processed separately
67
+ const sideListNode = usyncNode ? getBinaryNodeChild(usyncNode, 'side_list') : undefined;
68
+ if (sideListNode?.content && Array.isArray(sideListNode.content)) {
69
+ queryResult.sideList = sideListNode.content.reduce((acc, node) => {
70
+ const id = node?.attrs.jid;
71
+ if (id) {
72
+ const data = Array.isArray(node?.content)
73
+ ? Object.fromEntries(node.content
74
+ .map(content => {
75
+ const protocol = content.tag;
76
+ const parser = protocolMap[protocol];
77
+ if (parser) {
78
+ return [protocol, parser(content)];
79
+ }
80
+ else {
81
+ return [protocol, null];
82
+ }
83
+ })
84
+ .filter(([, b]) => b !== null))
85
+ : {};
86
+ acc.push({ ...data, id });
87
+ }
88
+ return acc;
89
+ }, []);
90
+ }
67
91
  return queryResult;
68
92
  }
69
93
  withDeviceProtocol() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vanzxy/baileys",
3
- "version": "1.2.6",
3
+ "version": "1.2.7",
4
4
  "description": "Enhanced Baileys fork by Vanzxy β€” based on @itsliaaa/baileys + @whiskeysockets/baileys with fixes for audio group status and clean media without newsletter button.",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
@@ -39,7 +39,6 @@
39
39
  "@adiwajshing/keyed-db": "^0.2.4",
40
40
  "@cacheable/node-cache": "^1.4.0",
41
41
  "@hapi/boom": "^9.1.3",
42
- "@vanzxy/baileys": "^1.1.2",
43
42
  "async-mutex": "^0.5.0",
44
43
  "fflate": "^0.8.2",
45
44
  "libsignal": "^6.0.0",