fca-neokex-fix 1.0.1

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 (114) hide show
  1. package/CHANGELOG.md +220 -0
  2. package/LICENSE +26 -0
  3. package/README.md +346 -0
  4. package/THEME_FEATURES.md +137 -0
  5. package/examples/README.md +131 -0
  6. package/examples/apply-ai-theme.js +127 -0
  7. package/examples/check-current-theme.js +74 -0
  8. package/examples/simple-bot.js +114 -0
  9. package/examples/test-bot.js +752 -0
  10. package/examples/test-logging.js +85 -0
  11. package/examples/theme-usage-example.js +53 -0
  12. package/index.js +2 -0
  13. package/package.json +105 -0
  14. package/src/apis/addExternalModule.js +24 -0
  15. package/src/apis/addUserToGroup.js +108 -0
  16. package/src/apis/changeAdminStatus.js +148 -0
  17. package/src/apis/changeArchivedStatus.js +61 -0
  18. package/src/apis/changeAvatar.js +103 -0
  19. package/src/apis/changeBio.js +69 -0
  20. package/src/apis/changeBlockedStatus.js +54 -0
  21. package/src/apis/changeGroupImage.js +136 -0
  22. package/src/apis/changeThreadColor.js +116 -0
  23. package/src/apis/comment.js +207 -0
  24. package/src/apis/createAITheme.js +129 -0
  25. package/src/apis/createNewGroup.js +79 -0
  26. package/src/apis/createPoll.js +73 -0
  27. package/src/apis/deleteMessage.js +44 -0
  28. package/src/apis/deleteThread.js +52 -0
  29. package/src/apis/editMessage.js +70 -0
  30. package/src/apis/emoji.js +124 -0
  31. package/src/apis/fetchThemeData.js +65 -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 +104 -0
  44. package/src/apis/getThemeInfo.js +96 -0
  45. package/src/apis/getThreadHistory.js +239 -0
  46. package/src/apis/getThreadInfo.js +257 -0
  47. package/src/apis/getThreadList.js +222 -0
  48. package/src/apis/getThreadPictures.js +58 -0
  49. package/src/apis/getUserID.js +83 -0
  50. package/src/apis/getUserInfo.js +495 -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 +796 -0
  57. package/src/apis/listenSpeed.js +170 -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 +41 -0
  62. package/src/apis/markAsSeen.js +70 -0
  63. package/src/apis/mqttDeltaValue.js +330 -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 +141 -0
  68. package/src/apis/produceMetaTheme.js +180 -0
  69. package/src/apis/realtime.js +161 -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 +281 -0
  74. package/src/apis/sendMessageMqtt.js +188 -0
  75. package/src/apis/sendTypingIndicator.js +41 -0
  76. package/src/apis/setMessageReaction.js +27 -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 +17 -0
  87. package/src/database/appStateBackup.js +189 -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/engine/client.js +91 -0
  94. package/src/engine/models/buildAPI.js +109 -0
  95. package/src/engine/models/loginHelper.js +326 -0
  96. package/src/engine/models/setOptions.js +53 -0
  97. package/src/utils/auth-helpers.js +149 -0
  98. package/src/utils/autoReLogin.js +169 -0
  99. package/src/utils/axios.js +290 -0
  100. package/src/utils/clients.js +270 -0
  101. package/src/utils/constants.js +396 -0
  102. package/src/utils/formatters/data/formatAttachment.js +370 -0
  103. package/src/utils/formatters/data/formatDelta.js +153 -0
  104. package/src/utils/formatters/index.js +159 -0
  105. package/src/utils/formatters/value/formatCookie.js +91 -0
  106. package/src/utils/formatters/value/formatDate.js +36 -0
  107. package/src/utils/formatters/value/formatID.js +16 -0
  108. package/src/utils/formatters.js +1067 -0
  109. package/src/utils/headers.js +199 -0
  110. package/src/utils/index.js +151 -0
  111. package/src/utils/monitoring.js +358 -0
  112. package/src/utils/rateLimiter.js +380 -0
  113. package/src/utils/tokenRefresh.js +311 -0
  114. package/src/utils/user-agents.js +238 -0
@@ -0,0 +1,380 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * Adaptive Rate Limiting Manager
5
+ * Prevents overwhelming Facebook servers and manages request cooldowns
6
+ * Enhanced with behavioral modeling and context-aware throttling
7
+ */
8
+
9
+ class RateLimiter {
10
+ constructor() {
11
+ this.threadCooldowns = new Map();
12
+ this.endpointCooldowns = new Map();
13
+ this.requestCounts = new Map();
14
+ this.errorCache = new Map();
15
+ this.requestHistory = new Map();
16
+ this.behavioralMetrics = new Map();
17
+ this.checkpointMode = false;
18
+ this.ERROR_CACHE_TTL = 300000; // 5 minutes
19
+ this.COOLDOWN_DURATION = 60000; // 60 seconds
20
+ this.MAX_REQUESTS_PER_MINUTE = 60;
21
+ this.MAX_CONCURRENT_REQUESTS = 8;
22
+ this.BURST_THRESHOLD = 5;
23
+ this.BURST_WINDOW_MS = 10000;
24
+ this.activeRequests = 0;
25
+ this.QUIET_HOURS = { start: 2, end: 6 };
26
+ }
27
+
28
+ /**
29
+ * Check if a thread is on cooldown
30
+ * @param {string} threadID - Thread identifier
31
+ * @returns {boolean}
32
+ */
33
+ isThreadOnCooldown(threadID) {
34
+ const cooldownEnd = this.threadCooldowns.get(threadID);
35
+ if (!cooldownEnd) return false;
36
+
37
+ const now = Date.now();
38
+ if (now >= cooldownEnd) {
39
+ this.threadCooldowns.delete(threadID);
40
+ return false;
41
+ }
42
+ return true;
43
+ }
44
+
45
+ /**
46
+ * Put a thread on cooldown
47
+ * @param {string} threadID - Thread identifier
48
+ * @param {number} duration - Cooldown duration in milliseconds
49
+ */
50
+ setThreadCooldown(threadID, duration = null) {
51
+ const cooldownDuration = duration || this.COOLDOWN_DURATION;
52
+ this.threadCooldowns.set(threadID, Date.now() + cooldownDuration);
53
+ }
54
+
55
+ /**
56
+ * Check if an endpoint is on cooldown
57
+ * @param {string} endpoint - Endpoint identifier
58
+ * @returns {boolean}
59
+ */
60
+ isEndpointOnCooldown(endpoint) {
61
+ const cooldownEnd = this.endpointCooldowns.get(endpoint);
62
+ if (!cooldownEnd) return false;
63
+
64
+ const now = Date.now();
65
+ if (now >= cooldownEnd) {
66
+ this.endpointCooldowns.delete(endpoint);
67
+ return false;
68
+ }
69
+ return true;
70
+ }
71
+
72
+ /**
73
+ * Put an endpoint on cooldown
74
+ * @param {string} endpoint - Endpoint identifier
75
+ * @param {number} duration - Cooldown duration in milliseconds
76
+ */
77
+ setEndpointCooldown(endpoint, duration = null) {
78
+ const cooldownDuration = duration || this.COOLDOWN_DURATION;
79
+ this.endpointCooldowns.set(endpoint, Date.now() + cooldownDuration);
80
+ }
81
+
82
+ /**
83
+ * Check if an error should be suppressed from logging
84
+ * @param {string} key - Error cache key
85
+ * @returns {boolean}
86
+ */
87
+ shouldSuppressError(key) {
88
+ const cachedTime = this.errorCache.get(key);
89
+ if (!cachedTime) {
90
+ this.errorCache.set(key, Date.now());
91
+ return false;
92
+ }
93
+
94
+ if (Date.now() - cachedTime > this.ERROR_CACHE_TTL) {
95
+ this.errorCache.set(key, Date.now());
96
+ return false;
97
+ }
98
+ return true;
99
+ }
100
+
101
+ /**
102
+ * Adaptive delay based on error type and retry count
103
+ * @param {number} retryCount - Current retry attempt
104
+ * @param {number} errorCode - Facebook error code
105
+ * @returns {number} Delay in milliseconds
106
+ */
107
+ getAdaptiveDelay(retryCount, errorCode = null) {
108
+ const baseDelays = [2000, 5000, 10000];
109
+
110
+ if (errorCode === 1545012 || errorCode === 1675004) {
111
+ return baseDelays[Math.min(retryCount, baseDelays.length - 1)] * 1.5;
112
+ }
113
+
114
+ return baseDelays[Math.min(retryCount, baseDelays.length - 1)];
115
+ }
116
+
117
+ /**
118
+ * Get current hour for circadian rhythm
119
+ * @returns {number}
120
+ */
121
+ getCurrentHour() {
122
+ return new Date().getHours();
123
+ }
124
+
125
+ /**
126
+ * Check if currently in quiet hours
127
+ * @returns {boolean}
128
+ */
129
+ isQuietHours() {
130
+ const hour = this.getCurrentHour();
131
+ return hour >= this.QUIET_HOURS.start && hour < this.QUIET_HOURS.end;
132
+ }
133
+
134
+ /**
135
+ * Get circadian multiplier based on time of day
136
+ * @returns {number}
137
+ */
138
+ getCircadianMultiplier() {
139
+ const hour = this.getCurrentHour();
140
+ if (hour >= 2 && hour < 6) return 3.0;
141
+ if (hour >= 0 && hour < 2) return 2.0;
142
+ if (hour >= 6 && hour < 9) return 1.2;
143
+ if (hour >= 9 && hour < 18) return 1.0;
144
+ if (hour >= 18 && hour < 22) return 1.1;
145
+ return 1.5;
146
+ }
147
+
148
+ /**
149
+ * Get context-aware delay based on conversation context
150
+ * @param {object} context - Request context
151
+ * @returns {Promise<number>}
152
+ */
153
+ async getContextAwareDelay(context = {}) {
154
+ const { threadID, messageType = 'text', hasAttachment = false, isReply = false } = context;
155
+
156
+ let baseDelay = 200;
157
+
158
+ if (hasAttachment) {
159
+ baseDelay += Math.random() * 1500 + 500;
160
+ }
161
+
162
+ if (isReply) {
163
+ baseDelay += Math.random() * 300;
164
+ }
165
+
166
+ const circadianMultiplier = this.getCircadianMultiplier();
167
+ baseDelay *= circadianMultiplier;
168
+
169
+ if (this.checkpointMode) {
170
+ baseDelay *= 5;
171
+ }
172
+
173
+ if (threadID && this.detectBurst(threadID)) {
174
+ baseDelay *= 2;
175
+ }
176
+
177
+ const variance = baseDelay * 0.3;
178
+ const finalDelay = baseDelay + (Math.random() * variance - variance / 2);
179
+
180
+ return Math.max(200, Math.floor(finalDelay));
181
+ }
182
+
183
+ /**
184
+ * Detect burst activity in thread
185
+ * @param {string} threadID
186
+ * @returns {boolean}
187
+ */
188
+ detectBurst(threadID) {
189
+ const now = Date.now();
190
+ const history = this.requestHistory.get(threadID) || [];
191
+ const recentRequests = history.filter(time => (now - time) < this.BURST_WINDOW_MS);
192
+ return recentRequests.length >= this.BURST_THRESHOLD;
193
+ }
194
+
195
+ /**
196
+ * Record request for burst detection
197
+ * @param {string} threadID
198
+ */
199
+ recordRequest(threadID) {
200
+ const now = Date.now();
201
+ const history = this.requestHistory.get(threadID) || [];
202
+ history.push(now);
203
+ const cleaned = history.filter(time => (now - time) < this.BURST_WINDOW_MS);
204
+ this.requestHistory.set(threadID, cleaned);
205
+ }
206
+
207
+ /**
208
+ * Track behavioral metrics
209
+ * @param {string} threadID
210
+ * @param {string} action
211
+ * @param {object} metadata
212
+ */
213
+ trackBehavior(threadID, action, metadata = {}) {
214
+ const metrics = this.behavioralMetrics.get(threadID) || {
215
+ messageCount: 0,
216
+ replyCount: 0,
217
+ attachmentCount: 0,
218
+ lastActivity: 0,
219
+ avgResponseTime: 0,
220
+ responseTimes: []
221
+ };
222
+
223
+ switch (action) {
224
+ case 'message':
225
+ metrics.messageCount++;
226
+ break;
227
+ case 'reply':
228
+ metrics.replyCount++;
229
+ if (metadata.responseTime) {
230
+ metrics.responseTimes.push(metadata.responseTime);
231
+ if (metrics.responseTimes.length > 10) {
232
+ metrics.responseTimes.shift();
233
+ }
234
+ metrics.avgResponseTime = metrics.responseTimes.reduce((a, b) => a + b, 0) / metrics.responseTimes.length;
235
+ }
236
+ break;
237
+ case 'attachment':
238
+ metrics.attachmentCount++;
239
+ break;
240
+ }
241
+
242
+ metrics.lastActivity = Date.now();
243
+ this.behavioralMetrics.set(threadID, metrics);
244
+ }
245
+
246
+ /**
247
+ * Add stochastic delay to simulate human behavior
248
+ * @param {object} context - Request context
249
+ * @returns {Promise<void>}
250
+ */
251
+ async addStochasticDelay(context = {}) {
252
+ const delay = await this.getContextAwareDelay(context);
253
+ await new Promise(resolve => setTimeout(resolve, delay));
254
+ if (context.threadID) {
255
+ this.recordRequest(context.threadID);
256
+ }
257
+ }
258
+
259
+ /**
260
+ * Add humanized delay to simulate human behavior
261
+ * @param {number} min - Minimum delay in milliseconds
262
+ * @param {number} max - Maximum delay in milliseconds
263
+ * @returns {Promise<void>}
264
+ */
265
+ async addHumanizedDelay(min = 200, max = 600) {
266
+ const delay = Math.floor(Math.random() * (max - min + 1)) + min;
267
+ await new Promise(resolve => setTimeout(resolve, delay));
268
+ }
269
+
270
+ /**
271
+ * Enter checkpoint-aware backoff mode
272
+ * @param {number} duration - Duration in milliseconds
273
+ */
274
+ enterCheckpointMode(duration = 3600000) {
275
+ this.checkpointMode = true;
276
+ setTimeout(() => {
277
+ this.checkpointMode = false;
278
+ }, duration);
279
+ }
280
+
281
+ /**
282
+ * Check if can make request based on rate limiting and add humanized delay
283
+ * @param {boolean|object} skipHumanDelayOrContext - Skip the humanized delay if boolean, or context object
284
+ * @returns {Promise<void>}
285
+ */
286
+ async checkRateLimit(skipHumanDelayOrContext = false) {
287
+ while (this.activeRequests >= this.MAX_CONCURRENT_REQUESTS) {
288
+ await new Promise(resolve => setTimeout(resolve, 100));
289
+ }
290
+
291
+ const isContextObject = typeof skipHumanDelayOrContext === 'object' && skipHumanDelayOrContext !== null;
292
+ const skipHumanDelay = typeof skipHumanDelayOrContext === 'boolean' ? skipHumanDelayOrContext : false;
293
+ const context = isContextObject ? skipHumanDelayOrContext : {};
294
+
295
+ if (!skipHumanDelay) {
296
+ if (isContextObject) {
297
+ await this.addStochasticDelay(context);
298
+ } else {
299
+ await this.addHumanizedDelay();
300
+ }
301
+ }
302
+
303
+ this.activeRequests++;
304
+
305
+ setTimeout(() => {
306
+ this.activeRequests = Math.max(0, this.activeRequests - 1);
307
+ }, 1000);
308
+ }
309
+
310
+ /**
311
+ * Clear expired entries from cache
312
+ */
313
+ cleanup() {
314
+ const now = Date.now();
315
+
316
+ for (const [key, time] of this.errorCache.entries()) {
317
+ if (now - time > this.ERROR_CACHE_TTL) {
318
+ this.errorCache.delete(key);
319
+ }
320
+ }
321
+
322
+ for (const [key, time] of this.threadCooldowns.entries()) {
323
+ if (now >= time) {
324
+ this.threadCooldowns.delete(key);
325
+ }
326
+ }
327
+
328
+ for (const [key, time] of this.endpointCooldowns.entries()) {
329
+ if (now >= time) {
330
+ this.endpointCooldowns.delete(key);
331
+ }
332
+ }
333
+
334
+ for (const [threadID, history] of this.requestHistory.entries()) {
335
+ const cleaned = history.filter(time => (now - time) < this.BURST_WINDOW_MS);
336
+ if (cleaned.length === 0) {
337
+ this.requestHistory.delete(threadID);
338
+ } else {
339
+ this.requestHistory.set(threadID, cleaned);
340
+ }
341
+ }
342
+
343
+ for (const [threadID, metrics] of this.behavioralMetrics.entries()) {
344
+ if ((now - metrics.lastActivity) > 3600000) {
345
+ this.behavioralMetrics.delete(threadID);
346
+ }
347
+ }
348
+ }
349
+
350
+ /**
351
+ * Get cooldown remaining time for thread
352
+ * @param {string} threadID - Thread identifier
353
+ * @returns {number} Remaining milliseconds
354
+ */
355
+ getCooldownRemaining(threadID) {
356
+ const cooldownEnd = this.threadCooldowns.get(threadID);
357
+ if (!cooldownEnd) return 0;
358
+ return Math.max(0, cooldownEnd - Date.now());
359
+ }
360
+
361
+ /**
362
+ * Get cooldown remaining time for endpoint
363
+ * @param {string} endpoint - Endpoint identifier
364
+ * @returns {number} Remaining milliseconds
365
+ */
366
+ getEndpointCooldownRemaining(endpoint) {
367
+ const cooldownEnd = this.endpointCooldowns.get(endpoint);
368
+ if (!cooldownEnd) return 0;
369
+ return Math.max(0, cooldownEnd - Date.now());
370
+ }
371
+ }
372
+
373
+ const globalRateLimiter = new RateLimiter();
374
+
375
+ setInterval(() => globalRateLimiter.cleanup(), 60000);
376
+
377
+ module.exports = {
378
+ RateLimiter,
379
+ globalRateLimiter
380
+ };
@@ -0,0 +1,311 @@
1
+ "use strict";
2
+
3
+ const utils = require('./index');
4
+
5
+ /**
6
+ * Token Refresh Manager
7
+ * Automatically refreshes fb_dtsg, lsd, and other tokens to prevent expiration
8
+ */
9
+
10
+ class TokenRefreshManager {
11
+ constructor() {
12
+ this.refreshInterval = null;
13
+ this.REFRESH_INTERVAL_MS = 24 * 60 * 60 * 1000; // 24 hours
14
+ this.lastRefresh = Date.now();
15
+ this.failureCount = 0;
16
+ this.MAX_FAILURES = 3;
17
+ this.onSessionExpiry = null;
18
+ this.onCheckpointDetected = null;
19
+ this.autoReLoginManager = null;
20
+ this.api = null;
21
+ this.fbLink = null;
22
+ this.ERROR_RETRIEVING = null;
23
+ }
24
+
25
+ /**
26
+ * Start automatic token refresh
27
+ * @param {Object} ctx - Application context
28
+ * @param {Object} defaultFuncs - Default functions
29
+ * @param {string} fbLink - Facebook link
30
+ */
31
+ startAutoRefresh(ctx, defaultFuncs, fbLink) {
32
+ if (this.refreshInterval) {
33
+ clearInterval(this.refreshInterval);
34
+ }
35
+
36
+ this.refreshInterval = setInterval(async () => {
37
+ try {
38
+ await this.refreshTokens(ctx, defaultFuncs, fbLink);
39
+ utils.log("TokenRefresh", "Tokens refreshed successfully");
40
+ } catch (error) {
41
+ utils.error("TokenRefresh", "Failed to refresh tokens:", error.message);
42
+ }
43
+ }, this.REFRESH_INTERVAL_MS);
44
+
45
+ utils.log("TokenRefresh", "Auto-refresh enabled (every 24 hours)");
46
+ }
47
+
48
+ /**
49
+ * Set API and link references for auto-relogin
50
+ * @param {Object} api - API instance
51
+ * @param {string} fbLink - Facebook link
52
+ * @param {string} ERROR_RETRIEVING - Error message
53
+ */
54
+ setAPIReferences(api, fbLink, ERROR_RETRIEVING) {
55
+ this.api = api;
56
+ this.fbLink = fbLink;
57
+ this.ERROR_RETRIEVING = ERROR_RETRIEVING;
58
+ }
59
+
60
+ /**
61
+ * Detect checkpoint or session errors with specific markers
62
+ * @param {string} html - HTML response
63
+ * @param {Error} error - Error object
64
+ * @param {string} responseUrl - Response URL for redirect detection
65
+ * @returns {object} Detection result
66
+ */
67
+ detectCheckpoint(html, error, responseUrl = null) {
68
+ if (responseUrl) {
69
+ if (responseUrl.includes('/checkpoint/') || responseUrl.includes('/login/') || responseUrl.includes('/login.php')) {
70
+ return { detected: true, type: 'url_redirect' };
71
+ }
72
+ }
73
+
74
+ if (html) {
75
+ if (html.includes('id="checkpoint_') || html.includes('name="checkpoint"')) {
76
+ return { detected: true, type: 'checkpoint_form' };
77
+ }
78
+
79
+ if (html.includes('id="login_form"') && html.includes('name="email"') && html.includes('name="pass"')) {
80
+ return { detected: true, type: 'login_page' };
81
+ }
82
+
83
+ if (html.includes('security check') && html.includes('verify')) {
84
+ return { detected: true, type: 'security_checkpoint' };
85
+ }
86
+
87
+ if (html.includes('session has expired') || html.includes('Please log in')) {
88
+ return { detected: true, type: 'session_expired' };
89
+ }
90
+ }
91
+
92
+ if (error && error.message) {
93
+ const errorMsg = error.message.toLowerCase();
94
+ if (errorMsg.includes('checkpoint')) {
95
+ return { detected: true, type: 'error_checkpoint' };
96
+ }
97
+ if (errorMsg.includes('session expired') || errorMsg.includes('session_expired')) {
98
+ return { detected: true, type: 'error_session_expired' };
99
+ }
100
+ if (errorMsg.includes('invalid session') || errorMsg.includes('invalid_session')) {
101
+ return { detected: true, type: 'error_invalid_session' };
102
+ }
103
+ }
104
+
105
+ return { detected: false, type: null };
106
+ }
107
+
108
+ /**
109
+ * Manually refresh tokens with retry logic
110
+ * @param {Object} ctx - Application context
111
+ * @param {Object} defaultFuncs - Default functions
112
+ * @param {string} fbLink - Facebook link
113
+ * @param {number} retryCount - Current retry attempt (internal use)
114
+ * @returns {Promise<boolean>}
115
+ */
116
+ async refreshTokens(ctx, defaultFuncs, fbLink, retryCount = 0) {
117
+ const MAX_RETRIES = 3;
118
+ const RETRY_DELAYS = [2000, 5000, 10000];
119
+
120
+ try {
121
+ const resp = await utils.get(fbLink, ctx.jar, null, ctx.globalOptions, { noRef: true });
122
+
123
+ const html = resp.body;
124
+ const responseUrl = resp.request && resp.request.uri ? resp.request.uri.href : null;
125
+
126
+ if (!html) {
127
+ throw new Error("Empty response from Facebook");
128
+ }
129
+
130
+ const checkpoint = this.detectCheckpoint(html, null, responseUrl);
131
+ if (checkpoint.detected) {
132
+ utils.warn("TokenRefresh", `Checkpoint detected: ${checkpoint.type}`);
133
+
134
+ if (this.onCheckpointDetected && typeof this.onCheckpointDetected === 'function') {
135
+ this.onCheckpointDetected(checkpoint.type);
136
+ }
137
+
138
+ if (this.autoReLoginManager && this.autoReLoginManager.isEnabled() && this.api) {
139
+ utils.log("TokenRefresh", "Triggering auto-relogin due to checkpoint...");
140
+ const reloginSuccess = await this.attemptRelogin(this.api, this.fbLink, this.ERROR_RETRIEVING);
141
+ if (reloginSuccess) {
142
+ return true;
143
+ }
144
+ }
145
+
146
+ throw new Error(`Checkpoint required: ${checkpoint.type}`);
147
+ }
148
+
149
+ const dtsgMatch = html.match(/"DTSGInitialData",\[],{"token":"([^"]+)"/);
150
+ if (dtsgMatch) {
151
+ ctx.fb_dtsg = dtsgMatch[1];
152
+ ctx.ttstamp = "2";
153
+ for (let i = 0; i < ctx.fb_dtsg.length; i++) {
154
+ ctx.ttstamp += ctx.fb_dtsg.charCodeAt(i);
155
+ }
156
+ } else {
157
+ throw new Error("Failed to extract fb_dtsg token");
158
+ }
159
+
160
+ const lsdMatch = html.match(/"LSD",\[],{"token":"([^"]+)"/);
161
+ if (lsdMatch) {
162
+ ctx.lsd = lsdMatch[1];
163
+ }
164
+
165
+ const jazoestMatch = html.match(/jazoest=(\d+)/);
166
+ if (jazoestMatch) {
167
+ ctx.jazoest = jazoestMatch[1];
168
+ }
169
+
170
+ const revisionMatch = html.match(/"client_revision":(\d+)/);
171
+ if (revisionMatch) {
172
+ ctx.__rev = revisionMatch[1];
173
+ }
174
+
175
+ this.lastRefresh = Date.now();
176
+ this.failureCount = 0;
177
+ return true;
178
+ } catch (error) {
179
+ this.failureCount++;
180
+ utils.error("TokenRefresh", `Refresh failed (attempt ${retryCount + 1}/${MAX_RETRIES + 1}):`, error.message);
181
+
182
+ const checkpoint = this.detectCheckpoint(null, error);
183
+ if (checkpoint.detected) {
184
+ utils.warn("TokenRefresh", `Checkpoint detected in error: ${checkpoint.type}`);
185
+
186
+ if (this.onCheckpointDetected && typeof this.onCheckpointDetected === 'function') {
187
+ this.onCheckpointDetected(checkpoint.type);
188
+ }
189
+ }
190
+
191
+ if (this.failureCount >= this.MAX_FAILURES) {
192
+ utils.error("TokenRefresh", `Maximum failures (${this.MAX_FAILURES}) reached. Session may be expired.`);
193
+ if (this.onSessionExpiry && typeof this.onSessionExpiry === 'function') {
194
+ this.onSessionExpiry(error);
195
+ }
196
+ return false;
197
+ }
198
+
199
+ if (retryCount < MAX_RETRIES) {
200
+ const delay = RETRY_DELAYS[retryCount];
201
+ utils.log("TokenRefresh", `Retrying in ${delay}ms...`);
202
+ await new Promise(resolve => setTimeout(resolve, delay));
203
+ return await this.refreshTokens(ctx, defaultFuncs, fbLink, retryCount + 1);
204
+ }
205
+
206
+ return false;
207
+ }
208
+ }
209
+
210
+ /**
211
+ * Set auto-relogin manager reference
212
+ * @param {Object} autoReLoginManager - AutoReLoginManager instance
213
+ */
214
+ setAutoReLoginManager(autoReLoginManager) {
215
+ this.autoReLoginManager = autoReLoginManager;
216
+ }
217
+
218
+ /**
219
+ * Attempt auto-relogin using the AutoReLoginManager
220
+ * @param {Object} api - API instance
221
+ * @param {string} fbLink - Facebook link
222
+ * @param {string} ERROR_RETRIEVING - Error message for retrieving
223
+ * @returns {Promise<boolean>}
224
+ */
225
+ async attemptRelogin(api, fbLink, ERROR_RETRIEVING) {
226
+ try {
227
+ if (!this.autoReLoginManager || !this.autoReLoginManager.isEnabled()) {
228
+ utils.warn("TokenRefresh", "AutoReLoginManager not available or not enabled");
229
+ return false;
230
+ }
231
+
232
+ utils.log("TokenRefresh", "Triggering auto-relogin via AutoReLoginManager");
233
+ const success = await this.autoReLoginManager.handleSessionExpiry(api, fbLink, ERROR_RETRIEVING);
234
+
235
+ if (success) {
236
+ this.resetFailureCount();
237
+ utils.log("TokenRefresh", "Auto-relogin completed successfully");
238
+ return true;
239
+ } else {
240
+ utils.error("TokenRefresh", "Auto-relogin failed");
241
+ return false;
242
+ }
243
+ } catch (error) {
244
+ utils.error("TokenRefresh", "Auto-relogin error:", error.message);
245
+ return false;
246
+ }
247
+ }
248
+
249
+ /**
250
+ * Set callback for checkpoint detection
251
+ * @param {Function} callback - Callback function to trigger on checkpoint
252
+ */
253
+ setCheckpointCallback(callback) {
254
+ this.onCheckpointDetected = callback;
255
+ }
256
+
257
+ /**
258
+ * Stop automatic token refresh
259
+ */
260
+ stopAutoRefresh() {
261
+ if (this.refreshInterval) {
262
+ clearInterval(this.refreshInterval);
263
+ this.refreshInterval = null;
264
+ utils.log("TokenRefresh", "Auto-refresh disabled");
265
+ }
266
+ }
267
+
268
+ /**
269
+ * Get time until next refresh
270
+ * @returns {number} Milliseconds until next refresh
271
+ */
272
+ getTimeUntilNextRefresh() {
273
+ if (!this.refreshInterval) return -1;
274
+ return Math.max(0, this.REFRESH_INTERVAL_MS - (Date.now() - this.lastRefresh));
275
+ }
276
+
277
+ /**
278
+ * Check if tokens need immediate refresh
279
+ * @returns {boolean}
280
+ */
281
+ needsImmediateRefresh() {
282
+ return (Date.now() - this.lastRefresh) >= this.REFRESH_INTERVAL_MS;
283
+ }
284
+
285
+ /**
286
+ * Set callback for session expiry detection
287
+ * @param {Function} callback - Callback function to trigger on session expiry
288
+ */
289
+ setSessionExpiryCallback(callback) {
290
+ this.onSessionExpiry = callback;
291
+ }
292
+
293
+ /**
294
+ * Reset failure count (useful after successful re-login)
295
+ */
296
+ resetFailureCount() {
297
+ this.failureCount = 0;
298
+ }
299
+
300
+ /**
301
+ * Get current failure count
302
+ * @returns {number}
303
+ */
304
+ getFailureCount() {
305
+ return this.failureCount;
306
+ }
307
+ }
308
+
309
+ module.exports = {
310
+ TokenRefreshManager
311
+ };