@lazyneoaz/testfca 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 (120) hide show
  1. package/CHANGELOG.md +229 -0
  2. package/COOKIE_LOGIN.md +208 -0
  3. package/LICENSE +3 -0
  4. package/README.md +492 -0
  5. package/index.js +2 -0
  6. package/package.json +120 -0
  7. package/scripts/build-go.mjs +54 -0
  8. package/scripts/detect-platform.mjs +36 -0
  9. package/scripts/download-prebuilt.mjs +119 -0
  10. package/scripts/package.mjs +6 -0
  11. package/scripts/postinstall.mjs +113 -0
  12. package/src/apis/addExternalModule.js +24 -0
  13. package/src/apis/addUserToGroup.js +108 -0
  14. package/src/apis/changeAdminStatus.js +148 -0
  15. package/src/apis/changeArchivedStatus.js +61 -0
  16. package/src/apis/changeAvatar.js +103 -0
  17. package/src/apis/changeBio.js +69 -0
  18. package/src/apis/changeBlockedStatus.js +54 -0
  19. package/src/apis/changeGroupImage.js +136 -0
  20. package/src/apis/changeThreadColor.js +116 -0
  21. package/src/apis/changeThreadEmoji.js +53 -0
  22. package/src/apis/comment.js +207 -0
  23. package/src/apis/createAITheme.js +129 -0
  24. package/src/apis/createNewGroup.js +79 -0
  25. package/src/apis/createPoll.js +73 -0
  26. package/src/apis/deleteMessage.js +52 -0
  27. package/src/apis/deleteThread.js +52 -0
  28. package/src/apis/e2ee.js +170 -0
  29. package/src/apis/editMessage.js +78 -0
  30. package/src/apis/emoji.js +124 -0
  31. package/src/apis/fetchThemeData.js +82 -0
  32. package/src/apis/follow.js +81 -0
  33. package/src/apis/forwardMessage.js +52 -0
  34. package/src/apis/friend.js +243 -0
  35. package/src/apis/gcmember.js +122 -0
  36. package/src/apis/gcname.js +123 -0
  37. package/src/apis/gcrule.js +119 -0
  38. package/src/apis/getAccess.js +111 -0
  39. package/src/apis/getBotInfo.js +88 -0
  40. package/src/apis/getBotInitialData.js +43 -0
  41. package/src/apis/getFriendsList.js +79 -0
  42. package/src/apis/getMessage.js +423 -0
  43. package/src/apis/getTheme.js +95 -0
  44. package/src/apis/getThemeInfo.js +116 -0
  45. package/src/apis/getThreadHistory.js +239 -0
  46. package/src/apis/getThreadInfo.js +267 -0
  47. package/src/apis/getThreadList.js +232 -0
  48. package/src/apis/getThreadPictures.js +58 -0
  49. package/src/apis/getUserID.js +117 -0
  50. package/src/apis/getUserInfo.js +513 -0
  51. package/src/apis/getUserInfoV2.js +146 -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 +1236 -0
  57. package/src/apis/listenSpeed.js +179 -0
  58. package/src/apis/logout.js +93 -0
  59. package/src/apis/markAsDelivered.js +47 -0
  60. package/src/apis/markAsRead.js +115 -0
  61. package/src/apis/markAsReadAll.js +40 -0
  62. package/src/apis/markAsSeen.js +70 -0
  63. package/src/apis/mqttDeltaValue.js +250 -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 +180 -0
  69. package/src/apis/realtime.js +182 -0
  70. package/src/apis/removeUserFromGroup.js +117 -0
  71. package/src/apis/resolvePhotoUrl.js +58 -0
  72. package/src/apis/searchForThread.js +154 -0
  73. package/src/apis/sendMessage.js +346 -0
  74. package/src/apis/sendMessageMqtt.js +248 -0
  75. package/src/apis/sendTypingIndicator.js +105 -0
  76. package/src/apis/setMessageReaction.js +38 -0
  77. package/src/apis/setMessageReactionMqtt.js +61 -0
  78. package/src/apis/setThreadTheme.js +260 -0
  79. package/src/apis/setThreadThemeMqtt.js +94 -0
  80. package/src/apis/share.js +107 -0
  81. package/src/apis/shareContact.js +66 -0
  82. package/src/apis/stickers.js +257 -0
  83. package/src/apis/story.js +181 -0
  84. package/src/apis/theme.js +233 -0
  85. package/src/apis/unfriend.js +47 -0
  86. package/src/apis/unsendMessage.js +25 -0
  87. package/src/database/appStateBackup.js +298 -0
  88. package/src/database/models/index.js +56 -0
  89. package/src/database/models/thread.js +31 -0
  90. package/src/database/models/user.js +32 -0
  91. package/src/database/threadData.js +101 -0
  92. package/src/database/userData.js +90 -0
  93. package/src/e2ee/bridge.js +275 -0
  94. package/src/e2ee/index.js +60 -0
  95. package/src/engine/client.js +95 -0
  96. package/src/engine/models/buildAPI.js +152 -0
  97. package/src/engine/models/loginHelper.js +574 -0
  98. package/src/engine/models/setOptions.js +88 -0
  99. package/src/types/index.d.ts +574 -0
  100. package/src/utils/antiSuspension.js +529 -0
  101. package/src/utils/auth-helpers.js +149 -0
  102. package/src/utils/autoReLogin.js +336 -0
  103. package/src/utils/axios.js +436 -0
  104. package/src/utils/cache.js +54 -0
  105. package/src/utils/clients.js +282 -0
  106. package/src/utils/constants.js +410 -0
  107. package/src/utils/formatters/data/formatAttachment.js +370 -0
  108. package/src/utils/formatters/data/formatDelta.js +109 -0
  109. package/src/utils/formatters/index.js +159 -0
  110. package/src/utils/formatters/value/formatCookie.js +91 -0
  111. package/src/utils/formatters/value/formatDate.js +36 -0
  112. package/src/utils/formatters/value/formatID.js +16 -0
  113. package/src/utils/formatters.js +1373 -0
  114. package/src/utils/headers.js +235 -0
  115. package/src/utils/index.js +153 -0
  116. package/src/utils/monitoring.js +333 -0
  117. package/src/utils/rateLimiter.js +319 -0
  118. package/src/utils/tokenRefresh.js +680 -0
  119. package/src/utils/user-agents.js +238 -0
  120. package/src/utils/validation.js +157 -0
@@ -0,0 +1,529 @@
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/NeoKEX
9
+ */
10
+
11
+ // IMPORTANT: Do NOT add session-management errors here (e.g. "not logged in",
12
+ // "session expired", "login required"). Those are normal authentication events
13
+ // handled by the re-login system. Adding them here causes the circuit breaker
14
+ // to trip on every session expiry, silently blocking all sends for 45 minutes.
15
+ const SUSPENSION_SIGNALS = [
16
+ 'checkpoint',
17
+ 'action_required',
18
+ 'account_locked',
19
+ 'account locked',
20
+ 'device_login',
21
+ 'account suspension',
22
+ 'account suspended',
23
+ 'account has been suspended',
24
+ 'account has been disabled',
25
+ 'your account has been disabled',
26
+ 'this account has been suspended',
27
+ 'account banned',
28
+ 'account has been banned',
29
+ 'unusual_activity',
30
+ 'unusual activity',
31
+ 'we noticed unusual activity',
32
+ 'suspicious activity',
33
+ 'verify_your_account',
34
+ 'verify your account',
35
+ 'confirm_your_identity',
36
+ 'confirm your identity',
37
+ 'confirm it\'s you',
38
+ 'confirm its you',
39
+ 'please verify your account',
40
+ 'please confirm your identity',
41
+ 'identity confirmation',
42
+ 'security_check',
43
+ 'security check required',
44
+ 'login_approvals',
45
+ 'login approvals',
46
+ 'two-factor authentication required',
47
+ 'too_many_requests',
48
+ 'too many requests',
49
+ 'rate limited',
50
+ 'rate_limit',
51
+ 'temporarily blocked',
52
+ 'temporarily_blocked',
53
+ 'your account has been temporarily blocked',
54
+ 'feature temporarily blocked',
55
+ 'feature temporarily unavailable',
56
+ 'automated behavior',
57
+ 'not a human',
58
+ 'bot detected',
59
+ 'automated_behavior',
60
+ 'bot_detected',
61
+ 'spam detected',
62
+ 'spam_detected',
63
+ 'looks like spam',
64
+ 'violates our community standards',
65
+ 'community standards violation',
66
+ 'this content isn\'t available',
67
+ 'you\'re blocked from',
68
+ 'blocked from sending',
69
+ 'disabled for violating',
70
+ 'policy violation',
71
+ 'action blocked'
72
+ ];
73
+
74
+ class AntiSuspension {
75
+ constructor() {
76
+ this.activityThrottler = new Map();
77
+ this.lastActivity = new Map();
78
+ this.typing = new Map();
79
+
80
+ this.messageDelayMs = 150; // Reduced from 200ms
81
+ this.threadDelayMs = 450; // Reduced from 600ms
82
+ this.loginAttempts = 0;
83
+ this.maxLoginAttempts = 5; // Increased from 3
84
+ this.loginCooldown = 180000; // Reduced from 300000ms (3 min instead of 5)
85
+
86
+ this.suspensionCircuitBreaker = {
87
+ tripped: false,
88
+ trippedAt: null,
89
+ cooldownMs: 45 * 60 * 1000,
90
+ signalCount: 0,
91
+ maxSignalsBeforeTrip: 2,
92
+ lastSignalAt: null
93
+ };
94
+
95
+ this.dailyStats = {
96
+ date: new Date().toDateString(),
97
+ messageCount: 0,
98
+ maxDailyMessages: 2500, // Increased from 1500
99
+ threadStats: new Map()
100
+ };
101
+
102
+ this.hourlyBucket = {
103
+ hour: new Date().getHours(),
104
+ count: 0,
105
+ maxPerHour: 350 // Increased from 220
106
+ };
107
+
108
+ this.sessionFingerprint = null;
109
+
110
+ this.warmup = {
111
+ active: false,
112
+ startedAt: null,
113
+ durationMs: 15 * 60 * 1000, // Reduced from 20 minutes
114
+ maxMessagesPerHour: 40 // Increased from 25
115
+ };
116
+
117
+ this._dailyResetInterval = setInterval(() => this._resetDailyStatsIfNeeded(), 60 * 1000);
118
+ this._hourlyResetInterval = setInterval(() => this._resetHourlyBucketIfNeeded(), 30 * 1000);
119
+
120
+ // Cleanup intervals on process exit to prevent memory leaks
121
+ process.on('exit', () => this._clearIntervals());
122
+ process.on('SIGINT', () => this._clearIntervals());
123
+ process.on('SIGTERM', () => this._clearIntervals());
124
+ }
125
+
126
+ _resetDailyStatsIfNeeded() {
127
+ const today = new Date().toDateString();
128
+ if (this.dailyStats.date !== today) {
129
+ this.dailyStats.date = today;
130
+ this.dailyStats.messageCount = 0;
131
+ this.dailyStats.threadStats.clear();
132
+ }
133
+ }
134
+
135
+ _resetHourlyBucketIfNeeded() {
136
+ const currentHour = new Date().getHours();
137
+ if (this.hourlyBucket.hour !== currentHour) {
138
+ this.hourlyBucket.hour = currentHour;
139
+ this.hourlyBucket.count = 0;
140
+ }
141
+ }
142
+
143
+ _clearIntervals() {
144
+ if (this._dailyResetInterval) {
145
+ clearInterval(this._dailyResetInterval);
146
+ this._dailyResetInterval = null;
147
+ }
148
+ if (this._hourlyResetInterval) {
149
+ clearInterval(this._hourlyResetInterval);
150
+ this._hourlyResetInterval = null;
151
+ }
152
+ }
153
+
154
+ _incrementDailyStats(threadID) {
155
+ this.dailyStats.messageCount++;
156
+ this.hourlyBucket.count++;
157
+
158
+ if (threadID) {
159
+ const ts = this.dailyStats.threadStats.get(String(threadID)) || { count: 0 };
160
+ ts.count++;
161
+ ts.lastActivity = Date.now();
162
+ this.dailyStats.threadStats.set(String(threadID), ts);
163
+ }
164
+ }
165
+
166
+ isDailyLimitReached() {
167
+ return this.dailyStats.messageCount >= this.dailyStats.maxDailyMessages;
168
+ }
169
+
170
+ isHourlyLimitReached() {
171
+ const limit = this.warmup.active
172
+ ? this.warmup.maxMessagesPerHour
173
+ : this.hourlyBucket.maxPerHour;
174
+ return this.hourlyBucket.count >= limit;
175
+ }
176
+
177
+ /**
178
+ * Returns a human-readable warning if a volume limit has been reached.
179
+ * Returns null if all limits are within safe range.
180
+ */
181
+ checkVolumeLimit(threadID) {
182
+ if (this.isDailyLimitReached()) {
183
+ return `Daily message limit reached (${this.dailyStats.messageCount}/${this.dailyStats.maxDailyMessages}). Pausing to avoid suspension.`;
184
+ }
185
+ if (this.isHourlyLimitReached()) {
186
+ const limit = this.warmup.active ? this.warmup.maxMessagesPerHour : this.hourlyBucket.maxPerHour;
187
+ return `Hourly message limit reached (${this.hourlyBucket.count}/${limit}). Pausing to avoid suspension.`;
188
+ }
189
+ return null;
190
+ }
191
+
192
+ enableWarmup() {
193
+ this.warmup.active = true;
194
+ this.warmup.startedAt = Date.now();
195
+ setTimeout(() => {
196
+ this.warmup.active = false;
197
+ }, this.warmup.durationMs);
198
+ }
199
+
200
+ lockSessionFingerprint(ua, secChUa, platform, locale, timezone) {
201
+ if (!this.sessionFingerprint) {
202
+ this.sessionFingerprint = { ua, secChUa, platform, locale, timezone, lockedAt: Date.now() };
203
+ }
204
+ return this.sessionFingerprint;
205
+ }
206
+
207
+ getSessionFingerprint() {
208
+ return this.sessionFingerprint;
209
+ }
210
+
211
+ detectSuspensionSignal(text) {
212
+ if (!text || typeof text !== 'string') return false;
213
+ const lower = text.toLowerCase();
214
+ const found = SUSPENSION_SIGNALS.some(signal => lower.includes(signal));
215
+ if (found) {
216
+ this._onSuspensionSignalDetected();
217
+ }
218
+ return found;
219
+ }
220
+
221
+ _onSuspensionSignalDetected() {
222
+ const cb = this.suspensionCircuitBreaker;
223
+ cb.signalCount++;
224
+ cb.lastSignalAt = Date.now();
225
+
226
+ if (cb.signalCount >= cb.maxSignalsBeforeTrip) {
227
+ if (!cb.tripped) {
228
+ cb.tripped = true;
229
+ cb.trippedAt = Date.now();
230
+ const { utils } = this._getUtils();
231
+ utils && utils.warn && utils.warn("AntiSuspension",
232
+ `Circuit breaker TRIPPED after ${cb.signalCount} suspension signals. ` +
233
+ `Pausing all activity for ${cb.cooldownMs / 60000} minutes.`);
234
+ }
235
+ }
236
+ }
237
+
238
+ _getUtils() {
239
+ try {
240
+ return { utils: require('./index') };
241
+ } catch (_) {
242
+ return {};
243
+ }
244
+ }
245
+
246
+ isCircuitBreakerTripped() {
247
+ const cb = this.suspensionCircuitBreaker;
248
+ if (!cb.tripped) return false;
249
+ const elapsed = Date.now() - cb.trippedAt;
250
+ if (elapsed >= cb.cooldownMs) {
251
+ cb.tripped = false;
252
+ cb.signalCount = 0;
253
+ cb.trippedAt = null;
254
+ return false;
255
+ }
256
+ return true;
257
+ }
258
+
259
+ getCircuitBreakerRemainingMs() {
260
+ const cb = this.suspensionCircuitBreaker;
261
+ if (!cb.tripped) return 0;
262
+ return Math.max(0, cb.cooldownMs - (Date.now() - cb.trippedAt));
263
+ }
264
+
265
+ tripCircuitBreaker(reason, durationMs) {
266
+ const cb = this.suspensionCircuitBreaker;
267
+ cb.tripped = true;
268
+ cb.trippedAt = Date.now();
269
+ if (durationMs) cb.cooldownMs = durationMs;
270
+ cb.signalCount = cb.maxSignalsBeforeTrip;
271
+ const { utils } = this._getUtils();
272
+ utils && utils.warn && utils.warn("AntiSuspension",
273
+ `Circuit breaker manually tripped: ${reason || 'manual'}. ` +
274
+ `Cooldown: ${(cb.cooldownMs / 60000).toFixed(1)} min`);
275
+ }
276
+
277
+ resetCircuitBreaker() {
278
+ this.suspensionCircuitBreaker.tripped = false;
279
+ this.suspensionCircuitBreaker.signalCount = 0;
280
+ this.suspensionCircuitBreaker.trippedAt = null;
281
+ }
282
+
283
+ async simulateTyping(threadID, messageLength = 50) {
284
+ const wpm = 38 + Math.random() * 24;
285
+ const charsPerMs = (wpm * 5) / 60000;
286
+ const typingDelay = Math.min(1200, Math.max(150, messageLength / charsPerMs));
287
+ const jitter = (Math.random() - 0.5) * 120;
288
+ return Math.round(typingDelay + jitter);
289
+ }
290
+
291
+ async addSmartDelay() {
292
+ const base = 100 + Math.random() * 200;
293
+ const jitter = (Math.random() - 0.5) * 40;
294
+ const total = Math.max(60, base + jitter);
295
+ await new Promise(resolve => setTimeout(resolve, total));
296
+ }
297
+
298
+ /**
299
+ * Add a longer random delay when volume is running high.
300
+ * Helps avoid patterns that look like automated batch sends.
301
+ */
302
+ async addAdaptiveDelay(threadID) {
303
+ const threadCount = this.dailyStats.threadStats.get(String(threadID))?.count || 0;
304
+ const globalCount = this.dailyStats.messageCount;
305
+
306
+ let base = 100;
307
+ if (globalCount > 1000) base = 400;
308
+ else if (globalCount > 500) base = 250;
309
+
310
+ if (threadCount > 60) base += 150;
311
+
312
+ const jitter = Math.random() * base * 0.3;
313
+ const total = Math.max(60, base + jitter);
314
+ await new Promise(resolve => setTimeout(resolve, total));
315
+ }
316
+
317
+ async enforceThreadThrottling(threadID) {
318
+ const lastTime = this.lastActivity.get(String(threadID)) || 0;
319
+ const timeSinceLastMsg = Date.now() - lastTime;
320
+ const minInterval = this.threadDelayMs + Math.random() * 150;
321
+
322
+ if (timeSinceLastMsg < minInterval) {
323
+ const waitTime = minInterval - timeSinceLastMsg;
324
+ await new Promise(resolve => setTimeout(resolve, waitTime));
325
+ }
326
+
327
+ this.lastActivity.set(String(threadID), Date.now());
328
+ return Date.now() - lastTime;
329
+ }
330
+
331
+ async enforceMessageRate() {
332
+ await new Promise(resolve =>
333
+ setTimeout(resolve, this.messageDelayMs + Math.random() * 100)
334
+ );
335
+ }
336
+
337
+ getHumanizedHeaders() {
338
+ const { randomUserAgent } = require('./user-agents');
339
+ const fp = this.sessionFingerprint;
340
+ const ua = fp ? { userAgent: fp.ua, secChUa: fp.secChUa, secChUaPlatform: fp.platform } : randomUserAgent();
341
+ return {
342
+ 'User-Agent': ua.userAgent,
343
+ 'Accept-Language': (fp && fp.locale) || 'en-US,en;q=0.9',
344
+ 'Accept-Encoding': 'gzip, deflate, br',
345
+ 'DNT': '1',
346
+ 'Connection': 'keep-alive',
347
+ 'Upgrade-Insecure-Requests': '1',
348
+ 'Sec-Ch-Ua': ua.secChUa || '',
349
+ 'Sec-Ch-Ua-Mobile': '?0',
350
+ 'Sec-Ch-Ua-Platform': ua.secChUaPlatform || '"Windows"',
351
+ 'Sec-Fetch-Dest': 'document',
352
+ 'Sec-Fetch-Mode': 'navigate',
353
+ 'Sec-Fetch-Site': 'none',
354
+ 'Cache-Control': 'max-age=0'
355
+ };
356
+ }
357
+
358
+ rotateUserAgent() {
359
+ const { randomUserAgent } = require('./user-agents');
360
+ if (this.sessionFingerprint) return this.sessionFingerprint.ua;
361
+ return randomUserAgent().userAgent;
362
+ }
363
+
364
+ trackLoginAttempt() {
365
+ this.loginAttempts++;
366
+ const isLocked = this.loginAttempts >= this.maxLoginAttempts;
367
+ return {
368
+ attempt: this.loginAttempts,
369
+ isLocked,
370
+ cooldownMs: isLocked ? this.loginCooldown : 0,
371
+ nextAttemptAt: isLocked ? Date.now() + this.loginCooldown : null
372
+ };
373
+ }
374
+
375
+ resetLoginAttempts() {
376
+ this.loginAttempts = 0;
377
+ }
378
+
379
+ checkAccountHealth(lastError) {
380
+ const isSuspected = lastError &&
381
+ SUSPENSION_SIGNALS.some(indicator =>
382
+ (lastError.message || '').toLowerCase().includes(indicator)
383
+ );
384
+
385
+ if (isSuspected) {
386
+ this._onSuspensionSignalDetected();
387
+ }
388
+
389
+ return {
390
+ suspended: isSuspected,
391
+ circuitBreakerTripped: this.isCircuitBreakerTripped(),
392
+ dailyLimitReached: this.isDailyLimitReached(),
393
+ hourlyLimitReached: this.isHourlyLimitReached(),
394
+ lastCheck: Date.now(),
395
+ recommendedAction: isSuspected ? 'WAIT_AND_RETRY' : 'CONTINUE',
396
+ circuitBreakerRemainingMs: this.getCircuitBreakerRemainingMs()
397
+ };
398
+ }
399
+
400
+ getRealisticActivityPattern() {
401
+ const hour = new Date().getHours();
402
+ const isNight = hour < 6 || hour >= 22;
403
+
404
+ return {
405
+ messageFrequency: isNight ? 'low' : 'normal',
406
+ nextActionDelayMs: isNight
407
+ ? 4000 + Math.random() * 6000
408
+ : 300 + Math.random() * 1200,
409
+ isActiveHours: !isNight,
410
+ recommendedCooldown: isNight ? 10000 : 1500
411
+ };
412
+ }
413
+
414
+ async safeRetry(fn, maxRetries = 3) {
415
+ for (let i = 0; i < maxRetries; i++) {
416
+ if (this.isCircuitBreakerTripped()) {
417
+ throw new Error('Circuit breaker is tripped. Stopping retries to protect account.');
418
+ }
419
+ try {
420
+ return await fn();
421
+ } catch (error) {
422
+ const msg = (error.message || '').toLowerCase();
423
+ const isSuspensionError = SUSPENSION_SIGNALS.some(s => msg.includes(s));
424
+ if (isSuspensionError) {
425
+ this._onSuspensionSignalDetected();
426
+ throw error;
427
+ }
428
+ if (i === maxRetries - 1) throw error;
429
+ const delay = Math.pow(2, i + 1) * 1000 + Math.random() * 800;
430
+ await new Promise(resolve => setTimeout(resolve, delay));
431
+ }
432
+ }
433
+ }
434
+
435
+ async batchOperations(operations) {
436
+ const results = [];
437
+ for (let i = 0; i < operations.length; i++) {
438
+ if (this.isCircuitBreakerTripped()) {
439
+ throw new Error('Circuit breaker tripped during batch operation.');
440
+ }
441
+ results.push(await this.safeRetry(() => operations[i]()));
442
+ if (i < operations.length - 1) {
443
+ await this.addSmartDelay();
444
+ }
445
+ }
446
+ return results;
447
+ }
448
+
449
+ /**
450
+ * Prepare before sending — single delay model.
451
+ * Enforces thread throttle and volume limits, respects circuit breaker.
452
+ * If volume limits are reached, throws to protect the account.
453
+ */
454
+ async prepareBeforeMessage(threadID, message) {
455
+ if (this.isCircuitBreakerTripped()) {
456
+ const remaining = this.getCircuitBreakerRemainingMs();
457
+ const waitMs = Math.min(remaining, 8000);
458
+ if (waitMs > 0) await new Promise(resolve => setTimeout(resolve, waitMs));
459
+ }
460
+
461
+ const volumeWarning = this.checkVolumeLimit(threadID);
462
+ if (volumeWarning) {
463
+ const { utils } = this._getUtils();
464
+ utils && utils.warn && utils.warn("AntiSuspension", volumeWarning);
465
+ // Add a safety pause instead of hard-blocking so callers can decide
466
+ await new Promise(resolve => setTimeout(resolve, 5000 + Math.random() * 3000));
467
+ }
468
+
469
+ await this.enforceThreadThrottling(threadID);
470
+ await this.addAdaptiveDelay(threadID);
471
+ this._incrementDailyStats(threadID);
472
+ }
473
+
474
+ getConfig() {
475
+ return {
476
+ messageDelayMs: this.messageDelayMs,
477
+ threadDelayMs: this.threadDelayMs,
478
+ maxLoginAttempts: this.maxLoginAttempts,
479
+ loginCooldownMs: this.loginCooldown,
480
+ circuitBreaker: {
481
+ tripped: this.suspensionCircuitBreaker.tripped,
482
+ signalCount: this.suspensionCircuitBreaker.signalCount,
483
+ remainingMs: this.getCircuitBreakerRemainingMs()
484
+ },
485
+ dailyStats: {
486
+ messageCount: this.dailyStats.messageCount,
487
+ maxDailyMessages: this.dailyStats.maxDailyMessages
488
+ },
489
+ hourlyStats: {
490
+ count: this.hourlyBucket.count,
491
+ maxPerHour: this.hourlyBucket.maxPerHour
492
+ },
493
+ warmupActive: this.warmup.active,
494
+ features: {
495
+ typeSimulation: true,
496
+ delayRandomization: true,
497
+ adaptiveDelay: true,
498
+ userAgentRotation: true,
499
+ activityPatternTracking: true,
500
+ autoSuspensionDetection: true,
501
+ exponentialBackoff: true,
502
+ circuitBreaker: true,
503
+ dailyVolumeLimiting: true,
504
+ hourlyVolumeLimiting: true,
505
+ sessionFingerprintLock: true,
506
+ warmupMode: true,
507
+ volumeWarnings: true
508
+ }
509
+ };
510
+ }
511
+
512
+ destroy() {
513
+ this._clearIntervals();
514
+ this.activityThrottler.clear();
515
+ this.lastActivity.clear();
516
+ this.typing.clear();
517
+ this.dailyStats.threadStats.clear();
518
+ }
519
+ }
520
+
521
+ const globalAntiSuspension = new AntiSuspension();
522
+
523
+ module.exports = {
524
+ AntiSuspension,
525
+ globalAntiSuspension,
526
+ SUSPENSION_SIGNALS,
527
+ initAntiSuspension: () => globalAntiSuspension,
528
+ getAntiSuspensionConfig: () => globalAntiSuspension.getConfig()
529
+ };
@@ -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
+ };