@cexy/hoonfca 1.0.1 → 1.0.3

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 (170) hide show
  1. package/.travis.yml +6 -0
  2. package/LICENSE-MIT +21 -0
  3. package/index.d.ts +620 -0
  4. package/index.js +778 -1
  5. package/package.json +31 -84
  6. package/src/{apis/addExternalModule.js → addExternalModule.js} +4 -5
  7. package/src/addUserToGroup.js +113 -0
  8. package/src/changeAdminStatus.js +95 -0
  9. package/src/changeApprovalMode.js +79 -0
  10. package/src/changeArchivedStatus.js +55 -0
  11. package/src/changeBio.js +76 -0
  12. package/src/changeBlockedStatus.js +47 -0
  13. package/src/changeBlockedStatusMqtt.js +86 -0
  14. package/src/changeGroupImage.js +133 -0
  15. package/src/changeNickname.js +59 -0
  16. package/src/changeThreadColor.js +71 -0
  17. package/src/{apis/changeThreadEmoji.js → changeThreadEmoji.js} +8 -6
  18. package/src/createNewGroup.js +88 -0
  19. package/src/createPoll.js +130 -0
  20. package/src/deleteMessage.js +56 -0
  21. package/src/deleteThread.js +56 -0
  22. package/src/editMessage.js +63 -0
  23. package/src/forwardAttachment.js +60 -0
  24. package/src/forwardMessage.js +63 -0
  25. package/src/getCurrentUserID.js +7 -0
  26. package/src/getEmojiUrl.js +29 -0
  27. package/src/getFriendsList.js +84 -0
  28. package/src/getGroupsList.js +83 -0
  29. package/src/getMarketplace.js +84 -0
  30. package/src/getNotifications.js +86 -0
  31. package/src/getPagesList.js +83 -0
  32. package/src/getStories.js +88 -0
  33. package/src/getThreadHistory.js +647 -0
  34. package/src/getThreadInfo.js +222 -0
  35. package/src/getThreadList.js +292 -0
  36. package/src/getThreadPictures.js +79 -0
  37. package/src/getUserID.js +66 -0
  38. package/src/getUserInfo.js +128 -0
  39. package/src/handleFriendRequest.js +57 -0
  40. package/src/handleMessageRequest.js +65 -0
  41. package/src/{apis/httpGet.js → httpGet.js} +15 -20
  42. package/src/httpPost.js +58 -0
  43. package/src/listenMqtt.js +1018 -0
  44. package/src/logout.js +75 -0
  45. package/src/markAsDelivered.js +58 -0
  46. package/src/{apis/markAsRead.js → markAsRead.js} +19 -30
  47. package/src/markAsReadAll.js +50 -0
  48. package/src/markAsSeen.js +61 -0
  49. package/src/muteThread.js +52 -0
  50. package/src/pinMessage.js +61 -0
  51. package/src/removeUserFromGroup.js +79 -0
  52. package/src/resolvePhotoUrl.js +45 -0
  53. package/src/searchForThread.js +53 -0
  54. package/src/searchUsers.js +89 -0
  55. package/src/sendComment.js +161 -0
  56. package/src/sendMessage.js +457 -0
  57. package/src/sendTypingIndicator.js +179 -0
  58. package/src/setBio.js +86 -0
  59. package/src/setMessageReaction.js +186 -0
  60. package/src/setPostReaction.js +71 -0
  61. package/src/setTheme.js +313 -0
  62. package/src/setTitle.js +90 -0
  63. package/src/shareContact.js +53 -0
  64. package/src/shareLink.js +62 -0
  65. package/src/threadColors.js +57 -0
  66. package/src/unfriend.js +53 -0
  67. package/src/unsendMessage.js +104 -0
  68. package/utils.js +1447 -0
  69. package/LICENSE +0 -3
  70. package/README.md +0 -491
  71. package/examples/login-with-cookies.js +0 -102
  72. package/examples/verify.js +0 -70
  73. package/src/apis/addUserToGroup.js +0 -108
  74. package/src/apis/changeAdminStatus.js +0 -148
  75. package/src/apis/changeArchivedStatus.js +0 -61
  76. package/src/apis/changeAvatar.js +0 -103
  77. package/src/apis/changeBio.js +0 -69
  78. package/src/apis/changeBlockedStatus.js +0 -54
  79. package/src/apis/changeGroupImage.js +0 -136
  80. package/src/apis/changeThreadColor.js +0 -116
  81. package/src/apis/comment.js +0 -207
  82. package/src/apis/createAITheme.js +0 -129
  83. package/src/apis/createNewGroup.js +0 -79
  84. package/src/apis/createPoll.js +0 -73
  85. package/src/apis/deleteMessage.js +0 -44
  86. package/src/apis/deleteThread.js +0 -52
  87. package/src/apis/e2ee.js +0 -17
  88. package/src/apis/editMessage.js +0 -70
  89. package/src/apis/emoji.js +0 -124
  90. package/src/apis/fetchThemeData.js +0 -82
  91. package/src/apis/follow.js +0 -81
  92. package/src/apis/forwardMessage.js +0 -52
  93. package/src/apis/friend.js +0 -243
  94. package/src/apis/gcmember.js +0 -122
  95. package/src/apis/gcname.js +0 -123
  96. package/src/apis/gcrule.js +0 -119
  97. package/src/apis/getAccess.js +0 -111
  98. package/src/apis/getBotInfo.js +0 -88
  99. package/src/apis/getBotInitialData.js +0 -43
  100. package/src/apis/getFriendsList.js +0 -79
  101. package/src/apis/getMessage.js +0 -423
  102. package/src/apis/getTheme.js +0 -95
  103. package/src/apis/getThemeInfo.js +0 -116
  104. package/src/apis/getThreadHistory.js +0 -239
  105. package/src/apis/getThreadInfo.js +0 -267
  106. package/src/apis/getThreadList.js +0 -232
  107. package/src/apis/getThreadPictures.js +0 -58
  108. package/src/apis/getUserID.js +0 -117
  109. package/src/apis/getUserInfo.js +0 -513
  110. package/src/apis/getUserInfoV2.js +0 -146
  111. package/src/apis/handleMessageRequest.js +0 -50
  112. package/src/apis/httpPost.js +0 -89
  113. package/src/apis/httpPostFormData.js +0 -69
  114. package/src/apis/listenMqtt.js +0 -1236
  115. package/src/apis/listenSpeed.js +0 -179
  116. package/src/apis/logout.js +0 -87
  117. package/src/apis/markAsDelivered.js +0 -47
  118. package/src/apis/markAsReadAll.js +0 -40
  119. package/src/apis/markAsSeen.js +0 -70
  120. package/src/apis/mqttDeltaValue.js +0 -278
  121. package/src/apis/muteThread.js +0 -45
  122. package/src/apis/nickname.js +0 -132
  123. package/src/apis/notes.js +0 -163
  124. package/src/apis/pinMessage.js +0 -150
  125. package/src/apis/produceMetaTheme.js +0 -180
  126. package/src/apis/realtime.js +0 -182
  127. package/src/apis/removeUserFromGroup.js +0 -117
  128. package/src/apis/resolvePhotoUrl.js +0 -58
  129. package/src/apis/searchForThread.js +0 -154
  130. package/src/apis/sendMessage.js +0 -354
  131. package/src/apis/sendMessageMqtt.js +0 -249
  132. package/src/apis/sendTypingIndicator.js +0 -91
  133. package/src/apis/setMessageReaction.js +0 -27
  134. package/src/apis/setMessageReactionMqtt.js +0 -61
  135. package/src/apis/setThreadTheme.js +0 -260
  136. package/src/apis/setThreadThemeMqtt.js +0 -94
  137. package/src/apis/share.js +0 -107
  138. package/src/apis/shareContact.js +0 -66
  139. package/src/apis/stickers.js +0 -257
  140. package/src/apis/story.js +0 -181
  141. package/src/apis/theme.js +0 -233
  142. package/src/apis/unfriend.js +0 -47
  143. package/src/apis/unsendMessage.js +0 -17
  144. package/src/engine/client.js +0 -92
  145. package/src/engine/models/buildAPI.js +0 -152
  146. package/src/engine/models/loginHelper.js +0 -519
  147. package/src/engine/models/setOptions.js +0 -88
  148. package/src/security/e2ee.js +0 -109
  149. package/src/types/index.d.ts +0 -498
  150. package/src/utils/antiSuspension.js +0 -506
  151. package/src/utils/auth-helpers.js +0 -149
  152. package/src/utils/autoReLogin.js +0 -336
  153. package/src/utils/axios.js +0 -436
  154. package/src/utils/cache.js +0 -54
  155. package/src/utils/clients.js +0 -282
  156. package/src/utils/constants.js +0 -410
  157. package/src/utils/formatters/data/formatAttachment.js +0 -370
  158. package/src/utils/formatters/data/formatDelta.js +0 -109
  159. package/src/utils/formatters/index.js +0 -159
  160. package/src/utils/formatters/value/formatCookie.js +0 -91
  161. package/src/utils/formatters/value/formatDate.js +0 -36
  162. package/src/utils/formatters/value/formatID.js +0 -16
  163. package/src/utils/formatters.js +0 -1373
  164. package/src/utils/headers.js +0 -230
  165. package/src/utils/index.js +0 -153
  166. package/src/utils/monitoring.js +0 -333
  167. package/src/utils/rateLimiter.js +0 -319
  168. package/src/utils/tokenRefresh.js +0 -657
  169. package/src/utils/user-agents.js +0 -238
  170. package/src/utils/validation.js +0 -157
@@ -1,657 +0,0 @@
1
- "use strict";
2
-
3
- const utils = require('./index');
4
- const { globalAutoReLoginManager } = require('./autoReLogin');
5
-
6
- /**
7
- * Token Refresh Manager - Enhanced for Maximum Reliability
8
- * Automatically refreshes fb_dtsg, lsd, and other tokens to prevent expiration
9
- */
10
-
11
- class TokenRefreshManager {
12
- constructor() {
13
- this.refreshInterval = null;
14
- this.REFRESH_INTERVAL_MS = 2 * 60 * 60 * 1000; // 2 hours base
15
- this.SESSION_CHECK_INTERVAL_MS = 20 * 60 * 1000; // 20 minutes - more frequent health checks
16
- this.PRESENCE_KEEPALIVE_MS = 5 * 60 * 1000; // 5 minutes - frequent keepalive
17
- this.COOKIE_REFRESH_INTERVAL_MS = 12 * 60 * 60 * 1000; // 12 hours - twice daily cookie refresh
18
- this.lastRefresh = Date.now();
19
- this.lastSessionCheck = Date.now();
20
- this.lastPresencePing = Date.now();
21
- this.lastCookieRefresh = Date.now();
22
- this.failureCount = 0;
23
- this.MAX_FAILURES = 15; // More tolerance for long-running bots
24
- this.onSessionExpiry = null;
25
- this.sessionHealthCheckInterval = null;
26
- this.presenceKeepaliveInterval = null;
27
- this.cookieRefreshInterval = null;
28
- // Lock mechanism to prevent concurrent refresh attempts
29
- this.isRefreshing = false;
30
- this.refreshLock = null;
31
- // Track consecutive successes for adaptive intervals
32
- this.consecutiveSuccesses = 0;
33
- // Store context for later use
34
- this.storedCtx = null;
35
- this.storedDefaultFuncs = null;
36
- this.storedFbLink = null;
37
- // Token cache to minimize requests
38
- this.tokenCache = new Map();
39
- }
40
-
41
- /**
42
- * Acquire refresh lock to prevent concurrent token refresh attempts
43
- * @returns {Promise<boolean>} true if lock acquired, false if already locked
44
- */
45
- async acquireRefreshLock() {
46
- if (this.isRefreshing) {
47
- // Wait for existing refresh to complete
48
- try {
49
- await this.refreshLock;
50
- } catch (_) {
51
- // Previous refresh failed, we can proceed
52
- }
53
- }
54
-
55
- if (this.isRefreshing) {
56
- return false; // Still refreshing after waiting
57
- }
58
-
59
- this.isRefreshing = true;
60
- this.refreshLock = new Promise((resolve) => {
61
- this._releaseLock = resolve;
62
- });
63
- return true;
64
- }
65
-
66
- /**
67
- * Release the refresh lock
68
- */
69
- releaseRefreshLock() {
70
- this.isRefreshing = false;
71
- if (this._releaseLock) {
72
- this._releaseLock();
73
- this._releaseLock = null;
74
- }
75
- }
76
-
77
- /**
78
- * Start automatic token refresh with optimized intervals
79
- * @param {Object} ctx - Application context
80
- * @param {Object} defaultFuncs - Default functions
81
- * @param {string} fbLink - Facebook link
82
- */
83
- startAutoRefresh(ctx, defaultFuncs, fbLink) {
84
- // Store context for later recovery
85
- this.storedCtx = ctx;
86
- this.storedDefaultFuncs = defaultFuncs;
87
- this.storedFbLink = fbLink;
88
-
89
- if (this.refreshInterval) {
90
- clearInterval(this.refreshInterval);
91
- this.refreshInterval = null;
92
- }
93
- if (this.sessionHealthCheckInterval) {
94
- clearInterval(this.sessionHealthCheckInterval);
95
- this.sessionHealthCheckInterval = null;
96
- }
97
- if (this.presenceKeepaliveInterval) {
98
- clearInterval(this.presenceKeepaliveInterval);
99
- this.presenceKeepaliveInterval = null;
100
- }
101
- if (this.cookieRefreshInterval) {
102
- clearInterval(this.cookieRefreshInterval);
103
- this.cookieRefreshInterval = null;
104
- }
105
-
106
- const scheduleNext = () => {
107
- // Adaptive interval: longer after consecutive successes
108
- const base = this.REFRESH_INTERVAL_MS;
109
- const successBonus = Math.min(this.consecutiveSuccesses * 15 * 60 * 1000, 4 * 60 * 60 * 1000); // Up to 4 hours bonus
110
- const interval = base + successBonus;
111
-
112
- this.refreshInterval = setTimeout(async () => {
113
- try {
114
- const refreshed = await this.refreshTokens(ctx, defaultFuncs, fbLink);
115
- if (refreshed) {
116
- this.consecutiveSuccesses++;
117
- utils.log("TokenRefresh", `Tokens refreshed successfully (consecutive successes: ${this.consecutiveSuccesses})`);
118
- } else {
119
- this.consecutiveSuccesses = 0;
120
- }
121
- } catch (error) {
122
- this.consecutiveSuccesses = 0;
123
- utils.error("TokenRefresh", "Failed to refresh tokens:", error.message);
124
- } finally {
125
- scheduleNext();
126
- }
127
- }, interval);
128
- utils.log("TokenRefresh", `Auto-refresh scheduled in ${Math.round(interval / 60000)}min`);
129
- };
130
-
131
- // Start session health checks - frequent to catch issues early
132
- this.sessionHealthCheckInterval = setInterval(async () => {
133
- try {
134
- const isHealthy = await this.checkSessionHealth(ctx, defaultFuncs, fbLink);
135
- if (isHealthy === 'network_error') {
136
- utils.warn("TokenRefresh", "Session health probe returned network error — skipping refresh (transient)");
137
- return;
138
- }
139
- if (!isHealthy) {
140
- utils.warn("TokenRefresh", "Session health check failed, triggering refresh");
141
- this.consecutiveSuccesses = 0;
142
- const refreshed = await this.refreshTokens(ctx, defaultFuncs, fbLink);
143
- if (!refreshed) {
144
- // Try alternative refresh method
145
- await this.refreshTokensAlternative(ctx, defaultFuncs, fbLink);
146
- }
147
- }
148
- } catch (error) {
149
- utils.error("TokenRefresh", "Session health check error:", error.message);
150
- }
151
- }, this.SESSION_CHECK_INTERVAL_MS);
152
-
153
- // Start presence keepalive for low-activity bots
154
- this.presenceKeepaliveInterval = setInterval(async () => {
155
- try {
156
- await this.sendPresenceKeepalive(ctx);
157
- } catch (error) {
158
- utils.warn("TokenRefresh", "Presence keepalive failed:", error.message);
159
- }
160
- }, this.PRESENCE_KEEPALIVE_MS);
161
-
162
- // Start cookie refresh interval
163
- this.cookieRefreshInterval = setInterval(async () => {
164
- try {
165
- await this.refreshCookies(ctx, defaultFuncs, fbLink);
166
- } catch (error) {
167
- utils.warn("TokenRefresh", "Cookie refresh failed:", error.message);
168
- }
169
- }, this.COOKIE_REFRESH_INTERVAL_MS);
170
-
171
- utils.log("TokenRefresh", `Session health checks every ${Math.round(this.SESSION_CHECK_INTERVAL_MS / 60000)}min, keepalive every ${Math.round(this.PRESENCE_KEEPALIVE_MS / 60000)}min, cookie refresh every 12h`);
172
-
173
- scheduleNext();
174
- }
175
-
176
- /**
177
- * Check if session is healthy using a lightweight AJAX ping instead of a
178
- * full homepage load. Fetching the full homepage every 2 hours is a clear
179
- * automation fingerprint; a small presence/ping endpoint is far less
180
- * conspicuous and produces a much smaller response.
181
- *
182
- * @param {Object} ctx - Application context
183
- * @param {Object} defaultFuncs - Default functions
184
- * @param {string} fbLink - Facebook link
185
- * @returns {Promise<boolean>}
186
- */
187
- async checkSessionHealth(ctx, defaultFuncs, fbLink) {
188
- try {
189
- // Use a lightweight AJAX endpoint that only returns a small JSON
190
- // payload — not the full multi-megabyte homepage.
191
- const probeUrl = 'https://www.facebook.com/ajax/presence/reconnect.php';
192
- const probeCtx = { ...ctx, _skipSessionInspect: true };
193
- const resp = await utils.get(
194
- probeUrl,
195
- ctx.jar,
196
- { reason: 'reconnect', __a: 1, __req: 'probe' },
197
- ctx.globalOptions,
198
- probeCtx
199
- );
200
-
201
- const body = resp.body;
202
- if (!body) return false;
203
-
204
- const bodyStr = typeof body === 'string' ? body : JSON.stringify(body);
205
-
206
- // If we get a login-page redirect in the probe response the session is gone
207
- const isLoginPage =
208
- bodyStr.includes('<form id="login_form"') ||
209
- bodyStr.includes('"login_page"') ||
210
- bodyStr.includes('id="loginbutton"');
211
-
212
- if (isLoginPage) return false;
213
-
214
- const isCheckpoint =
215
- bodyStr.includes('"checkpoint"') && bodyStr.includes('"flow_type"');
216
- if (isCheckpoint) return false;
217
-
218
- this.lastSessionCheck = Date.now();
219
- return true;
220
- } catch (error) {
221
- const msg = error.message || '';
222
- const code = error.code || '';
223
- const NETWORK_CODES = ['ECONNRESET','ETIMEDOUT','ECONNREFUSED','ENETUNREACH',
224
- 'EHOSTUNREACH','EAI_AGAIN','ENOTFOUND','ESOCKETTIMEDOUT'];
225
- const isNetworkErr = NETWORK_CODES.some(c => code === c || msg.includes(c)) ||
226
- msg.includes('socket hang up') || msg.includes('network error');
227
- if (isNetworkErr) {
228
- utils.warn("TokenRefresh", "Session health check: network error (ignoring):", msg);
229
- // Return a sentinel so callers can skip refresh instead of triggering re-login.
230
- return 'network_error';
231
- }
232
- if (msg.includes('Not logged in') || msg.includes('Session has expired')) {
233
- utils.error("TokenRefresh", "Session health check: session expired:", msg);
234
- return false;
235
- }
236
- utils.warn("TokenRefresh", "Session health check unexpected error (treating as healthy):", msg);
237
- return true;
238
- }
239
- }
240
-
241
- /**
242
- * Manually refresh tokens with retry logic
243
- * @param {Object} ctx - Application context
244
- * @param {Object} defaultFuncs - Default functions
245
- * @param {string} fbLink - Facebook link
246
- * @param {number} retryCount - Current retry attempt (internal use)
247
- * @returns {Promise<boolean>}
248
- */
249
- async refreshTokens(ctx, defaultFuncs, fbLink, retryCount = 0) {
250
- // Acquire lock to prevent concurrent refresh attempts
251
- const lockAcquired = await this.acquireRefreshLock();
252
- if (!lockAcquired) {
253
- utils.warn("TokenRefresh", "Token refresh already in progress, skipping concurrent request");
254
- return false;
255
- }
256
-
257
- const MAX_RETRIES = 3;
258
- const RETRY_DELAYS = [2000, 5000, 10000];
259
-
260
- try {
261
- // Validate ctx.jar exists
262
- if (!ctx || !ctx.jar) {
263
- throw new Error("Invalid context: cookie jar not available");
264
- }
265
-
266
- const resp = await utils.get(fbLink, ctx.jar, null, ctx.globalOptions, { noRef: true });
267
-
268
- const html = resp.body;
269
- if (!html) {
270
- throw new Error("Empty response from Facebook");
271
- }
272
-
273
- // Precise check - broad html.includes("login") is a false positive because
274
- // Facebook includes the word "login" all over authenticated pages too.
275
- const isLoginPage = html.includes('<form id="login_form"') ||
276
- html.includes('id="loginbutton"') ||
277
- html.includes('"login_page"') ||
278
- html.includes('id="email" name="email"');
279
- const isCheckpoint = html.includes('"checkpoint"') && html.includes('"flow_type"');
280
-
281
- if (isLoginPage || isCheckpoint) {
282
- if (isCheckpoint) {
283
- try {
284
- const { globalAntiSuspension } = require('./antiSuspension');
285
- globalAntiSuspension.tripCircuitBreaker('checkpoint_on_token_refresh', 60 * 60 * 1000);
286
- } catch (lockErr) {
287
- utils.warn("TokenRefresh", "Failed to trip circuit breaker for checkpoint:", lockErr.message);
288
- }
289
- }
290
- throw new Error("Session expired or checkpoint required");
291
- }
292
-
293
- const dtsgMatch = html.match(/"DTSGInitialData",\[],{"token":"([^"]+)"/);
294
- if (dtsgMatch) {
295
- ctx.fb_dtsg = dtsgMatch[1];
296
- ctx.ttstamp = "2";
297
- for (let i = 0; i < ctx.fb_dtsg.length; i++) {
298
- ctx.ttstamp += ctx.fb_dtsg.charCodeAt(i);
299
- }
300
- } else {
301
- throw new Error("Failed to extract fb_dtsg token");
302
- }
303
-
304
- const lsdMatch = html.match(/"LSD",\[],{"token":"([^"]+)"/);
305
- if (lsdMatch) {
306
- ctx.lsd = lsdMatch[1];
307
- }
308
-
309
- const jazoestMatch = html.match(/jazoest=(\d+)/);
310
- if (jazoestMatch) {
311
- ctx.jazoest = jazoestMatch[1];
312
- }
313
-
314
- const revisionMatch = html.match(/"client_revision":(\d+)/);
315
- if (revisionMatch) {
316
- ctx.__rev = revisionMatch[1];
317
- }
318
-
319
- // Extract additional tokens for better session persistence
320
- const dtsgAgMatch = html.match(/"DTSGAGInitialData",\[],{"token":"([^"]+)"/);
321
- if (dtsgAgMatch) {
322
- ctx.fb_dtsg_ag = dtsgAgMatch[1];
323
- }
324
-
325
- const spinRMatch = html.match(/"__spin_r":(\d+)/);
326
- if (spinRMatch) {
327
- ctx.__spin_r = spinRMatch[1];
328
- }
329
-
330
- const spinBMatch = html.match(/"__spin_b":"([^"]+)"/);
331
- if (spinBMatch) {
332
- ctx.__spin_b = spinBMatch[1];
333
- }
334
-
335
- const spinTMatch = html.match(/"__spin_t":(\d+)/);
336
- if (spinTMatch) {
337
- ctx.__spin_t = spinTMatch[1];
338
- }
339
-
340
- const hsiMatch = html.match(/"hsi":"(\d+)"/);
341
- if (hsiMatch) {
342
- ctx.hsi = hsiMatch[1];
343
- }
344
-
345
- const dynMatch = html.match(/"dyn":"([^"]+)"/);
346
- if (dynMatch) {
347
- ctx.dyn = dynMatch[1];
348
- }
349
-
350
- const csrMatch = html.match(/"csr":"([^"]+)"/);
351
- if (csrMatch) {
352
- ctx.csr = csrMatch[1];
353
- }
354
-
355
- this.lastRefresh = Date.now();
356
- this.failureCount = 0;
357
- try {
358
- if (globalAutoReLoginManager && globalAutoReLoginManager.isEnabled && globalAutoReLoginManager.isEnabled()) {
359
- const appState = utils.getAppState(ctx.jar);
360
- globalAutoReLoginManager.updateAppState(appState);
361
- }
362
- } catch (updateErr) {
363
- utils.warn("TokenRefresh", "Failed to update appState in re-login manager:", updateErr.message);
364
- }
365
- return true;
366
- } catch (error) {
367
- this.failureCount++;
368
- utils.error("TokenRefresh", `Refresh failed (attempt ${retryCount + 1}/${MAX_RETRIES + 1}):`, error.message);
369
-
370
- if (this.failureCount >= this.MAX_FAILURES) {
371
- utils.error("TokenRefresh", `Maximum failures (${this.MAX_FAILURES}) reached. Session may be expired.`);
372
- if (this.onSessionExpiry && typeof this.onSessionExpiry === 'function') {
373
- this.onSessionExpiry(error);
374
- }
375
- return false;
376
- }
377
-
378
- if (retryCount < MAX_RETRIES) {
379
- const delay = RETRY_DELAYS[retryCount];
380
- utils.log("TokenRefresh", `Retrying in ${delay}ms...`);
381
- // Release the lock BEFORE the recursive call — the inner call
382
- // needs to acquire it and would deadlock waiting on a lock held
383
- // by this call (which in turn is blocked awaiting the inner call).
384
- this.releaseRefreshLock();
385
- await new Promise(resolve => setTimeout(resolve, delay));
386
- return await this.refreshTokens(ctx, defaultFuncs, fbLink, retryCount + 1);
387
- }
388
-
389
- return false;
390
- } finally {
391
- // Always release the lock
392
- this.releaseRefreshLock();
393
- }
394
- }
395
-
396
- /**
397
- * Alternative token refresh using different endpoints - fallback method
398
- * @param {Object} ctx - Application context
399
- * @param {Object} defaultFuncs - Default functions
400
- * @param {string} fbLink - Facebook link
401
- * @returns {Promise<boolean>}
402
- */
403
- async refreshTokensAlternative(ctx, defaultFuncs, fbLink) {
404
- const endpoints = [
405
- 'https://www.facebook.com/ajax/bootloader-endpoint/',
406
- 'https://www.facebook.com/ajax/navigation/',
407
- 'https://www.messenger.com/',
408
- 'https://www.facebook.com/messages/'
409
- ];
410
-
411
- for (const endpoint of endpoints) {
412
- try {
413
- utils.log("TokenRefresh", `Trying alternative endpoint: ${endpoint}`);
414
- const resp = await utils.get(endpoint, ctx.jar, { __a: 1 }, ctx.globalOptions, { noRef: true, _skipSessionInspect: true });
415
- const html = resp.body;
416
-
417
- if (!html || typeof html !== 'string') continue;
418
-
419
- // Check for login page
420
- if (html.includes('<form id="login_form"') || html.includes('id="loginbutton"')) {
421
- continue; // Try next endpoint
422
- }
423
-
424
- // Try to extract tokens with multiple patterns
425
- const tokens = this.extractTokensFromHtml(html);
426
- if (tokens.fb_dtsg) {
427
- ctx.fb_dtsg = tokens.fb_dtsg;
428
- ctx.ttstamp = "2";
429
- for (let i = 0; i < ctx.fb_dtsg.length; i++) {
430
- ctx.ttstamp += ctx.fb_dtsg.charCodeAt(i);
431
- }
432
- }
433
- if (tokens.lsd) ctx.lsd = tokens.lsd;
434
- if (tokens.jazoest) ctx.jazoest = tokens.jazoest;
435
- if (tokens.__rev) ctx.__rev = tokens.__rev;
436
-
437
- if (tokens.fb_dtsg) {
438
- utils.log("TokenRefresh", `Alternative refresh successful via ${endpoint}`);
439
- this.lastRefresh = Date.now();
440
- this.failureCount = 0;
441
-
442
- return true;
443
- }
444
- } catch (error) {
445
- utils.warn("TokenRefresh", `Alternative endpoint ${endpoint} failed:`, error.message);
446
- continue;
447
- }
448
- }
449
-
450
- return false;
451
- }
452
-
453
- /**
454
- * Extract all tokens from HTML using multiple regex patterns
455
- * @param {string} html - HTML content
456
- * @returns {Object} Extracted tokens
457
- */
458
- extractTokensFromHtml(html) {
459
- const tokens = {};
460
-
461
- // DTSG patterns
462
- const dtsgPatterns = [
463
- /"DTSGInitialData",\[],{"token":"([^"]+)"/,
464
- /name="fb_dtsg" value="([^"]+)"/,
465
- /"token":"([^"]+)"[^}]*"DTSGInitialData"/,
466
- /DTSG[^}]*token[^"]*"([^"]+)"/
467
- ];
468
-
469
- for (const pattern of dtsgPatterns) {
470
- const match = html.match(pattern);
471
- if (match && match[1]) {
472
- tokens.fb_dtsg = match[1];
473
- break;
474
- }
475
- }
476
-
477
- // LSD patterns
478
- const lsdPatterns = [
479
- /"LSD",\[],{"token":"([^"]+)"/,
480
- /name="lsd" value="([^"]+)"/
481
- ];
482
-
483
- for (const pattern of lsdPatterns) {
484
- const match = html.match(pattern);
485
- if (match && match[1]) {
486
- tokens.lsd = match[1];
487
- break;
488
- }
489
- }
490
-
491
- // Jazoest
492
- const jazoestMatch = html.match(/jazoest=(\d+)/) || html.match(/name="jazoest" value="(\d+)/);
493
- if (jazoestMatch) tokens.jazoest = jazoestMatch[1];
494
-
495
- // Revision
496
- const revMatch = html.match(/"client_revision":(\d+)/) || html.match(/"__rev":(\d+)/);
497
- if (revMatch) tokens.__rev = revMatch[1];
498
-
499
- // Additional tokens
500
- const hsiMatch = html.match(/"hsi":"(\d+)"/);
501
- if (hsiMatch) tokens.hsi = hsiMatch[1];
502
-
503
- const dynMatch = html.match(/"dyn":"([^"]+)"/);
504
- if (dynMatch) tokens.dyn = dynMatch[1];
505
-
506
- const csrMatch = html.match(/"csr":"([^"]+)"/);
507
- if (csrMatch) tokens.csr = csrMatch[1];
508
-
509
- return tokens;
510
- }
511
-
512
- /**
513
- * Send a lightweight presence keepalive ping to prevent session expiration
514
- * due to inactivity for low-activity bots.
515
- * @param {Object} ctx - Application context
516
- */
517
- async sendPresenceKeepalive(ctx) {
518
- try {
519
- // Use the same lightweight endpoint as session health check
520
- const probeUrl = 'https://www.facebook.com/ajax/presence/reconnect.php';
521
- const resp = await utils.get(
522
- probeUrl,
523
- ctx.jar,
524
- { reason: 'keepalive', __a: 1, __req: 'keepalive' },
525
- ctx.globalOptions,
526
- { noRef: true, _skipSessionInspect: true }
527
- );
528
- this.lastPresencePing = Date.now();
529
- return true;
530
- } catch (error) {
531
- // Silent failure - don't spam logs for keepalive failures
532
- return false;
533
- }
534
- }
535
-
536
- /**
537
- * Refresh cookies to extend session lifetime
538
- * @param {Object} ctx - Application context
539
- * @param {Object} defaultFuncs - Default functions
540
- * @param {string} fbLink - Facebook link
541
- */
542
- async refreshCookies(ctx, defaultFuncs, fbLink) {
543
- try {
544
- // Visit a lightweight page to refresh cookies
545
- const probeUrl = 'https://www.facebook.com/ajax/bootloader-endpoint/';
546
- const probeCtx = { ...ctx, _skipSessionInspect: true };
547
- await utils.get(
548
- probeUrl,
549
- ctx.jar,
550
- { __a: 1, __req: 'cookierefresh' },
551
- ctx.globalOptions,
552
- probeCtx
553
- );
554
-
555
- this.lastCookieRefresh = Date.now();
556
- utils.log("TokenRefresh", "Cookies refreshed successfully");
557
-
558
- return true;
559
- } catch (error) {
560
- utils.warn("TokenRefresh", "Cookie refresh failed:", error.message);
561
- return false;
562
- }
563
- }
564
-
565
- /**
566
- * Stop automatic token refresh
567
- */
568
- stopAutoRefresh() {
569
- if (this.refreshInterval) {
570
- clearTimeout(this.refreshInterval);
571
- this.refreshInterval = null;
572
- utils.log("TokenRefresh", "Auto-refresh disabled");
573
- }
574
- if (this.sessionHealthCheckInterval) {
575
- clearInterval(this.sessionHealthCheckInterval);
576
- this.sessionHealthCheckInterval = null;
577
- utils.log("TokenRefresh", "Session health checks disabled");
578
- }
579
- if (this.presenceKeepaliveInterval) {
580
- clearInterval(this.presenceKeepaliveInterval);
581
- this.presenceKeepaliveInterval = null;
582
- utils.log("TokenRefresh", "Presence keepalive disabled");
583
- }
584
- if (this.cookieRefreshInterval) {
585
- clearInterval(this.cookieRefreshInterval);
586
- this.cookieRefreshInterval = null;
587
- utils.log("TokenRefresh", "Cookie refresh disabled");
588
- }
589
- }
590
-
591
- /**
592
- * Get time until next refresh
593
- * @returns {number} Milliseconds until next refresh
594
- */
595
- getTimeUntilNextRefresh() {
596
- if (!this.refreshInterval) return -1;
597
- return Math.max(0, this.REFRESH_INTERVAL_MS - (Date.now() - this.lastRefresh));
598
- }
599
-
600
- /**
601
- * Check if tokens need immediate refresh
602
- * @returns {boolean}
603
- */
604
- needsImmediateRefresh() {
605
- return (Date.now() - this.lastRefresh) >= this.REFRESH_INTERVAL_MS;
606
- }
607
-
608
- /**
609
- * Set callback for session expiry detection
610
- * @param {Function} callback - Callback function to trigger on session expiry
611
- */
612
- setSessionExpiryCallback(callback) {
613
- this.onSessionExpiry = callback;
614
- }
615
-
616
- /**
617
- * Reset failure count (useful after successful re-login)
618
- */
619
- resetFailureCount() {
620
- this.failureCount = 0;
621
- }
622
-
623
- /**
624
- * Get current failure count
625
- * @returns {number}
626
- */
627
- getFailureCount() {
628
- return this.failureCount;
629
- }
630
-
631
- /**
632
- * Get comprehensive status for monitoring and debugging
633
- * @returns {Object}
634
- */
635
- getStatus() {
636
- const now = Date.now();
637
- return {
638
- lastRefresh: this.lastRefresh,
639
- lastSessionCheck: this.lastSessionCheck,
640
- lastPresencePing: this.lastPresencePing,
641
- timeSinceLastRefresh: now - this.lastRefresh,
642
- timeSinceLastSessionCheck: now - this.lastSessionCheck,
643
- timeSinceLastPresencePing: now - this.lastPresencePing,
644
- nextRefreshIn: this.getTimeUntilNextRefresh(),
645
- failureCount: this.failureCount,
646
- maxFailures: this.MAX_FAILURES,
647
- isHealthy: this.failureCount < this.MAX_FAILURES,
648
- refreshIntervalMs: this.REFRESH_INTERVAL_MS,
649
- sessionCheckIntervalMs: this.SESSION_CHECK_INTERVAL_MS,
650
- presenceKeepaliveIntervalMs: this.PRESENCE_KEEPALIVE_MS
651
- };
652
- }
653
- }
654
-
655
- module.exports = {
656
- TokenRefreshManager
657
- };