discord-self-lite 0.1.7 → 0.1.8

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "discord-self-lite",
3
- "version": "0.1.7",
3
+ "version": "0.1.8",
4
4
  "description": "Lightweight Discord selfbot library",
5
5
  "keywords": [
6
6
  "discord",
@@ -23,6 +23,9 @@ class RestManager {
23
23
  this.globalRateLimit = null; // { reset }
24
24
  this.requestQueue = [];
25
25
  this.processingQueue = false;
26
+
27
+ // Circuit breaker state for heavily rate-limited routes
28
+ this.circuitBreakers = new Map(); // routeKey -> { failures, lastFailure, openUntil }
26
29
  }
27
30
 
28
31
  /**
@@ -33,7 +36,56 @@ class RestManager {
33
36
  * @throws {Error} If the request fails
34
37
  */
35
38
  async request(endpoint, options = {}) {
39
+ // Check rate limits before queuing the request with more aggressive logic
40
+ const method = options.method || "GET";
41
+ const routeKey = this.getRouteKey(endpoint, method);
42
+
43
+ // Check circuit breaker - reject immediately if route is temporarily disabled
44
+ const circuitBreaker = this.circuitBreakers.get(routeKey);
45
+ if (circuitBreaker && circuitBreaker.openUntil > Date.now()) {
46
+ const remainingTime = circuitBreaker.openUntil - Date.now();
47
+ throw new Error(
48
+ `Circuit breaker open for ${routeKey}. Retry in ${remainingTime}ms`,
49
+ );
50
+ }
51
+
52
+ if (this.isRateLimited(endpoint, method)) {
53
+ const rateLimit = this.rateLimits.get(routeKey);
54
+ const globalLimited =
55
+ this.globalRateLimit && Date.now() < this.globalRateLimit.reset;
56
+
57
+ // Calculate delay with minimum 2s buffer and more conservative timing
58
+ const baseDelay = globalLimited
59
+ ? this.globalRateLimit.reset - Date.now()
60
+ : rateLimit
61
+ ? rateLimit.reset - Date.now()
62
+ : 1000;
63
+
64
+ // Add buffer time and ensure minimum delay
65
+ const delay = Math.max(baseDelay + 2000, 3000); // Minimum 3s delay
66
+
67
+ console.log(
68
+ `🚫 Pre-emptive rate limit wait: ${delay}ms for ${routeKey} (${endpoint})`,
69
+ );
70
+ await this.sleep(delay);
71
+
72
+ // Double-check after waiting - if still limited, reject immediately
73
+ if (this.isRateLimited(endpoint, method)) {
74
+ throw new Error(`Endpoint still rate limited after wait: ${routeKey}`);
75
+ }
76
+ }
77
+
36
78
  return new Promise((resolve, reject) => {
79
+ // Prevent queue from growing too large during rate limit storms
80
+ if (this.requestQueue.length >= 50) {
81
+ reject(
82
+ new Error(
83
+ `Request queue full (${this.requestQueue.length} requests). Rate limiting too aggressive.`,
84
+ ),
85
+ );
86
+ return;
87
+ }
88
+
37
89
  this.requestQueue.push({
38
90
  endpoint,
39
91
  options,
@@ -107,8 +159,8 @@ class RestManager {
107
159
  const result = await this.makeRequest(endpoint, options);
108
160
  resolve(result);
109
161
 
110
- // Small delay between requests to be nice to Discord
111
- await this.sleep(100);
162
+ // Increased delay between requests to be more conservative with rate limits
163
+ await this.sleep(500);
112
164
  } catch (error) {
113
165
  reject(error);
114
166
  }
@@ -152,25 +204,52 @@ class RestManager {
152
204
  response.headers.get("x-ratelimit-global") === "true";
153
205
  const routeKey = this.getRouteKey(endpoint, options.method || "GET");
154
206
 
207
+ // Exponential backoff: increase delay based on attempt number
208
+ const backoffDelay = retryAfter * Math.pow(1.5, attempt);
209
+
155
210
  console.log(
156
211
  `🚫 Rate limited! ${
157
212
  isGlobal ? "Global" : `Route (${routeKey})`
158
- } limit for ${endpoint}, retry after ${retryAfter}ms`,
213
+ } limit for ${endpoint}, retry after ${backoffDelay}ms (attempt ${attempt + 1})`,
159
214
  );
160
215
 
216
+ // Circuit breaker: track consecutive failures
217
+ if (!isGlobal) {
218
+ const breaker = this.circuitBreakers.get(routeKey) || {
219
+ failures: 0,
220
+ lastFailure: 0,
221
+ openUntil: 0,
222
+ };
223
+ breaker.failures++;
224
+ breaker.lastFailure = Date.now();
225
+
226
+ // Open circuit breaker after 3 consecutive failures within 30 seconds
227
+ if (
228
+ breaker.failures >= 3 &&
229
+ Date.now() - breaker.lastFailure < 30000
230
+ ) {
231
+ breaker.openUntil = Date.now() + 60000; // Open for 60 seconds
232
+ console.log(
233
+ `🔌 Circuit breaker OPENED for ${routeKey} - too many rate limits`,
234
+ );
235
+ }
236
+
237
+ this.circuitBreakers.set(routeKey, breaker);
238
+ }
239
+
161
240
  if (isGlobal) {
162
- this.globalRateLimit = { reset: Date.now() + retryAfter };
241
+ this.globalRateLimit = { reset: Date.now() + backoffDelay };
163
242
  } else {
164
243
  // Update route-specific rate limit for 429 responses
165
244
  this.rateLimits.set(routeKey, {
166
245
  remaining: 0,
167
- reset: Date.now() + retryAfter,
246
+ reset: Date.now() + backoffDelay,
168
247
  limit: this.rateLimits.get(routeKey)?.limit || 5, // Default limit if unknown
169
248
  updatedAt: Date.now(),
170
249
  });
171
250
  }
172
251
 
173
- await this.sleep(retryAfter);
252
+ await this.sleep(backoffDelay);
174
253
  attempt++;
175
254
  continue;
176
255
  }
@@ -184,9 +263,16 @@ class RestManager {
184
263
 
185
264
  // Handle 204 No Content (successful but no body)
186
265
  if (response.status === 204) {
266
+ // Reset circuit breaker on successful response
267
+ const routeKey = this.getRouteKey(endpoint, options.method || "GET");
268
+ this.circuitBreakers.delete(routeKey);
187
269
  return null;
188
270
  }
189
271
 
272
+ // Reset circuit breaker on successful response
273
+ const routeKey = this.getRouteKey(endpoint, options.method || "GET");
274
+ this.circuitBreakers.delete(routeKey);
275
+
190
276
  return await response.json();
191
277
  } catch (error) {
192
278
  if (attempt === maxRetries - 1) {
@@ -258,6 +344,23 @@ class RestManager {
258
344
  this.rateLimits.delete(key);
259
345
  }
260
346
  }
347
+
348
+ // Clean up expired circuit breakers
349
+ for (const [key, breaker] of this.circuitBreakers.entries()) {
350
+ if (now > breaker.openUntil) {
351
+ this.circuitBreakers.delete(key);
352
+ }
353
+ }
354
+ }
355
+
356
+ /**
357
+ * Sleep for a specified number of milliseconds
358
+ * @private
359
+ * @param {number} ms - Milliseconds to sleep
360
+ * @returns {Promise<void>}
361
+ */
362
+ sleep(ms) {
363
+ return new Promise((resolve) => setTimeout(resolve, ms));
261
364
  }
262
365
 
263
366
  /**