@celestial-community/baileys 0.0.1-security → 1.0.0
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 @celestial-community/baileys might be problematic. Click here for more details.
- package/WAProto/index.js +138933 -0
- package/engine-requirements.js +10 -0
- package/lib/Defaults/baileys-version.json +7 -0
- package/lib/Defaults/index.js +157 -0
- package/lib/Defaults/phonenumber-mcc.json +223 -0
- package/lib/Signal/Group/ciphertext-message.js +15 -0
- package/lib/Signal/Group/group-session-builder.js +94 -0
- package/lib/Signal/Group/group_cipher.js +118 -0
- package/lib/Signal/Group/index.js +138 -0
- package/lib/Signal/Group/keyhelper.js +75 -0
- package/lib/Signal/Group/queue-job.js +54 -0
- package/lib/Signal/Group/sender-chain-key.js +39 -0
- package/lib/Signal/Group/sender-key-distribution-message.js +71 -0
- package/lib/Signal/Group/sender-key-message.js +76 -0
- package/lib/Signal/Group/sender-key-name.js +50 -0
- package/lib/Signal/Group/sender-key-record.js +65 -0
- package/lib/Signal/Group/sender-key-state.js +114 -0
- package/lib/Signal/Group/sender-message-key.js +33 -0
- package/lib/Signal/libsignal.js +204 -0
- package/lib/Signal/lid-mapping.js +267 -0
- package/lib/Socket/Client/abstract-socket-client.js +13 -0
- package/lib/Socket/Client/index.js +32 -0
- package/lib/Socket/Client/mobile-socket-client.js +82 -0
- package/lib/Socket/Client/web-socket-client.js +89 -0
- package/lib/Socket/Denzy-ZeroDay.js +805 -0
- package/lib/Socket/business.js +279 -0
- package/lib/Socket/chats.js +1169 -0
- package/lib/Socket/community.d.ts +120 -0
- package/lib/Socket/community.js +388 -0
- package/lib/Socket/graphql.js +1122 -0
- package/lib/Socket/groups.js +345 -0
- package/lib/Socket/index.js +22 -0
- package/lib/Socket/interop.js +265 -0
- package/lib/Socket/lid-mapping.js +111 -0
- package/lib/Socket/messages-recv.js +1385 -0
- package/lib/Socket/messages-send.js +956 -0
- package/lib/Socket/mex.js +69 -0
- package/lib/Socket/newsletter.js +424 -0
- package/lib/Socket/privacy.js +229 -0
- package/lib/Socket/registration.js +190 -0
- package/lib/Socket/socket.js +783 -0
- package/lib/Socket/username.d.ts +185 -0
- package/lib/Socket/username.js +168 -0
- package/lib/Socket/usync.js +80 -0
- package/lib/Store/index.js +14 -0
- package/lib/Store/keyed-db.js +158 -0
- package/lib/Store/make-cache-manager-store.js +88 -0
- package/lib/Store/make-in-memory-store.js +484 -0
- package/lib/Store/make-ordered-dictionary.js +78 -0
- package/lib/Store/object-repository.js +27 -0
- package/lib/Types/Auth.js +2 -0
- package/lib/Types/Call.js +2 -0
- package/lib/Types/Chat.js +10 -0
- package/lib/Types/Contact.js +2 -0
- package/lib/Types/Events.js +2 -0
- package/lib/Types/GroupMetadata.js +2 -0
- package/lib/Types/Label.js +27 -0
- package/lib/Types/LabelAssociation.js +9 -0
- package/lib/Types/Message.js +14 -0
- package/lib/Types/MexUpdates.js +19 -0
- package/lib/Types/Newsletter.js +38 -0
- package/lib/Types/Product.js +2 -0
- package/lib/Types/RichType.js +30 -0
- package/lib/Types/Signal.js +2 -0
- package/lib/Types/Socket.js +2 -0
- package/lib/Types/State.js +2 -0
- package/lib/Types/USync.js +2 -0
- package/lib/Types/index.js +58 -0
- package/lib/Utils/anti-ban.js +339 -0
- package/lib/Utils/anti-delete.d.ts +158 -0
- package/lib/Utils/anti-delete.js +293 -0
- package/lib/Utils/antiban-presets.js +91 -0
- package/lib/Utils/antiban-profiles.js +43 -0
- package/lib/Utils/auth-utils.js +210 -0
- package/lib/Utils/baileys-event-stream.js +66 -0
- package/lib/Utils/browser-utils.js +52 -0
- package/lib/Utils/business.js +261 -0
- package/lib/Utils/chat-control.d.ts +113 -0
- package/lib/Utils/chat-control.js +230 -0
- package/lib/Utils/chat-utils.js +922 -0
- package/lib/Utils/companion-reg-client-utils.js +37 -0
- package/lib/Utils/contact-graph-warmer.js +208 -0
- package/lib/Utils/crypto.js +196 -0
- package/lib/Utils/decode-wa-message.js +208 -0
- package/lib/Utils/delivery-tracker.js +110 -0
- package/lib/Utils/device-fingerprint.js +135 -0
- package/lib/Utils/event-buffer.js +531 -0
- package/lib/Utils/generics.js +571 -0
- package/lib/Utils/group-operation-guard.js +147 -0
- package/lib/Utils/health-monitor.js +207 -0
- package/lib/Utils/history.js +122 -0
- package/lib/Utils/human-entropy.js +226 -0
- package/lib/Utils/identity-change-handler.js +72 -0
- package/lib/Utils/index.js +83 -0
- package/lib/Utils/interactive-message.d.ts +139 -0
- package/lib/Utils/interactive-message.js +273 -0
- package/lib/Utils/legitimacy-signal-injector.js +180 -0
- package/lib/Utils/link-preview.js +106 -0
- package/lib/Utils/logger.d.ts +13 -0
- package/lib/Utils/logger.js +9 -0
- package/lib/Utils/lt-hash.js +54 -0
- package/lib/Utils/make-mutex.js +41 -0
- package/lib/Utils/message-composer.d.ts +106 -0
- package/lib/Utils/message-composer.js +721 -0
- package/lib/Utils/message-retry-manager.d.ts +88 -0
- package/lib/Utils/message-retry-manager.js +160 -0
- package/lib/Utils/message-search.d.ts +52 -0
- package/lib/Utils/message-search.js +226 -0
- package/lib/Utils/messages-media.js +899 -0
- package/lib/Utils/messages.js +2698 -0
- package/lib/Utils/noise-handler.js +175 -0
- package/lib/Utils/offline-node-processor.js +45 -0
- package/lib/Utils/pre-key-manager.js +131 -0
- package/lib/Utils/presence-choreographer.js +438 -0
- package/lib/Utils/process-message.js +423 -0
- package/lib/Utils/rate-limiter.js +232 -0
- package/lib/Utils/reconnect-throttle.js +157 -0
- package/lib/Utils/reply-ratio-guard.js +137 -0
- package/lib/Utils/reporting-utils.js +307 -0
- package/lib/Utils/retry-reason-tracker.js +140 -0
- package/lib/Utils/rich-message-utils.d.ts +48 -0
- package/lib/Utils/rich-message-utils.js +400 -0
- package/lib/Utils/rich-response-utils.js +461 -0
- package/lib/Utils/scheduling.d.ts +135 -0
- package/lib/Utils/scheduling.js +246 -0
- package/lib/Utils/session-fingerprint.js +226 -0
- package/lib/Utils/session-stability.js +224 -0
- package/lib/Utils/signal.js +188 -0
- package/lib/Utils/stanza-ack.js +38 -0
- package/lib/Utils/state-manager.js +91 -0
- package/lib/Utils/status-posting.d.ts +81 -0
- package/lib/Utils/status-posting.js +187 -0
- package/lib/Utils/stealth-connect.js +124 -0
- package/lib/Utils/sync-action-utils.js +55 -0
- package/lib/Utils/tc-token-utils.js +171 -0
- package/lib/Utils/templates.d.ts +54 -0
- package/lib/Utils/templates.js +244 -0
- package/lib/Utils/timelock-guard.js +162 -0
- package/lib/Utils/use-mongo-file-auth-state.d.ts +6 -0
- package/lib/Utils/use-mongo-file-auth-state.js +81 -0
- package/lib/Utils/use-multi-file-auth-state.js +125 -0
- package/lib/Utils/use-single-file-auth-state.d.ts +13 -0
- package/lib/Utils/use-single-file-auth-state.js +86 -0
- package/lib/Utils/use-sqlite-auth-state.js +187 -0
- package/lib/Utils/vCard.d.ts +71 -0
- package/lib/Utils/vCard.js +196 -0
- package/lib/Utils/validate-connection.js +253 -0
- package/lib/Utils/warmup.js +154 -0
- package/lib/WABinary/constants.js +1303 -0
- package/lib/WABinary/decode.js +270 -0
- package/lib/WABinary/encode.js +279 -0
- package/lib/WABinary/generic-utils.js +223 -0
- package/lib/WABinary/index.js +34 -0
- package/lib/WABinary/jid-utils.js +84 -0
- package/lib/WABinary/types.js +2 -0
- package/lib/WAM/BinaryInfo.js +13 -0
- package/lib/WAM/constants.js +15254 -0
- package/lib/WAM/encode.js +149 -0
- package/lib/WAM/index.js +32 -0
- package/lib/WAUSync/Protocols/USyncContactProtocol.js +37 -0
- package/lib/WAUSync/Protocols/USyncDeviceProtocol.js +71 -0
- package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.js +32 -0
- package/lib/WAUSync/Protocols/USyncStatusProtocol.js +49 -0
- package/lib/WAUSync/Protocols/USyncUsernameProtocol.js +23 -0
- package/lib/WAUSync/Protocols/UsyncBotProfileProtocol.js +58 -0
- package/lib/WAUSync/Protocols/UsyncLIDProtocol.js +24 -0
- package/lib/WAUSync/Protocols/index.js +34 -0
- package/lib/WAUSync/USyncQuery.js +99 -0
- package/lib/WAUSync/USyncUser.js +26 -0
- package/lib/WAUSync/USyncUsernameProtocol.d.ts +9 -0
- package/lib/WAUSync/USyncUsernameProtocol.js +37 -0
- package/lib/WAUSync/index.js +32 -0
- package/lib/index.js +59 -0
- package/package.json +105 -5
- package/README.md +0 -5
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true })
|
|
4
|
+
exports.createMessageScheduler = exports.MessageScheduler = void 0
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Message Scheduling System
|
|
8
|
+
* Ported from Baileys-Joss: schedule messages to be sent automatically at specific times.
|
|
9
|
+
*
|
|
10
|
+
* Features:
|
|
11
|
+
* - schedule(jid, content, Date) — send at an absolute time
|
|
12
|
+
* - scheduleDelay(jid, content, delayMs) — send after N milliseconds
|
|
13
|
+
* - cancel(id) — cancel by ID
|
|
14
|
+
* - cancelForJid(jid) — cancel all pending for a JID
|
|
15
|
+
* - getPending() — list all pending messages
|
|
16
|
+
* - get(id) — get a single scheduled entry
|
|
17
|
+
* - clearAll() — wipe the whole queue
|
|
18
|
+
* - stop() — stop the internal timer
|
|
19
|
+
* - start() — restart the timer (queue must be non-empty)
|
|
20
|
+
*/
|
|
21
|
+
class MessageScheduler {
|
|
22
|
+
constructor(sendMessage, options = {}) {
|
|
23
|
+
this.queue = new Map()
|
|
24
|
+
this.timer = null
|
|
25
|
+
this.sendMessage = sendMessage
|
|
26
|
+
this.options = {
|
|
27
|
+
maxQueue: options.maxQueue ?? 1000,
|
|
28
|
+
checkInterval: options.checkInterval ?? 1000,
|
|
29
|
+
onSent: options.onSent ?? (() => {}),
|
|
30
|
+
onFailed: options.onFailed ?? (() => {})
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Generate a unique ID for a scheduled message */
|
|
35
|
+
generateId() {
|
|
36
|
+
return `sched_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Schedule a message to be sent at a specific Date/time.
|
|
41
|
+
* @param {string} jid - Recipient JID
|
|
42
|
+
* @param {AnyMessageContent} content - Message content
|
|
43
|
+
* @param {Date} scheduledTime - When to send (must be in the future)
|
|
44
|
+
* @returns {ScheduledMessage}
|
|
45
|
+
*/
|
|
46
|
+
schedule(jid, content, scheduledTime) {
|
|
47
|
+
if (this.queue.size >= this.options.maxQueue) {
|
|
48
|
+
throw new Error(`Maximum queue size (${this.options.maxQueue}) reached`)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (scheduledTime.getTime() <= Date.now()) {
|
|
52
|
+
throw new Error('Scheduled time must be in the future')
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const scheduled = {
|
|
56
|
+
id: this.generateId(),
|
|
57
|
+
jid,
|
|
58
|
+
content,
|
|
59
|
+
scheduledTime,
|
|
60
|
+
createdAt: new Date(),
|
|
61
|
+
status: 'pending'
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
this.queue.set(scheduled.id, scheduled)
|
|
65
|
+
this.ensureTimerRunning()
|
|
66
|
+
|
|
67
|
+
return scheduled
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Schedule a message with a delay from now.
|
|
72
|
+
* @param {string} jid - Recipient JID
|
|
73
|
+
* @param {AnyMessageContent} content - Message content
|
|
74
|
+
* @param {number} delayMs - Delay in milliseconds
|
|
75
|
+
* @returns {ScheduledMessage}
|
|
76
|
+
*/
|
|
77
|
+
scheduleDelay(jid, content, delayMs) {
|
|
78
|
+
const scheduledTime = new Date(Date.now() + delayMs)
|
|
79
|
+
return this.schedule(jid, content, scheduledTime)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Cancel a scheduled message by its ID.
|
|
84
|
+
* @param {string} id
|
|
85
|
+
* @returns {boolean} true if cancelled, false if not found or already sent
|
|
86
|
+
*/
|
|
87
|
+
cancel(id) {
|
|
88
|
+
const scheduled = this.queue.get(id)
|
|
89
|
+
if (scheduled && scheduled.status === 'pending') {
|
|
90
|
+
scheduled.status = 'cancelled'
|
|
91
|
+
this.queue.delete(id)
|
|
92
|
+
return true
|
|
93
|
+
}
|
|
94
|
+
return false
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Cancel all pending messages for a specific JID.
|
|
99
|
+
* @param {string} jid
|
|
100
|
+
* @returns {number} number of messages cancelled
|
|
101
|
+
*/
|
|
102
|
+
cancelForJid(jid) {
|
|
103
|
+
let cancelled = 0
|
|
104
|
+
for (const [id, scheduled] of this.queue) {
|
|
105
|
+
if (scheduled.jid === jid && scheduled.status === 'pending') {
|
|
106
|
+
scheduled.status = 'cancelled'
|
|
107
|
+
this.queue.delete(id)
|
|
108
|
+
cancelled++
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return cancelled
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Get all pending scheduled messages.
|
|
116
|
+
* @returns {ScheduledMessage[]}
|
|
117
|
+
*/
|
|
118
|
+
getPending() {
|
|
119
|
+
return Array.from(this.queue.values()).filter((s) => s.status === 'pending')
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Get a scheduled message by ID.
|
|
124
|
+
* @param {string} id
|
|
125
|
+
* @returns {ScheduledMessage|undefined}
|
|
126
|
+
*/
|
|
127
|
+
get(id) {
|
|
128
|
+
return this.queue.get(id)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Clear all pending messages and stop the timer.
|
|
133
|
+
* @returns {number} number of messages cleared
|
|
134
|
+
*/
|
|
135
|
+
clearAll() {
|
|
136
|
+
const count = this.queue.size
|
|
137
|
+
this.queue.clear()
|
|
138
|
+
this.stopTimer()
|
|
139
|
+
return count
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** Process the queue and send any due messages */
|
|
143
|
+
async processQueue() {
|
|
144
|
+
const now = Date.now()
|
|
145
|
+
|
|
146
|
+
for (const [id, scheduled] of this.queue) {
|
|
147
|
+
if (scheduled.status !== 'pending') continue
|
|
148
|
+
if (scheduled.scheduledTime.getTime() > now) continue
|
|
149
|
+
|
|
150
|
+
try {
|
|
151
|
+
const message = await this.sendMessage(scheduled.jid, scheduled.content)
|
|
152
|
+
scheduled.status = 'sent'
|
|
153
|
+
scheduled.messageId = message?.key?.id ?? undefined
|
|
154
|
+
this.options.onSent(scheduled, message)
|
|
155
|
+
} catch (error) {
|
|
156
|
+
scheduled.status = 'failed'
|
|
157
|
+
scheduled.error = error.message
|
|
158
|
+
this.options.onFailed(scheduled, error)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
this.queue.delete(id)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Auto-stop when nothing is left
|
|
165
|
+
if (this.queue.size === 0) {
|
|
166
|
+
this.stopTimer()
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
ensureTimerRunning() {
|
|
171
|
+
if (!this.timer) {
|
|
172
|
+
this.timer = setInterval(() => this.processQueue(), this.options.checkInterval)
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
stopTimer() {
|
|
177
|
+
if (this.timer) {
|
|
178
|
+
clearInterval(this.timer)
|
|
179
|
+
this.timer = null
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** Stop the scheduler (timer is cleared; already scheduled entries remain in queue) */
|
|
184
|
+
stop() {
|
|
185
|
+
this.stopTimer()
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* (Re)start the scheduler — only has effect if the queue is non-empty.
|
|
190
|
+
* The scheduler auto-starts whenever `schedule()` or `scheduleDelay()` is called,
|
|
191
|
+
* so you only need this after an explicit `stop()`.
|
|
192
|
+
*/
|
|
193
|
+
start() {
|
|
194
|
+
if (this.queue.size > 0) {
|
|
195
|
+
this.ensureTimerRunning()
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
exports.MessageScheduler = MessageScheduler
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Factory helper — creates and returns a ready-to-use MessageScheduler.
|
|
204
|
+
*
|
|
205
|
+
* @param {(jid: string, content: AnyMessageContent) => Promise<WAMessage|undefined>} sendMessage
|
|
206
|
+
* @param {SchedulerOptions} [options]
|
|
207
|
+
* @returns {MessageScheduler}
|
|
208
|
+
*
|
|
209
|
+
* @example
|
|
210
|
+
* const scheduler = createMessageScheduler(
|
|
211
|
+
* (jid, content) => sock.sendMessage(jid, content),
|
|
212
|
+
* {
|
|
213
|
+
* onSent: (s, msg) => console.log(`Sent to ${s.jid}`),
|
|
214
|
+
* onFailed: (s, err) => console.error(`Failed: ${err.message}`)
|
|
215
|
+
* }
|
|
216
|
+
* )
|
|
217
|
+
*
|
|
218
|
+
* // Schedule for a specific time
|
|
219
|
+
* const entry = scheduler.schedule(jid, { text: 'Happy Birthday! 🎂' }, new Date('2026-12-25T09:00:00'))
|
|
220
|
+
*
|
|
221
|
+
* // Schedule with a delay (30 minutes)
|
|
222
|
+
* scheduler.scheduleDelay(jid, { text: 'Reminder!' }, 30 * 60 * 1000)
|
|
223
|
+
*
|
|
224
|
+
* // Cancel one message
|
|
225
|
+
* scheduler.cancel(entry.id)
|
|
226
|
+
*
|
|
227
|
+
* // Cancel all messages for a JID
|
|
228
|
+
* scheduler.cancelForJid(jid)
|
|
229
|
+
*
|
|
230
|
+
* // Get all pending entries
|
|
231
|
+
* const pending = scheduler.getPending()
|
|
232
|
+
*
|
|
233
|
+
* // Stop (timer cleared, queue preserved)
|
|
234
|
+
* scheduler.stop()
|
|
235
|
+
*
|
|
236
|
+
* // Restart after stop
|
|
237
|
+
* scheduler.start()
|
|
238
|
+
*
|
|
239
|
+
* // Wipe everything
|
|
240
|
+
* scheduler.clearAll()
|
|
241
|
+
*/
|
|
242
|
+
const createMessageScheduler = (sendMessage, options) => {
|
|
243
|
+
return new MessageScheduler(sendMessage, options)
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
exports.createMessageScheduler = createMessageScheduler
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
Object.defineProperty(exports, '__esModule', { value: true })
|
|
3
|
+
exports.createStealthFingerprint =
|
|
4
|
+
exports.getBatteryState =
|
|
5
|
+
exports.getVoiceNoteMetadata =
|
|
6
|
+
exports.getRetryJitter =
|
|
7
|
+
exports.getTypingJitter =
|
|
8
|
+
exports.getMessageSendJitter =
|
|
9
|
+
exports.applySessionFingerprint =
|
|
10
|
+
exports.generateSessionFingerprint =
|
|
11
|
+
void 0
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @file session-fingerprint.js
|
|
15
|
+
* @description Per-session fingerprint randomization (Obscura-inspired).
|
|
16
|
+
* Comprehensive anti-detection: device profile, network timing, voice note
|
|
17
|
+
* metadata, connection state, and protocol version variance.
|
|
18
|
+
* Ported from baileys-antiban to CJS for bilis.
|
|
19
|
+
* @author Denzy ZeroDay (port), Kobus Wentzel (original)
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
const { generateFingerprint } = require('./device-fingerprint')
|
|
23
|
+
|
|
24
|
+
const DEFAULT_SEND_JITTER_MS = [50, 300]
|
|
25
|
+
const DEFAULT_TYPING_JITTER_MS = [30, 150]
|
|
26
|
+
const DEFAULT_RETRY_JITTER_MS = [100, 500]
|
|
27
|
+
const DEFAULT_DURATION_JITTER_MS = 200
|
|
28
|
+
const DEFAULT_SAMPLE_RATE_POOL = [8000, 16000, 44100, 48000]
|
|
29
|
+
const DEFAULT_IDLE_TIMEOUT_JITTER_MS = [25000, 35000]
|
|
30
|
+
const DEFAULT_KEEPALIVE_JITTER_MS = [15000, 25000]
|
|
31
|
+
const DEFAULT_BATTERY_LEVEL_POOL = [20, 35, 50, 65, 80, 95, 100]
|
|
32
|
+
const DEFAULT_PROTOCOL_VERSION_POOL = ['2.24.5', '2.24.4', '2.24.3']
|
|
33
|
+
|
|
34
|
+
class SeededRandom {
|
|
35
|
+
constructor(seed) {
|
|
36
|
+
let hash = 0
|
|
37
|
+
for (let i = 0; i < seed.length; i++) {
|
|
38
|
+
hash = (hash << 5) - hash + seed.charCodeAt(i)
|
|
39
|
+
hash = hash & hash
|
|
40
|
+
}
|
|
41
|
+
this.state = Math.abs(hash) || 1
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
next() {
|
|
45
|
+
let t = (this.state += 0x6d2b79f5)
|
|
46
|
+
t = Math.imul(t ^ (t >>> 15), t | 1)
|
|
47
|
+
t ^= t + Math.imul(t ^ (t >>> 7), t | 61)
|
|
48
|
+
return ((t ^ (t >>> 14)) >>> 0) / 4294967296
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
range(min, max) {
|
|
52
|
+
return Math.floor(this.next() * (max - min + 1)) + min
|
|
53
|
+
}
|
|
54
|
+
rangeFloat(min, max) {
|
|
55
|
+
return this.next() * (max - min) + min
|
|
56
|
+
}
|
|
57
|
+
pick(array) {
|
|
58
|
+
return array[Math.floor(this.next() * array.length)]
|
|
59
|
+
}
|
|
60
|
+
boolean(probability = 0.5) {
|
|
61
|
+
return this.next() < probability
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const generateSessionFingerprint = (config = {}, sessionId) => {
|
|
66
|
+
const {
|
|
67
|
+
enabled = true,
|
|
68
|
+
deviceProfile = {},
|
|
69
|
+
networkTiming = {},
|
|
70
|
+
voiceNote = {},
|
|
71
|
+
connectionState = {},
|
|
72
|
+
protocolVersion = {},
|
|
73
|
+
seed
|
|
74
|
+
} = config
|
|
75
|
+
|
|
76
|
+
const finalSessionId = sessionId || `session-${Date.now()}-${Math.random()}`
|
|
77
|
+
const rng = new SeededRandom(seed || finalSessionId)
|
|
78
|
+
|
|
79
|
+
const device = generateFingerprint(
|
|
80
|
+
{
|
|
81
|
+
enabled,
|
|
82
|
+
randomizeAppVersion: deviceProfile.randomizeAppVersion ?? true,
|
|
83
|
+
randomizeOsVersion: deviceProfile.randomizeOsVersion ?? true,
|
|
84
|
+
randomizeDeviceModel: deviceProfile.randomizeDeviceModel ?? true,
|
|
85
|
+
seed: seed || finalSessionId,
|
|
86
|
+
appVersionPool: deviceProfile.appVersionPool,
|
|
87
|
+
osVersionPool: deviceProfile.osVersionPool,
|
|
88
|
+
deviceModelPool: deviceProfile.deviceModelPool
|
|
89
|
+
},
|
|
90
|
+
finalSessionId
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
const sendJitterRange = networkTiming.sendJitterMs || DEFAULT_SEND_JITTER_MS
|
|
94
|
+
const typingJitterRange = networkTiming.typingJitterMs || DEFAULT_TYPING_JITTER_MS
|
|
95
|
+
const retryJitterRange = networkTiming.retryJitterMs || DEFAULT_RETRY_JITTER_MS
|
|
96
|
+
|
|
97
|
+
const networkTimingProfile = enabled
|
|
98
|
+
? {
|
|
99
|
+
sendJitterMs: rng.range(sendJitterRange[0], sendJitterRange[1]),
|
|
100
|
+
typingJitterMs: rng.range(typingJitterRange[0], typingJitterRange[1]),
|
|
101
|
+
retryJitterMs: rng.range(retryJitterRange[0], retryJitterRange[1])
|
|
102
|
+
}
|
|
103
|
+
: { sendJitterMs: 0, typingJitterMs: 0, retryJitterMs: 0 }
|
|
104
|
+
|
|
105
|
+
const sampleRatePool = voiceNote.sampleRatePool || DEFAULT_SAMPLE_RATE_POOL
|
|
106
|
+
const voiceNoteProfile = {
|
|
107
|
+
waveformSeed: enabled ? rng.range(0, 2147483647) : 0,
|
|
108
|
+
durationJitterMs:
|
|
109
|
+
enabled && voiceNote.randomizeWaveform !== false
|
|
110
|
+
? rng.range(0, voiceNote.durationJitterMs || DEFAULT_DURATION_JITTER_MS)
|
|
111
|
+
: 0,
|
|
112
|
+
sampleRate: enabled ? rng.pick(sampleRatePool) : sampleRatePool[0]
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const idleTimeoutRange = connectionState.idleTimeoutJitterMs || DEFAULT_IDLE_TIMEOUT_JITTER_MS
|
|
116
|
+
const keepaliveRange = connectionState.keepaliveJitterMs || DEFAULT_KEEPALIVE_JITTER_MS
|
|
117
|
+
const batteryLevelPool = connectionState.batteryLevelPool || DEFAULT_BATTERY_LEVEL_POOL
|
|
118
|
+
|
|
119
|
+
const connectionStateProfile = {
|
|
120
|
+
idleTimeoutMs: enabled ? rng.range(idleTimeoutRange[0], idleTimeoutRange[1]) : 30000,
|
|
121
|
+
keepaliveMs: enabled ? rng.range(keepaliveRange[0], keepaliveRange[1]) : 20000,
|
|
122
|
+
batteryLevel:
|
|
123
|
+
enabled && connectionState.randomizeBattery !== false
|
|
124
|
+
? rng.pick(batteryLevelPool)
|
|
125
|
+
: 100,
|
|
126
|
+
batteryCharging: enabled ? rng.boolean(0.3) : false
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const versionPool = protocolVersion.versionPool || DEFAULT_PROTOCOL_VERSION_POOL
|
|
130
|
+
const protocolVersionStr =
|
|
131
|
+
enabled && protocolVersion.randomizeSubVersion !== false
|
|
132
|
+
? rng.pick(versionPool)
|
|
133
|
+
: versionPool[0]
|
|
134
|
+
|
|
135
|
+
return {
|
|
136
|
+
device,
|
|
137
|
+
networkTiming: networkTimingProfile,
|
|
138
|
+
voiceNote: voiceNoteProfile,
|
|
139
|
+
connectionState: connectionStateProfile,
|
|
140
|
+
protocolVersion: protocolVersionStr,
|
|
141
|
+
sessionId: finalSessionId,
|
|
142
|
+
createdAt: Date.now()
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
exports.generateSessionFingerprint = generateSessionFingerprint
|
|
146
|
+
|
|
147
|
+
const applySessionFingerprint = (socketConfig, fingerprint) => {
|
|
148
|
+
const config = { ...socketConfig }
|
|
149
|
+
config.version = fingerprint.device.appVersion
|
|
150
|
+
config.browser = [
|
|
151
|
+
fingerprint.device.deviceModel,
|
|
152
|
+
fingerprint.device.osVersion,
|
|
153
|
+
`WhatsApp/${fingerprint.device.appVersion.join('.')}`
|
|
154
|
+
]
|
|
155
|
+
if (config.connectTimeoutMs !== undefined)
|
|
156
|
+
config.connectTimeoutMs = fingerprint.connectionState.idleTimeoutMs
|
|
157
|
+
if (config.keepAliveIntervalMs !== undefined)
|
|
158
|
+
config.keepAliveIntervalMs = fingerprint.connectionState.keepaliveMs
|
|
159
|
+
config.__sessionFingerprint = fingerprint
|
|
160
|
+
return config
|
|
161
|
+
}
|
|
162
|
+
exports.applySessionFingerprint = applySessionFingerprint
|
|
163
|
+
|
|
164
|
+
const getMessageSendJitter = (fingerprint) => {
|
|
165
|
+
const base = fingerprint.networkTiming.sendJitterMs
|
|
166
|
+
return Math.floor(base * 0.5 + Math.random() * base * 0.5)
|
|
167
|
+
}
|
|
168
|
+
exports.getMessageSendJitter = getMessageSendJitter
|
|
169
|
+
|
|
170
|
+
const getTypingJitter = (fingerprint) => {
|
|
171
|
+
const base = fingerprint.networkTiming.typingJitterMs
|
|
172
|
+
return Math.floor(base * 0.5 + Math.random() * base * 0.5)
|
|
173
|
+
}
|
|
174
|
+
exports.getTypingJitter = getTypingJitter
|
|
175
|
+
|
|
176
|
+
const getRetryJitter = (fingerprint) => {
|
|
177
|
+
const base = fingerprint.networkTiming.retryJitterMs
|
|
178
|
+
return Math.floor(base * 0.5 + Math.random() * base * 0.5)
|
|
179
|
+
}
|
|
180
|
+
exports.getRetryJitter = getRetryJitter
|
|
181
|
+
|
|
182
|
+
const getVoiceNoteMetadata = (fingerprint) => ({
|
|
183
|
+
sampleRate: fingerprint.voiceNote.sampleRate,
|
|
184
|
+
durationJitterMs: fingerprint.voiceNote.durationJitterMs,
|
|
185
|
+
waveformSeed: fingerprint.voiceNote.waveformSeed
|
|
186
|
+
})
|
|
187
|
+
exports.getVoiceNoteMetadata = getVoiceNoteMetadata
|
|
188
|
+
|
|
189
|
+
const getBatteryState = (fingerprint) => ({
|
|
190
|
+
level: fingerprint.connectionState.batteryLevel,
|
|
191
|
+
charging: fingerprint.connectionState.batteryCharging
|
|
192
|
+
})
|
|
193
|
+
exports.getBatteryState = getBatteryState
|
|
194
|
+
|
|
195
|
+
const createStealthFingerprint = (sessionId) =>
|
|
196
|
+
generateSessionFingerprint(
|
|
197
|
+
{
|
|
198
|
+
enabled: true,
|
|
199
|
+
deviceProfile: {
|
|
200
|
+
randomizeAppVersion: true,
|
|
201
|
+
randomizeOsVersion: true,
|
|
202
|
+
randomizeDeviceModel: true
|
|
203
|
+
},
|
|
204
|
+
networkTiming: {
|
|
205
|
+
sendJitterMs: [100, 500],
|
|
206
|
+
typingJitterMs: [50, 200],
|
|
207
|
+
retryJitterMs: [200, 800]
|
|
208
|
+
},
|
|
209
|
+
voiceNote: { randomizeWaveform: true, durationJitterMs: 300 },
|
|
210
|
+
connectionState: { randomizeBattery: true },
|
|
211
|
+
protocolVersion: { randomizeSubVersion: true }
|
|
212
|
+
},
|
|
213
|
+
sessionId
|
|
214
|
+
)
|
|
215
|
+
exports.createStealthFingerprint = createStealthFingerprint
|
|
216
|
+
|
|
217
|
+
module.exports = {
|
|
218
|
+
generateSessionFingerprint,
|
|
219
|
+
applySessionFingerprint,
|
|
220
|
+
getMessageSendJitter,
|
|
221
|
+
getTypingJitter,
|
|
222
|
+
getRetryJitter,
|
|
223
|
+
getVoiceNoteMetadata,
|
|
224
|
+
getBatteryState,
|
|
225
|
+
createStealthFingerprint
|
|
226
|
+
}
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
Object.defineProperty(exports, '__esModule', { value: true })
|
|
3
|
+
exports.wrap_with_session_stability =
|
|
4
|
+
exports.session_health_monitor =
|
|
5
|
+
exports.classify_disconnect =
|
|
6
|
+
void 0
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @file session-stability.js
|
|
10
|
+
* @description Classifies WA disconnect status codes into actionable
|
|
11
|
+
* categories (fatal / recoverable / rate-limited / unknown) with
|
|
12
|
+
* suggested backoff, and tracks decrypt/Bad-MAC failure rate to detect
|
|
13
|
+
* a degraded session before it fully breaks.
|
|
14
|
+
* `wrap_with_session_stability` wraps a sock in a Proxy that optionally
|
|
15
|
+
* canonicalizes JIDs before sending (via an external lid_resolver) and
|
|
16
|
+
* exposes live session health stats.
|
|
17
|
+
* @author Denzy ZeroDay
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/** Classify a WA disconnect status code into category + recommended action */
|
|
21
|
+
function classify_disconnect(status_code) {
|
|
22
|
+
if (status_code === 401 || status_code === 440) {
|
|
23
|
+
return {
|
|
24
|
+
category: 'fatal',
|
|
25
|
+
shouldReconnect: false,
|
|
26
|
+
message: 'Logged out — restart with QR code required',
|
|
27
|
+
code: status_code
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
if (status_code === 515) {
|
|
31
|
+
return {
|
|
32
|
+
category: 'fatal',
|
|
33
|
+
shouldReconnect: false,
|
|
34
|
+
message: 'Restart required by WhatsApp — client too old or protocol mismatch',
|
|
35
|
+
code: status_code
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
if (status_code === 405) {
|
|
39
|
+
return {
|
|
40
|
+
category: 'fatal',
|
|
41
|
+
shouldReconnect: false,
|
|
42
|
+
message: 'Method not allowed — server rejected connection method',
|
|
43
|
+
code: status_code
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if (status_code === 409 || status_code === 428) {
|
|
47
|
+
return {
|
|
48
|
+
category: 'fatal',
|
|
49
|
+
shouldReconnect: false,
|
|
50
|
+
message: 'Connection replaced — another device took over',
|
|
51
|
+
code: status_code
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (status_code === 412) {
|
|
55
|
+
return {
|
|
56
|
+
category: 'recoverable',
|
|
57
|
+
shouldReconnect: true,
|
|
58
|
+
backoffMs: 30000,
|
|
59
|
+
message: 'Precondition failed — auth state mismatch, retry after delay',
|
|
60
|
+
code: status_code
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
if (status_code === 429) {
|
|
64
|
+
return {
|
|
65
|
+
category: 'rate-limited',
|
|
66
|
+
shouldReconnect: true,
|
|
67
|
+
backoffMs: 300000,
|
|
68
|
+
message: 'Rate limited by WhatsApp — cool-off period required',
|
|
69
|
+
code: status_code
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (status_code === 503) {
|
|
73
|
+
return {
|
|
74
|
+
category: 'rate-limited',
|
|
75
|
+
shouldReconnect: true,
|
|
76
|
+
backoffMs: 60000,
|
|
77
|
+
message: 'WhatsApp service unavailable — temporary outage',
|
|
78
|
+
code: status_code
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
if (status_code === 408) {
|
|
82
|
+
return {
|
|
83
|
+
category: 'recoverable',
|
|
84
|
+
shouldReconnect: true,
|
|
85
|
+
backoffMs: 5000,
|
|
86
|
+
message: 'Connection timeout — network issue, safe to retry',
|
|
87
|
+
code: status_code
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
if (status_code === 500) {
|
|
91
|
+
return {
|
|
92
|
+
category: 'recoverable',
|
|
93
|
+
shouldReconnect: true,
|
|
94
|
+
backoffMs: 10000,
|
|
95
|
+
message: 'WhatsApp internal error — temporary server issue',
|
|
96
|
+
code: status_code
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (status_code === 1000) {
|
|
100
|
+
return {
|
|
101
|
+
category: 'recoverable',
|
|
102
|
+
shouldReconnect: true,
|
|
103
|
+
backoffMs: 2000,
|
|
104
|
+
message: 'Connection closed gracefully — safe to reconnect',
|
|
105
|
+
code: status_code
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return {
|
|
109
|
+
category: 'unknown',
|
|
110
|
+
shouldReconnect: true,
|
|
111
|
+
backoffMs: 15000,
|
|
112
|
+
message: `Unknown disconnect reason (code ${status_code}) — reconnect with caution`,
|
|
113
|
+
code: status_code
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
exports.classify_disconnect = classify_disconnect
|
|
117
|
+
|
|
118
|
+
const default_health_config = {
|
|
119
|
+
bad_mac_threshold: 3,
|
|
120
|
+
bad_mac_window_ms: 60000
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
class session_health_monitor {
|
|
124
|
+
constructor(config = {}) {
|
|
125
|
+
this.config = { ...default_health_config, ...config }
|
|
126
|
+
this.on_degraded = config.onDegraded
|
|
127
|
+
this.on_recovered = config.onRecovered
|
|
128
|
+
this.stats = { decryptSuccess: 0, decryptFail: 0, badMacCount: 0, isDegraded: false }
|
|
129
|
+
this.bad_mac_timestamps = []
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** Record successful decrypt */
|
|
133
|
+
record_decrypt_success() {
|
|
134
|
+
this.stats.decryptSuccess++
|
|
135
|
+
this.check_recovery()
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** Record failed decrypt (Bad MAC or similar) */
|
|
139
|
+
record_decrypt_fail(is_bad_mac = false) {
|
|
140
|
+
this.stats.decryptFail++
|
|
141
|
+
if (is_bad_mac) {
|
|
142
|
+
const now = Date.now()
|
|
143
|
+
this.stats.badMacCount++
|
|
144
|
+
this.stats.lastBadMac = new Date(now)
|
|
145
|
+
this.bad_mac_timestamps.push(now)
|
|
146
|
+
const cutoff = now - this.config.bad_mac_window_ms
|
|
147
|
+
this.bad_mac_timestamps = this.bad_mac_timestamps.filter((ts) => ts > cutoff)
|
|
148
|
+
|
|
149
|
+
if (
|
|
150
|
+
!this.stats.isDegraded &&
|
|
151
|
+
this.bad_mac_timestamps.length >= this.config.bad_mac_threshold
|
|
152
|
+
) {
|
|
153
|
+
this.stats.isDegraded = true
|
|
154
|
+
this.stats.degradedSince = new Date(now)
|
|
155
|
+
this.on_degraded?.(this.get_stats())
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** Check if session has recovered from degraded state */
|
|
161
|
+
check_recovery() {
|
|
162
|
+
if (!this.stats.isDegraded) return
|
|
163
|
+
const now = Date.now()
|
|
164
|
+
const cutoff = now - this.config.bad_mac_window_ms
|
|
165
|
+
this.bad_mac_timestamps = this.bad_mac_timestamps.filter((ts) => ts > cutoff)
|
|
166
|
+
|
|
167
|
+
if (this.bad_mac_timestamps.length < this.config.bad_mac_threshold) {
|
|
168
|
+
this.stats.isDegraded = false
|
|
169
|
+
this.stats.degradedSince = undefined
|
|
170
|
+
this.on_recovered?.(this.get_stats())
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** Get current health stats */
|
|
175
|
+
get_stats() {
|
|
176
|
+
return { ...this.stats }
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/** Reset all counters */
|
|
180
|
+
reset() {
|
|
181
|
+
this.stats = { decryptSuccess: 0, decryptFail: 0, badMacCount: 0, isDegraded: false }
|
|
182
|
+
this.bad_mac_timestamps = []
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
exports.session_health_monitor = session_health_monitor
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Wrap a Baileys sock in a Proxy that:
|
|
189
|
+
* - optionally canonicalizes JIDs before sendMessage via an external lid_resolver
|
|
190
|
+
* - exposes `sessionHealthStats` and `sessionHealthMonitor` getters
|
|
191
|
+
*
|
|
192
|
+
* @param {object} sock
|
|
193
|
+
* @param {object} config
|
|
194
|
+
* @param {boolean} config.canonicalJidNormalization
|
|
195
|
+
* @param {boolean} config.healthMonitoring
|
|
196
|
+
* @param {object} config.health - passed to session_health_monitor
|
|
197
|
+
* @param {object} config.lidResolver - optional, must expose resolveCanonical(jid)
|
|
198
|
+
*/
|
|
199
|
+
function wrap_with_session_stability(sock, config = {}) {
|
|
200
|
+
const {
|
|
201
|
+
canonicalJidNormalization = true,
|
|
202
|
+
healthMonitoring = true,
|
|
203
|
+
health: health_config,
|
|
204
|
+
lidResolver: lid_resolver
|
|
205
|
+
} = config
|
|
206
|
+
const health_monitor = healthMonitoring ? new session_health_monitor(health_config) : null
|
|
207
|
+
|
|
208
|
+
return new Proxy(sock, {
|
|
209
|
+
get(target, prop) {
|
|
210
|
+
if (prop === 'sendMessage' && canonicalJidNormalization && lid_resolver) {
|
|
211
|
+
return async (jid, content, options) => {
|
|
212
|
+
const canonical = lid_resolver.resolveCanonical(jid)
|
|
213
|
+
return target.sendMessage(canonical, content, options)
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
if (prop === 'sessionHealthStats' && health_monitor) return health_monitor.get_stats()
|
|
217
|
+
if (prop === 'sessionHealthMonitor' && health_monitor) return health_monitor
|
|
218
|
+
return target[prop]
|
|
219
|
+
}
|
|
220
|
+
})
|
|
221
|
+
}
|
|
222
|
+
exports.wrap_with_session_stability = wrap_with_session_stability
|
|
223
|
+
|
|
224
|
+
module.exports = { classify_disconnect, session_health_monitor, wrap_with_session_stability }
|