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