@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 +155 -41
- package/lib/Defaults/index.js +10 -3
- package/lib/Socket/dugong.js +20 -14
- package/lib/Socket/groups.js +22 -15
- package/lib/Socket/messages-recv.js +28 -18
- package/lib/Socket/messages-send.js +18 -4
- package/lib/Socket/socket.js +36 -2
- package/lib/Store/make-in-memory-store.js +10 -2
- package/lib/Utils/crypto.js +31 -1
- package/lib/Utils/decode-wa-message.js +3 -1
- package/lib/Utils/event-buffer.js +17 -3
- package/lib/Utils/generics.js +1 -1
- package/lib/Utils/history.js +13 -0
- package/lib/Utils/messages-media.js +48 -22
- package/lib/Utils/messages.js +37 -6
- package/lib/Utils/process-message.js +49 -40
- package/lib/Utils/rich-message-utils.js +38 -3
- package/lib/Utils/use-sqlite-auth-state.js +22 -0
- package/lib/Utils/validate-connection.js +2 -2
- package/lib/WAUSync/Protocols/USyncDeviceProtocol.js +13 -4
- package/lib/WAUSync/Protocols/index.js +2 -0
- package/lib/WAUSync/USyncQuery.js +26 -2
- package/package.json +1 -2
package/README.md
CHANGED
|
@@ -1,61 +1,175 @@
|
|
|
1
|
-
#
|
|
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
|
+

|
|
7
|
+

|
|
8
|
+

|
|
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
|
-
|
|
100
|
+
```js
|
|
101
|
+
message.isSystemNotification
|
|
102
|
+
```
|
|
4
103
|
|
|
5
|
-
|
|
104
|
+
Useful for filtering:
|
|
6
105
|
|
|
7
|
-
|
|
106
|
+
- E2E notices
|
|
107
|
+
- Meta service notices
|
|
108
|
+
- Other system-generated messages
|
|
8
109
|
|
|
9
|
-
|
|
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
|
-
|
|
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
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
- New chat message cap
|
|
30
|
-
- `validateSession` checks
|
|
31
|
-
- `fetchNewChatMessageCap`, `fetchAccountReachoutTimelock`
|
|
114
|
+
```bash
|
|
115
|
+
npm install @vanzxy/baileys
|
|
116
|
+
```
|
|
32
117
|
|
|
33
|
-
|
|
118
|
+
GitHub:
|
|
34
119
|
|
|
35
120
|
```bash
|
|
36
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
53
|
-
- [`@whiskeysockets/baileys`](https://github.com/WhiskeySockets/Baileys)
|
|
168
|
+
# β οΈ Disclaimer
|
|
54
169
|
|
|
55
|
-
|
|
170
|
+
This project is an independent fork.
|
|
171
|
+
Use responsibly and follow WhatsApp Terms of Service.
|
|
56
172
|
|
|
57
|
-
|
|
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
|
-
|
|
175
|
+
Made with π by Vanzxy
|
package/lib/Defaults/index.js
CHANGED
|
@@ -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 = '
|
|
12
|
-
export const LIBRARY_NAME = '
|
|
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
|
package/lib/Socket/dugong.js
CHANGED
|
@@ -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
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
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
|
}
|
package/lib/Socket/groups.js
CHANGED
|
@@ -139,21 +139,28 @@ export const makeGroupsSocket = (config) => {
|
|
|
139
139
|
});
|
|
140
140
|
},
|
|
141
141
|
groupParticipantsUpdate: async (jid, participants, action) => {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
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
|
-
//
|
|
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:
|
|
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 =
|
|
853
|
+
result.participant = pictureNode?.attrs.author;
|
|
851
854
|
result.key = {
|
|
852
855
|
...(result.key || {}),
|
|
853
|
-
participant:
|
|
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
|
-
//
|
|
1497
|
-
//
|
|
1498
|
-
//
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
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
|
package/lib/Socket/socket.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
72
|
-
//
|
|
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();
|
package/lib/Utils/crypto.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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; //
|
|
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
|
}
|
package/lib/Utils/generics.js
CHANGED
|
@@ -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/
|
|
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,
|
package/lib/Utils/history.js
CHANGED
|
@@ -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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
|
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
|
-
|
|
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 (
|
|
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 =
|
|
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) {
|
package/lib/Utils/messages.js
CHANGED
|
@@ -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:
|
|
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, //
|
|
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
|
-
|
|
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
|
-
|
|
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; //
|
|
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
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
const
|
|
703
|
-
const
|
|
704
|
-
const
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
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
|
-
|
|
724
|
-
|
|
725
|
-
|
|
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
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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(
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
|
|
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
|
-
//
|
|
66
|
-
//
|
|
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.
|
|
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",
|