@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,333 +0,0 @@
1
- "use strict";
2
-
3
- const utils = require('./index');
4
-
5
- class ProductionMonitor {
6
- constructor() {
7
- this.metrics = {
8
- requests: {
9
- total: 0,
10
- success: 0,
11
- failed: 0,
12
- byEndpoint: new Map()
13
- },
14
- errors: {
15
- total: 0,
16
- byType: new Map(),
17
- byCode: new Map(),
18
- recent: []
19
- },
20
- performance: {
21
- avgResponseTime: 0,
22
- slowRequests: [],
23
- requestTimes: []
24
- },
25
- session: {
26
- loginTime: null,
27
- lastActivity: null,
28
- tokenRefreshCount: 0,
29
- reconnectCount: 0
30
- },
31
- rateLimiting: {
32
- hitCount: 0,
33
- cooldowns: 0,
34
- delayedRequests: 0
35
- }
36
- };
37
-
38
- this.config = {
39
- logLevel: 'info',
40
- enableMetrics: true,
41
- enableErrorTracking: true,
42
- performanceThreshold: 5000,
43
- errorRetentionCount: 100,
44
- metricsInterval: 60000
45
- };
46
-
47
- this.startTime = Date.now();
48
- this.metricsInterval = null;
49
- }
50
-
51
- setConfig(options) {
52
- Object.assign(this.config, options);
53
- }
54
-
55
- trackRequest(endpoint, success, responseTime, error = null) {
56
- if (!this.config.enableMetrics) return;
57
-
58
- this.metrics.requests.total++;
59
- if (success) {
60
- this.metrics.requests.success++;
61
- } else {
62
- this.metrics.requests.failed++;
63
- }
64
-
65
- if (!this.metrics.requests.byEndpoint.has(endpoint)) {
66
- this.metrics.requests.byEndpoint.set(endpoint, {
67
- total: 0,
68
- success: 0,
69
- failed: 0,
70
- avgTime: 0
71
- });
72
- }
73
-
74
- const endpointStats = this.metrics.requests.byEndpoint.get(endpoint);
75
- endpointStats.total++;
76
- if (success) {
77
- endpointStats.success++;
78
- } else {
79
- endpointStats.failed++;
80
- }
81
-
82
- endpointStats.avgTime =
83
- (endpointStats.avgTime * (endpointStats.total - 1) + responseTime) / endpointStats.total;
84
-
85
- this.trackPerformance(endpoint, responseTime);
86
-
87
- if (error) {
88
- this.trackError(error, endpoint);
89
- }
90
-
91
- this.metrics.session.lastActivity = Date.now();
92
- }
93
-
94
- trackPerformance(endpoint, responseTime) {
95
- this.metrics.performance.requestTimes.push(responseTime);
96
-
97
- if (this.metrics.performance.requestTimes.length > 1000) {
98
- this.metrics.performance.requestTimes.shift();
99
- }
100
-
101
- const sum = this.metrics.performance.requestTimes.reduce((a, b) => a + b, 0);
102
- this.metrics.performance.avgResponseTime =
103
- sum / this.metrics.performance.requestTimes.length;
104
-
105
- if (responseTime > this.config.performanceThreshold) {
106
- this.metrics.performance.slowRequests.push({
107
- endpoint,
108
- responseTime,
109
- timestamp: Date.now()
110
- });
111
-
112
- if (this.metrics.performance.slowRequests.length > 50) {
113
- this.metrics.performance.slowRequests.shift();
114
- }
115
-
116
- utils.warn("Performance", `Slow request: ${endpoint} took ${responseTime}ms`);
117
- }
118
- }
119
-
120
- trackError(error, context = '') {
121
- if (!this.config.enableErrorTracking) return;
122
-
123
- this.metrics.errors.total++;
124
-
125
- const errorType = error.errorType || error.name || 'UnknownError';
126
- const errorCode = error.errorCode || error.code || 'N/A';
127
-
128
- this.metrics.errors.byType.set(
129
- errorType,
130
- (this.metrics.errors.byType.get(errorType) || 0) + 1
131
- );
132
-
133
- this.metrics.errors.byCode.set(
134
- errorCode,
135
- (this.metrics.errors.byCode.get(errorCode) || 0) + 1
136
- );
137
-
138
- this.metrics.errors.recent.push({
139
- type: errorType,
140
- code: errorCode,
141
- message: error.message,
142
- context,
143
- timestamp: Date.now(),
144
- stack: error.stack
145
- });
146
-
147
- if (this.metrics.errors.recent.length > this.config.errorRetentionCount) {
148
- this.metrics.errors.recent.shift();
149
- }
150
- }
151
-
152
- trackRateLimit(type, threadID = null) {
153
- this.metrics.rateLimiting.hitCount++;
154
-
155
- if (type === 'cooldown') {
156
- this.metrics.rateLimiting.cooldowns++;
157
- } else if (type === 'delayed') {
158
- this.metrics.rateLimiting.delayedRequests++;
159
- }
160
- }
161
-
162
- trackTokenRefresh() {
163
- this.metrics.session.tokenRefreshCount++;
164
- }
165
-
166
- trackReconnect() {
167
- this.metrics.session.reconnectCount++;
168
- }
169
-
170
- setLoginTime() {
171
- this.metrics.session.loginTime = Date.now();
172
- }
173
-
174
- getMetrics() {
175
- const uptime = Date.now() - this.startTime;
176
- const sessionDuration = this.metrics.session.loginTime
177
- ? Date.now() - this.metrics.session.loginTime
178
- : 0;
179
-
180
- return {
181
- uptime,
182
- sessionDuration,
183
- requests: {
184
- ...this.metrics.requests,
185
- byEndpoint: Object.fromEntries(this.metrics.requests.byEndpoint),
186
- successRate: this.metrics.requests.total > 0
187
- ? (this.metrics.requests.success / this.metrics.requests.total * 100).toFixed(2) + '%'
188
- : 'N/A'
189
- },
190
- errors: {
191
- ...this.metrics.errors,
192
- byType: Object.fromEntries(this.metrics.errors.byType),
193
- byCode: Object.fromEntries(this.metrics.errors.byCode),
194
- errorRate: this.metrics.requests.total > 0
195
- ? (this.metrics.errors.total / this.metrics.requests.total * 100).toFixed(2) + '%'
196
- : 'N/A'
197
- },
198
- performance: this.metrics.performance,
199
- session: this.metrics.session,
200
- rateLimiting: this.metrics.rateLimiting
201
- };
202
- }
203
-
204
- getHealth() {
205
- const metrics = this.getMetrics();
206
- const health = {
207
- status: 'healthy',
208
- checks: {},
209
- timestamp: Date.now()
210
- };
211
-
212
- const errorRate = this.metrics.requests.total > 0
213
- ? (this.metrics.errors.total / this.metrics.requests.total) * 100
214
- : 0;
215
-
216
- health.checks.errorRate = {
217
- status: errorRate < 5 ? 'pass' : errorRate < 15 ? 'warn' : 'fail',
218
- value: errorRate.toFixed(2) + '%',
219
- threshold: '5%'
220
- };
221
-
222
- health.checks.performance = {
223
- status: this.metrics.performance.avgResponseTime < 2000 ? 'pass' :
224
- this.metrics.performance.avgResponseTime < 5000 ? 'warn' : 'fail',
225
- value: Math.round(this.metrics.performance.avgResponseTime) + 'ms',
226
- threshold: '2000ms'
227
- };
228
-
229
- health.checks.session = {
230
- status: this.metrics.session.loginTime ? 'pass' : 'fail',
231
- value: this.metrics.session.loginTime ? 'active' : 'not logged in'
232
- };
233
-
234
- health.checks.rateLimiting = {
235
- status: this.metrics.rateLimiting.hitCount < 100 ? 'pass' :
236
- this.metrics.rateLimiting.hitCount < 500 ? 'warn' : 'fail',
237
- value: this.metrics.rateLimiting.hitCount,
238
- threshold: '100 hits'
239
- };
240
-
241
- const failedChecks = Object.values(health.checks).filter(c => c.status === 'fail').length;
242
- const warnChecks = Object.values(health.checks).filter(c => c.status === 'warn').length;
243
-
244
- if (failedChecks > 0) {
245
- health.status = 'unhealthy';
246
- } else if (warnChecks > 0) {
247
- health.status = 'degraded';
248
- }
249
-
250
- return health;
251
- }
252
-
253
- reset() {
254
- this.metrics.requests.total = 0;
255
- this.metrics.requests.success = 0;
256
- this.metrics.requests.failed = 0;
257
- this.metrics.requests.byEndpoint.clear();
258
- this.metrics.errors.total = 0;
259
- this.metrics.errors.byType.clear();
260
- this.metrics.errors.byCode.clear();
261
- this.metrics.errors.recent = [];
262
- this.metrics.performance.slowRequests = [];
263
- this.metrics.performance.requestTimes = [];
264
- this.metrics.rateLimiting.hitCount = 0;
265
- this.metrics.rateLimiting.cooldowns = 0;
266
- this.metrics.rateLimiting.delayedRequests = 0;
267
-
268
- utils.success("Monitoring", "All metrics have been reset successfully");
269
- }
270
-
271
- displayHealthStatus() {
272
- const health = this.getHealth();
273
- console.log('\n' + '='.repeat(60));
274
- if (health.status === 'healthy') {
275
- console.log('SYSTEM HEALTH: HEALTHY');
276
- } else if (health.status === 'degraded') {
277
- console.log('SYSTEM HEALTH: DEGRADED');
278
- } else {
279
- console.log('SYSTEM HEALTH: UNHEALTHY');
280
- }
281
- console.log('='.repeat(60));
282
- Object.entries(health.checks).forEach(([name, check]) => {
283
- const nameFormatted = name.charAt(0).toUpperCase() + name.slice(1);
284
- console.log(`\n${nameFormatted}:`);
285
- console.log(` Status: ${check.status.toUpperCase()}`);
286
- console.log(` Value: ${check.value}`);
287
- if (check.threshold) {
288
- console.log(` Threshold: ${check.threshold}`);
289
- }
290
- });
291
- console.log('\n' + '='.repeat(60) + '\n');
292
- return health;
293
- }
294
-
295
- startPeriodicReporting(interval = 60000) {
296
- if (this.metricsInterval) {
297
- clearInterval(this.metricsInterval);
298
- }
299
-
300
- this.metricsInterval = setInterval(() => {
301
- const metrics = this.getMetrics();
302
- console.log('\n' + '='.repeat(60));
303
- console.log('PERFORMANCE METRICS REPORT');
304
- console.log('='.repeat(60));
305
- console.log('\nRequests:');
306
- console.log(` Total: ${metrics.requests.total}`);
307
- console.log(` Success Rate: ${metrics.requests.successRate}`);
308
- console.log('\nErrors:');
309
- console.log(` Total: ${metrics.errors.total}`);
310
- console.log(` Error Rate: ${metrics.errors.errorRate}`);
311
- console.log('\nPerformance:');
312
- const avgTime = Math.round(metrics.performance.avgResponseTime);
313
- console.log(` Avg Response Time: ${avgTime}ms`);
314
- console.log('\nRate Limiting:');
315
- console.log(` Total Hits: ${metrics.rateLimiting.hitCount}`);
316
- console.log('='.repeat(60) + '\n');
317
- }, interval);
318
- }
319
-
320
- stopPeriodicReporting() {
321
- if (this.metricsInterval) {
322
- clearInterval(this.metricsInterval);
323
- this.metricsInterval = null;
324
- }
325
- }
326
- }
327
-
328
- const globalMonitor = new ProductionMonitor();
329
-
330
- module.exports = {
331
- ProductionMonitor,
332
- globalMonitor
333
- };
@@ -1,319 +0,0 @@
1
- "use strict";
2
-
3
- /**
4
- * Adaptive Rate Limiting Manager - Optimized for Performance
5
- * Sliding-window per-minute and per-second rate limiting to prevent
6
- * Facebook from flagging automated behaviour.
7
- */
8
-
9
- class RateLimiter {
10
- constructor() {
11
- this.threadCooldowns = new Map();
12
- this.endpointCooldowns = new Map();
13
- this.errorCache = new Map();
14
-
15
- this.ERROR_CACHE_TTL = 300000;
16
- this.COOLDOWN_DURATION = 60000;
17
- this.MAX_REQUESTS_PER_MINUTE = 80; // Increased from 50 for better throughput
18
- this.MAX_CONCURRENT_REQUESTS = 8; // Increased from 5
19
-
20
- this.activeRequests = 0;
21
-
22
- // Sliding window: store timestamps of recent requests
23
- this._requestWindow = [];
24
- this._WINDOW_MS = 60000;
25
-
26
- // Per-endpoint sliding windows
27
- this._endpointWindows = new Map();
28
- this._MAX_PER_ENDPOINT_PER_MINUTE = 30; // Increased from 20
29
-
30
- // Fast path cache for cooldown checks
31
- this._cooldownCache = new Map();
32
- this._COOLDOWN_CACHE_TTL = 500; // 500ms cache for cooldown checks
33
- }
34
-
35
- configure(opts = {}) {
36
- if (typeof opts.maxConcurrentRequests === 'number' && opts.maxConcurrentRequests > 0 && opts.maxConcurrentRequests <= 50) {
37
- this.MAX_CONCURRENT_REQUESTS = Math.floor(opts.maxConcurrentRequests);
38
- }
39
- if (typeof opts.maxRequestsPerMinute === 'number' && opts.maxRequestsPerMinute > 0 && opts.maxRequestsPerMinute <= 1000) {
40
- this.MAX_REQUESTS_PER_MINUTE = Math.floor(opts.maxRequestsPerMinute);
41
- }
42
- if (typeof opts.requestCooldownMs === 'number' && opts.requestCooldownMs >= 0 && opts.requestCooldownMs <= 10 * 60 * 1000) {
43
- this.COOLDOWN_DURATION = Math.floor(opts.requestCooldownMs);
44
- }
45
- if (typeof opts.errorCacheTtlMs === 'number' && opts.errorCacheTtlMs >= 0 && opts.errorCacheTtlMs <= 24 * 60 * 60 * 1000) {
46
- this.ERROR_CACHE_TTL = Math.floor(opts.errorCacheTtlMs);
47
- }
48
- }
49
-
50
- // ─── Thread cooldowns ─────────────────────────────────────────────────────
51
-
52
- isThreadOnCooldown(threadID) {
53
- const now = Date.now();
54
- // Fast path: check cache first
55
- const cacheKey = `t:${threadID}`;
56
- const cached = this._cooldownCache.get(cacheKey);
57
- if (cached && now - cached.ts < this._COOLDOWN_CACHE_TTL) {
58
- return cached.result;
59
- }
60
-
61
- const cooldownEnd = this.threadCooldowns.get(threadID);
62
- let result = false;
63
- if (!cooldownEnd) {
64
- result = false;
65
- } else if (now >= cooldownEnd) {
66
- this.threadCooldowns.delete(threadID);
67
- result = false;
68
- } else {
69
- result = true;
70
- }
71
-
72
- this._cooldownCache.set(cacheKey, { ts: now, result });
73
- return result;
74
- }
75
-
76
- setThreadCooldown(threadID, duration = null) {
77
- this.threadCooldowns.set(threadID, Date.now() + (duration || this.COOLDOWN_DURATION));
78
- // Invalidate cache
79
- this._cooldownCache.delete(`t:${threadID}`);
80
- }
81
-
82
- // ─── Endpoint cooldowns ───────────────────────────────────────────────────
83
-
84
- isEndpointOnCooldown(endpoint) {
85
- const now = Date.now();
86
- // Fast path: check cache
87
- const cacheKey = `e:${endpoint}`;
88
- const cached = this._cooldownCache.get(cacheKey);
89
- if (cached && now - cached.ts < this._COOLDOWN_CACHE_TTL) {
90
- return cached.result;
91
- }
92
-
93
- const cooldownEnd = this.endpointCooldowns.get(endpoint);
94
- let result = false;
95
- if (!cooldownEnd) {
96
- result = false;
97
- } else if (now >= cooldownEnd) {
98
- this.endpointCooldowns.delete(endpoint);
99
- result = false;
100
- } else {
101
- result = true;
102
- }
103
-
104
- this._cooldownCache.set(cacheKey, { ts: now, result });
105
- return result;
106
- }
107
-
108
- setEndpointCooldown(endpoint, duration = null) {
109
- this.endpointCooldowns.set(endpoint, Date.now() + (duration || this.COOLDOWN_DURATION));
110
- this._cooldownCache.delete(`e:${endpoint}`);
111
- }
112
-
113
- // ─── Error suppression ────────────────────────────────────────────────────
114
-
115
- shouldSuppressError(key) {
116
- const cachedTime = this.errorCache.get(key);
117
- if (!cachedTime) {
118
- this.errorCache.set(key, Date.now());
119
- return false;
120
- }
121
- if (Date.now() - cachedTime > this.ERROR_CACHE_TTL) {
122
- this.errorCache.set(key, Date.now());
123
- return false;
124
- }
125
- return true;
126
- }
127
-
128
- // ─── Sliding-window rate checking ─────────────────────────────────────────
129
-
130
- /**
131
- * Prune timestamps older than the window and return the current count.
132
- * Optimized with batch pruning.
133
- */
134
- _pruneWindow(arr) {
135
- const cutoff = Date.now() - this._WINDOW_MS;
136
- // Binary search for the first valid timestamp
137
- let left = 0, right = arr.length;
138
- while (left < right) {
139
- const mid = Math.floor((left + right) / 2);
140
- if (arr[mid] < cutoff) {
141
- left = mid + 1;
142
- } else {
143
- right = mid;
144
- }
145
- }
146
- if (left > 0) {
147
- arr.splice(0, left);
148
- }
149
- return arr.length;
150
- }
151
-
152
- isGloballyRateLimited() {
153
- const count = this._pruneWindow(this._requestWindow);
154
- return count >= this.MAX_REQUESTS_PER_MINUTE;
155
- }
156
-
157
- isEndpointRateLimited(endpoint) {
158
- if (!this._endpointWindows.has(endpoint)) return false;
159
- const count = this._pruneWindow(this._endpointWindows.get(endpoint));
160
- return count >= this._MAX_PER_ENDPOINT_PER_MINUTE;
161
- }
162
-
163
- _recordRequest(endpoint) {
164
- const now = Date.now();
165
- this._requestWindow.push(now);
166
- // Prune less frequently for better performance
167
- if (this._requestWindow.length > this.MAX_REQUESTS_PER_MINUTE * 3) {
168
- this._pruneWindow(this._requestWindow);
169
- }
170
- if (endpoint) {
171
- if (!this._endpointWindows.has(endpoint)) {
172
- this._endpointWindows.set(endpoint, []);
173
- }
174
- const ew = this._endpointWindows.get(endpoint);
175
- ew.push(now);
176
- if (ew.length > this._MAX_PER_ENDPOINT_PER_MINUTE * 3) {
177
- this._pruneWindow(ew);
178
- }
179
- }
180
- }
181
-
182
- // ─── Adaptive delay ───────────────────────────────────────────────────────
183
-
184
- getAdaptiveDelay(retryCount, errorCode = null) {
185
- const baseDelays = [1000, 2500, 5000, 10000]; // Reduced base delays
186
- const base = baseDelays[Math.min(retryCount, baseDelays.length - 1)];
187
-
188
- if (errorCode === 1545012 || errorCode === 1675004) {
189
- return base * 1.5; // Reduced from 2x
190
- }
191
- if (errorCode === 368 || errorCode === 10) {
192
- return base * 2; // Reduced from 3x
193
- }
194
- return base;
195
- }
196
-
197
- async addHumanizedDelay(min = 100, max = 350) {
198
- // Speed mode: humanized send delay disabled — no artificial wait.
199
- return;
200
- }
201
-
202
- /**
203
- * Check global and concurrent rate limits.
204
- * Will wait until below limit, then record the request.
205
- */
206
- async checkRateLimit(skipHumanDelay = false, endpoint = null) {
207
- // Fast path: try immediate slot acquisition
208
- if (this.activeRequests < this.MAX_CONCURRENT_REQUESTS) {
209
- if (!this.isGloballyRateLimited()) {
210
- if (!endpoint || !this.isEndpointRateLimited(endpoint)) {
211
- if (!skipHumanDelay) {
212
- await this.addHumanizedDelay();
213
- }
214
- this.activeRequests++;
215
- this._recordRequest(endpoint);
216
- return () => {
217
- this.activeRequests = Math.max(0, this.activeRequests - 1);
218
- };
219
- }
220
- }
221
- }
222
-
223
- // Slow path: wait for slot
224
- while (this.activeRequests >= this.MAX_CONCURRENT_REQUESTS) {
225
- await new Promise(resolve => setTimeout(resolve, 50)); // Reduced from 100ms
226
- }
227
-
228
- // Wait for per-minute global window to clear
229
- let waitCycles = 0;
230
- const maxCycles = 120; // Increased from 60
231
- while (this.isGloballyRateLimited()) {
232
- if (waitCycles++ > maxCycles) break;
233
- await new Promise(resolve => setTimeout(resolve, 500)); // Reduced from 1000ms
234
- }
235
-
236
- // Wait for per-endpoint window to clear
237
- if (endpoint) {
238
- let epCycles = 0;
239
- const maxEpCycles = 60; // Increased from 30
240
- while (this.isEndpointRateLimited(endpoint)) {
241
- if (epCycles++ > maxEpCycles) break;
242
- await new Promise(resolve => setTimeout(resolve, 500));
243
- }
244
- }
245
-
246
- if (!skipHumanDelay) {
247
- await this.addHumanizedDelay();
248
- }
249
-
250
- this.activeRequests++;
251
- this._recordRequest(endpoint);
252
-
253
- return () => {
254
- this.activeRequests = Math.max(0, this.activeRequests - 1);
255
- };
256
- }
257
-
258
- // ─── Cleanup ──────────────────────────────────────────────────────────────
259
-
260
- cleanup() {
261
- const now = Date.now();
262
-
263
- for (const [key, time] of this.errorCache.entries()) {
264
- if (now - time > this.ERROR_CACHE_TTL) this.errorCache.delete(key);
265
- }
266
- for (const [key, time] of this.threadCooldowns.entries()) {
267
- if (now >= time) this.threadCooldowns.delete(key);
268
- }
269
- for (const [key, time] of this.endpointCooldowns.entries()) {
270
- if (now >= time) this.endpointCooldowns.delete(key);
271
- }
272
-
273
- // Prune all endpoint windows
274
- for (const [key, arr] of this._endpointWindows.entries()) {
275
- this._pruneWindow(arr);
276
- if (arr.length === 0) this._endpointWindows.delete(key);
277
- }
278
- this._pruneWindow(this._requestWindow);
279
-
280
- // Clear cooldown cache
281
- this._cooldownCache.clear();
282
- }
283
-
284
- getStats() {
285
- this._pruneWindow(this._requestWindow);
286
- return {
287
- activeRequests: this.activeRequests,
288
- maxConcurrentRequests: this.MAX_CONCURRENT_REQUESTS,
289
- maxRequestsPerMinute: this.MAX_REQUESTS_PER_MINUTE,
290
- requestsInLastMinute: this._requestWindow.length,
291
- threadCooldowns: this.threadCooldowns.size,
292
- endpointCooldowns: this.endpointCooldowns.size,
293
- errorCacheSize: this.errorCache.size
294
- };
295
- }
296
-
297
- getCooldownRemaining(threadID) {
298
- const cooldownEnd = this.threadCooldowns.get(threadID);
299
- if (!cooldownEnd) return 0;
300
- return Math.max(0, cooldownEnd - Date.now());
301
- }
302
-
303
- getEndpointCooldownRemaining(endpoint) {
304
- const cooldownEnd = this.endpointCooldowns.get(endpoint);
305
- if (!cooldownEnd) return 0;
306
- return Math.max(0, cooldownEnd - Date.now());
307
- }
308
- }
309
-
310
- const globalRateLimiter = new RateLimiter();
311
-
312
- setInterval(() => globalRateLimiter.cleanup(), 60000);
313
-
314
- module.exports = {
315
- RateLimiter,
316
- globalRateLimiter,
317
- configureRateLimiter: (opts) => globalRateLimiter.configure(opts),
318
- getRateLimiterStats: () => globalRateLimiter.getStats()
319
- };