@lazyneoaz/nkxchat 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.
Files changed (123) hide show
  1. package/LICENSE +3 -0
  2. package/README.md +199 -0
  3. package/examples/login-with-cookies.js +102 -0
  4. package/examples/verify.js +70 -0
  5. package/index.js +2 -0
  6. package/package.json +84 -0
  7. package/src/apis/addExternalModule.js +24 -0
  8. package/src/apis/addUserToGroup.js +108 -0
  9. package/src/apis/changeAdminStatus.js +148 -0
  10. package/src/apis/changeArchivedStatus.js +61 -0
  11. package/src/apis/changeAvatar.js +103 -0
  12. package/src/apis/changeBio.js +69 -0
  13. package/src/apis/changeBlockedStatus.js +54 -0
  14. package/src/apis/changeGroupImage.js +136 -0
  15. package/src/apis/changeThreadColor.js +116 -0
  16. package/src/apis/changeThreadEmoji.js +53 -0
  17. package/src/apis/comment.js +207 -0
  18. package/src/apis/createAITheme.js +129 -0
  19. package/src/apis/createNewGroup.js +79 -0
  20. package/src/apis/createPoll.js +73 -0
  21. package/src/apis/deleteMessage.js +44 -0
  22. package/src/apis/deleteThread.js +52 -0
  23. package/src/apis/editMessage.js +70 -0
  24. package/src/apis/emoji.js +124 -0
  25. package/src/apis/enableAutoSaveAppState.js +69 -0
  26. package/src/apis/fetchThemeData.js +113 -0
  27. package/src/apis/follow.js +81 -0
  28. package/src/apis/forwardAttachment.js +178 -0
  29. package/src/apis/forwardMessage.js +52 -0
  30. package/src/apis/friend.js +243 -0
  31. package/src/apis/gcmember.js +122 -0
  32. package/src/apis/gcname.js +123 -0
  33. package/src/apis/gcrule.js +119 -0
  34. package/src/apis/getAccess.js +111 -0
  35. package/src/apis/getBotInfo.js +88 -0
  36. package/src/apis/getBotInitialData.js +43 -0
  37. package/src/apis/getEmojiUrl.js +40 -0
  38. package/src/apis/getFriendsList.js +79 -0
  39. package/src/apis/getMessage.js +423 -0
  40. package/src/apis/getTheme.js +123 -0
  41. package/src/apis/getThemeInfo.js +116 -0
  42. package/src/apis/getThemePictures.js +87 -0
  43. package/src/apis/getThreadColors.js +119 -0
  44. package/src/apis/getThreadHistory.js +239 -0
  45. package/src/apis/getThreadInfo.js +267 -0
  46. package/src/apis/getThreadList.js +232 -0
  47. package/src/apis/getThreadPictures.js +58 -0
  48. package/src/apis/getUserID.js +117 -0
  49. package/src/apis/getUserInfo.js +513 -0
  50. package/src/apis/getUserInfoV2.js +146 -0
  51. package/src/apis/handleFriendRequest.js +66 -0
  52. package/src/apis/handleMessageRequest.js +50 -0
  53. package/src/apis/httpGet.js +63 -0
  54. package/src/apis/httpPost.js +89 -0
  55. package/src/apis/httpPostFormData.js +69 -0
  56. package/src/apis/listenMqtt.js +924 -0
  57. package/src/apis/listenSpeed.js +178 -0
  58. package/src/apis/logout.js +63 -0
  59. package/src/apis/markAsDelivered.js +47 -0
  60. package/src/apis/markAsRead.js +95 -0
  61. package/src/apis/markAsReadAll.js +40 -0
  62. package/src/apis/markAsSeen.js +70 -0
  63. package/src/apis/mqttDeltaValue.js +252 -0
  64. package/src/apis/muteThread.js +45 -0
  65. package/src/apis/nickname.js +132 -0
  66. package/src/apis/notes.js +163 -0
  67. package/src/apis/pinMessage.js +150 -0
  68. package/src/apis/produceMetaTheme.js +160 -0
  69. package/src/apis/realtime.js +182 -0
  70. package/src/apis/refreshFb_dtsg.js +94 -0
  71. package/src/apis/removeUserFromGroup.js +117 -0
  72. package/src/apis/resolvePhotoUrl.js +58 -0
  73. package/src/apis/searchForThread.js +154 -0
  74. package/src/apis/sendEffect.js +306 -0
  75. package/src/apis/sendMessage.js +353 -0
  76. package/src/apis/sendMessageMqtt.js +255 -0
  77. package/src/apis/sendTypingIndicator.js +40 -0
  78. package/src/apis/setMessageReaction.js +27 -0
  79. package/src/apis/setMessageReactionMqtt.js +61 -0
  80. package/src/apis/setPostReaction.js +118 -0
  81. package/src/apis/setThreadTheme.js +210 -0
  82. package/src/apis/setThreadThemeMqtt.js +94 -0
  83. package/src/apis/setTitle.js +26 -0
  84. package/src/apis/share.js +106 -0
  85. package/src/apis/shareContact.js +66 -0
  86. package/src/apis/stickers.js +257 -0
  87. package/src/apis/story.js +181 -0
  88. package/src/apis/theme.js +233 -0
  89. package/src/apis/unfriend.js +47 -0
  90. package/src/apis/unsendMessage.js +17 -0
  91. package/src/apis/uploadAttachment.js +87 -0
  92. package/src/database/appStateBackup.js +189 -0
  93. package/src/database/models/index.js +56 -0
  94. package/src/database/models/thread.js +31 -0
  95. package/src/database/models/user.js +32 -0
  96. package/src/database/threadData.js +101 -0
  97. package/src/database/userData.js +90 -0
  98. package/src/engine/client.js +92 -0
  99. package/src/engine/models/buildAPI.js +118 -0
  100. package/src/engine/models/loginHelper.js +492 -0
  101. package/src/engine/models/setOptions.js +88 -0
  102. package/src/types/index.d.ts +498 -0
  103. package/src/utils/antiSuspension.js +516 -0
  104. package/src/utils/auth-helpers.js +149 -0
  105. package/src/utils/autoReLogin.js +237 -0
  106. package/src/utils/axios.js +368 -0
  107. package/src/utils/cache.js +54 -0
  108. package/src/utils/clients.js +279 -0
  109. package/src/utils/constants.js +525 -0
  110. package/src/utils/formatters/data/formatAttachment.js +370 -0
  111. package/src/utils/formatters/data/formatDelta.js +109 -0
  112. package/src/utils/formatters/index.js +159 -0
  113. package/src/utils/formatters/value/formatCookie.js +91 -0
  114. package/src/utils/formatters/value/formatDate.js +36 -0
  115. package/src/utils/formatters/value/formatID.js +16 -0
  116. package/src/utils/formatters.js +1369 -0
  117. package/src/utils/headers.js +235 -0
  118. package/src/utils/index.js +152 -0
  119. package/src/utils/monitoring.js +333 -0
  120. package/src/utils/rateLimiter.js +251 -0
  121. package/src/utils/tokenRefresh.js +285 -0
  122. package/src/utils/user-agents.js +238 -0
  123. package/src/utils/validation.js +157 -0
@@ -0,0 +1,516 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * Anti-Suspension Module
5
+ * Comprehensive protection against Facebook bot account suspension.
6
+ * Designed to be fast (single-delay model) yet stealth.
7
+ *
8
+ * Credits: NeoKEX — https://github.com/lazyneoaz — https://neoaz.is-a.dev
9
+ */
10
+
11
+ const SUSPENSION_SIGNALS = [
12
+ 'checkpoint',
13
+ 'action_required',
14
+ 'account_locked',
15
+ 'account locked',
16
+ 'device_login',
17
+ 'account suspension',
18
+ 'account suspended',
19
+ 'account has been suspended',
20
+ 'account has been disabled',
21
+ 'your account has been disabled',
22
+ 'this account has been suspended',
23
+ 'account banned',
24
+ 'account has been banned',
25
+ 'unusual_activity',
26
+ 'unusual activity',
27
+ 'we noticed unusual activity',
28
+ 'suspicious activity',
29
+ 'verify_your_account',
30
+ 'verify your account',
31
+ 'confirm_your_identity',
32
+ 'confirm your identity',
33
+ 'confirm it\'s you',
34
+ 'confirm its you',
35
+ 'please verify your account',
36
+ 'please confirm your identity',
37
+ 'identity confirmation',
38
+ 'security_check',
39
+ 'security check required',
40
+ 'login_approvals',
41
+ 'login approvals',
42
+ 'two-factor authentication required',
43
+ 'too_many_requests',
44
+ 'too many requests',
45
+ 'rate limited',
46
+ 'rate_limit',
47
+ 'temporarily blocked',
48
+ 'temporarily_blocked',
49
+ 'your account has been temporarily blocked',
50
+ 'please try again later',
51
+ 'feature temporarily blocked',
52
+ 'feature temporarily unavailable',
53
+ 'something went wrong',
54
+ 'automated behavior',
55
+ 'not a human',
56
+ 'bot detected',
57
+ 'automated_behavior',
58
+ 'bot_detected',
59
+ 'spam detected',
60
+ 'spam_detected',
61
+ 'looks like spam',
62
+ 'violates our community standards',
63
+ 'community standards violation',
64
+ 'this content isn\'t available',
65
+ 'you\'re blocked from',
66
+ 'blocked from sending',
67
+ 'disabled for violating',
68
+ 'policy violation',
69
+ 'action blocked',
70
+ 'session expired',
71
+ 'session has expired',
72
+ 'not logged in',
73
+ 'login required',
74
+ 'authentication required',
75
+ 'invalid session',
76
+ 'please log in again',
77
+ 'your session has ended'
78
+ ];
79
+
80
+ class AntiSuspension {
81
+ constructor() {
82
+ this.activityThrottler = new Map();
83
+ this.lastActivity = new Map();
84
+ this.typing = new Map();
85
+
86
+ this.messageDelayMs = 300;
87
+ this.threadDelayMs = 800;
88
+ this.loginAttempts = 0;
89
+ this.maxLoginAttempts = 3;
90
+ this.loginCooldown = 300000;
91
+
92
+ this.suspensionCircuitBreaker = {
93
+ tripped: false,
94
+ trippedAt: null,
95
+ cooldownMs: 45 * 60 * 1000,
96
+ signalCount: 0,
97
+ maxSignalsBeforeTrip: 2,
98
+ lastSignalAt: null
99
+ };
100
+
101
+ this.dailyStats = {
102
+ date: new Date().toDateString(),
103
+ messageCount: 0,
104
+ maxDailyMessages: 1200,
105
+ threadStats: new Map()
106
+ };
107
+
108
+ this.hourlyBucket = {
109
+ hour: new Date().getHours(),
110
+ count: 0,
111
+ maxPerHour: 180
112
+ };
113
+
114
+ this.sessionFingerprint = null;
115
+
116
+ this.warmup = {
117
+ active: false,
118
+ startedAt: null,
119
+ durationMs: 20 * 60 * 1000,
120
+ maxMessagesPerHour: 25
121
+ };
122
+
123
+ this._dailyResetInterval = setInterval(() => this._resetDailyStatsIfNeeded(), 60 * 1000);
124
+ this._hourlyResetInterval = setInterval(() => this._resetHourlyBucketIfNeeded(), 30 * 1000);
125
+ }
126
+
127
+ _resetDailyStatsIfNeeded() {
128
+ const today = new Date().toDateString();
129
+ if (this.dailyStats.date !== today) {
130
+ this.dailyStats.date = today;
131
+ this.dailyStats.messageCount = 0;
132
+ this.dailyStats.threadStats.clear();
133
+ }
134
+ }
135
+
136
+ _resetHourlyBucketIfNeeded() {
137
+ const currentHour = new Date().getHours();
138
+ if (this.hourlyBucket.hour !== currentHour) {
139
+ this.hourlyBucket.hour = currentHour;
140
+ this.hourlyBucket.count = 0;
141
+ }
142
+ }
143
+
144
+ _incrementDailyStats(threadID) {
145
+ this.dailyStats.messageCount++;
146
+ this.hourlyBucket.count++;
147
+
148
+ if (threadID) {
149
+ const ts = this.dailyStats.threadStats.get(String(threadID)) || { count: 0 };
150
+ ts.count++;
151
+ ts.lastActivity = Date.now();
152
+ this.dailyStats.threadStats.set(String(threadID), ts);
153
+ }
154
+ }
155
+
156
+ isDailyLimitReached() {
157
+ return this.dailyStats.messageCount >= this.dailyStats.maxDailyMessages;
158
+ }
159
+
160
+ isHourlyLimitReached() {
161
+ const limit = this.warmup.active
162
+ ? this.warmup.maxMessagesPerHour
163
+ : this.hourlyBucket.maxPerHour;
164
+ return this.hourlyBucket.count >= limit;
165
+ }
166
+
167
+ /**
168
+ * Returns a human-readable warning if a volume limit has been reached.
169
+ * Returns null if all limits are within safe range.
170
+ */
171
+ checkVolumeLimit(threadID) {
172
+ if (this.isDailyLimitReached()) {
173
+ return `Daily message limit reached (${this.dailyStats.messageCount}/${this.dailyStats.maxDailyMessages}). Pausing to avoid suspension.`;
174
+ }
175
+ if (this.isHourlyLimitReached()) {
176
+ const limit = this.warmup.active ? this.warmup.maxMessagesPerHour : this.hourlyBucket.maxPerHour;
177
+ return `Hourly message limit reached (${this.hourlyBucket.count}/${limit}). Pausing to avoid suspension.`;
178
+ }
179
+ return null;
180
+ }
181
+
182
+ enableWarmup() {
183
+ this.warmup.active = true;
184
+ this.warmup.startedAt = Date.now();
185
+ setTimeout(() => {
186
+ this.warmup.active = false;
187
+ }, this.warmup.durationMs);
188
+ }
189
+
190
+ lockSessionFingerprint(ua, secChUa, platform, locale, timezone) {
191
+ if (!this.sessionFingerprint) {
192
+ this.sessionFingerprint = { ua, secChUa, platform, locale, timezone, lockedAt: Date.now() };
193
+ }
194
+ return this.sessionFingerprint;
195
+ }
196
+
197
+ getSessionFingerprint() {
198
+ return this.sessionFingerprint;
199
+ }
200
+
201
+ detectSuspensionSignal(text) {
202
+ if (!text || typeof text !== 'string') return false;
203
+ const lower = text.toLowerCase();
204
+ const found = SUSPENSION_SIGNALS.some(signal => lower.includes(signal));
205
+ if (found) {
206
+ this._onSuspensionSignalDetected();
207
+ }
208
+ return found;
209
+ }
210
+
211
+ _onSuspensionSignalDetected() {
212
+ const cb = this.suspensionCircuitBreaker;
213
+ cb.signalCount++;
214
+ cb.lastSignalAt = Date.now();
215
+
216
+ if (cb.signalCount >= cb.maxSignalsBeforeTrip) {
217
+ if (!cb.tripped) {
218
+ cb.tripped = true;
219
+ cb.trippedAt = Date.now();
220
+ const { utils } = this._getUtils();
221
+ utils && utils.warn && utils.warn("AntiSuspension",
222
+ `Circuit breaker TRIPPED after ${cb.signalCount} suspension signals. ` +
223
+ `Pausing all activity for ${cb.cooldownMs / 60000} minutes.`);
224
+ }
225
+ }
226
+ }
227
+
228
+ _getUtils() {
229
+ try {
230
+ return { utils: require('./index') };
231
+ } catch (_) {
232
+ return {};
233
+ }
234
+ }
235
+
236
+ isCircuitBreakerTripped() {
237
+ const cb = this.suspensionCircuitBreaker;
238
+ if (!cb.tripped) return false;
239
+ const elapsed = Date.now() - cb.trippedAt;
240
+ if (elapsed >= cb.cooldownMs) {
241
+ cb.tripped = false;
242
+ cb.signalCount = 0;
243
+ cb.trippedAt = null;
244
+ return false;
245
+ }
246
+ return true;
247
+ }
248
+
249
+ getCircuitBreakerRemainingMs() {
250
+ const cb = this.suspensionCircuitBreaker;
251
+ if (!cb.tripped) return 0;
252
+ return Math.max(0, cb.cooldownMs - (Date.now() - cb.trippedAt));
253
+ }
254
+
255
+ tripCircuitBreaker(reason, durationMs) {
256
+ const cb = this.suspensionCircuitBreaker;
257
+ cb.tripped = true;
258
+ cb.trippedAt = Date.now();
259
+ if (durationMs) cb.cooldownMs = durationMs;
260
+ cb.signalCount = cb.maxSignalsBeforeTrip;
261
+ const { utils } = this._getUtils();
262
+ utils && utils.warn && utils.warn("AntiSuspension",
263
+ `Circuit breaker manually tripped: ${reason || 'manual'}. ` +
264
+ `Cooldown: ${(cb.cooldownMs / 60000).toFixed(1)} min`);
265
+ }
266
+
267
+ resetCircuitBreaker() {
268
+ this.suspensionCircuitBreaker.tripped = false;
269
+ this.suspensionCircuitBreaker.signalCount = 0;
270
+ this.suspensionCircuitBreaker.trippedAt = null;
271
+ }
272
+
273
+ async simulateTyping(threadID, messageLength = 50) {
274
+ const wpm = 38 + Math.random() * 24;
275
+ const charsPerMs = (wpm * 5) / 60000;
276
+ const typingDelay = Math.min(1200, Math.max(150, messageLength / charsPerMs));
277
+ const jitter = (Math.random() - 0.5) * 120;
278
+ return Math.round(typingDelay + jitter);
279
+ }
280
+
281
+ async addSmartDelay() {
282
+ const base = 150 + Math.random() * 250;
283
+ const jitter = (Math.random() - 0.5) * 60;
284
+ const total = Math.max(80, base + jitter);
285
+ await new Promise(resolve => setTimeout(resolve, total));
286
+ }
287
+
288
+ /**
289
+ * Add a longer random delay when volume is running high.
290
+ * Helps avoid patterns that look like automated batch sends.
291
+ */
292
+ async addAdaptiveDelay(threadID) {
293
+ const threadCount = this.dailyStats.threadStats.get(String(threadID))?.count || 0;
294
+ const globalCount = this.dailyStats.messageCount;
295
+
296
+ let base = 150;
297
+ if (globalCount > 800) base = 600;
298
+ else if (globalCount > 400) base = 350;
299
+
300
+ if (threadCount > 50) base += 200;
301
+
302
+ const jitter = Math.random() * base * 0.4;
303
+ const total = Math.max(80, base + jitter);
304
+ await new Promise(resolve => setTimeout(resolve, total));
305
+ }
306
+
307
+ async enforceThreadThrottling(threadID) {
308
+ const lastTime = this.lastActivity.get(String(threadID)) || 0;
309
+ const timeSinceLastMsg = Date.now() - lastTime;
310
+ const minInterval = this.threadDelayMs + Math.random() * 200;
311
+
312
+ if (timeSinceLastMsg < minInterval) {
313
+ const waitTime = minInterval - timeSinceLastMsg;
314
+ await new Promise(resolve => setTimeout(resolve, waitTime));
315
+ }
316
+
317
+ this.lastActivity.set(String(threadID), Date.now());
318
+ return Date.now() - lastTime;
319
+ }
320
+
321
+ async enforceMessageRate() {
322
+ await new Promise(resolve =>
323
+ setTimeout(resolve, this.messageDelayMs + Math.random() * 150)
324
+ );
325
+ }
326
+
327
+ getHumanizedHeaders() {
328
+ const { randomUserAgent } = require('./user-agents');
329
+ const fp = this.sessionFingerprint;
330
+ const ua = fp ? { userAgent: fp.ua, secChUa: fp.secChUa, secChUaPlatform: fp.platform } : randomUserAgent();
331
+ return {
332
+ 'User-Agent': ua.userAgent,
333
+ 'Accept-Language': (fp && fp.locale) || 'en-US,en;q=0.9',
334
+ 'Accept-Encoding': 'gzip, deflate, br',
335
+ 'DNT': '1',
336
+ 'Connection': 'keep-alive',
337
+ 'Upgrade-Insecure-Requests': '1',
338
+ 'Sec-Ch-Ua': ua.secChUa || '',
339
+ 'Sec-Ch-Ua-Mobile': '?0',
340
+ 'Sec-Ch-Ua-Platform': ua.secChUaPlatform || '"Windows"',
341
+ 'Sec-Fetch-Dest': 'document',
342
+ 'Sec-Fetch-Mode': 'navigate',
343
+ 'Sec-Fetch-Site': 'none',
344
+ 'Cache-Control': 'max-age=0'
345
+ };
346
+ }
347
+
348
+ rotateUserAgent() {
349
+ const { randomUserAgent } = require('./user-agents');
350
+ if (this.sessionFingerprint) return this.sessionFingerprint.ua;
351
+ return randomUserAgent().userAgent;
352
+ }
353
+
354
+ trackLoginAttempt() {
355
+ this.loginAttempts++;
356
+ const isLocked = this.loginAttempts >= this.maxLoginAttempts;
357
+ return {
358
+ attempt: this.loginAttempts,
359
+ isLocked,
360
+ cooldownMs: isLocked ? this.loginCooldown : 0,
361
+ nextAttemptAt: isLocked ? Date.now() + this.loginCooldown : null
362
+ };
363
+ }
364
+
365
+ resetLoginAttempts() {
366
+ this.loginAttempts = 0;
367
+ }
368
+
369
+ checkAccountHealth(lastError) {
370
+ const isSuspected = lastError &&
371
+ SUSPENSION_SIGNALS.some(indicator =>
372
+ (lastError.message || '').toLowerCase().includes(indicator)
373
+ );
374
+
375
+ if (isSuspected) {
376
+ this._onSuspensionSignalDetected();
377
+ }
378
+
379
+ return {
380
+ suspended: isSuspected,
381
+ circuitBreakerTripped: this.isCircuitBreakerTripped(),
382
+ dailyLimitReached: this.isDailyLimitReached(),
383
+ hourlyLimitReached: this.isHourlyLimitReached(),
384
+ lastCheck: Date.now(),
385
+ recommendedAction: isSuspected ? 'WAIT_AND_RETRY' : 'CONTINUE',
386
+ circuitBreakerRemainingMs: this.getCircuitBreakerRemainingMs()
387
+ };
388
+ }
389
+
390
+ getRealisticActivityPattern() {
391
+ const hour = new Date().getHours();
392
+ const isNight = hour < 6 || hour >= 22;
393
+
394
+ return {
395
+ messageFrequency: isNight ? 'low' : 'normal',
396
+ nextActionDelayMs: isNight
397
+ ? 4000 + Math.random() * 6000
398
+ : 300 + Math.random() * 1200,
399
+ isActiveHours: !isNight,
400
+ recommendedCooldown: isNight ? 10000 : 1500
401
+ };
402
+ }
403
+
404
+ async safeRetry(fn, maxRetries = 3) {
405
+ for (let i = 0; i < maxRetries; i++) {
406
+ if (this.isCircuitBreakerTripped()) {
407
+ throw new Error('Circuit breaker is tripped. Stopping retries to protect account.');
408
+ }
409
+ try {
410
+ return await fn();
411
+ } catch (error) {
412
+ const msg = (error.message || '').toLowerCase();
413
+ const isSuspensionError = SUSPENSION_SIGNALS.some(s => msg.includes(s));
414
+ if (isSuspensionError) {
415
+ this._onSuspensionSignalDetected();
416
+ throw error;
417
+ }
418
+ if (i === maxRetries - 1) throw error;
419
+ const delay = Math.pow(2, i + 1) * 1000 + Math.random() * 800;
420
+ await new Promise(resolve => setTimeout(resolve, delay));
421
+ }
422
+ }
423
+ }
424
+
425
+ async batchOperations(operations) {
426
+ const results = [];
427
+ for (let i = 0; i < operations.length; i++) {
428
+ if (this.isCircuitBreakerTripped()) {
429
+ throw new Error('Circuit breaker tripped during batch operation.');
430
+ }
431
+ results.push(await this.safeRetry(() => operations[i]()));
432
+ if (i < operations.length - 1) {
433
+ await this.addSmartDelay();
434
+ }
435
+ }
436
+ return results;
437
+ }
438
+
439
+ /**
440
+ * Prepare before sending — single delay model.
441
+ * Enforces thread throttle and volume limits, respects circuit breaker.
442
+ * If volume limits are reached, throws to protect the account.
443
+ */
444
+ async prepareBeforeMessage(threadID, message) {
445
+ if (this.isCircuitBreakerTripped()) {
446
+ const remaining = this.getCircuitBreakerRemainingMs();
447
+ const waitMs = Math.min(remaining, 8000);
448
+ if (waitMs > 0) await new Promise(resolve => setTimeout(resolve, waitMs));
449
+ }
450
+
451
+ const volumeWarning = this.checkVolumeLimit(threadID);
452
+ if (volumeWarning) {
453
+ const { utils } = this._getUtils();
454
+ utils && utils.warn && utils.warn("AntiSuspension", volumeWarning);
455
+ // Add a safety pause instead of hard-blocking so callers can decide
456
+ await new Promise(resolve => setTimeout(resolve, 5000 + Math.random() * 3000));
457
+ }
458
+
459
+ await this.enforceThreadThrottling(threadID);
460
+ await this.addAdaptiveDelay(threadID);
461
+ this._incrementDailyStats(threadID);
462
+ }
463
+
464
+ getConfig() {
465
+ return {
466
+ messageDelayMs: this.messageDelayMs,
467
+ threadDelayMs: this.threadDelayMs,
468
+ maxLoginAttempts: this.maxLoginAttempts,
469
+ loginCooldownMs: this.loginCooldown,
470
+ circuitBreaker: {
471
+ tripped: this.suspensionCircuitBreaker.tripped,
472
+ signalCount: this.suspensionCircuitBreaker.signalCount,
473
+ remainingMs: this.getCircuitBreakerRemainingMs()
474
+ },
475
+ dailyStats: {
476
+ messageCount: this.dailyStats.messageCount,
477
+ maxDailyMessages: this.dailyStats.maxDailyMessages
478
+ },
479
+ hourlyStats: {
480
+ count: this.hourlyBucket.count,
481
+ maxPerHour: this.hourlyBucket.maxPerHour
482
+ },
483
+ warmupActive: this.warmup.active,
484
+ features: {
485
+ typeSimulation: true,
486
+ delayRandomization: true,
487
+ adaptiveDelay: true,
488
+ userAgentRotation: true,
489
+ activityPatternTracking: true,
490
+ autoSuspensionDetection: true,
491
+ exponentialBackoff: true,
492
+ circuitBreaker: true,
493
+ dailyVolumeLimiting: true,
494
+ hourlyVolumeLimiting: true,
495
+ sessionFingerprintLock: true,
496
+ warmupMode: true,
497
+ volumeWarnings: true
498
+ }
499
+ };
500
+ }
501
+
502
+ destroy() {
503
+ clearInterval(this._dailyResetInterval);
504
+ clearInterval(this._hourlyResetInterval);
505
+ }
506
+ }
507
+
508
+ const globalAntiSuspension = new AntiSuspension();
509
+
510
+ module.exports = {
511
+ AntiSuspension,
512
+ globalAntiSuspension,
513
+ SUSPENSION_SIGNALS,
514
+ initAntiSuspension: () => globalAntiSuspension,
515
+ getAntiSuspensionConfig: () => globalAntiSuspension.getConfig()
516
+ };
@@ -0,0 +1,149 @@
1
+ "use strict";
2
+
3
+ const REGION_MAP = new Map([
4
+ ["PRN", { code: "PRN", name: "Pacific Northwest Region", location: "Khu vực Tây Bắc Thái Bình Dương" }],
5
+ ["VLL", { code: "VLL", name: "Valley Region", location: "Valley" }],
6
+ ["ASH", { code: "ASH", name: "Ashburn Region", location: "Ashburn" }],
7
+ ["DFW", { code: "DFW", name: "Dallas/Fort Worth Region", location: "Dallas/Fort Worth" }],
8
+ ["LLA", { code: "LLA", name: "Los Angeles Region", location: "Los Angeles" }],
9
+ ["FRA", { code: "FRA", name: "Frankfurt", location: "Frankfurt" }],
10
+ ["SIN", { code: "SIN", name: "Singapore", location: "Singapore" }],
11
+ ["NRT", { code: "NRT", name: "Tokyo", location: "Japan" }],
12
+ ["HKG", { code: "HKG", name: "Hong Kong", location: "Hong Kong" }],
13
+ ["SYD", { code: "SYD", name: "Sydney", location: "Sydney" }],
14
+ ["PNB", { code: "PNB", name: "Pacific Northwest - Beta", location: "Pacific Northwest" }]
15
+ ]);
16
+
17
+ /**
18
+ * Parses the region from HTML response content with fallback to default.
19
+ * @param {string} html - The HTML response from Facebook.
20
+ * @returns {string} The region code (e.g., "PRN", "VLL", etc.).
21
+ */
22
+ function parseRegion(html) {
23
+ try {
24
+ const match1 = html.match(/"endpoint":"([^"]+)"/);
25
+ const match2 = match1 ? null : html.match(/endpoint\\":\\"([^\\"]+)\\"/);
26
+ const rawEndpoint = (match1 && match1[1]) || (match2 && match2[1]);
27
+
28
+ if (!rawEndpoint) {
29
+ return "PRN";
30
+ }
31
+
32
+ const endpoint = rawEndpoint.replace(/\\\//g, "/");
33
+
34
+ try {
35
+ const url = new URL(endpoint);
36
+ const regionParam = url.searchParams ? url.searchParams.get("region") : null;
37
+
38
+ if (regionParam) {
39
+ const regionCode = regionParam.toUpperCase();
40
+ if (REGION_MAP.has(regionCode)) {
41
+ return regionCode;
42
+ }
43
+ }
44
+ } catch (urlError) {
45
+ }
46
+
47
+ return "PRN";
48
+ } catch (error) {
49
+ return "PRN";
50
+ }
51
+ }
52
+
53
+ /**
54
+ * Generates a TOTP code from a secret using a basic TOTP algorithm.
55
+ * This is a fallback implementation that doesn't require external dependencies.
56
+ * @param {string} secret - The TOTP secret (base32 encoded).
57
+ * @returns {Promise<string>} The generated 6-digit TOTP code.
58
+ */
59
+ async function genTotp(secret) {
60
+ try {
61
+ const cleaned = String(secret || "")
62
+ .replace(/\s+/g, "")
63
+ .toUpperCase();
64
+
65
+ if (!cleaned) {
66
+ throw new Error("TOTP secret is empty");
67
+ }
68
+
69
+ try {
70
+ const totpGenerator = require('totp-generator');
71
+ if (typeof totpGenerator.TOTP !== 'undefined' && typeof totpGenerator.TOTP.generate === 'function') {
72
+ const result = await totpGenerator.TOTP.generate(cleaned);
73
+ return typeof result === 'object' ? result.otp : result;
74
+ } else if (typeof totpGenerator === 'function') {
75
+ return totpGenerator(cleaned);
76
+ } else {
77
+ return totpGenerator(cleaned);
78
+ }
79
+ } catch (requireError) {
80
+ const crypto = require('crypto');
81
+
82
+ function base32Decode(base32) {
83
+ const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
84
+ let bits = '';
85
+
86
+ for (let i = 0; i < base32.length; i++) {
87
+ const val = alphabet.indexOf(base32.charAt(i).toUpperCase());
88
+ if (val === -1) continue;
89
+ bits += val.toString(2).padStart(5, '0');
90
+ }
91
+
92
+ const bytes = [];
93
+ for (let i = 0; i + 8 <= bits.length; i += 8) {
94
+ bytes.push(parseInt(bits.substr(i, 8), 2));
95
+ }
96
+
97
+ return Buffer.from(bytes);
98
+ }
99
+
100
+ const key = base32Decode(cleaned);
101
+ const epoch = Math.floor(Date.now() / 1000);
102
+ const timeCounter = Math.floor(epoch / 30);
103
+
104
+ const buffer = Buffer.alloc(8);
105
+ buffer.writeBigUInt64BE(BigInt(timeCounter));
106
+
107
+ const hmac = crypto.createHmac('sha1', key);
108
+ hmac.update(buffer);
109
+ const digest = hmac.digest();
110
+
111
+ const offset = digest[digest.length - 1] & 0x0f;
112
+ const code = (
113
+ ((digest[offset] & 0x7f) << 24) |
114
+ ((digest[offset + 1] & 0xff) << 16) |
115
+ ((digest[offset + 2] & 0xff) << 8) |
116
+ (digest[offset + 3] & 0xff)
117
+ ) % 1000000;
118
+
119
+ return code.toString().padStart(6, '0');
120
+ }
121
+ } catch (error) {
122
+ throw new Error(`Failed to generate TOTP code: ${error.message}`);
123
+ }
124
+ }
125
+
126
+ /**
127
+ * Gets region information by code.
128
+ * @param {string} code - The region code.
129
+ * @returns {object|null} The region information or null if not found.
130
+ */
131
+ function getRegionInfo(code) {
132
+ return REGION_MAP.get(code.toUpperCase()) || null;
133
+ }
134
+
135
+ /**
136
+ * Gets all available regions.
137
+ * @returns {Array<object>} Array of all region objects.
138
+ */
139
+ function getAllRegions() {
140
+ return Array.from(REGION_MAP.values());
141
+ }
142
+
143
+ module.exports = {
144
+ REGION_MAP,
145
+ parseRegion,
146
+ genTotp,
147
+ getRegionInfo,
148
+ getAllRegions
149
+ };