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.
- package/CHANGELOG.md +220 -0
- package/LICENSE +26 -0
- package/README.md +346 -0
- package/THEME_FEATURES.md +137 -0
- package/examples/README.md +131 -0
- package/examples/apply-ai-theme.js +127 -0
- package/examples/check-current-theme.js +74 -0
- package/examples/simple-bot.js +114 -0
- package/examples/test-bot.js +752 -0
- package/examples/test-logging.js +85 -0
- package/examples/theme-usage-example.js +53 -0
- package/index.js +2 -0
- package/package.json +105 -0
- package/src/apis/addExternalModule.js +24 -0
- package/src/apis/addUserToGroup.js +108 -0
- package/src/apis/changeAdminStatus.js +148 -0
- package/src/apis/changeArchivedStatus.js +61 -0
- package/src/apis/changeAvatar.js +103 -0
- package/src/apis/changeBio.js +69 -0
- package/src/apis/changeBlockedStatus.js +54 -0
- package/src/apis/changeGroupImage.js +136 -0
- package/src/apis/changeThreadColor.js +116 -0
- package/src/apis/comment.js +207 -0
- package/src/apis/createAITheme.js +129 -0
- package/src/apis/createNewGroup.js +79 -0
- package/src/apis/createPoll.js +73 -0
- package/src/apis/deleteMessage.js +44 -0
- package/src/apis/deleteThread.js +52 -0
- package/src/apis/editMessage.js +70 -0
- package/src/apis/emoji.js +124 -0
- package/src/apis/fetchThemeData.js +65 -0
- package/src/apis/follow.js +81 -0
- package/src/apis/forwardMessage.js +52 -0
- package/src/apis/friend.js +243 -0
- package/src/apis/gcmember.js +122 -0
- package/src/apis/gcname.js +123 -0
- package/src/apis/gcrule.js +119 -0
- package/src/apis/getAccess.js +111 -0
- package/src/apis/getBotInfo.js +88 -0
- package/src/apis/getBotInitialData.js +43 -0
- package/src/apis/getFriendsList.js +79 -0
- package/src/apis/getMessage.js +423 -0
- package/src/apis/getTheme.js +104 -0
- package/src/apis/getThemeInfo.js +96 -0
- package/src/apis/getThreadHistory.js +239 -0
- package/src/apis/getThreadInfo.js +257 -0
- package/src/apis/getThreadList.js +222 -0
- package/src/apis/getThreadPictures.js +58 -0
- package/src/apis/getUserID.js +83 -0
- package/src/apis/getUserInfo.js +495 -0
- package/src/apis/getUserInfoV2.js +146 -0
- package/src/apis/handleMessageRequest.js +50 -0
- package/src/apis/httpGet.js +63 -0
- package/src/apis/httpPost.js +89 -0
- package/src/apis/httpPostFormData.js +69 -0
- package/src/apis/listenMqtt.js +796 -0
- package/src/apis/listenSpeed.js +170 -0
- package/src/apis/logout.js +63 -0
- package/src/apis/markAsDelivered.js +47 -0
- package/src/apis/markAsRead.js +95 -0
- package/src/apis/markAsReadAll.js +41 -0
- package/src/apis/markAsSeen.js +70 -0
- package/src/apis/mqttDeltaValue.js +330 -0
- package/src/apis/muteThread.js +45 -0
- package/src/apis/nickname.js +132 -0
- package/src/apis/notes.js +163 -0
- package/src/apis/pinMessage.js +141 -0
- package/src/apis/produceMetaTheme.js +180 -0
- package/src/apis/realtime.js +161 -0
- package/src/apis/removeUserFromGroup.js +117 -0
- package/src/apis/resolvePhotoUrl.js +58 -0
- package/src/apis/searchForThread.js +154 -0
- package/src/apis/sendMessage.js +281 -0
- package/src/apis/sendMessageMqtt.js +188 -0
- package/src/apis/sendTypingIndicator.js +41 -0
- package/src/apis/setMessageReaction.js +27 -0
- package/src/apis/setMessageReactionMqtt.js +61 -0
- package/src/apis/setThreadTheme.js +260 -0
- package/src/apis/setThreadThemeMqtt.js +94 -0
- package/src/apis/share.js +107 -0
- package/src/apis/shareContact.js +66 -0
- package/src/apis/stickers.js +257 -0
- package/src/apis/story.js +181 -0
- package/src/apis/theme.js +233 -0
- package/src/apis/unfriend.js +47 -0
- package/src/apis/unsendMessage.js +17 -0
- package/src/database/appStateBackup.js +189 -0
- package/src/database/models/index.js +56 -0
- package/src/database/models/thread.js +31 -0
- package/src/database/models/user.js +32 -0
- package/src/database/threadData.js +101 -0
- package/src/database/userData.js +90 -0
- package/src/engine/client.js +91 -0
- package/src/engine/models/buildAPI.js +109 -0
- package/src/engine/models/loginHelper.js +326 -0
- package/src/engine/models/setOptions.js +53 -0
- package/src/utils/auth-helpers.js +149 -0
- package/src/utils/autoReLogin.js +169 -0
- package/src/utils/axios.js +290 -0
- package/src/utils/clients.js +270 -0
- package/src/utils/constants.js +396 -0
- package/src/utils/formatters/data/formatAttachment.js +370 -0
- package/src/utils/formatters/data/formatDelta.js +153 -0
- package/src/utils/formatters/index.js +159 -0
- package/src/utils/formatters/value/formatCookie.js +91 -0
- package/src/utils/formatters/value/formatDate.js +36 -0
- package/src/utils/formatters/value/formatID.js +16 -0
- package/src/utils/formatters.js +1067 -0
- package/src/utils/headers.js +199 -0
- package/src/utils/index.js +151 -0
- package/src/utils/monitoring.js +358 -0
- package/src/utils/rateLimiter.js +380 -0
- package/src/utils/tokenRefresh.js +311 -0
- 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
|
+
};
|