@prototypevip/baileys 0.0.8 → 0.0.9

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.
Files changed (54) hide show
  1. package/lib/AntiBan/antiban.js +922 -0
  2. package/lib/AntiBan/banRecoveryOrchestrator.js +284 -0
  3. package/lib/AntiBan/cli.js +177 -0
  4. package/lib/AntiBan/contactGraph.js +236 -0
  5. package/lib/AntiBan/contentVariator.js +150 -0
  6. package/lib/AntiBan/credsSnapshot.js +121 -0
  7. package/lib/AntiBan/deliveryTracker.js +107 -0
  8. package/lib/AntiBan/deviceFingerprint.js +106 -0
  9. package/lib/AntiBan/fleetEventStore.js +157 -0
  10. package/lib/AntiBan/groupOperationGuard.js +116 -0
  11. package/lib/AntiBan/health.js +207 -0
  12. package/lib/AntiBan/humanEntropy.js +337 -0
  13. package/lib/AntiBan/index.js +77 -0
  14. package/lib/AntiBan/instanceCoordinator.js +175 -0
  15. package/lib/AntiBan/jidCanonicalizer.js +256 -0
  16. package/lib/AntiBan/jidCircuitBreaker.js +186 -0
  17. package/lib/AntiBan/legitimacySignalInjector.js +231 -0
  18. package/lib/AntiBan/lidFirstResolver.js +174 -0
  19. package/lib/AntiBan/lidResolver.js +324 -0
  20. package/lib/AntiBan/messageQueue.js +187 -0
  21. package/lib/AntiBan/messageRecovery.js +283 -0
  22. package/lib/AntiBan/messageTypeRegistry.js +369 -0
  23. package/lib/AntiBan/observability.js +145 -0
  24. package/lib/AntiBan/patch.js +302 -0
  25. package/lib/AntiBan/persist.js +87 -0
  26. package/lib/AntiBan/presenceChoreographer.js +430 -0
  27. package/lib/AntiBan/presets.js +105 -0
  28. package/lib/AntiBan/profiles.js +31 -0
  29. package/lib/AntiBan/proxyRotator.js +306 -0
  30. package/lib/AntiBan/rateLimiter.js +261 -0
  31. package/lib/AntiBan/readReceiptVariance.js +88 -0
  32. package/lib/AntiBan/reconnectThrottle.js +180 -0
  33. package/lib/AntiBan/replyRatio.js +161 -0
  34. package/lib/AntiBan/reputationVoucher.js +373 -0
  35. package/lib/AntiBan/retryReason.js +91 -0
  36. package/lib/AntiBan/retryTracker.js +172 -0
  37. package/lib/AntiBan/scheduler.js +111 -0
  38. package/lib/AntiBan/sessionFingerprint.js +248 -0
  39. package/lib/AntiBan/sessionStability.js +330 -0
  40. package/lib/AntiBan/stateAdapter.js +73 -0
  41. package/lib/AntiBan/stateExport.js +152 -0
  42. package/lib/AntiBan/stealthConnect.js +130 -0
  43. package/lib/AntiBan/timelockGuard.js +181 -0
  44. package/lib/AntiBan/topologyThrottler.js +339 -0
  45. package/lib/AntiBan/warmup.js +140 -0
  46. package/lib/AntiBan/webhooks.js +80 -0
  47. package/lib/AntiBan/wrapper.js +456 -0
  48. package/lib/Socket/groups.js +26 -21
  49. package/lib/Socket/index.js +139 -7
  50. package/lib/Socket/messages-send.js +68 -5
  51. package/lib/Utils/ai-rich-formatter.js +256 -0
  52. package/lib/Utils/index.js +1 -0
  53. package/lib/index.js +1 -1
  54. package/package.json +1 -1
@@ -0,0 +1,456 @@
1
+ /**
2
+ * Socket Wrapper — Drop-in replacement that wraps sendMessage with anti-ban protection
3
+ *
4
+ * Works with both baileys and @oxidezap/baileyrs transports.
5
+ *
6
+ * Usage with baileys:
7
+ * import makeWASocket from 'baileys';
8
+ * import { wrapSocket } from 'baileys-antiban';
9
+ *
10
+ * const sock = makeWASocket({ ... });
11
+ * const safeSock = wrapSocket(sock);
12
+ *
13
+ * Usage with baileyrs:
14
+ * import { makeWASocket } from '@oxidezap/baileyrs';
15
+ * import { wrapSocket } from 'baileys-antiban';
16
+ *
17
+ * const sock = makeWASocket({ ... });
18
+ * const safeSock = wrapSocket(sock);
19
+ *
20
+ * // Use safeSock.sendMessage() — automatically rate-limited and monitored
21
+ * await safeSock.sendMessage(jid, { text: 'Hello!' });
22
+ *
23
+ * // Check health anytime
24
+ * console.log(safeSock.antiban.getStats());
25
+ *
26
+ * Note: reachoutTimeLock timelock module silently noops on baileyrs until upstream
27
+ * emits reachoutTimeLock events — confirmed NOT present in baileyrs v0.0.8.
28
+ * Timelock guard will operate in detection-only mode (relies on 463 errors only).
29
+ */
30
+ import { AntiBan } from './antiban.js';
31
+ import { DeafSessionDetector } from './sessionStability.js';
32
+ import { GroupOperationGuard } from './groupOperationGuard.js';
33
+ import { LegitimacySignalInjector } from './legitimacySignalInjector.js';
34
+ /**
35
+ * Wrap a Baileys socket with anti-ban protection.
36
+ * The returned socket has the same API but sendMessage() is protected.
37
+ */
38
+ export function wrapSocket(sock, config, warmUpState, wrapOptions) {
39
+ const antiban = new AntiBan(config, warmUpState);
40
+ const options = {
41
+ autoRespondToIncoming: false,
42
+ ...wrapOptions,
43
+ };
44
+ // Deaf session detector — optional, enabled via wrapOptions.deafSession
45
+ const deafDetector = options.deafSession
46
+ ? new DeafSessionDetector(options.deafSession)
47
+ : null;
48
+ if (deafDetector)
49
+ deafDetector.attach(sock);
50
+ // Group operation guard — intercept group methods with rate limiting
51
+ if (options.groupOpGuard !== false) {
52
+ const guard = new GroupOperationGuard(options.groupOpGuard || {});
53
+ const origUpdate = sock.groupParticipantsUpdate?.bind(sock);
54
+ if (origUpdate) {
55
+ sock.groupParticipantsUpdate = async (jid, participants, action) => {
56
+ const op = action === 'add' ? 'add' : action === 'remove' ? 'remove' : 'add';
57
+ const check = guard.check(op, jid);
58
+ if (!check.allowed)
59
+ throw new Error(check.reason || 'Group operation rate limited');
60
+ return origUpdate(jid, participants, action);
61
+ };
62
+ }
63
+ const origCreate = sock.groupCreate?.bind(sock);
64
+ if (origCreate) {
65
+ sock.groupCreate = async (subject, participants) => {
66
+ const check = guard.check('create', subject);
67
+ if (!check.allowed)
68
+ throw new Error(check.reason || 'Group creation rate limited');
69
+ return origCreate(subject, participants);
70
+ };
71
+ }
72
+ }
73
+ // Legitimacy signal injector — wrap sendMessage for typo injection
74
+ let legitimacyInjector = null;
75
+ if (options.legitimacySignals !== false) {
76
+ legitimacyInjector = new LegitimacySignalInjector(typeof options.legitimacySignals === 'object' ? options.legitimacySignals : {});
77
+ }
78
+ // Hook into Baileys events for health monitoring
79
+ // Prefer ev.process() (Baileys ≥ late 2022) for batched event handling
80
+ // Fall back to ev.on() for older versions
81
+ if (typeof sock.ev.process === 'function') {
82
+ sock.ev.process(async (events) => {
83
+ // Handle connection updates
84
+ if (events['connection.update']) {
85
+ const update = events['connection.update'];
86
+ if (update.connection === 'close') {
87
+ const reason = update.lastDisconnect?.error?.output?.statusCode || 'unknown';
88
+ antiban.onDisconnect(reason);
89
+ antiban.destroy(); // Clean up all timers
90
+ deafDetector?.onDisconnect();
91
+ deafDetector?.destroy();
92
+ // Fleet event: detect ban-like disconnect codes
93
+ if (options.fleetEventStore) {
94
+ const banCodes = [401, 403, 428, 515]; // 401=logged out, 403=forbidden, 428=banned, 515=restart required
95
+ if (typeof reason === 'number' && banCodes.includes(reason)) {
96
+ void options.fleetEventStore.emit('ban', { statusCode: reason });
97
+ }
98
+ }
99
+ }
100
+ if (update.connection === 'open') {
101
+ antiban.onReconnect();
102
+ deafDetector?.onConnect();
103
+ // Fleet event: recovery from previous ban/warn
104
+ if (options.fleetEventStore) {
105
+ const health = antiban.getStats().health;
106
+ if (health.risk === 'low') {
107
+ void options.fleetEventStore.emit('recovery', { risk: health.risk });
108
+ }
109
+ }
110
+ }
111
+ // Reachout timelock detection
112
+ if (update.reachoutTimeLock) {
113
+ antiban.timelock.onTimelockUpdate({
114
+ isActive: update.reachoutTimeLock.isActive,
115
+ timeEnforcementEnds: update.reachoutTimeLock.timeEnforcementEnds,
116
+ enforcementType: update.reachoutTimeLock.enforcementType,
117
+ });
118
+ // Fleet event: timelock
119
+ if (options.fleetEventStore && update.reachoutTimeLock.isActive) {
120
+ void options.fleetEventStore.emit('timelock', {
121
+ enforcementType: update.reachoutTimeLock.enforcementType,
122
+ endsAt: update.reachoutTimeLock.timeEnforcementEnds,
123
+ });
124
+ }
125
+ }
126
+ }
127
+ // Catch 463 errors from message updates + track retries + learn LID mappings
128
+ if (events['messages.update']) {
129
+ const updates = events['messages.update'];
130
+ deafDetector?.onMessageActivity();
131
+ for (const update of updates) {
132
+ // 463 error detection
133
+ if (update?.update?.messageStubParameters) {
134
+ const params = Array.isArray(update.update.messageStubParameters)
135
+ ? update.update.messageStubParameters
136
+ : [];
137
+ if (params.includes(463) || params.includes('463')) {
138
+ antiban.timelock.record463Error();
139
+ // Fleet event: rate limit detected
140
+ if (options.fleetEventStore) {
141
+ void options.fleetEventStore.emit('rate_limit', { error: '463' });
142
+ }
143
+ }
144
+ }
145
+ // Retry tracking
146
+ antiban.retryTracker.onMessageUpdate(update);
147
+ // Delivery receipt tracking
148
+ const status = update?.update?.status;
149
+ const msgId = update?.key?.id;
150
+ // status 3 = DELIVERY_ACK (double tick), status 4 = READ
151
+ if (msgId && (status === 3 || status === 4)) {
152
+ antiban.onDeliveryReceipt(msgId);
153
+ }
154
+ }
155
+ // LID canonicalizer learning
156
+ antiban.jidCanonicalizer?.onMessageUpdate(updates);
157
+ }
158
+ // Register known chats from incoming messages + handle reply suggestions + learn LID mappings
159
+ if (events['messages.upsert']) {
160
+ const { messages } = events['messages.upsert'];
161
+ deafDetector?.onMessageActivity();
162
+ // Learn LID mappings FIRST (before any other processing)
163
+ antiban.jidCanonicalizer?.onIncomingEvent(events['messages.upsert']);
164
+ for (const msg of messages || []) {
165
+ const jid = msg.key?.remoteJid;
166
+ if (!jid)
167
+ continue;
168
+ // Register known chat
169
+ antiban.timelock.registerKnownChat(jid);
170
+ // Skip self messages
171
+ const isSelf = msg.key?.fromMe || false;
172
+ if (isSelf)
173
+ continue;
174
+ // Extract message text
175
+ const msgText = msg.message?.conversation ||
176
+ msg.message?.extendedTextMessage?.text ||
177
+ msg.message?.imageMessage?.caption ||
178
+ msg.message?.videoMessage?.caption ||
179
+ '';
180
+ // Handle incoming message (updates reply ratio + contact graph)
181
+ const replySuggestion = antiban.onIncomingMessage(jid, msgText);
182
+ // Auto-respond if enabled and suggested
183
+ if (options.autoRespondToIncoming && replySuggestion.shouldReply && replySuggestion.suggestedText) {
184
+ // Random delay 3-15s
185
+ const replyDelay = Math.floor(Math.random() * 12000) + 3000;
186
+ setTimeout(async () => {
187
+ try {
188
+ await sock.sendMessage(jid, { text: replySuggestion.suggestedText });
189
+ }
190
+ catch (error) {
191
+ // Silently fail — auto-reply is best-effort
192
+ }
193
+ }, replyDelay);
194
+ }
195
+ }
196
+ }
197
+ });
198
+ }
199
+ else {
200
+ // Fallback to ev.on() for older Baileys versions
201
+ sock.ev.on('connection.update', (update) => {
202
+ if (update.connection === 'close') {
203
+ const reason = update.lastDisconnect?.error?.output?.statusCode || 'unknown';
204
+ antiban.onDisconnect(reason);
205
+ antiban.destroy(); // Clean up all timers
206
+ deafDetector?.onDisconnect();
207
+ deafDetector?.destroy();
208
+ // Fleet event: detect ban-like disconnect codes
209
+ if (options.fleetEventStore) {
210
+ const banCodes = [401, 403, 428, 515]; // 401=logged out, 403=forbidden, 428=banned, 515=restart required
211
+ if (typeof reason === 'number' && banCodes.includes(reason)) {
212
+ void options.fleetEventStore.emit('ban', { statusCode: reason });
213
+ }
214
+ }
215
+ }
216
+ if (update.connection === 'open') {
217
+ antiban.onReconnect();
218
+ deafDetector?.onConnect();
219
+ // Fleet event: recovery from previous ban/warn
220
+ if (options.fleetEventStore) {
221
+ const health = antiban.getStats().health;
222
+ if (health.risk === 'low') {
223
+ void options.fleetEventStore.emit('recovery', { risk: health.risk });
224
+ }
225
+ }
226
+ }
227
+ // Reachout timelock detection
228
+ if (update.reachoutTimeLock) {
229
+ antiban.timelock.onTimelockUpdate({
230
+ isActive: update.reachoutTimeLock.isActive,
231
+ timeEnforcementEnds: update.reachoutTimeLock.timeEnforcementEnds,
232
+ enforcementType: update.reachoutTimeLock.enforcementType,
233
+ });
234
+ // Fleet event: timelock
235
+ if (options.fleetEventStore && update.reachoutTimeLock.isActive) {
236
+ void options.fleetEventStore.emit('timelock', {
237
+ enforcementType: update.reachoutTimeLock.enforcementType,
238
+ endsAt: update.reachoutTimeLock.timeEnforcementEnds,
239
+ });
240
+ }
241
+ }
242
+ });
243
+ // Catch 463 errors from message updates + track retries + learn LID mappings
244
+ sock.ev.on('messages.update', (updates) => {
245
+ deafDetector?.onMessageActivity();
246
+ for (const update of updates) {
247
+ // 463 error detection
248
+ if (update?.update?.messageStubParameters) {
249
+ const params = update.update.messageStubParameters;
250
+ if (params.includes(463) || params.includes('463')) {
251
+ antiban.timelock.record463Error();
252
+ // Fleet event: rate limit detected
253
+ if (options.fleetEventStore) {
254
+ void options.fleetEventStore.emit('rate_limit', { error: '463' });
255
+ }
256
+ }
257
+ }
258
+ // Retry tracking
259
+ antiban.retryTracker.onMessageUpdate(update);
260
+ // Delivery receipt tracking
261
+ const status = update?.update?.status;
262
+ const msgId = update?.key?.id;
263
+ // status 3 = DELIVERY_ACK (double tick), status 4 = READ
264
+ if (msgId && (status === 3 || status === 4)) {
265
+ antiban.onDeliveryReceipt(msgId);
266
+ }
267
+ }
268
+ // LID canonicalizer learning
269
+ antiban.jidCanonicalizer?.onMessageUpdate(updates);
270
+ });
271
+ // Register known chats from incoming messages + handle reply suggestions + learn LID mappings
272
+ sock.ev.on('messages.upsert', (upsert) => {
273
+ const { messages } = upsert;
274
+ deafDetector?.onMessageActivity();
275
+ // Learn LID mappings FIRST (before any other processing)
276
+ antiban.jidCanonicalizer?.onIncomingEvent(upsert);
277
+ for (const msg of messages || []) {
278
+ const jid = msg.key?.remoteJid;
279
+ if (!jid)
280
+ continue;
281
+ // Register known chat
282
+ antiban.timelock.registerKnownChat(jid);
283
+ // Skip self messages
284
+ const isSelf = msg.key?.fromMe || false;
285
+ if (isSelf)
286
+ continue;
287
+ // Extract message text
288
+ const msgText = msg.message?.conversation ||
289
+ msg.message?.extendedTextMessage?.text ||
290
+ msg.message?.imageMessage?.caption ||
291
+ msg.message?.videoMessage?.caption ||
292
+ '';
293
+ // Handle incoming message (updates reply ratio + contact graph)
294
+ const replySuggestion = antiban.onIncomingMessage(jid, msgText);
295
+ // Auto-respond if enabled and suggested
296
+ if (options.autoRespondToIncoming && replySuggestion.shouldReply && replySuggestion.suggestedText) {
297
+ // Random delay 3-15s
298
+ const replyDelay = Math.floor(Math.random() * 12000) + 3000;
299
+ setTimeout(async () => {
300
+ try {
301
+ await sock.sendMessage(jid, { text: replySuggestion.suggestedText });
302
+ }
303
+ catch (error) {
304
+ // Silently fail — auto-reply is best-effort
305
+ }
306
+ }, replyDelay);
307
+ }
308
+ }
309
+ });
310
+ }
311
+ // Create proxy that intercepts sendMessage
312
+ const originalSendMessage = sock.sendMessage.bind(sock);
313
+ // Serializes beforeSend→afterSend so rate limiter accounting is accurate
314
+ // under concurrent sends. Without this, all concurrent callers read the same
315
+ // committed state before any afterSend records, bypassing per-minute limits.
316
+ let sendLock = Promise.resolve();
317
+ const wrappedSendMessage = async (jid, content, options) => {
318
+ /**
319
+ * LID/PN Canonicalization — Normalize JID to canonical form
320
+ *
321
+ * This mitigates the LID/PN race that causes "Bad MAC / No Session / Invalid PreKey"
322
+ * errors (Baileys #1769, our PR #2372). When a message event arrives under one form
323
+ * (e.g. LID) but the crypto session was established under another (e.g. PN), decryption
324
+ * fails. By normalizing all outbound targets to a single form, we reduce this race.
325
+ *
326
+ * This is middleware-layer mitigation only. Root fix requires PR #2372 merged upstream.
327
+ */
328
+ const canonicalJid = antiban.jidCanonicalizer?.canonicalizeTarget(jid) || jid;
329
+ // Extract text content for rate limiter analysis
330
+ let text = content?.text || content?.caption || content?.image?.caption || '';
331
+ // Check for typo injection (only for text messages)
332
+ let typoResult = null;
333
+ if (legitimacyInjector && content?.text && typeof content.text === 'string' && content.text.length > 10) {
334
+ typoResult = legitimacyInjector.shouldInjectTypo(content.text);
335
+ }
336
+ // Chain this send onto the previous — each waits for the prior send's
337
+ // afterSend to commit before running its own beforeSend check.
338
+ const sendResult = sendLock.then(async () => {
339
+ // Circuit breaker check (before antiban.beforeSend)
340
+ if (options.circuitBreaker) {
341
+ if (!options.circuitBreaker.canSend(canonicalJid)) {
342
+ throw new Error('[baileys-antiban] circuit-breaker: send blocked for ' + canonicalJid);
343
+ }
344
+ }
345
+ const decision = await antiban.beforeSend(canonicalJid, text);
346
+ if (!decision.allowed) {
347
+ throw new Error(`[baileys-antiban] Message blocked: ${decision.reason}`);
348
+ }
349
+ // Apply delay from rate limiter
350
+ if (decision.delayMs > 0) {
351
+ await new Promise(resolve => setTimeout(resolve, decision.delayMs));
352
+ }
353
+ // Apply circuit breaker jitter for broadcast messages
354
+ if (options.circuitBreaker) {
355
+ const isBroadcast = canonicalJid.endsWith('@broadcast') || canonicalJid.endsWith('@newsletter');
356
+ const jitter = options.circuitBreaker.getJitter(isBroadcast);
357
+ if (jitter > 0) {
358
+ await new Promise(resolve => setTimeout(resolve, jitter));
359
+ }
360
+ }
361
+ // Send message (using canonical JID)
362
+ // If typo injection is active, send typo first, then correction
363
+ try {
364
+ let result;
365
+ if (typoResult) {
366
+ // Send typo version first
367
+ const typoContent = { ...content, text: typoResult.typoText };
368
+ await originalSendMessage(canonicalJid, typoContent, options);
369
+ // Wait for correction delay
370
+ await new Promise(r => setTimeout(r, typoResult.correctionDelay));
371
+ // Send corrected version
372
+ const correctionContent = { ...content, text: typoResult.correctionText };
373
+ result = await originalSendMessage(canonicalJid, correctionContent, options);
374
+ }
375
+ else {
376
+ // Normal send
377
+ result = await originalSendMessage(canonicalJid, content, options);
378
+ }
379
+ // Pass msgId to afterSend for delivery tracking
380
+ const msgId = result?.key?.id;
381
+ antiban.afterSend(canonicalJid, text, msgId);
382
+ antiban.timelock.registerKnownChat(canonicalJid);
383
+ // Clear retry tracking on successful send
384
+ if (msgId) {
385
+ antiban.retryTracker.clear(msgId);
386
+ }
387
+ // Circuit breaker success
388
+ if (options.circuitBreaker) {
389
+ options.circuitBreaker.recordSuccess(canonicalJid);
390
+ }
391
+ return result;
392
+ }
393
+ catch (error) {
394
+ // Circuit breaker failure
395
+ if (options.circuitBreaker) {
396
+ options.circuitBreaker.recordFailure(canonicalJid);
397
+ }
398
+ // Baileys PR #2587: partial-encrypt Boom now carries structured data:
399
+ // error.data.failed[] — per-recipient { jid, error } failures
400
+ // error.data.firstCause — most likely root cause string
401
+ // Extract for richer health-monitor diagnostics vs plain error.message.
402
+ const boomData = error?.data;
403
+ if (boomData?.failed?.length) {
404
+ const cause = boomData.firstCause ?? 'unknown';
405
+ const failedJids = boomData.failed.map((f) => f.jid).join(', ');
406
+ antiban.afterSendFailed(`encrypt-all-failed firstCause=${cause} jids=[${failedJids}]`);
407
+ }
408
+ else {
409
+ antiban.afterSendFailed(error instanceof Error ? error.message : String(error));
410
+ }
411
+ throw error;
412
+ }
413
+ });
414
+ // Advance the lock regardless of success/failure so the chain never stalls
415
+ sendLock = sendResult.then(() => { }, () => { });
416
+ return sendResult;
417
+ };
418
+ // Return enhanced socket
419
+ const wrapped = Object.create(sock);
420
+ wrapped.sendMessage = wrappedSendMessage;
421
+ wrapped.antiban = antiban;
422
+ // Expose destroy method directly so consumers can call it manually if needed
423
+ wrapped.antiban.destroy = antiban.destroy.bind(antiban);
424
+ return wrapped;
425
+ }
426
+ /**
427
+ * Helper function to create a wrapped socket with device fingerprint applied.
428
+ *
429
+ * This combines device fingerprint generation, socket creation, and wrapping
430
+ * into a single call.
431
+ *
432
+ * Usage:
433
+ * import makeWASocket from 'baileys';
434
+ * import { wrapSocketWithFingerprint } from 'baileys-antiban';
435
+ *
436
+ * const wrapped = wrapSocketWithFingerprint(makeWASocket, socketConfig, {
437
+ * fingerprintSeed: 'stable-seed-123',
438
+ * groupOpGuard: {},
439
+ * legitimacySignals: {}
440
+ * });
441
+ *
442
+ * @param makeWASocket - Baileys makeWASocket factory function
443
+ * @param socketConfig - Base socket configuration
444
+ * @param wrapOptions - Combined wrapper options + fingerprintSeed
445
+ */
446
+ export function wrapSocketWithFingerprint(makeWASocket, socketConfig, wrapOptions) {
447
+ // Import fingerprint functions dynamically to avoid circular deps
448
+ const { generateFingerprint, applyFingerprint } = require('./deviceFingerprint.js');
449
+ const seed = wrapOptions?.fingerprintSeed ||
450
+ socketConfig.auth?.creds?.me?.id ||
451
+ String(Date.now());
452
+ const fp = generateFingerprint({ seed });
453
+ const config = applyFingerprint(socketConfig, fp);
454
+ const sock = makeWASocket(config);
455
+ return wrapSocket(sock, undefined, undefined, wrapOptions);
456
+ }
@@ -440,31 +440,36 @@ export const extractGroupMetadata = (result) => {
440
440
  memberAddMode: memberAddMode,
441
441
  participants: getBinaryNodeChildren(group, "participant").map(
442
442
  ({ attrs }) => {
443
- const id = jidNormalizedUser(attrs.jid);
444
- const phone = attrs.phone_number
445
- ? jidNormalizedUser(attrs.phone_number)
446
- : isPnUser(id)
447
- ? id
448
- : undefined;
449
- const lid = attrs.lid
450
- ? jidNormalizedUser(attrs.lid)
451
- : isLidUser(id)
452
- ? id
453
- : undefined;
443
+ // مهم: الـ id هنا لازم يفضل زي ما السيرفر بعته (attrs.jid) بالظبط,
444
+ // من غير ما نلمسه أو نبدله بـ lid/phone بديل. السبب: القيمة دي هي
445
+ // اللي بتتبعت زي ما هي في participantsList لـ getUSyncDevices وفي
446
+ // الـ addressing_mode بتاع الرسالة الصادرة (messages-send.js) —
447
+ // فلو الجروب addressingMode="pn" لازم كل الـ ids تكون بصيغة PN,
448
+ // ولو "lid" لازم تكون كلها LID. أي تبديل هنا بيسبب mismatch بين
449
+ // هوية المشارك والـ addressing_mode المعلن، وده اللي كان بيطلع
450
+ // "بيشفر"/Waiting for this message لمشاركين الـ username بالذات —
451
+ // مش مشكلة سيشن عشان كده افتكاك السيشن وإعادة الدخول ما كانتش بتحل حاجة.
452
+ // عرض الرقم/اليوزرنيم للمستخدم (لو الرقم مخفي) اتحل بالفعل في مكان
453
+ // تاني (resolveDisplayIdentity) وده المكان الصح ليه، مش هنا.
454
+ const id = attrs.jid;
454
455
  return {
455
- // لو مفيش رقم ظاهر (حساب يوزرنيم/خاصية إخفاء الرقم)، استخدم الـ
456
- // LID بدل ما تسيبها undefined — undefined كانت بتوصل لقايمة
457
- // المشاركين اللي بتتبعت لـ getUSyncDevices وتكسر استعلام
458
- // الأجهزة للجروب كله، مع إن الاستعلام المباشر بالـ LID بيرجع
459
- // فورًا لو استخدمناه صح
460
- id: phone || lid,
461
- lid: lid,
462
- jid: phone || lid,
463
- admin: attrs.type || false,
456
+ id,
457
+ // jid هنا نفس قيمة id بالظبط (مش قيمة تانية زي الكود القديم) —
458
+ // مُبقّاها بس عشان بعض أوامر البوت (كتم.js, مجتمع.js, muteSystem.js,
459
+ // context/group.js) بتقرا p.jid كـ fallback؛ ما بتأثرش على
460
+ // participantsList/addressing_mode لأنها زي id بالظبط دلوقتي
461
+ jid: id,
462
+ phoneNumber:
463
+ isLidUser(id) && isPnUser(attrs.phone_number)
464
+ ? attrs.phone_number
465
+ : undefined,
466
+ lid: isPnUser(id) && isLidUser(attrs.lid) ? attrs.lid : undefined,
467
+ username: attrs.participant_username || attrs.username || undefined,
468
+ admin: attrs.type || null,
464
469
  };
465
470
  },
466
471
  ),
467
472
  ephemeralDuration: eph ? +eph : undefined,
468
473
  };
469
474
  return metadata;
470
- };
475
+ };